From f93b1b8d7b5e6f4b73e73513e74f6e84c377dfaa Mon Sep 17 00:00:00 2001 From: Jaime Ita Passos Date: Mon, 19 Apr 2021 04:01:49 -0300 Subject: [PATCH 001/227] Raise the skin limit to 256. --- src/d_clisrv.c | 6 +- src/d_main.c | 7 +- src/d_netcmd.c | 18 +++-- src/d_player.h | 2 +- src/deh_soc.c | 31 ++++----- src/dehacked.c | 2 +- src/doomdef.h | 3 +- src/g_game.c | 2 +- src/hardware/hw_md2.c | 16 +++-- src/hardware/hw_md2.h | 2 +- src/hardware/hw_model.c | 3 +- src/lua_playerlib.c | 3 +- src/m_cond.c | 3 +- src/m_menu.c | 141 ++++++++++++++++++++++------------------ src/m_menu.h | 7 +- src/p_saveg.c | 4 +- src/r_picformats.c | 9 ++- src/r_skins.c | 55 ++++++++-------- src/r_skins.h | 5 +- src/sounds.h | 4 +- src/st_stuff.c | 15 ++++- src/st_stuff.h | 6 +- 22 files changed, 195 insertions(+), 149 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 7c2dec6a1..f1c24fe5a 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -2463,6 +2463,8 @@ void CL_ClearPlayer(INT32 playernum) { if (players[playernum].mo) P_RemoveMobj(players[playernum].mo); + if (players[playernum].availabilities) + Z_Free(players[playernum].availabilities); memset(&players[playernum], 0, sizeof (player_t)); memset(playeraddress[playernum], 0, sizeof(*playeraddress)); } @@ -4486,9 +4488,9 @@ static INT16 Consistancy(void) { if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) continue; - + mo = (mobj_t *)th; - + if (mo->flags & (MF_SPECIAL | MF_SOLID | MF_PUSHABLE | MF_BOSS | MF_MISSILE | MF_SPRING | MF_MONITOR | MF_FIRE | MF_ENEMY | MF_PAIN | MF_STICKY)) { ret -= mo->type; diff --git a/src/d_main.c b/src/d_main.c index 23a2c0133..e9d236a9b 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -813,7 +813,8 @@ void D_StartTitle(void) for (i = 0; i < MAXPLAYERS; i++) CL_ClearPlayer(i); - players[consoleplayer].availabilities = players[1].availabilities = R_GetSkinAvailabilities(); // players[1] is supposed to be for 2p + R_GetSkinAvailabilities(&players[consoleplayer].availabilities); + R_GetSkinAvailabilities(&players[1].availabilities); // players[1] is supposed to be for 2p splitscreen = false; SplitScreen_OnChange(); @@ -1216,10 +1217,6 @@ void D_SRB2Main(void) // Make backups of some SOCcable tables. P_BackupTables(); - // Setup character tables - // Have to be done here before files are loaded - M_InitCharacterTables(); - mainwads = 3; // doesn't include music.dta #ifdef USE_PATCH_DTA mainwads++; diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 09f9d4651..14349d559 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -1235,7 +1235,7 @@ static void SendNameAndColor(void) && !strcmp(cv_skin.string, skins[players[consoleplayer].skin].name)) return; - players[consoleplayer].availabilities = R_GetSkinAvailabilities(); + R_GetSkinAvailabilities(&players[consoleplayer].availabilities); // We'll handle it later if we're not playing. if (!Playing()) @@ -1319,9 +1319,9 @@ static void SendNameAndColor(void) // Finally write out the complete packet and send it off. WRITESTRINGN(p, cv_playername.zstring, MAXPLAYERNAME); - WRITEUINT32(p, (UINT32)players[consoleplayer].availabilities); WRITEUINT16(p, (UINT16)cv_playercolor.value); WRITEUINT8(p, (UINT8)cv_skin.value); + WRITEMEM(p, players[consoleplayer].availabilities, sizeof(bitarray_t) * numskins); SendNetXCmd(XD_NAMEANDCOLOR, buf, p - buf); } @@ -1363,7 +1363,7 @@ static void SendNameAndColor2(void) } } - players[secondplaya].availabilities = R_GetSkinAvailabilities(); + R_GetSkinAvailabilities(&players[secondplaya].availabilities); // We'll handle it later if we're not playing. if (!Playing()) @@ -1455,10 +1455,13 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) #endif READSTRINGN(*cp, name, MAXPLAYERNAME); - p->availabilities = READUINT32(*cp); color = READUINT16(*cp); skin = READUINT8(*cp); + if (p->availabilities == NULL) + p->availabilities = Z_Malloc(sizeof(bitarray_t) * numskins, PU_STATIC, NULL); + READMEM(*cp, p->availabilities, sizeof(bitarray_t) * numskins); + // set name if (player_name_changes[playernum] < MAXNAMECHANGES) { @@ -1491,9 +1494,9 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) kick = true; // availabilities - for (s = 0; s < MAXSKINS; s++) + for (s = 0; s < numskins; s++) { - if (!skins[s].availability && (p->availabilities & (1 << s))) + if (!skins[s].availability && in_bit_array(p->availabilities, s)) { kick = true; break; @@ -4278,7 +4281,8 @@ void Command_ExitGame_f(void) for (i = 0; i < MAXPLAYERS; i++) CL_ClearPlayer(i); - players[consoleplayer].availabilities = players[1].availabilities = R_GetSkinAvailabilities(); // players[1] is supposed to be for 2p + R_GetSkinAvailabilities(&players[consoleplayer].availabilities); + R_GetSkinAvailabilities(&players[1].availabilities); // players[1] is supposed to be for 2p splitscreen = false; SplitScreen_OnChange(); diff --git a/src/d_player.h b/src/d_player.h index 2e7afed88..d90dd79ff 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -379,7 +379,7 @@ typedef struct player_s UINT16 skincolor; INT32 skin; - UINT32 availabilities; + bitarray_t *availabilities; UINT32 score; // player score fixed_t dashspeed; // dashing speed diff --git a/src/deh_soc.c b/src/deh_soc.c index 5b12ea1b0..046ed885a 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -162,23 +162,20 @@ void clear_levels(void) static boolean findFreeSlot(INT32 *num) { - // Send the character select entry to a free slot. - while (*num < MAXSKINS && (description[*num].used)) - *num = *num+1; + if (description) + { + // Send the character select entry to a free slot. + while (*num < numdescriptions && (description[*num].used)) + (*num)++; + } - // No more free slots. :( - if (*num >= MAXSKINS) + // No more free slots. + if (*num >= MAXCHARACTERSLOTS) return false; + else if (*num >= numdescriptions) + M_InitCharacterTables((*num) + 1); - // Redesign your logo. (See M_DrawSetupChoosePlayerMenu in m_menu.c...) - description[*num].picname[0] = '\0'; - description[*num].nametag[0] = '\0'; - description[*num].displayname[0] = '\0'; - description[*num].oppositecolor = SKINCOLOR_NONE; - description[*num].tagtextcolor = SKINCOLOR_NONE; - description[*num].tagoutlinecolor = SKINCOLOR_NONE; - - // Found one! ^_^ + // Found one! return (description[*num].used = true); } @@ -891,7 +888,7 @@ void readspriteinfo(MYFILE *f, INT32 num, boolean sprite2) INT32 value; #endif char *lastline; - INT32 skinnumbers[MAXSKINS]; + INT32 *skinnumbers = NULL; INT32 foundskins = 0; // allocate a spriteinfo @@ -980,6 +977,8 @@ void readspriteinfo(MYFILE *f, INT32 num, boolean sprite2) break; } + if (skinnumbers == NULL) + skinnumbers = Z_Malloc(sizeof(INT32) * numskins, PU_STATIC, NULL); skinnumbers[foundskins] = skinnum; foundskins++; } @@ -1043,6 +1042,8 @@ void readspriteinfo(MYFILE *f, INT32 num, boolean sprite2) Z_Free(s); Z_Free(info); + if (skinnumbers) + Z_Free(skinnumbers); } void readsprite2(MYFILE *f, INT32 num) diff --git a/src/dehacked.c b/src/dehacked.c index c2ea28d27..8d3c5725f 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -241,7 +241,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) i = 0; if (fastcmp(word, "CHARACTER")) { - if (i >= 0 && i < 32) + if (i >= 0 && i < MAXCHARACTERSLOTS) readPlayer(f, i); else { diff --git a/src/doomdef.h b/src/doomdef.h index 52abc9597..e7831f689 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -220,7 +220,8 @@ extern char logfilename[1024]; // NOTE: it needs more than this to increase the number of players... #define MAXPLAYERS 32 -#define MAXSKINS 32 +#define MAXSKINS 256 +#define MAXCHARACTERSLOTS (MAXSKINS * 2) #define PLAYERSMASK (MAXPLAYERS-1) #define MAXPLAYERNAME 21 diff --git a/src/g_game.c b/src/g_game.c index 13423ce77..7f48556ff 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2486,7 +2486,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) UINT8 mare; UINT16 skincolor; INT32 skin; - UINT32 availabilities; + bitarray_t *availabilities; tic_t jointime; tic_t quittime; boolean spectator; diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c index 5caf344f7..cba322b2a 100644 --- a/src/hardware/hw_md2.c +++ b/src/hardware/hw_md2.c @@ -71,7 +71,7 @@ #endif md2_t md2_models[NUMSPRITES]; -md2_t md2_playermodels[MAXSKINS]; +md2_t *md2_playermodels = NULL; /* @@ -490,7 +490,11 @@ void HWR_InitModels(void) size_t prefixlen; CONS_Printf("HWR_InitModels()...\n"); - for (s = 0; s < MAXSKINS; s++) + + if (numskins && md2_playermodels == NULL) + md2_playermodels = Z_Malloc(sizeof(md2_t), PU_STATIC, NULL); + + for (s = 0; s < numskins; s++) { md2_playermodels[s].scale = -1.0f; md2_playermodels[s].model = NULL; @@ -501,6 +505,7 @@ void HWR_InitModels(void) md2_playermodels[s].notfound = true; md2_playermodels[s].error = false; } + for (i = 0; i < NUMSPRITES; i++) { md2_models[i].scale = -1.0f; @@ -561,7 +566,7 @@ void HWR_InitModels(void) addskinmodel: // add player model - for (s = 0; s < MAXSKINS; s++) + for (s = 0; s < numskins; s++) { if (stricmp(skinname, skins[s].name) == 0) { @@ -581,7 +586,7 @@ modelfound: fclose(f); } -void HWR_AddPlayerModel(int skin) // For skins that were added after startup +void HWR_AddPlayerModel(INT32 skin) // For skins that were added after startup { FILE *f; char name[24], filename[32]; @@ -608,6 +613,9 @@ void HWR_AddPlayerModel(int skin) // For skins that were added after startup } } + // realloc player models table + md2_playermodels = Z_Realloc(md2_playermodels, sizeof(md2_t) * numskins, PU_STATIC, NULL); + // length of the player model prefix prefixlen = strlen(PLAYERMODELPREFIX); diff --git a/src/hardware/hw_md2.h b/src/hardware/hw_md2.h index 0f4d2c7bc..42787115a 100644 --- a/src/hardware/hw_md2.h +++ b/src/hardware/hw_md2.h @@ -37,7 +37,7 @@ typedef struct } md2_t; extern md2_t md2_models[NUMSPRITES]; -extern md2_t md2_playermodels[MAXSKINS]; +extern md2_t *md2_playermodels; void HWR_InitModels(void); void HWR_AddPlayerModel(INT32 skin); diff --git a/src/hardware/hw_model.c b/src/hardware/hw_model.c index 4ed03744b..86e2a0c70 100644 --- a/src/hardware/hw_model.c +++ b/src/hardware/hw_model.c @@ -10,6 +10,7 @@ #include "../doomdef.h" #include "../doomtype.h" #include "../info.h" +#include "../r_skins.h" #include "../z_zone.h" #include "hw_model.h" #include "hw_md2load.h" @@ -238,7 +239,7 @@ void HWR_ReloadModels(void) size_t i; INT32 s; - for (s = 0; s < MAXSKINS; s++) + for (s = 0; s < numskins; s++) { if (md2_playermodels[s].model) LoadModelSprite2(md2_playermodels[s].model); diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 0eb54808f..b05e21ca9 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -16,6 +16,7 @@ #include "d_player.h" #include "g_game.h" #include "p_local.h" +#include "r_skins.h" #include "lua_script.h" #include "lua_libs.h" @@ -161,7 +162,7 @@ static int player_get(lua_State *L) else if (fastcmp(field,"skin")) lua_pushinteger(L, plr->skin); else if (fastcmp(field,"availabilities")) - lua_pushinteger(L, plr->availabilities); + lua_pushinteger(L, R_GetAvailabilitiesBits(plr->availabilities)); else if (fastcmp(field,"score")) lua_pushinteger(L, plr->score); else if (fastcmp(field,"dashspeed")) diff --git a/src/m_cond.c b/src/m_cond.c index 36fcd7cf2..44ac94373 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -285,7 +285,8 @@ void M_SilentUpdateUnlockablesAndEmblems(void) unlockables[i].unlocked = M_Achieved(unlockables[i].conditionset - 1); } - players[consoleplayer].availabilities = players[1].availabilities = R_GetSkinAvailabilities(); // players[1] is supposed to be for 2p + R_GetSkinAvailabilities(&players[consoleplayer].availabilities); + R_GetSkinAvailabilities(&players[1].availabilities); // players[1] is supposed to be for 2p } // Emblem unlocking shit diff --git a/src/m_menu.c b/src/m_menu.c index 0fca39801..0c5c706a8 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -126,7 +126,9 @@ M_waiting_mode_t m_waiting_mode = M_NOT_WAITING; const char *quitmsg[NUM_QUITMESSAGES]; // Stuff for customizing the player select screen Tails 09-22-2003 -description_t description[MAXSKINS]; +description_t *description = NULL; +INT32 numdescriptions = 0; + INT16 char_on = -1, startchar = 0; static char *char_notes = NULL; @@ -255,7 +257,7 @@ static void M_ConfirmTeamScramble(INT32 choice); static void M_ConfirmTeamChange(INT32 choice); static void M_SecretsMenu(INT32 choice); static void M_SetupChoosePlayer(INT32 choice); -static UINT8 M_SetupChoosePlayerDirect(INT32 choice); +static UINT16 M_SetupChoosePlayerDirect(INT32 choice); static void M_QuitSRB2(INT32 choice); menu_t SP_MainDef, OP_MainDef; menu_t MISC_ScrambleTeamDef, MISC_ChangeTeamDef; @@ -3970,24 +3972,32 @@ void M_Init(void) #endif } -void M_InitCharacterTables(void) +static void M_InitCharacterDescription(INT32 i) { - UINT8 i; - // Setup description table - for (i = 0; i < MAXSKINS; i++) - { - description[i].used = false; - strcpy(description[i].notes, "???"); - strcpy(description[i].picname, ""); - strcpy(description[i].nametag, ""); - strcpy(description[i].skinname, ""); - strcpy(description[i].displayname, ""); - description[i].prev = description[i].next = 0; - description[i].charpic = NULL; - description[i].namepic = NULL; - description[i].oppositecolor = description[i].tagtextcolor = description[i].tagoutlinecolor = 0; - } + description_t *desc = &description[i]; + desc->picname[0] = '\0'; + desc->nametag[0] = '\0'; + desc->skinname[0] = '\0'; + desc->displayname[0] = '\0'; + desc->prev = desc->next = 0; + desc->charpic = NULL; + desc->namepic = NULL; + desc->oppositecolor = SKINCOLOR_NONE; + desc->tagtextcolor = SKINCOLOR_NONE; + desc->tagoutlinecolor = SKINCOLOR_NONE; + strcpy(desc->notes, "???"); +} + +void M_InitCharacterTables(INT32 num) +{ + INT32 i = numdescriptions; + + description = Z_Realloc(description, sizeof(description_t) * num, PU_STATIC, NULL); + numdescriptions = num; + + for (; i < numdescriptions; i++) + M_InitCharacterDescription(i); } // ========================================================================== @@ -4981,7 +4991,7 @@ static void M_PatchSkinNameTable(void) for (j = 0; j < MAXSKINS; j++) { - if (skins[j].name[0] != '\0' && R_SkinUsable(-1, j)) + if (j < numskins && skins[j].name[0] != '\0' && R_SkinUsable(-1, j)) { skins_cons_t[j].strvalue = skins[j].realname; skins_cons_t[j].value = j+1; @@ -8961,64 +8971,67 @@ static void M_CacheCharacterSelectEntry(INT32 i, INT32 skinnum) description[i].namepic = W_CachePatchName(description[i].nametag, PU_PATCH); } -static UINT8 M_SetupChoosePlayerDirect(INT32 choice) +static UINT16 M_SetupChoosePlayerDirect(INT32 choice) { INT32 skinnum; - UINT8 i; - UINT8 firstvalid = 255, lastvalid = 255; + UINT16 i; + UINT16 firstvalid = 65535, lastvalid = 65535; boolean allowed = false; - char *and; (void)choice; if (!mapheaderinfo[startmap-1] || mapheaderinfo[startmap-1]->forcecharacter[0] == '\0') { - for (i = 0; i < MAXSKINS; i++) // Handle charsels, availability, and unlocks. + for (i = 0; i < numdescriptions; i++) // Handle charsels, availability, and unlocks. { - if (description[i].used) // If the character's disabled through SOC, there's nothing we can do for it. + char *and; + + // If the character's disabled through SOC, there's nothing we can do for it. + if (!description[i].used) + continue; + + and = strchr(description[i].skinname, '&'); + + if (and) { - and = strchr(description[i].skinname, '&'); - if (and) - { - char firstskin[SKINNAMESIZE+1]; - if (mapheaderinfo[startmap-1] && mapheaderinfo[startmap-1]->typeoflevel & TOL_NIGHTS) // skip tagteam characters for NiGHTS levels - continue; - strncpy(firstskin, description[i].skinname, (and - description[i].skinname)); - firstskin[(and - description[i].skinname)] = '\0'; - description[i].skinnum[0] = R_SkinAvailable(firstskin); - description[i].skinnum[1] = R_SkinAvailable(and+1); - } + char firstskin[SKINNAMESIZE+1]; + if (mapheaderinfo[startmap-1] && mapheaderinfo[startmap-1]->typeoflevel & TOL_NIGHTS) // skip tagteam characters for NiGHTS levels + continue; + strncpy(firstskin, description[i].skinname, (and - description[i].skinname)); + firstskin[(and - description[i].skinname)] = '\0'; + description[i].skinnum[0] = R_SkinAvailable(firstskin); + description[i].skinnum[1] = R_SkinAvailable(and+1); + } + else + { + description[i].skinnum[0] = R_SkinAvailable(description[i].skinname); + description[i].skinnum[1] = -1; + } + + skinnum = description[i].skinnum[0]; + + if ((skinnum != -1) && (R_SkinUsable(-1, skinnum))) + { + // Handling order. + if (firstvalid == 65535) + firstvalid = i; else { - description[i].skinnum[0] = R_SkinAvailable(description[i].skinname); - description[i].skinnum[1] = -1; + description[i].prev = lastvalid; + description[lastvalid].next = i; } - skinnum = description[i].skinnum[0]; - if ((skinnum != -1) && (R_SkinUsable(-1, skinnum))) - { - // Handling order. - if (firstvalid == 255) - firstvalid = i; - else - { - description[i].prev = lastvalid; - description[lastvalid].next = i; - } - lastvalid = i; + lastvalid = i; - if (i == char_on) - allowed = true; + if (i == char_on) + allowed = true; - M_CacheCharacterSelectEntry(i, skinnum); - } - // else -- Technically, character select icons without corresponding skins get bundled away behind this too. Sucks to be them. + M_CacheCharacterSelectEntry(i, skinnum); } + // else -- Technically, character select icons without corresponding skins get bundled away behind this too. Sucks to be them. } } if (firstvalid == lastvalid) // We're being forced into a specific character, so might as well just skip it. - { return firstvalid; - } // One last bit of order we can't do in the iteration above. description[firstvalid].prev = lastvalid; @@ -9027,7 +9040,7 @@ static UINT8 M_SetupChoosePlayerDirect(INT32 choice) if (!allowed) { char_on = firstvalid; - if (startchar > 0 && startchar < MAXSKINS) + if (startchar > 0 && startchar < numdescriptions) { INT16 workchar = startchar; while (workchar--) @@ -9035,13 +9048,13 @@ static UINT8 M_SetupChoosePlayerDirect(INT32 choice) } } - return MAXSKINS; + return MAXCHARACTERSLOTS; } static void M_SetupChoosePlayer(INT32 choice) { - UINT8 skinset = M_SetupChoosePlayerDirect(choice); - if (skinset != MAXSKINS) + UINT16 skinset = M_SetupChoosePlayerDirect(choice); + if (skinset != MAXCHARACTERSLOTS) { M_ChoosePlayer(skinset); return; @@ -9362,7 +9375,7 @@ static void M_ChoosePlayer(INT32 choice) UINT8 skinnum; // skip this if forcecharacter or no characters available - if (choice == 255) + if (choice == 65535) { skinnum = botskin = 0; botingame = false; @@ -10431,7 +10444,7 @@ static void M_MarathonLiveEventBackup(INT32 choice) // Going to Marathon menu... static void M_Marathon(INT32 choice) { - UINT8 skinset; + UINT16 skinset; INT32 mapnum = 0; if (choice != -1 && FIL_FileExists(liveeventbackup)) @@ -10454,7 +10467,7 @@ static void M_Marathon(INT32 choice) skinset = M_SetupChoosePlayerDirect(-1); - SP_MarathonMenu[marathonplayer].status = (skinset == MAXSKINS) ? IT_KEYHANDLER|IT_STRING : IT_NOTHING|IT_DISABLED; + SP_MarathonMenu[marathonplayer].status = (skinset == MAXCHARACTERSLOTS) ? IT_KEYHANDLER|IT_STRING : IT_NOTHING|IT_DISABLED; while (mapnum < NUMMAPS) { diff --git a/src/m_menu.h b/src/m_menu.h index 0465128ef..018d5c7e7 100644 --- a/src/m_menu.h +++ b/src/m_menu.h @@ -200,8 +200,8 @@ void M_Drawer(void); // Called by D_SRB2Main, loads the config file. void M_Init(void); -// Called by D_SRB2Main also, sets up the playermenu and description tables. -void M_InitCharacterTables(void); +// Called by deh_soc.c, sets up the playermenu and description tables. +void M_InitCharacterTables(INT32 num); // Called by intro code to force menu up upon a keypress, // does nothing if menu is already up. @@ -431,7 +431,8 @@ typedef struct INT32 gamemap; } saveinfo_t; -extern description_t description[MAXSKINS]; +extern description_t *description; +extern INT32 numdescriptions; extern consvar_t cv_showfocuslost; extern consvar_t cv_newgametype, cv_nextmap, cv_chooseskin, cv_serversort; diff --git a/src/p_saveg.c b/src/p_saveg.c index 03229e740..80bdfa09e 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -139,7 +139,7 @@ static void P_NetArchivePlayers(void) WRITEUINT8(save_p, players[i].skincolor); WRITEINT32(save_p, players[i].skin); - WRITEUINT32(save_p, players[i].availabilities); + WRITEMEM(save_p, players[i].availabilities, sizeof(bitarray_t) * numskins); WRITEUINT32(save_p, players[i].score); WRITEFIXED(save_p, players[i].dashspeed); WRITESINT8(save_p, players[i].lives); @@ -353,7 +353,7 @@ static void P_NetUnArchivePlayers(void) players[i].skincolor = READUINT8(save_p); players[i].skin = READINT32(save_p); - players[i].availabilities = READUINT32(save_p); + READMEM(save_p, players[i].availabilities, sizeof(bitarray_t) * numskins); players[i].score = READUINT32(save_p); players[i].dashspeed = READFIXED(save_p); // dashing speed players[i].lives = READSINT8(save_p); diff --git a/src/r_picformats.c b/src/r_picformats.c index f87362c76..ff2b9f15a 100644 --- a/src/r_picformats.c +++ b/src/r_picformats.c @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 2005-2009 by Andrey "entryway" Budko. -// Copyright (C) 2018-2020 by Jaime "Lactozilla" Passos. +// Copyright (C) 2018-2021 by Jaime "Lactozilla" Passos. // Copyright (C) 2019-2020 by Sonic Team Junior. // // This program is free software distributed under the @@ -1494,7 +1494,7 @@ static void R_ParseSpriteInfo(boolean spr2) spritenum_t sprnum = NUMSPRITES; playersprite_t spr2num = NUMPLAYERSPRITES; INT32 i; - INT32 skinnumbers[MAXSKINS]; + INT32 *skinnumbers = NULL; INT32 foundskins = 0; // Sprite name @@ -1592,6 +1592,8 @@ static void R_ParseSpriteInfo(boolean spr2) if (skinnum == -1) I_Error("Error parsing SPRTINFO lump: Unknown skin \"%s\"", skinName); + if (skinnumbers == NULL) + skinnumbers = Z_Malloc(sizeof(INT32) * numskins, PU_STATIC, NULL); skinnumbers[foundskins] = skinnum; foundskins++; } @@ -1630,8 +1632,11 @@ static void R_ParseSpriteInfo(boolean spr2) { I_Error("Error parsing SPRTINFO lump: Expected \"{\" for sprite \"%s\", got \"%s\"",newSpriteName,sprinfoToken); } + Z_Free(sprinfoToken); Z_Free(info); + if (skinnumbers) + Z_Free(skinnumbers); } // diff --git a/src/r_skins.c b/src/r_skins.c index 6f150f234..bfbc3e18f 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -18,6 +18,7 @@ #include "st_stuff.h" #include "w_wad.h" #include "z_zone.h" +#include "m_menu.h" #include "m_misc.h" #include "info.h" // spr2names #include "i_video.h" // rendermode @@ -32,13 +33,7 @@ #endif INT32 numskins = 0; -skin_t skins[MAXSKINS]; - -// FIXTHIS: don't work because it must be inistilised before the config load -//#define SKINVALUES -#ifdef SKINVALUES -CV_PossibleValue_t skin_cons_t[MAXSKINS+1]; -#endif +skin_t *skins = NULL; // // P_GetSkinSprite2 @@ -160,30 +155,37 @@ static void Sk_SetDefaultValue(skin_t *skin) // void R_InitSkins(void) { -#ifdef SKINVALUES - INT32 i; - - for (i = 0; i <= MAXSKINS; i++) - { - skin_cons_t[i].value = 0; - skin_cons_t[i].strvalue = NULL; - } -#endif - // no default skin! numskins = 0; } -UINT32 R_GetSkinAvailabilities(void) +void R_GetSkinAvailabilities(bitarray_t **response) +{ + INT32 s; + + if (*response == NULL) + (*response) = Z_Calloc(sizeof(bitarray_t) * numskins, PU_STATIC, NULL); + + for (s = 0; s < numskins; s++) + { + if (skins[s].availability && unlockables[skins[s].availability - 1].unlocked) + set_bit_array((*response), s); + else + unset_bit_array((*response), s); + } +} + +UINT32 R_GetAvailabilitiesBits(bitarray_t *availabilities) { INT32 s; UINT32 response = 0; - for (s = 0; s < MAXSKINS; s++) + for (s = 0; s < min(numskins, 32); s++) { - if (skins[s].availability && unlockables[skins[s].availability - 1].unlocked) + if (in_bit_array(availabilities, s)) response |= (1 << s); } + return response; } @@ -193,7 +195,7 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum) { return ((skinnum == -1) // Simplifies things elsewhere, since there's already plenty of checks for less-than-0... || (!skins[skinnum].availability) - || (((netgame || multiplayer) && playernum != -1) ? (players[playernum].availabilities & (1 << skinnum)) : (unlockables[skins[skinnum].availability - 1].unlocked)) + || (((netgame || multiplayer) && playernum != -1) ? (in_bit_array(players[playernum].availabilities, skinnum)) : (unlockables[skins[skinnum].availability - 1].unlocked)) || (modeattacking) // If you have someone else's run you might as well take a look || (Playing() && (R_SkinAvailable(mapheaderinfo[gamemap-1]->forcecharacter) == skinnum)) // Force 1. || (netgame && (cv_forceskin.value == skinnum)) // Force 2. @@ -594,6 +596,7 @@ void R_AddSkins(UINT16 wadnum) buf2[size] = '\0'; // set defaults + skins = Z_Realloc(skins, sizeof(skin_t) * (numskins + 1), PU_STATIC, NULL); skin = &skins[numskins]; Sk_SetDefaultValue(skin); skin->wadnum = wadnum; @@ -695,17 +698,13 @@ next_token: if (!skin->availability) // Safe to print... CONS_Printf(M_GetText("Added skin '%s'\n"), skin->name); -#ifdef SKINVALUES - skin_cons_t[numskins].value = numskins; - skin_cons_t[numskins].strvalue = skin->name; -#endif + + numskins++; #ifdef HWRENDER if (rendermode == render_opengl) - HWR_AddPlayerModel(numskins); + HWR_AddPlayerModel(numskins-1); #endif - - numskins++; } return; } diff --git a/src/r_skins.h b/src/r_skins.h index fbbb38743..f2e76afcb 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -86,7 +86,7 @@ typedef struct /// Externs extern INT32 numskins; -extern skin_t skins[MAXSKINS]; +extern skin_t *skins; /// Function prototypes void R_InitSkins(void); @@ -94,7 +94,8 @@ void R_InitSkins(void); void SetPlayerSkin(INT32 playernum,const char *skinname); void SetPlayerSkinByNum(INT32 playernum,INT32 skinnum); // Tails 03-16-2002 boolean R_SkinUsable(INT32 playernum, INT32 skinnum); -UINT32 R_GetSkinAvailabilities(void); +void R_GetSkinAvailabilities(bitarray_t **response); +UINT32 R_GetAvailabilitiesBits(bitarray_t *availabilities); INT32 R_SkinAvailable(const char *name); void R_PatchSkins(UINT16 wadnum); void R_AddSkins(UINT16 wadnum); diff --git a/src/sounds.h b/src/sounds.h index e49dd2f3e..7a2e3bc25 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -43,7 +43,7 @@ typedef enum // free sfx for S_AddSoundFx() #define NUMSFXFREESLOTS 1600 // Matches SOC Editor. -#define NUMSKINSFXSLOTS (MAXSKINS*NUMSKINSOUNDS) +#define NUMSKINSFXSLOTS (128*NUMSKINSOUNDS) // // SoundFX struct. @@ -874,7 +874,7 @@ typedef enum // free slots for S_AddSoundFx() at run-time -------------------- sfx_freeslot0, // - // ... 60 free sounds here ... + // ... 1600 free sounds here ... // sfx_lastfreeslot = sfx_freeslot0 + NUMSFXFREESLOTS-1, // end of freeslots --------------------------------------------- diff --git a/src/st_stuff.c b/src/st_stuff.c index a1fbbec03..cd4582f6d 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -50,8 +50,8 @@ UINT16 objectsdrawn = 0; // STATUS BAR DATA // -patch_t *faceprefix[MAXSKINS]; // face status patches -patch_t *superprefix[MAXSKINS]; // super face status patches +patch_t **faceprefix; // face status patches +patch_t **superprefix; // super face status patches // ------------------------------------------ // status bar overlay @@ -368,6 +368,17 @@ void ST_ReloadSkinFaceGraphics(void) { INT32 i; + if (faceprefix) + Z_Free(faceprefix); + if (superprefix) + Z_Free(superprefix); + + if (!numskins) + return; + + faceprefix = Z_Malloc(sizeof(patch_t *) * numskins, PU_STATIC, NULL); + superprefix = Z_Malloc(sizeof(patch_t *) * numskins, PU_STATIC, NULL); + for (i = 0; i < numskins; i++) ST_LoadFaceGraphics(i); } diff --git a/src/st_stuff.h b/src/st_stuff.h index 4ea307d2b..c31651cd5 100644 --- a/src/st_stuff.h +++ b/src/st_stuff.h @@ -42,7 +42,7 @@ void ST_UnloadGraphics(void); void ST_LoadGraphics(void); // face load graphics, called when skin changes -void ST_LoadFaceGraphics(INT32 playernum); +void ST_LoadFaceGraphics(INT32 skinnum); void ST_ReloadSkinFaceGraphics(void); void ST_doPaletteStuff(void); @@ -76,8 +76,8 @@ extern patch_t *sboscore; extern patch_t *sbotime; extern patch_t *sbocolon; extern patch_t *sboperiod; -extern patch_t *faceprefix[MAXSKINS]; // face status patches -extern patch_t *superprefix[MAXSKINS]; // super face status patches +extern patch_t **faceprefix; // face status patches +extern patch_t **superprefix; // super face status patches extern patch_t *livesback; extern patch_t *stlivex; extern patch_t *ngradeletters[7]; From 7c67d32c6b8a2ead11117daf94afbe322f18be13 Mon Sep 17 00:00:00 2001 From: Radicalicious Date: Fri, 18 Jun 2021 00:51:36 -0400 Subject: [PATCH 002/227] Update deh_tables.c --- src/deh_tables.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/deh_tables.c b/src/deh_tables.c index 5733d9b0e..df993fee8 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -361,8 +361,9 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_XDEATHSTATE", "S_RAISESTATE", - // Thok + // Thok effect and spin trail "S_THOK", + "S_THOKEFFECT", // Player "S_PLAY_STND", @@ -3490,7 +3491,8 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_NULL", "MT_UNKNOWN", - "MT_THOK", // Thok! mobj + "MT_THOK", // Spin trail mobj + "MT_THOKEFFECT", // Thok boom effect "MT_PLAYER", "MT_TAILSOVERLAY", // c: "MT_METALJETFUME", From a9a5d5fa026782197e878d8cd1e178d44eb4f696 Mon Sep 17 00:00:00 2001 From: Radicalicious Date: Fri, 18 Jun 2021 00:53:29 -0400 Subject: [PATCH 003/227] Update info.h --- src/info.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/info.h b/src/info.h index 604922beb..34b56b2cc 100644 --- a/src/info.h +++ b/src/info.h @@ -567,7 +567,8 @@ typedef enum sprite SPR_NULL, // invisible object SPR_UNKN, - SPR_THOK, // Thok! mobj + SPR_THOK, // Spin trail mobj + SPR_THKE, // Thok boom effect SPR_PLAY, // Enemies @@ -1163,8 +1164,9 @@ typedef enum state S_XDEATHSTATE, S_RAISESTATE, - // Thok + // Thok boom effect and spin trail S_THOK, + S_THOKEFFECT, // Player S_PLAY_STND, @@ -4312,7 +4314,8 @@ typedef enum mobj_type MT_NULL, MT_UNKNOWN, - MT_THOK, // Thok! mobj + MT_THOK, // Spin trail mobj + MT_THOKEFFECT, // Thok boom effect MT_PLAYER, MT_TAILSOVERLAY, // c: MT_METALJETFUME, From 863dc6be38529326ab88580bf02a94a6482add46 Mon Sep 17 00:00:00 2001 From: Radicalicious Date: Fri, 18 Jun 2021 00:55:14 -0400 Subject: [PATCH 004/227] Update info.c --- src/info.c | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/info.c b/src/info.c index 29a79b1d6..351d1ea88 100644 --- a/src/info.c +++ b/src/info.c @@ -33,7 +33,8 @@ char sprnames[NUMSPRITES + 1][5] = "NULL", // invisible object "UNKN", - "THOK", // Thok! mobj + "THOK", // Spin trail mobj + "THKE", // Thok boom effect "PLAY", // Enemies @@ -697,8 +698,9 @@ state_t states[NUMSTATES] = {SPR_UNKN, FF_FULLBRIGHT, -1, {A_InfoState}, 5, 0, S_NULL}, // S_XDEATHSTATE {SPR_UNKN, FF_FULLBRIGHT, -1, {A_InfoState}, 6, 0, S_NULL}, // S_RAISESTATE - // Thok + // Spin trail and thok boom effect {SPR_THOK, FF_TRANS50, 8, {NULL}, 0, 0, S_NULL}, // S_THOK + {SPR_THKE, FF_TRANS50|FF_PAPERSPRITE, 8, {NULL}, 0, 0, S_NULL}, // S_THOKEFFECT // Player {SPR_PLAY, SPR2_STND|FF_ANIMATE, 105, {NULL}, 0, 7, S_PLAY_WAIT}, // S_PLAY_STND @@ -4011,6 +4013,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags S_NULL // raisestate }, + + { // MT_THOKEFFECT + -1, // doomednum + S_THOK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 8, // speed + 32*FRACUNIT, // radius + 64*FRACUNIT, // height + 0, // display offset + 16, // mass + 0, // damage + sfx_None, // activesound + MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags + S_NULL // raisestate + }, { // MT_PLAYER -1, // doomednum From 87bcdb8643503f0ad765366223649517e1cbf7da Mon Sep 17 00:00:00 2001 From: Radicalicious Date: Fri, 18 Jun 2021 00:57:33 -0400 Subject: [PATCH 005/227] Update p_user.c --- src/p_user.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/p_user.c b/src/p_user.c index c5f919c78..f920ba4a6 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2075,7 +2075,19 @@ void P_SpawnThokMobj(player_t *player) if (type == MT_GHOST) mobj = P_SpawnGhostMobj(player->mo); // virtually does everything here for us - else + else if (type == MT_THOKEFFECT) // Thok boom effect for Sonic + { + mobj = P_SpawnMobjFromMobj(player->mo, 0, 0, player->mo->scale * 24, type); + mobj->angle = player->mo->angle + ANGLE_90; + mobj->fuse = 7; + mobj->scale = FRACUNIT / 3; + mobj->destscale = 10*FRACUNIT; + mobj->colorized = true; + mobj->color = player->mo->color; + mobj->momx = -player->mo->momx / 2; + mobj->momy = -player->mo->momy / 2; + } + else // Normal thok object handling { if (player->mo->eflags & MFE_VERTICALFLIP) zheight = player->mo->z + player->mo->height + FixedDiv(P_GetPlayerHeight(player) - player->mo->height, 3*FRACUNIT) - FixedMul(mobjinfo[type].height, player->mo->scale); From 1160b97a41fd535c0d74918caff0f8a33cebaafc Mon Sep 17 00:00:00 2001 From: Radicalicious Date: Fri, 18 Jun 2021 01:08:41 -0400 Subject: [PATCH 006/227] Hopefully fix ghosts without fucking my next branch --- src/g_demo.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/g_demo.c b/src/g_demo.c index 9d3b86015..9f955f13b 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -784,6 +784,19 @@ void G_GhostTicker(void) mobj = P_SpawnGhostMobj(g->mo); // does a large portion of the work for us mobj->frame = (mobj->frame & ~FF_FRAMEMASK)|tr_trans60<mo, 0, 0, g->mo->scale * 24, type); + mobj->angle = g->mo->angle + ANGLE_90; + mobj->fuse = 7; + mobj->scale = FRACUNIT / 3; + mobj->destscale = 10*FRACUNIT; + mobj->colorized = true; + mobj->color = g->mo->color; + mobj->momx = -g->mo->momx / 2; + mobj->momy = -g->mo->momy / 2; + } + else { mobj = P_SpawnMobjFromMobj(g->mo, 0, 0, -FixedDiv(FixedMul(g->mo->info->height, g->mo->scale) - g->mo->height,3*FRACUNIT), MT_THOK); @@ -1074,6 +1087,18 @@ void G_ReadMetalTic(mobj_t *metal) { mobj = P_SpawnGhostMobj(metal); // does a large portion of the work for us } + else if (type == MT_THOKEFFECT) + { + mobj = P_SpawnMobjFromMobj(metal, 0, 0, metal->scale * 24, type); + mobj->angle = metal->angle + ANGLE_90; + mobj->fuse = 7; + mobj->scale = FRACUNIT / 3; + mobj->destscale = 10*FRACUNIT; + mobj->colorized = true; + mobj->color = metal->color; + mobj->momx = -metal->momx / 2; + mobj->momy = -metal->momy / 2; + } else { mobj = P_SpawnMobjFromMobj(metal, 0, 0, -FixedDiv(FixedMul(metal->info->height, metal->scale) - metal->height,3*FRACUNIT), MT_THOK); From 44bdfd8eafb466b710ed98dd4f71363798ce0395 Mon Sep 17 00:00:00 2001 From: Radicalicious Date: Fri, 18 Jun 2021 02:40:44 -0400 Subject: [PATCH 007/227] Update hw_light.c --- src/hardware/hw_light.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hardware/hw_light.c b/src/hardware/hw_light.c index 987d70c69..71c450d5b 100644 --- a/src/hardware/hw_light.c +++ b/src/hardware/hw_light.c @@ -138,6 +138,7 @@ light_t *t_lspr[NUMSPRITES] = &lspr[NOLIGHT], // SPR_UNKN &lspr[NOLIGHT], // SPR_THOK + &lspr[NOLIGHT], // SPR_THKE &lspr[SUPERSONIC_L],// SPR_PLAY // Enemies From afb2a9b1dfd80685c1e903c7bd5a3ad742a90996 Mon Sep 17 00:00:00 2001 From: Radicalicious Date: Fri, 18 Jun 2021 02:41:36 -0400 Subject: [PATCH 008/227] Update info.c --- src/info.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/info.c b/src/info.c index 351d1ea88..d8b07a9f5 100644 --- a/src/info.c +++ b/src/info.c @@ -4037,7 +4037,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 16, // mass 0, // damage sfx_None, // activesound - MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags S_NULL // raisestate }, From 3e2e1399a372ccec3d8ec24e64c28ef3cb15c389 Mon Sep 17 00:00:00 2001 From: Radicalicious Date: Fri, 18 Jun 2021 02:49:07 -0400 Subject: [PATCH 009/227] Update g_demo.c --- src/g_demo.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/g_demo.c b/src/g_demo.c index 9f955f13b..962582493 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -786,11 +786,11 @@ void G_GhostTicker(void) } else if (type == MT_THOKEFFECT) { - mobj = P_SpawnMobjFromMobj(g->mo, 0, 0, g->mo->scale * 24, type); + mobj = P_SpawnMobjFromMobj(g->mo, 0, 0, FRACUNIT * 24, type); mobj->angle = g->mo->angle + ANGLE_90; mobj->fuse = 7; - mobj->scale = FRACUNIT / 3; - mobj->destscale = 10*FRACUNIT; + mobj->scale = g->mo->scale / 3; + mobj->destscale = 10 * g->mo->scale; mobj->colorized = true; mobj->color = g->mo->color; mobj->momx = -g->mo->momx / 2; @@ -1089,11 +1089,11 @@ void G_ReadMetalTic(mobj_t *metal) } else if (type == MT_THOKEFFECT) { - mobj = P_SpawnMobjFromMobj(metal, 0, 0, metal->scale * 24, type); + mobj = P_SpawnMobjFromMobj(metal, 0, 0, FRACUNIT * 24, type); mobj->angle = metal->angle + ANGLE_90; mobj->fuse = 7; - mobj->scale = FRACUNIT / 3; - mobj->destscale = 10*FRACUNIT; + mobj->scale = metal->scale / 3; + mobj->destscale = 10 * metal->scale; mobj->colorized = true; mobj->color = metal->color; mobj->momx = -metal->momx / 2; From bde1b08cbe647ed167306cae224f54a8fbd72b2f Mon Sep 17 00:00:00 2001 From: Radicalicious Date: Fri, 18 Jun 2021 02:50:08 -0400 Subject: [PATCH 010/227] Update p_user.c --- src/p_user.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/p_user.c b/src/p_user.c index f920ba4a6..83b95c3c8 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2077,11 +2077,11 @@ void P_SpawnThokMobj(player_t *player) mobj = P_SpawnGhostMobj(player->mo); // virtually does everything here for us else if (type == MT_THOKEFFECT) // Thok boom effect for Sonic { - mobj = P_SpawnMobjFromMobj(player->mo, 0, 0, player->mo->scale * 24, type); + mobj = P_SpawnMobjFromMobj(player->mo, 0, 0, FRACUNIT * 24, type); mobj->angle = player->mo->angle + ANGLE_90; mobj->fuse = 7; - mobj->scale = FRACUNIT / 3; - mobj->destscale = 10*FRACUNIT; + mobj->scale = player->mo->scale / 3; + mobj->destscale = 10 * player->mo->scale; mobj->colorized = true; mobj->color = player->mo->color; mobj->momx = -player->mo->momx / 2; From 38be20af960aa6d421022e284c12ce56dd2fe314 Mon Sep 17 00:00:00 2001 From: Radicalicious Date: Fri, 18 Jun 2021 11:28:50 -0400 Subject: [PATCH 011/227] Fix indentation --- src/g_demo.c | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/g_demo.c b/src/g_demo.c index 962582493..89863f6c0 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -786,15 +786,15 @@ void G_GhostTicker(void) } else if (type == MT_THOKEFFECT) { - mobj = P_SpawnMobjFromMobj(g->mo, 0, 0, FRACUNIT * 24, type); - mobj->angle = g->mo->angle + ANGLE_90; - mobj->fuse = 7; - mobj->scale = g->mo->scale / 3; - mobj->destscale = 10 * g->mo->scale; - mobj->colorized = true; - mobj->color = g->mo->color; - mobj->momx = -g->mo->momx / 2; - mobj->momy = -g->mo->momy / 2; + mobj = P_SpawnMobjFromMobj(g->mo, 0, 0, FRACUNIT * 24, type); + mobj->angle = g->mo->angle + ANGLE_90; + mobj->fuse = 7; + mobj->scale = g->mo->scale / 3; + mobj->destscale = 10 * g->mo->scale; + mobj->colorized = true; + mobj->color = g->mo->color; + mobj->momx = -g->mo->momx / 2; + mobj->momy = -g->mo->momy / 2; } else @@ -1090,14 +1090,14 @@ void G_ReadMetalTic(mobj_t *metal) else if (type == MT_THOKEFFECT) { mobj = P_SpawnMobjFromMobj(metal, 0, 0, FRACUNIT * 24, type); - mobj->angle = metal->angle + ANGLE_90; - mobj->fuse = 7; - mobj->scale = metal->scale / 3; - mobj->destscale = 10 * metal->scale; - mobj->colorized = true; - mobj->color = metal->color; - mobj->momx = -metal->momx / 2; - mobj->momy = -metal->momy / 2; + mobj->angle = metal->angle + ANGLE_90; + mobj->fuse = 7; + mobj->scale = metal->scale / 3; + mobj->destscale = 10 * metal->scale; + mobj->colorized = true; + mobj->color = metal->color; + mobj->momx = -metal->momx / 2; + mobj->momy = -metal->momy / 2; } else { From a6ab670294d908db328a8508fb1f9d9e3d6aeb30 Mon Sep 17 00:00:00 2001 From: Radicalicious Date: Fri, 18 Jun 2021 11:29:43 -0400 Subject: [PATCH 012/227] Update info.c --- src/info.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/info.c b/src/info.c index d8b07a9f5..bc5e23055 100644 --- a/src/info.c +++ b/src/info.c @@ -4016,7 +4016,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { // MT_THOKEFFECT -1, // doomednum - S_THOK, // spawnstate + S_THOKEFFECT, // spawnstate 1000, // spawnhealth S_NULL, // seestate sfx_None, // seesound From 4129140b4ac967e3fc1f8e03acfb044ec3d4443a Mon Sep 17 00:00:00 2001 From: Radicalicious Date: Sun, 20 Jun 2021 21:01:40 -0400 Subject: [PATCH 013/227] Update p_user.c --- src/p_user.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_user.c b/src/p_user.c index 83b95c3c8..9a68e6e9e 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2077,7 +2077,7 @@ void P_SpawnThokMobj(player_t *player) mobj = P_SpawnGhostMobj(player->mo); // virtually does everything here for us else if (type == MT_THOKEFFECT) // Thok boom effect for Sonic { - mobj = P_SpawnMobjFromMobj(player->mo, 0, 0, FRACUNIT * 24, type); + mobj = P_SpawnMobjFromMobj(player->mo, 0, 0, FixedDiv(player->mo->height, player->mo->scale)*3/4, type); mobj->angle = player->mo->angle + ANGLE_90; mobj->fuse = 7; mobj->scale = player->mo->scale / 3; From e6fbd482adfb5452fd924eed0ab77c77b8e521b3 Mon Sep 17 00:00:00 2001 From: Radicalicious Date: Sun, 20 Jun 2021 21:02:32 -0400 Subject: [PATCH 014/227] Update g_demo.c --- src/g_demo.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/g_demo.c b/src/g_demo.c index 89863f6c0..05d173dcf 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -786,7 +786,7 @@ void G_GhostTicker(void) } else if (type == MT_THOKEFFECT) { - mobj = P_SpawnMobjFromMobj(g->mo, 0, 0, FRACUNIT * 24, type); + mobj = P_SpawnMobjFromMobj(g->mo, 0, 0, FixedDiv(g->mo->height, g->mo->scale)*3/4, type); mobj->angle = g->mo->angle + ANGLE_90; mobj->fuse = 7; mobj->scale = g->mo->scale / 3; @@ -1089,7 +1089,7 @@ void G_ReadMetalTic(mobj_t *metal) } else if (type == MT_THOKEFFECT) { - mobj = P_SpawnMobjFromMobj(metal, 0, 0, FRACUNIT * 24, type); + mobj = P_SpawnMobjFromMobj(metal, 0, 0, FixedDiv(metal->height, metal->scale)*3/4, type); mobj->angle = metal->angle + ANGLE_90; mobj->fuse = 7; mobj->scale = metal->scale / 3; From f2472d88b8a5c0b4b873f2491b1b3f3d2374cfe7 Mon Sep 17 00:00:00 2001 From: lachablock Date: Mon, 9 Aug 2021 17:30:05 +1000 Subject: [PATCH 015/227] Fix some crashes --- src/hardware/hw_md2.c | 2 +- src/hardware/hw_model.c | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c index 6f9863406..81e20f069 100644 --- a/src/hardware/hw_md2.c +++ b/src/hardware/hw_md2.c @@ -492,7 +492,7 @@ void HWR_InitModels(void) CONS_Printf("HWR_InitModels()...\n"); if (numskins && md2_playermodels == NULL) - md2_playermodels = Z_Malloc(sizeof(md2_t), PU_STATIC, NULL); + md2_playermodels = Z_Malloc(sizeof(md2_t) * numskins, PU_STATIC, NULL); for (s = 0; s < numskins; s++) { diff --git a/src/hardware/hw_model.c b/src/hardware/hw_model.c index 86e2a0c70..52f9ce6ea 100644 --- a/src/hardware/hw_model.c +++ b/src/hardware/hw_model.c @@ -239,11 +239,13 @@ void HWR_ReloadModels(void) size_t i; INT32 s; - for (s = 0; s < numskins; s++) - { - if (md2_playermodels[s].model) - LoadModelSprite2(md2_playermodels[s].model); - } + if (md2_playermodels != NULL) + for (s = 0; s < numskins; s++) + { + CONS_Printf("%u\n", s); + if (md2_playermodels[s].model) + LoadModelSprite2(md2_playermodels[s].model); + } for (i = 0; i < NUMSPRITES; i++) { From 2aa9bb59ef15570248a39c1eb4b6adcbb5a2e821 Mon Sep 17 00:00:00 2001 From: Jaime Ita Passos Date: Mon, 9 Aug 2021 15:57:07 -0300 Subject: [PATCH 016/227] Memory management fixes Date: Mon Aug 9 15:57:07 2021 -0300 --- src/command.c | 2 +- src/d_netcmd.c | 52 ++++----- src/deh_soc.c | 2 +- src/f_finale.c | 14 +-- src/g_demo.c | 18 +-- src/g_game.c | 22 ++-- src/hardware/hw_main.c | 12 +- src/hardware/hw_md2.c | 238 +++++++++++--------------------------- src/hardware/hw_md2.h | 8 +- src/hardware/hw_md2load.c | 4 +- src/hardware/hw_md3load.c | 4 +- src/hardware/hw_model.c | 29 ++--- src/hardware/hw_model.h | 9 +- src/hu_stuff.c | 2 +- src/lua_hudlib.c | 8 +- src/lua_mobjlib.c | 4 +- src/lua_skinlib.c | 12 +- src/m_menu.c | 66 +++++------ src/p_enemy.c | 16 +-- src/p_mobj.c | 20 ++-- src/p_saveg.c | 8 +- src/p_setup.c | 44 +++---- src/p_spec.c | 2 +- src/p_user.c | 30 ++--- src/r_draw.c | 2 +- src/r_picformats.c | 2 +- src/r_skins.c | 28 ++--- src/r_skins.h | 3 +- src/r_things.c | 20 +--- src/st_stuff.c | 22 ++-- src/v_video.c | 4 +- src/y_inter.c | 16 +-- 32 files changed, 303 insertions(+), 420 deletions(-) diff --git a/src/command.c b/src/command.c index ae4a7178e..2897922e1 100644 --- a/src/command.c +++ b/src/command.c @@ -1944,7 +1944,7 @@ static void CV_SetValueMaybeStealth(consvar_t *var, INT32 value, boolean stealth if ((value < 0) || (value >= numskins)) tmpskin = "None"; else - tmpskin = skins[value].name; + tmpskin = skins[value]->name; strncpy(val, tmpskin, SKINNAMESIZE); } else diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 0a7172b12..d5da66066 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -1187,9 +1187,9 @@ static void ForceAllSkins(INT32 forcedskin) if (!dedicated) // But don't do this for dedicated servers, of course. { if (i == consoleplayer) - CV_StealthSet(&cv_skin, skins[forcedskin].name); + CV_StealthSet(&cv_skin, skins[forcedskin]->name); else if (i == secondarydisplayplayer) - CV_StealthSet(&cv_skin2, skins[forcedskin].name); + CV_StealthSet(&cv_skin2, skins[forcedskin]->name); } } } @@ -1221,8 +1221,8 @@ static void SendNameAndColor(void) CV_StealthSetValue(&cv_playercolor, players[consoleplayer].skincolor); else if (skincolors[atoi(cv_playercolor.defaultvalue)].accessible) CV_StealthSet(&cv_playercolor, cv_playercolor.defaultvalue); - else if (skins[players[consoleplayer].skin].prefcolor && skincolors[skins[players[consoleplayer].skin].prefcolor].accessible) - CV_StealthSetValue(&cv_playercolor, skins[players[consoleplayer].skin].prefcolor); + else if (skins[players[consoleplayer].skin]->prefcolor && skincolors[skins[players[consoleplayer].skin]->prefcolor].accessible) + CV_StealthSetValue(&cv_playercolor, skins[players[consoleplayer].skin]->prefcolor); else { UINT16 i = 0; while (iname)) return; players[consoleplayer].availabilities = R_GetSkinAvailabilities(); @@ -1257,7 +1257,7 @@ static void SendNameAndColor(void) if (metalrecording) { // Starring Metal Sonic as themselves, obviously. SetPlayerSkinByNum(consoleplayer, 5); - CV_StealthSet(&cv_skin, skins[5].name); + CV_StealthSet(&cv_skin, skins[5]->name); } else if ((foundskin = R_SkinAvailable(cv_skin.string)) != -1 && R_SkinUsable(consoleplayer, foundskin)) { @@ -1268,11 +1268,11 @@ static void SendNameAndColor(void) //notsame = (cv_skin.value != players[consoleplayer].skin); SetPlayerSkin(consoleplayer, cv_skin.string); - CV_StealthSet(&cv_skin, skins[cv_skin.value].name); + CV_StealthSet(&cv_skin, skins[cv_skin.value]->name); /*if (notsame) { - CV_StealthSetValue(&cv_playercolor, skins[cv_skin.value].prefcolor); + CV_StealthSetValue(&cv_playercolor, skins[cv_skin.value]->prefcolor); players[consoleplayer].skincolor = cv_playercolor.value % numskincolors; @@ -1283,7 +1283,7 @@ static void SendNameAndColor(void) else { cv_skin.value = players[consoleplayer].skin; - CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name); + CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin]->name); // will always be same as current SetPlayerSkin(consoleplayer, cv_skin.string); } @@ -1306,7 +1306,7 @@ static void SendNameAndColor(void) // Don't change skin if the server doesn't want you to. if (!CanChangeSkin(consoleplayer)) - CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name); + CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin]->name); // check if player has the skin loaded (cv_skin may have // the name of a skin that was available in the previous game) @@ -1314,7 +1314,7 @@ static void SendNameAndColor(void) if ((cv_skin.value < 0) || !R_SkinUsable(consoleplayer, cv_skin.value)) { INT32 defaultSkinNum = GetPlayerDefaultSkin(consoleplayer); - CV_StealthSet(&cv_skin, skins[defaultSkinNum].name); + CV_StealthSet(&cv_skin, skins[defaultSkinNum]->name); cv_skin.value = defaultSkinNum; } @@ -1355,8 +1355,8 @@ static void SendNameAndColor2(void) CV_StealthSetValue(&cv_playercolor2, players[secondplaya].skincolor); else if (skincolors[atoi(cv_playercolor2.defaultvalue)].accessible) CV_StealthSet(&cv_playercolor, cv_playercolor2.defaultvalue); - else if (skins[players[secondplaya].skin].prefcolor && skincolors[skins[players[secondplaya].skin].prefcolor].accessible) - CV_StealthSetValue(&cv_playercolor2, skins[players[secondplaya].skin].prefcolor); + else if (skins[players[secondplaya].skin]->prefcolor && skincolors[skins[players[secondplaya].skin]->prefcolor].accessible) + CV_StealthSetValue(&cv_playercolor2, skins[players[secondplaya].skin]->prefcolor); else { UINT16 i = 0; while (iname); } else if ((foundskin = R_SkinAvailable(cv_skin2.string)) != -1 && R_SkinUsable(secondplaya, foundskin)) { @@ -1408,11 +1408,11 @@ static void SendNameAndColor2(void) //notsame = (cv_skin2.value != players[secondplaya].skin); SetPlayerSkin(secondplaya, cv_skin2.string); - CV_StealthSet(&cv_skin2, skins[cv_skin2.value].name); + CV_StealthSet(&cv_skin2, skins[cv_skin2.value]->name); /*if (notsame) { - CV_StealthSetValue(&cv_playercolor2, skins[players[secondplaya].skin].prefcolor); + CV_StealthSetValue(&cv_playercolor2, skins[players[secondplaya].skin]->prefcolor); players[secondplaya].skincolor = cv_playercolor2.value % numskincolors; @@ -1423,7 +1423,7 @@ static void SendNameAndColor2(void) else { cv_skin2.value = players[secondplaya].skin; - CV_StealthSet(&cv_skin2, skins[players[secondplaya].skin].name); + CV_StealthSet(&cv_skin2, skins[players[secondplaya].skin]->name); // will always be same as current SetPlayerSkin(secondplaya, cv_skin2.string); } @@ -1512,7 +1512,7 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) { illegalMask &= ~(1 << i); } - + if ((p->availabilities & illegalMask) != 0) { kick = true; @@ -1534,9 +1534,9 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) SetPlayerSkinByNum(playernum, forcedskin); if (playernum == consoleplayer) - CV_StealthSet(&cv_skin, skins[forcedskin].name); + CV_StealthSet(&cv_skin, skins[forcedskin]->name); else if (playernum == secondarydisplayplayer) - CV_StealthSet(&cv_skin2, skins[forcedskin].name); + CV_StealthSet(&cv_skin2, skins[forcedskin]->name); } else SetPlayerSkinByNum(playernum, skin); @@ -2112,7 +2112,7 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) if (modeattacking) { SetPlayerSkinByNum(0, cv_chooseskin.value-1); - players[0].skincolor = skins[players[0].skin].prefcolor; + players[0].skincolor = skins[players[0].skin]->prefcolor; CV_StealthSetValue(&cv_playercolor, players[0].skincolor); } @@ -4467,7 +4467,7 @@ static void ForceSkin_OnChange(void) CONS_Printf("The server has lifted the forced skin restrictions.\n"); else { - CONS_Printf("The server is restricting all players to skin \"%s\".\n",skins[cv_forceskin.value].name); + CONS_Printf("The server is restricting all players to skin \"%s\".\n",skins[cv_forceskin.value]->name); ForceAllSkins(cv_forceskin.value); } } @@ -4508,7 +4508,7 @@ static void Skin_OnChange(void) if (!(cv_debug || devparm) && !(multiplayer || netgame) // In single player. && (gamestate != GS_WAITINGPLAYERS)) // allows command line -warp x +skin y { - CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name); + CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin]->name); return; } @@ -4517,7 +4517,7 @@ static void Skin_OnChange(void) else { CONS_Alert(CONS_NOTICE, M_GetText("You can't change your skin at the moment.\n")); - CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name); + CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin]->name); } } @@ -4536,7 +4536,7 @@ static void Skin2_OnChange(void) else { CONS_Alert(CONS_NOTICE, M_GetText("You can't change your skin at the moment.\n")); - CV_StealthSet(&cv_skin2, skins[players[secondarydisplayplayer].skin].name); + CV_StealthSet(&cv_skin2, skins[players[secondarydisplayplayer].skin]->name); } } @@ -4554,7 +4554,7 @@ static void Color_OnChange(void) { if (!(cv_debug || devparm) && !(multiplayer || netgame)) // In single player. { - CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name); + CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin]->name); return; } diff --git a/src/deh_soc.c b/src/deh_soc.c index eb08cf0c5..4c63a4d6a 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -1055,7 +1055,7 @@ void readspriteinfo(MYFILE *f, INT32 num, boolean sprite2) for (i = 0; i < foundskins; i++) { size_t skinnum = skinnumbers[i]; - skin_t *skin = &skins[skinnum]; + skin_t *skin = skins[skinnum]; spriteinfo_t *sprinfo = skin->sprinfo; M_Memcpy(&sprinfo[num], info, sizeof(spriteinfo_t)); } diff --git a/src/f_finale.c b/src/f_finale.c index 4d9a8f825..27f62ec5b 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -1624,9 +1624,9 @@ void F_GameEvaluationDrawer(void) rtatext = (marathonmode & MA_INGAME) ? "In-game timer" : "RTA timer"; cuttext = (marathonmode & MA_NOCUTSCENES) ? "" : " w/ cutscenes"; if (botskin) - endingtext = va("%s & %s, %s%s", skins[players[consoleplayer].skin].realname, skins[botskin-1].realname, rtatext, cuttext); + endingtext = va("%s & %s, %s%s", skins[players[consoleplayer].skin]->realname, skins[botskin-1]->realname, rtatext, cuttext); else - endingtext = va("%s, %s%s", skins[players[consoleplayer].skin].realname, rtatext, cuttext); + endingtext = va("%s, %s%s", skins[players[consoleplayer].skin]->realname, rtatext, cuttext); V_DrawCenteredString(BASEVIDWIDTH/2, 182, V_SNAPTOBOTTOM|(ultimatemode ? V_REDMAP : V_YELLOWMAP), endingtext); } } @@ -1748,9 +1748,9 @@ static void F_CacheEnding(void) UINT8 skinnum = players[consoleplayer].skin; spritedef_t *sprdef; spriteframe_t *sprframe; - if (skins[skinnum].sprites[SPR2_XTRA].numframes > (XTRA_ENDING+2)) + if (skins[skinnum]->sprites[SPR2_XTRA].numframes > (XTRA_ENDING+2)) { - sprdef = &skins[skinnum].sprites[SPR2_XTRA]; + sprdef = &skins[skinnum]->sprites[SPR2_XTRA]; // character head, skin specific sprframe = &sprdef->spriteframes[XTRA_ENDING]; endfwrk[0] = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH_LOWPRIORITY); @@ -2196,7 +2196,7 @@ void F_EndingDrawer(void) boolean donttouch = false; const char *str; if (goodending) - str = va("[S] %s: Engage.", skins[players[consoleplayer].skin].realname); + str = va("[S] %s: Engage.", skins[players[consoleplayer].skin]->realname); else str = "[S] Eggman: Abscond."; @@ -3576,7 +3576,7 @@ void F_StartContinue(void) S_ChangeMusicInternal("_conti", false); S_StopSounds(); - contskins[0] = &skins[players[consoleplayer].skin]; + contskins[0] = skins[players[consoleplayer].skin]; cont_spr2[0][0] = P_GetSkinSprite2(contskins[0], SPR2_CNT1, NULL); cont_spr2[0][2] = contskins[0]->contangle & 7; contcolormaps[0] = R_GetTranslationColormap(players[consoleplayer].skin, players[consoleplayer].skincolor, GTC_CACHE); @@ -3592,7 +3592,7 @@ void F_StartContinue(void) else // HACK secondplaya = 1; - contskins[1] = &skins[players[secondplaya].skin]; + contskins[1] = skins[players[secondplaya].skin]; cont_spr2[1][0] = P_GetSkinSprite2(contskins[1], SPR2_CNT4, NULL); cont_spr2[1][2] = (contskins[1]->contangle >> 3) & 7; contcolormaps[1] = R_GetTranslationColormap(players[secondplaya].skin, players[secondplaya].skincolor, GTC_CACHE); diff --git a/src/g_demo.c b/src/g_demo.c index 701f930e5..5187bf07e 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -488,7 +488,7 @@ void G_WriteGhostTic(mobj_t *ghost) if (ghost->player->followmobj->colorized) followtic |= FZT_COLORIZED; if (followtic & FZT_SKIN) - WRITEUINT8(demo_p,(UINT8)(((skin_t *)(ghost->player->followmobj->skin))-skins)); + WRITEUINT8(demo_p,(UINT8)(((skin_t *)ghost->player->followmobj->skin)->skinnum)); oldghost.flags2 |= MF2_AMBUSH; } @@ -757,7 +757,7 @@ void G_GhostTicker(void) g->mo->color = SKINCOLOR_WHITE; break; case GHC_NIGHTSSKIN: // not actually a colour - g->mo->skin = &skins[DEFAULTNIGHTSSKIN]; + g->mo->skin = skins[DEFAULTNIGHTSSKIN]; break; } } @@ -900,7 +900,7 @@ void G_GhostTicker(void) follow->colorized = true; if (followtic & FZT_SKIN) - follow->skin = &skins[READUINT8(g->p)]; + follow->skin = skins[READUINT8(g->p)]; } if (follow) { @@ -1144,7 +1144,7 @@ void G_ReadMetalTic(mobj_t *metal) follow->colorized = true; if (followtic & FZT_SKIN) - follow->skin = &skins[READUINT8(metal_p)]; + follow->skin = skins[READUINT8(metal_p)]; } if (follow) { @@ -1329,7 +1329,7 @@ void G_WriteMetalTic(mobj_t *metal) if (metal->player->followmobj->colorized) followtic |= FZT_COLORIZED; if (followtic & FZT_SKIN) - WRITEUINT8(demo_p,(UINT8)(((skin_t *)(metal->player->followmobj->skin))-skins)); + WRITEUINT8(demo_p,(UINT8)(((skin_t *)metal->player->followmobj->skin)->skinnum)); oldmetal.flags2 |= MF2_AMBUSH; } @@ -1976,7 +1976,7 @@ void G_DoPlayDemo(char *defdemoname) G_InitNew(false, G_BuildMapName(gamemap), true, true, false); // Set color - players[0].skincolor = skins[players[0].skin].prefcolor; + players[0].skincolor = skins[players[0].skin]->prefcolor; for (i = 0; i < numskincolors; i++) if (!stricmp(skincolors[i].name,color)) { @@ -2221,11 +2221,11 @@ void G_AddGhost(char *defdemoname) gh->oldmo.z = gh->mo->z; // Set skin - gh->mo->skin = &skins[0]; + gh->mo->skin = skins[0]; for (i = 0; i < numskins; i++) - if (!stricmp(skins[i].name,skin)) + if (!stricmp(skins[i]->name,skin)) { - gh->mo->skin = &skins[i]; + gh->mo->skin = skins[i]; break; } gh->oldmo.skin = gh->mo->skin; diff --git a/src/g_game.c b/src/g_game.c index 13fdab877..6b3354de7 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -582,14 +582,14 @@ static void G_UpdateRecordReplays(void) I_Error("Out of memory for replay filepath\n"); sprintf(gpath,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(gamemap)); - snprintf(lastdemo, 255, "%s-%s-last.lmp", gpath, skins[cv_chooseskin.value-1].name); + snprintf(lastdemo, 255, "%s-%s-last.lmp", gpath, skins[cv_chooseskin.value-1]->name); if (FIL_FileExists(lastdemo)) { UINT8 *buf; size_t len = FIL_ReadFile(lastdemo, &buf); - snprintf(bestdemo, 255, "%s-%s-time-best.lmp", gpath, skins[cv_chooseskin.value-1].name); + snprintf(bestdemo, 255, "%s-%s-time-best.lmp", gpath, skins[cv_chooseskin.value-1]->name); if (!FIL_FileExists(bestdemo) || G_CmpDemoTime(bestdemo, lastdemo) & 1) { // Better time, save this demo. if (FIL_FileExists(bestdemo)) @@ -598,7 +598,7 @@ static void G_UpdateRecordReplays(void) CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW RECORD TIME!"), M_GetText("Saved replay as"), bestdemo); } - snprintf(bestdemo, 255, "%s-%s-score-best.lmp", gpath, skins[cv_chooseskin.value-1].name); + snprintf(bestdemo, 255, "%s-%s-score-best.lmp", gpath, skins[cv_chooseskin.value-1]->name); if (!FIL_FileExists(bestdemo) || (G_CmpDemoTime(bestdemo, lastdemo) & (1<<1))) { // Better score, save this demo. if (FIL_FileExists(bestdemo)) @@ -607,7 +607,7 @@ static void G_UpdateRecordReplays(void) CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW HIGH SCORE!"), M_GetText("Saved replay as"), bestdemo); } - snprintf(bestdemo, 255, "%s-%s-rings-best.lmp", gpath, skins[cv_chooseskin.value-1].name); + snprintf(bestdemo, 255, "%s-%s-rings-best.lmp", gpath, skins[cv_chooseskin.value-1]->name); if (!FIL_FileExists(bestdemo) || (G_CmpDemoTime(bestdemo, lastdemo) & (1<<2))) { // Better rings, save this demo. if (FIL_FileExists(bestdemo)) @@ -704,14 +704,14 @@ void G_SetNightsRecords(void) I_Error("Out of memory for replay filepath\n"); sprintf(gpath,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(gamemap)); - snprintf(lastdemo, 255, "%s-%s-last.lmp", gpath, skins[cv_chooseskin.value-1].name); + snprintf(lastdemo, 255, "%s-%s-last.lmp", gpath, skins[cv_chooseskin.value-1]->name); if (FIL_FileExists(lastdemo)) { UINT8 *buf; size_t len = FIL_ReadFile(lastdemo, &buf); - snprintf(bestdemo, 255, "%s-%s-time-best.lmp", gpath, skins[cv_chooseskin.value-1].name);; + snprintf(bestdemo, 255, "%s-%s-time-best.lmp", gpath, skins[cv_chooseskin.value-1]->name);; if (!FIL_FileExists(bestdemo) || G_CmpDemoTime(bestdemo, lastdemo) & 1) { // Better time, save this demo. if (FIL_FileExists(bestdemo)) @@ -720,7 +720,7 @@ void G_SetNightsRecords(void) CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW RECORD TIME!"), M_GetText("Saved replay as"), bestdemo); } - snprintf(bestdemo, 255, "%s-%s-score-best.lmp", gpath, skins[cv_chooseskin.value-1].name); + snprintf(bestdemo, 255, "%s-%s-score-best.lmp", gpath, skins[cv_chooseskin.value-1]->name); if (!FIL_FileExists(bestdemo) || (G_CmpDemoTime(bestdemo, lastdemo) & (1<<1))) { // Better score, save this demo. if (FIL_FileExists(bestdemo)) @@ -4733,7 +4733,7 @@ cleanup: // void G_DeferedInitNew(boolean pultmode, const char *mapname, INT32 pickedchar, boolean SSSG, boolean FLS) { - UINT16 color = skins[pickedchar].prefcolor; + UINT16 color = skins[pickedchar]->prefcolor; paused = false; if (demoplayback) @@ -4746,7 +4746,7 @@ void G_DeferedInitNew(boolean pultmode, const char *mapname, INT32 pickedchar, b if (savedata.lives > 0) { if ((botingame = ((botskin = savedata.botskin) != 0))) - botcolor = skins[botskin-1].prefcolor; + botcolor = skins[botskin-1]->prefcolor; } else if (splitscreen != SSSG) { @@ -4754,9 +4754,9 @@ void G_DeferedInitNew(boolean pultmode, const char *mapname, INT32 pickedchar, b SplitScreen_OnChange(); } - color = skins[pickedchar].prefcolor; + color = skins[pickedchar]->prefcolor; SetPlayerSkinByNum(consoleplayer, pickedchar); - CV_StealthSet(&cv_skin, skins[pickedchar].name); + CV_StealthSet(&cv_skin, skins[pickedchar]->name); CV_StealthSetValue(&cv_playercolor, color); if (mapname) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index d413e3bbe..66df7d2cd 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -4870,7 +4870,7 @@ static void HWR_DrawSprites(void) if (spr->mobj && spr->mobj->skin && spr->mobj->sprite == SPR_PLAY) { - if (!cv_glmodels.value || md2_playermodels[(skin_t*)spr->mobj->skin-skins].notfound || md2_playermodels[(skin_t*)spr->mobj->skin-skins].scale < 0.0f) + if (!cv_glmodels.value || !md2_playermodels[((skin_t*)spr->mobj->skin)->skinnum].found || md2_playermodels[((skin_t*)spr->mobj->skin)->skinnum].scale < 0.0f) HWR_DrawSprite(spr); else { @@ -4880,7 +4880,7 @@ static void HWR_DrawSprites(void) } else { - if (!cv_glmodels.value || md2_models[spr->mobj->sprite].notfound || md2_models[spr->mobj->sprite].scale < 0.0f) + if (!cv_glmodels.value || !md2_models[spr->mobj->sprite].found || md2_models[spr->mobj->sprite].scale < 0.0f) HWR_DrawSprite(spr); else { @@ -5029,11 +5029,11 @@ static void HWR_ProjectSprite(mobj_t *thing) if (cv_glmodels.value) //Yellow: Only MD2's dont disappear { if (thing->skin && thing->sprite == SPR_PLAY) - md2 = &md2_playermodels[( (skin_t *)thing->skin - skins )]; + md2 = &md2_playermodels[((skin_t *)thing->skin)->skinnum]; else md2 = &md2_models[thing->sprite]; - if (md2->notfound || md2->scale < 0.0f) + if (!md2->found || md2->scale < 0.0f) return; } else @@ -5359,8 +5359,8 @@ static void HWR_ProjectSprite(mobj_t *thing) } else if (thing->skin && thing->sprite == SPR_PLAY) // This thing is a player! { - size_t skinnum = (skin_t*)thing->skin-skins; - vis->colormap = R_GetTranslationColormap((INT32)skinnum, thing->color, GTC_CACHE); + INT32 skinnum = ((skin_t*)thing->skin)->skinnum; + vis->colormap = R_GetTranslationColormap(skinnum, thing->color, GTC_CACHE); } else vis->colormap = R_GetTranslationColormap(TC_DEFAULT, vis->mobj->color ? vis->mobj->color : SKINCOLOR_CYAN, GTC_CACHE); diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c index 81e20f069..b54e14089 100644 --- a/src/hardware/hw_md2.c +++ b/src/hardware/hw_md2.c @@ -34,6 +34,7 @@ #include "../m_misc.h" #include "../w_wad.h" #include "../z_zone.h" +#include "../r_state.h" #include "../r_things.h" #include "../r_draw.h" #include "../p_tick.h" @@ -72,6 +73,7 @@ md2_t md2_models[NUMSPRITES]; md2_t *md2_playermodels = NULL; +size_t md2_numplayermodels = 0; /* @@ -483,28 +485,6 @@ static boolean nomd2s = false; void HWR_InitModels(void) { size_t i; - INT32 s; - FILE *f; - char name[24], filename[32]; - float scale, offset; - size_t prefixlen; - - CONS_Printf("HWR_InitModels()...\n"); - - if (numskins && md2_playermodels == NULL) - md2_playermodels = Z_Malloc(sizeof(md2_t) * numskins, PU_STATIC, NULL); - - for (s = 0; s < numskins; s++) - { - md2_playermodels[s].scale = -1.0f; - md2_playermodels[s].model = NULL; - md2_playermodels[s].grpatch = NULL; - md2_playermodels[s].notexturefile = false; - md2_playermodels[s].noblendfile = false; - md2_playermodels[s].skin = -1; - md2_playermodels[s].notfound = true; - md2_playermodels[s].error = false; - } for (i = 0; i < NUMSPRITES; i++) { @@ -513,82 +493,23 @@ void HWR_InitModels(void) md2_models[i].grpatch = NULL; md2_models[i].notexturefile = false; md2_models[i].noblendfile = false; - md2_models[i].skin = -1; - md2_models[i].notfound = true; + md2_models[i].found = false; md2_models[i].error = false; } - // read the models.dat file - //Filename checking fixed ~Monster Iestyn and Golden - f = fopen(va("%s"PATHSEP"%s", srb2home, "models.dat"), "rt"); - - if (!f) - { - f = fopen(va("%s"PATHSEP"%s", srb2path, "models.dat"), "rt"); - if (!f) - { - CONS_Printf("%s %s\n", M_GetText("Error while loading models.dat:"), strerror(errno)); - nomd2s = true; - return; - } - } - - // length of the player model prefix - prefixlen = strlen(PLAYERMODELPREFIX); - - while (fscanf(f, "%25s %31s %f %f", name, filename, &scale, &offset) == 4) - { - char *skinname = name; - size_t len = strlen(name); - - // check for the player model prefix. - if (!strnicmp(name, PLAYERMODELPREFIX, prefixlen) && (len > prefixlen)) - { - skinname += prefixlen; - goto addskinmodel; - } - - // add sprite model - if (len == 4) // must be 4 characters long exactly. otherwise it's not a sprite name. - { - for (i = 0; i < NUMSPRITES; i++) - { - if (stricmp(name, sprnames[i]) == 0) - { - md2_models[i].scale = scale; - md2_models[i].offset = offset; - md2_models[i].notfound = false; - strcpy(md2_models[i].filename, filename); - goto modelfound; - } - } - } - -addskinmodel: - // add player model - for (s = 0; s < numskins; s++) - { - if (stricmp(skinname, skins[s].name) == 0) - { - md2_playermodels[s].skin = s; - md2_playermodels[s].scale = scale; - md2_playermodels[s].offset = offset; - md2_playermodels[s].notfound = false; - strcpy(md2_playermodels[s].filename, filename); - goto modelfound; - } - } - -modelfound: - // move on to next line... - continue; - } - fclose(f); + if (numsprites && numskins) + HWR_LoadModels(); } -void HWR_AddPlayerModel(INT32 skin) // For skins that were added after startup +void HWR_LoadModels(void) { + size_t i; + INT32 s; FILE *f; + + // name[24] is used to check for names in the models.dat file that match with sprites or player skins + // sprite names are always 4 characters long, and names is for player skins can be up to 19 characters long + // PLAYERMODELPREFIX is 6 characters long char name[24], filename[32]; float scale, offset; size_t prefixlen; @@ -596,7 +517,23 @@ void HWR_AddPlayerModel(INT32 skin) // For skins that were added after startup if (nomd2s) return; - //CONS_Printf("HWR_AddPlayerModel()...\n"); + // realloc player models table + if (numskins != (INT32)md2_numplayermodels) + { + md2_numplayermodels = (size_t)numskins; + md2_playermodels = Z_Realloc(md2_playermodels, sizeof(md2_t) * md2_numplayermodels, PU_STATIC, NULL); + + for (s = 0; s < numskins; s++) + { + md2_playermodels[s].scale = -1.0f; + md2_playermodels[s].model = NULL; + md2_playermodels[s].grpatch = NULL; + md2_playermodels[s].notexturefile = false; + md2_playermodels[s].noblendfile = false; + md2_playermodels[s].found = false; + md2_playermodels[s].error = false; + } + } // read the models.dat file //Filename checking fixed ~Monster Iestyn and Golden @@ -613,92 +550,57 @@ void HWR_AddPlayerModel(INT32 skin) // For skins that were added after startup } } - // realloc player models table - md2_playermodels = Z_Realloc(md2_playermodels, sizeof(md2_t) * numskins, PU_STATIC, NULL); - // length of the player model prefix prefixlen = strlen(PLAYERMODELPREFIX); - // Check for any models that match the names of player skins! while (fscanf(f, "%25s %31s %f %f", name, filename, &scale, &offset) == 4) { char *skinname = name; size_t len = strlen(name); - // ignore the player model prefix. + // Check for the player model prefix. if (!strnicmp(name, PLAYERMODELPREFIX, prefixlen) && (len > prefixlen)) + { skinname += prefixlen; - - if (stricmp(skinname, skins[skin].name) == 0) - { - md2_playermodels[skin].skin = skin; - md2_playermodels[skin].scale = scale; - md2_playermodels[skin].offset = offset; - md2_playermodels[skin].notfound = false; - strcpy(md2_playermodels[skin].filename, filename); - goto playermodelfound; + goto addskinmodel; } + + // Add sprite models. + // Must be 4 characters long exactly. Otherwise, it's not a sprite name. + if (len == 4) + { + for (i = 0; i < numsprites; i++) + { + if (stricmp(name, sprnames[i]) == 0) + { + md2_models[i].scale = scale; + md2_models[i].offset = offset; + md2_models[i].found = true; + strcpy(md2_models[i].filename, filename); + goto modelfound; + } + } + } + +addskinmodel: + // Add player models. + for (s = 0; s < numskins; s++) + { + if (stricmp(skinname, skins[s]->name) == 0) + { + md2_playermodels[s].scale = scale; + md2_playermodels[s].offset = offset; + md2_playermodels[s].found = true; + strcpy(md2_playermodels[s].filename, filename); + goto modelfound; + } + } + +modelfound: + // Move on to the next line... + continue; } - md2_playermodels[skin].notfound = true; -playermodelfound: - fclose(f); -} - -void HWR_AddSpriteModel(size_t spritenum) // For sprites that were added after startup -{ - FILE *f; - // name[24] is used to check for names in the models.dat file that match with sprites or player skins - // sprite names are always 4 characters long, and names is for player skins can be up to 19 characters long - // PLAYERMODELPREFIX is 6 characters long - char name[24], filename[32]; - float scale, offset; - - if (nomd2s) - return; - - if (spritenum == SPR_PLAY) // Handled already NEWMD2: Per sprite, per-skin check - return; - - // Read the models.dat file - //Filename checking fixed ~Monster Iestyn and Golden - f = fopen(va("%s"PATHSEP"%s", srb2home, "models.dat"), "rt"); - - if (!f) - { - f = fopen(va("%s"PATHSEP"%s", srb2path, "models.dat"), "rt"); - if (!f) - { - CONS_Printf("%s %s\n", M_GetText("Error while loading models.dat:"), strerror(errno)); - nomd2s = true; - return; - } - } - - // Check for any models that match the names of sprite names! - while (fscanf(f, "%25s %31s %f %f", name, filename, &scale, &offset) == 4) - { - // length of the sprite name - size_t len = strlen(name); - if (len != 4) // must be 4 characters long exactly. otherwise it's not a sprite name. - continue; - - // check for the player model prefix. - if (!strnicmp(name, PLAYERMODELPREFIX, strlen(PLAYERMODELPREFIX))) - continue; // that's not a sprite... - - if (stricmp(name, sprnames[spritenum]) == 0) - { - md2_models[spritenum].scale = scale; - md2_models[spritenum].offset = offset; - md2_models[spritenum].notfound = false; - strcpy(md2_models[spritenum].filename, filename); - goto spritemodelfound; - } - } - - md2_models[spritenum].notfound = true; -spritemodelfound: fclose(f); } @@ -1369,8 +1271,8 @@ boolean HWR_DrawModel(gl_vissprite_t *spr) // 2. draw model with correct position, rotation,... if (spr->mobj->skin && spr->mobj->sprite == SPR_PLAY) // Use the player MD2 list if the mobj has a skin and is using the player sprites { - md2 = &md2_playermodels[(skin_t*)spr->mobj->skin-skins]; - md2->skin = (skin_t*)spr->mobj->skin-skins; + INT32 skinnum = ((skin_t*)spr->mobj->skin)->skinnum; + md2 = &md2_playermodels[skinnum]; sprinfo = &((skin_t *)spr->mobj->skin)->sprinfo[spr->mobj->sprite2]; } else @@ -1468,7 +1370,7 @@ boolean HWR_DrawModel(gl_vissprite_t *spr) skinnum = TC_RAINBOW; } else if (spr->mobj->skin && spr->mobj->sprite == SPR_PLAY) - skinnum = (INT32)((skin_t*)spr->mobj->skin-skins); + skinnum = ((skin_t*)spr->mobj->skin)->skinnum; else skinnum = TC_DEFAULT; } diff --git a/src/hardware/hw_md2.h b/src/hardware/hw_md2.h index 4b36df7f8..ff14f6274 100644 --- a/src/hardware/hw_md2.h +++ b/src/hardware/hw_md2.h @@ -31,17 +31,17 @@ typedef struct boolean notexturefile; // true if texture file was not found void *blendgrpatch; boolean noblendfile; // true if blend texture file was not found - boolean notfound; - INT32 skin; + boolean found; boolean error; } md2_t; extern md2_t md2_models[NUMSPRITES]; extern md2_t *md2_playermodels; +extern size_t md2_numplayermodels; void HWR_InitModels(void); -void HWR_AddPlayerModel(INT32 skin); -void HWR_AddSpriteModel(size_t spritenum); +void HWR_LoadModels(void); + boolean HWR_DrawModel(gl_vissprite_t *spr); #define PLAYERMODELPREFIX "PLAYER" diff --git a/src/hardware/hw_md2load.c b/src/hardware/hw_md2load.c index fed81e411..ce8eb35f3 100644 --- a/src/hardware/hw_md2load.c +++ b/src/hardware/hw_md2load.c @@ -328,8 +328,8 @@ model_t *MD2_LoadModel(const char *fileName, int ztag, boolean useFloat) texcoords = (md2texcoord_t*)&buffer[header->offsetST]; frames = (md2frame_t*)&buffer[header->offsetFrames]; - retModel->framenames = (char*)Z_Calloc(header->numFrames*16, ztag, 0); - fname = retModel->framenames; + retModel->frameNames = (char*)Z_Calloc(header->numFrames*16, ztag, 0); + fname = retModel->frameNames; for (i = 0; i < header->numFrames; i++) { md2frame_t *fr = (md2frame_t*)&buffer[header->offsetFrames + foffset]; diff --git a/src/hardware/hw_md3load.c b/src/hardware/hw_md3load.c index 87931d27b..eccc48424 100644 --- a/src/hardware/hw_md3load.c +++ b/src/hardware/hw_md3load.c @@ -230,8 +230,8 @@ model_t *MD3_LoadModel(const char *fileName, int ztag, boolean useFloat) retModel->meshes = (mesh_t*)Z_Calloc(sizeof(mesh_t)*retModel->numMeshes, ztag, 0); frames = (md3Frame*)&buffer[mdh->offsetFrames]; - retModel->framenames = (char*)Z_Calloc(mdh->numFrames*16, ztag, 0); - fname = retModel->framenames; + retModel->frameNames = (char*)Z_Calloc(mdh->numFrames*16, ztag, 0); + fname = retModel->frameNames; for (i = 0; i < mdh->numFrames; i++) { memcpy(fname, frames->name, 16); diff --git a/src/hardware/hw_model.c b/src/hardware/hw_model.c index 52f9ce6ea..8aaf0ba08 100644 --- a/src/hardware/hw_model.c +++ b/src/hardware/hw_model.c @@ -11,6 +11,7 @@ #include "../doomtype.h" #include "../info.h" #include "../r_skins.h" +#include "../r_state.h" #include "../z_zone.h" #include "hw_model.h" #include "hw_md2load.h" @@ -142,9 +143,7 @@ tag_t *GetTagByName(model_t *model, char *name, int frame) // // LoadModel // -// Load a model and -// convert it to the -// internal format. +// Load a model and convert it to the internal format. // model_t *LoadModel(const char *filename, int ztag) { @@ -194,9 +193,6 @@ model_t *LoadModel(const char *filename, int ztag) return NULL; } - model->mdlFilename = (char*)Z_Malloc(strlen(filename)+1, ztag, 0); - strcpy(model->mdlFilename, filename); - Optimize(model); GeneratePolygonNormals(model, ztag); LoadModelSprite2(model); @@ -237,17 +233,16 @@ model_t *LoadModel(const char *filename, int ztag) void HWR_ReloadModels(void) { size_t i; - INT32 s; - if (md2_playermodels != NULL) - for (s = 0; s < numskins; s++) - { - CONS_Printf("%u\n", s); - if (md2_playermodels[s].model) - LoadModelSprite2(md2_playermodels[s].model); - } + HWR_LoadModels(); - for (i = 0; i < NUMSPRITES; i++) + for (i = 0; i < md2_numplayermodels; i++) + { + if (md2_playermodels[i].model) + LoadModelSprite2(md2_playermodels[i].model); + } + + for (i = 0; i < numsprites; i++) { if (md2_models[i].model) LoadModelInterpolationSettings(md2_models[i].model); @@ -258,7 +253,7 @@ void LoadModelInterpolationSettings(model_t *model) { INT32 i; INT32 numframes = model->meshes[0].numFrames; - char *framename = model->framenames; + char *framename = model->frameNames; if (!framename) return; @@ -298,7 +293,7 @@ void LoadModelSprite2(model_t *model) INT32 i; modelspr2frames_t *spr2frames = NULL; INT32 numframes = model->meshes[0].numFrames; - char *framename = model->framenames; + char *framename = model->frameNames; if (!framename) return; diff --git a/src/hardware/hw_model.h b/src/hardware/hw_model.h index 6b39eb24d..f057271df 100644 --- a/src/hardware/hw_model.h +++ b/src/hardware/hw_model.h @@ -91,17 +91,14 @@ typedef struct model_s { int maxNumFrames; - int numMaterials; - material_t *materials; int numMeshes; mesh_t *meshes; + int numMaterials; + material_t *materials; int numTags; tag_t *tags; - char *mdlFilename; - boolean unloaded; - - char *framenames; + char *frameNames; boolean interpolate[256]; modelspr2frames_t *spr2frames; diff --git a/src/hu_stuff.c b/src/hu_stuff.c index e0eaf8fb1..cca000c87 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -2240,7 +2240,7 @@ void HU_Erase(void) // IN-LEVEL MULTIPLAYER RANKINGS //====================================================================== -#define supercheckdef (!(players[tab[i].num].charflags & SF_NOSUPERSPRITES) && ((players[tab[i].num].powers[pw_super] && players[tab[i].num].mo && (players[tab[i].num].mo->state < &states[S_PLAY_SUPER_TRANS1] || players[tab[i].num].mo->state >= &states[S_PLAY_SUPER_TRANS6])) || (players[tab[i].num].powers[pw_carry] == CR_NIGHTSMODE && skins[players[tab[i].num].skin].flags & SF_SUPER))) +#define supercheckdef (!(players[tab[i].num].charflags & SF_NOSUPERSPRITES) && ((players[tab[i].num].powers[pw_super] && players[tab[i].num].mo && (players[tab[i].num].mo->state < &states[S_PLAY_SUPER_TRANS1] || players[tab[i].num].mo->state >= &states[S_PLAY_SUPER_TRANS6])) || (players[tab[i].num].powers[pw_carry] == CR_NIGHTSMODE && skins[players[tab[i].num].skin]->flags & SF_SUPER))) #define greycheckdef (players[tab[i].num].spectator || players[tab[i].num].playerstate == PST_DEAD || (G_IsSpecialStage(gamemap) && players[tab[i].num].exiting)) // diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c index 9a3e676d5..20dbd7dce 100644 --- a/src/lua_hudlib.c +++ b/src/lua_hudlib.c @@ -501,7 +501,7 @@ static int libd_getSprite2Patch(lua_State *L) { const char *name = luaL_checkstring(L, 1); for (i = 0; i < numskins; i++) - if (fastcmp(skins[i].name, name)) + if (fastcmp(skins[i]->name, name)) break; if (i >= numskins) return 0; @@ -543,9 +543,9 @@ static int libd_getSprite2Patch(lua_State *L) if (super) j |= FF_SPR2SUPER; - j = P_GetSkinSprite2(&skins[i], j, NULL); // feed skin and current sprite2 through to change sprite2 used if necessary + j = P_GetSkinSprite2(skins[i], j, NULL); // feed skin and current sprite2 through to change sprite2 used if necessary - sprdef = &skins[i].sprites[j]; + sprdef = &skins[i]->sprites[j]; // set frame number frame = luaL_optinteger(L, 2, 0); @@ -573,7 +573,7 @@ static int libd_getSprite2Patch(lua_State *L) INT32 rot = R_GetRollAngle(rollangle); if (rot) { - patch_t *rotsprite = Patch_GetRotatedSprite(sprframe, frame, angle, sprframe->flip & (1<flip & (1<sprinfo[j], rot); LUA_PushUserdata(L, rotsprite, META_PATCH); lua_pushboolean(L, false); lua_pushboolean(L, true); diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c index cf8ccab2c..d56f0d019 100644 --- a/src/lua_mobjlib.c +++ b/src/lua_mobjlib.c @@ -638,10 +638,10 @@ static int mobj_set(lua_State *L) strlcpy(skin, luaL_checkstring(L, 3), sizeof skin); strlwr(skin); // all skin names are lowercase for (i = 0; i < numskins; i++) - if (fastcmp(skins[i].name, skin)) + if (fastcmp(skins[i]->name, skin)) { if (!mo->player || R_SkinUsable(mo->player-players, i)) - mo->skin = &skins[i]; + mo->skin = skins[i]; return 0; } return luaL_error(L, "mobj.skin '%s' not found!", skin); diff --git a/src/lua_skinlib.c b/src/lua_skinlib.c index e66a379e9..1a3b1d067 100644 --- a/src/lua_skinlib.c +++ b/src/lua_skinlib.c @@ -226,7 +226,7 @@ static int skin_num(lua_State *L) // skins are always valid, only added, never removed I_Assert(skin != NULL); - lua_pushinteger(L, skin-skins); + lua_pushinteger(L, skin->skinnum); return 1; } @@ -245,14 +245,14 @@ static int lib_iterateSkins(lua_State *L) lua_remove(L, 1); // state is unused. if (!lua_isnil(L, 1)) - i = (INT32)(*((skin_t **)luaL_checkudata(L, 1, META_SKIN)) - skins) + 1; + i = (INT32)((*((skin_t **)luaL_checkudata(L, 1, META_SKIN)))->skinnum) + 1; else i = 0; // skins are always valid, only added, never removed if (i < numskins) { - LUA_PushUserdata(L, &skins[i], META_SKIN); + LUA_PushUserdata(L, skins[i], META_SKIN); return 1; } @@ -272,7 +272,7 @@ static int lib_getSkin(lua_State *L) return luaL_error(L, "skins[] index %d out of range (0 - %d)", i, MAXSKINS-1); if (i >= numskins) return 0; - LUA_PushUserdata(L, &skins[i], META_SKIN); + LUA_PushUserdata(L, skins[i], META_SKIN); return 1; } @@ -287,9 +287,9 @@ static int lib_getSkin(lua_State *L) // find skin by name for (i = 0; i < numskins; i++) - if (fastcmp(skins[i].name, field)) + if (fastcmp(skins[i]->name, field)) { - LUA_PushUserdata(L, &skins[i], META_SKIN); + LUA_PushUserdata(L, skins[i], META_SKIN); return 1; } diff --git a/src/m_menu.c b/src/m_menu.c index e83398ef1..ca4486bf6 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -2311,7 +2311,7 @@ void Nextmap_OnChange(void) SP_NightsAttackMenu[naghost].status = IT_DISABLED; // Check if file exists, if not, disable REPLAY option - sprintf(tabase,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s",srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), skins[cv_chooseskin.value-1].name); + sprintf(tabase,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s",srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), skins[cv_chooseskin.value-1]->name); #ifdef OLDNREPLAYNAME sprintf(tabaseold,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s",srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value)); @@ -2382,7 +2382,7 @@ void Nextmap_OnChange(void) SP_TimeAttackMenu[taghost].status = IT_DISABLED; // Check if file exists, if not, disable REPLAY option - sprintf(tabase,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s",srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), skins[cv_chooseskin.value-1].name); + sprintf(tabase,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s",srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), skins[cv_chooseskin.value-1]->name); for (i = 0; i < 5; i++) { SP_ReplayMenu[i].status = IT_DISABLED; SP_GuestReplayMenu[i].status = IT_DISABLED; @@ -3080,7 +3080,7 @@ static void M_ChangeCvar(INT32 choice) { SINT8 skinno = R_SkinAvailable(cv_chooseskin.string); if (skinno != -1) - CV_SetValue(cv,skins[skinno].prefcolor); + CV_SetValue(cv,skins[skinno]->prefcolor); return; } CV_Set(cv,cv->defaultvalue); @@ -4993,9 +4993,9 @@ static void M_PatchSkinNameTable(void) for (j = 0; j < MAXSKINS; j++) { - if (j < numskins && skins[j].name[0] != '\0' && R_SkinUsable(-1, j)) + if (j < numskins && skins[j]->name[0] != '\0' && R_SkinUsable(-1, j)) { - skins_cons_t[j].strvalue = skins[j].realname; + skins_cons_t[j].strvalue = skins[j]->realname; skins_cons_t[j].value = j+1; } else @@ -8397,7 +8397,7 @@ static void M_DrawLoadGameData(void) savetodraw--; if (savegameinfo[savetodraw].lives > 0) - charskin = &skins[savegameinfo[savetodraw].skinnum]; + charskin = skins[savegameinfo[savetodraw].skinnum]; // signpost background { @@ -8531,7 +8531,7 @@ static void M_DrawLoadGameData(void) // botskin first if (savegameinfo[savetodraw].botskin) { - skin_t *charbotskin = &skins[savegameinfo[savetodraw].botskin-1]; + skin_t *charbotskin = skins[savegameinfo[savetodraw].botskin-1]; sprdef = &charbotskin->sprites[SPR2_SIGN]; if (!sprdef->numframes) goto skipbot; @@ -9091,9 +9091,9 @@ static void M_CacheCharacterSelectEntry(INT32 i, INT32 skinnum) { if (!(description[i].picname[0])) { - if (skins[skinnum].sprites[SPR2_XTRA].numframes > XTRA_CHARSEL) + if (skins[skinnum]->sprites[SPR2_XTRA].numframes > XTRA_CHARSEL) { - spritedef_t *sprdef = &skins[skinnum].sprites[SPR2_XTRA]; + spritedef_t *sprdef = &skins[skinnum]->sprites[SPR2_XTRA]; spriteframe_t *sprframe = &sprdef->spriteframes[XTRA_CHARSEL]; description[i].charpic = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH); } @@ -9308,7 +9308,7 @@ static void M_DrawSetupChoosePlayerMenu(void) { const INT32 my = 16; - skin_t *charskin = &skins[0]; + skin_t *charskin = skins[0]; INT32 skinnum = 0; UINT16 col; UINT8 *colormap = NULL; @@ -9340,7 +9340,7 @@ static void M_DrawSetupChoosePlayerMenu(void) // Find skin number from description[] skinnum = description[char_on].skinnum[0]; - charskin = &skins[skinnum]; + charskin = skins[skinnum]; // Use the opposite of the character's skincolor col = description[char_on].oppositecolor; @@ -9443,7 +9443,7 @@ static void M_DrawSetupChoosePlayerMenu(void) prevoutlinecolor = description[prev].tagoutlinecolor; if (prevtext[0] == '\0') prevpatch = description[prev].namepic; - charskin = &skins[description[prev].skinnum[0]]; + charskin = skins[description[prev].skinnum[0]]; if (!prevtextcolor) prevtextcolor = charskin->prefcolor; if (!prevoutlinecolor) @@ -9473,7 +9473,7 @@ static void M_DrawSetupChoosePlayerMenu(void) nextoutlinecolor = description[next].tagoutlinecolor; if (nexttext[0] == '\0') nextpatch = description[next].namepic; - charskin = &skins[description[next].skinnum[0]]; + charskin = skins[description[next].skinnum[0]]; if (!nexttextcolor) nexttextcolor = charskin->prefcolor; if (!nextoutlinecolor) @@ -9531,7 +9531,7 @@ static void M_ChoosePlayer(INT32 choice) if ((botingame = (description[choice].skinnum[1] != -1))) { // this character has a second skin botskin = (UINT8)(description[choice].skinnum[1]+1); - botcolor = skins[description[choice].skinnum[1]].prefcolor; + botcolor = skins[description[choice].skinnum[1]]->prefcolor; } else botskin = botcolor = 0; @@ -9816,7 +9816,7 @@ void M_DrawTimeAttackMenu(void) { INT32 i, x, y, empatx, empaty, cursory = 0; UINT16 dispstatus; - patch_t *PictureOfUrFace; // my WHAT + patch_t *PictureOfUrFace; patch_t *empatch; M_SetMenuCurBackground("RECATKBG"); @@ -9885,9 +9885,9 @@ void M_DrawTimeAttackMenu(void) // Character face! { - if (skins[cv_chooseskin.value-1].sprites[SPR2_XTRA].numframes > XTRA_CHARSEL) + if (skins[cv_chooseskin.value-1]->sprites[SPR2_XTRA].numframes > XTRA_CHARSEL) { - spritedef_t *sprdef = &skins[cv_chooseskin.value-1].sprites[SPR2_XTRA]; + spritedef_t *sprdef = &skins[cv_chooseskin.value-1]->sprites[SPR2_XTRA]; spriteframe_t *sprframe = &sprdef->spriteframes[XTRA_CHARSEL]; PictureOfUrFace = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH); } @@ -10326,7 +10326,7 @@ static void M_ChooseNightsAttack(INT32 choice) I_Error("Out of memory for replay filepath\n"); sprintf(gpath,"replay"PATHSEP"%s"PATHSEP"%s", timeattackfolder, G_BuildMapName(cv_nextmap.value)); - snprintf(nameofdemo, sizeof nameofdemo, "%s-%s-last", gpath, skins[cv_chooseskin.value-1].name); + snprintf(nameofdemo, sizeof nameofdemo, "%s-%s-last", gpath, skins[cv_chooseskin.value-1]->name); if (!cv_autorecord.value) remove(va("%s"PATHSEP"%s.lmp", srb2home, nameofdemo)); @@ -10355,7 +10355,7 @@ static void M_ChooseTimeAttack(INT32 choice) I_Error("Out of memory for replay filepath\n"); sprintf(gpath,"replay"PATHSEP"%s"PATHSEP"%s", timeattackfolder, G_BuildMapName(cv_nextmap.value)); - snprintf(nameofdemo, sizeof nameofdemo, "%s-%s-last", gpath, skins[cv_chooseskin.value-1].name); + snprintf(nameofdemo, sizeof nameofdemo, "%s-%s-last", gpath, skins[cv_chooseskin.value-1]->name); if (!cv_autorecord.value) remove(va("%s"PATHSEP"%s.lmp", srb2home, nameofdemo)); @@ -10395,7 +10395,7 @@ static void M_ReplayTimeAttack(INT32 choice) return; } // srb2/replay/main/map01-sonic-time-best.lmp - G_DoPlayDemo(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), skins[cv_chooseskin.value-1].name, which)); + G_DoPlayDemo(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), skins[cv_chooseskin.value-1]->name, which)); } else if (currentMenu == &SP_NightsReplayDef) { @@ -10415,7 +10415,7 @@ static void M_ReplayTimeAttack(INT32 choice) return; } - demoname = va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), skins[cv_chooseskin.value-1].name, which); + demoname = va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), skins[cv_chooseskin.value-1]->name, which); #ifdef OLDNREPLAYNAME // Check for old style named NiGHTS replay if a new style replay doesn't exist. if (!FIL_FileExists(demoname)) @@ -10445,7 +10445,7 @@ static void M_OverwriteGuest(const char *which) char *rguest = Z_StrDup(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-guest.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value))); UINT8 *buf; size_t len; - len = FIL_ReadFile(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), skins[cv_chooseskin.value-1].name, which), &buf); + len = FIL_ReadFile(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), skins[cv_chooseskin.value-1]->name, which), &buf); if (!len) { return; @@ -11875,11 +11875,11 @@ static void M_DrawSetupMultiPlayerMenu(void) // draw skin string V_DrawRightAlignedString(BASEVIDWIDTH - x, y, ((MP_PlayerSetupMenu[1].status & IT_TYPE) == IT_SPACE ? V_TRANSLUCENT : 0)|(itemOn == 1 ? V_YELLOWMAP : 0)|V_ALLOWLOWERCASE, - skins[setupm_fakeskin].realname); + skins[setupm_fakeskin]->realname); if (itemOn == 1 && (MP_PlayerSetupMenu[1].status & IT_TYPE) != IT_SPACE) { - V_DrawCharacter(BASEVIDWIDTH - x - 10 - V_StringWidth(skins[setupm_fakeskin].realname, V_ALLOWLOWERCASE) - (skullAnimCounter/5), y, + V_DrawCharacter(BASEVIDWIDTH - x - 10 - V_StringWidth(skins[setupm_fakeskin]->realname, V_ALLOWLOWERCASE) - (skullAnimCounter/5), y, '\x1C' | V_YELLOWMAP, false); V_DrawCharacter(BASEVIDWIDTH - x + 2 + (skullAnimCounter/5), y, '\x1D' | V_YELLOWMAP, false); @@ -11900,7 +11900,7 @@ static void M_DrawSetupMultiPlayerMenu(void) // draw box around character V_DrawFill(x-(charw/2), y, charw, 84, 159); - sprdef = &skins[setupm_fakeskin].sprites[multi_spr2]; + sprdef = &skins[setupm_fakeskin]->sprites[multi_spr2]; if (!setupm_fakecolor->color || !sprdef->numframes) // should never happen but hey, who knows goto faildraw; @@ -11921,7 +11921,7 @@ static void M_DrawSetupMultiPlayerMenu(void) V_DrawFixedPatch( x<highresscale, skins[setupm_fakeskin]->shieldscale), flags, patch, colormap); goto colordraw; @@ -12051,7 +12051,7 @@ static void M_HandleSetupMultiPlayer(INT32 choice) setupm_fakeskin = numskins-1; } while ((prev_setupm_fakeskin != setupm_fakeskin) && !(R_SkinUsable(-1, setupm_fakeskin))); - multi_spr2 = P_GetSkinSprite2(&skins[setupm_fakeskin], SPR2_WALK, NULL); + multi_spr2 = P_GetSkinSprite2(skins[setupm_fakeskin], SPR2_WALK, NULL); } else if (itemOn == 2) // player color { @@ -12067,7 +12067,7 @@ static void M_HandleSetupMultiPlayer(INT32 choice) { S_StartSound(NULL,sfx_strpst); // you know what? always putting these in the buffer won't hurt anything. - COM_BufAddText (va("%s \"%s\"\n",setupm_cvdefaultskin->name,skins[setupm_fakeskin].name)); + COM_BufAddText (va("%s \"%s\"\n",setupm_cvdefaultskin->name,skins[setupm_fakeskin]->name)); COM_BufAddText (va("%s %d\n",setupm_cvdefaultcolor->name,setupm_fakecolor->color)); break; } @@ -12084,7 +12084,7 @@ static void M_HandleSetupMultiPlayer(INT32 choice) setupm_fakeskin = 0; } while ((prev_setupm_fakeskin != setupm_fakeskin) && !(R_SkinUsable(-1, setupm_fakeskin))); - multi_spr2 = P_GetSkinSprite2(&skins[setupm_fakeskin], SPR2_WALK, NULL); + multi_spr2 = P_GetSkinSprite2(skins[setupm_fakeskin], SPR2_WALK, NULL); } else if (itemOn == 2) // player color { @@ -12105,7 +12105,7 @@ static void M_HandleSetupMultiPlayer(INT32 choice) } else if (itemOn == 2) { - UINT16 col = skins[setupm_fakeskin].prefcolor; + UINT16 col = skins[setupm_fakeskin]->prefcolor; if ((setupm_fakecolor->color != col) && skincolors[col].accessible) { S_StartSound(NULL,sfx_menu1); // Tails @@ -12195,7 +12195,7 @@ static void M_SetupMultiPlayer(INT32 choice) else MP_PlayerSetupMenu[2].status = (IT_KEYHANDLER|IT_STRING); - multi_spr2 = P_GetSkinSprite2(&skins[setupm_fakeskin], SPR2_WALK, NULL); + multi_spr2 = P_GetSkinSprite2(skins[setupm_fakeskin], SPR2_WALK, NULL); MP_PlayerSetupDef.prevMenu = currentMenu; M_SetupNextMenu(&MP_PlayerSetupDef); @@ -12239,7 +12239,7 @@ static void M_SetupMultiPlayer2(INT32 choice) else MP_PlayerSetupMenu[2].status = (IT_KEYHANDLER|IT_STRING); - multi_spr2 = P_GetSkinSprite2(&skins[setupm_fakeskin], SPR2_WALK, NULL); + multi_spr2 = P_GetSkinSprite2(skins[setupm_fakeskin], SPR2_WALK, NULL); MP_PlayerSetupDef.prevMenu = currentMenu; M_SetupNextMenu(&MP_PlayerSetupDef); @@ -12257,7 +12257,7 @@ static boolean M_QuitMultiPlayerMenu(void) setupm_name[l] =0; COM_BufAddText (va("%s \"%s\"\n",setupm_cvname->name,setupm_name)); } - COM_BufAddText (va("%s \"%s\"\n",setupm_cvskin->name,skins[setupm_fakeskin].name)); + COM_BufAddText (va("%s \"%s\"\n",setupm_cvskin->name,skins[setupm_fakeskin]->name)); // send color if changed if (setupm_fakecolor->color != setupm_cvcolor->value) COM_BufAddText (va("%s %d\n",setupm_cvcolor->name,setupm_fakecolor->color)); diff --git a/src/p_enemy.c b/src/p_enemy.c index 6a92c5d33..5504b0d1c 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -3609,7 +3609,7 @@ void A_1upThinker(mobj_t *actor) } } - if (closestplayer == -1 || skins[players[closestplayer].skin].sprites[SPR2_LIFE].numframes == 0) + if (closestplayer == -1 || skins[players[closestplayer].skin]->sprites[SPR2_LIFE].numframes == 0) { // Closest player not found (no players in game?? may be empty dedicated server!), or does not have correct sprite. if (actor->tracer) { @@ -3627,7 +3627,7 @@ void A_1upThinker(mobj_t *actor) { P_SetTarget(&actor->tracer, P_SpawnMobj(actor->x, actor->y, actor->z, MT_OVERLAY)); P_SetTarget(&actor->tracer->target, actor); - actor->tracer->skin = &skins[players[closestplayer].skin]; // required here to prevent spr2 default showing stand for a single frame + actor->tracer->skin = skins[players[closestplayer].skin]; // required here to prevent spr2 default showing stand for a single frame P_SetMobjState(actor->tracer, actor->info->seestate); // The overlay is going to be one tic early turning off and on @@ -3637,7 +3637,7 @@ void A_1upThinker(mobj_t *actor) } actor->tracer->color = players[closestplayer].mo->color; - actor->tracer->skin = &skins[players[closestplayer].skin]; + actor->tracer->skin = skins[players[closestplayer].skin]; } // Function: A_MonitorPop @@ -3707,7 +3707,7 @@ void A_MonitorPop(mobj_t *actor) P_SetTarget(&newmobj->tracer, livesico); livesico->color = newmobj->target->player->mo->color; - livesico->skin = &skins[newmobj->target->player->skin]; + livesico->skin = skins[newmobj->target->player->skin]; P_SetMobjState(livesico, newmobj->info->seestate); // We're using the overlay, so use the overlay 1up sprite (no text) @@ -3793,7 +3793,7 @@ void A_GoldMonitorPop(mobj_t *actor) P_SetTarget(&newmobj->tracer, livesico); livesico->color = newmobj->target->player->mo->color; - livesico->skin = &skins[newmobj->target->player->skin]; + livesico->skin = skins[newmobj->target->player->skin]; P_SetMobjState(livesico, newmobj->info->seestate); // We're using the overlay, so use the overlay 1up sprite (no text) @@ -5172,7 +5172,7 @@ void A_SignPlayer(mobj_t *actor) if (!actor->target->player) return; - skin = &skins[actor->target->player->skin]; + skin = skins[actor->target->player->skin]; facecolor = actor->target->player->skincolor; if (signcolor) @@ -5203,10 +5203,10 @@ void A_SignPlayer(mobj_t *actor) if (!SignSkinCheck(player, skincount)) skinnum++; } - skin = &skins[skinnum]; + skin = skins[skinnum]; } else // specific skin - skin = &skins[locvar1]; + skin = skins[locvar1]; facecolor = skin->prefcolor; if (signcolor) diff --git a/src/p_mobj.c b/src/p_mobj.c index da7385be5..4fd4e94d8 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -207,7 +207,7 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state) return P_SetPlayerMobjState(mobj, S_PLAY_FALL); // Catch swimming versus flying - if ((state == S_PLAY_FLY || (state == S_PLAY_GLIDE && skins[player->skin].sprites[SPR2_SWIM].numframes)) + if ((state == S_PLAY_FLY || (state == S_PLAY_GLIDE && skins[player->skin]->sprites[SPR2_SWIM].numframes)) && player->mo->eflags & MFE_UNDERWATER && !player->skidtime) return P_SetPlayerMobjState(player->mo, S_PLAY_SWIM); else if (state == S_PLAY_SWIM && !(player->mo->eflags & MFE_UNDERWATER)) @@ -325,7 +325,7 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state) mobj->tics = st->tics; // Adjust the player's animation speed to match their velocity. - if (state == S_PLAY_STND && player->powers[pw_super] && skins[player->skin].sprites[SPR2_WAIT|FF_SPR2SUPER].numframes == 0) // if no super wait, don't wait at all + if (state == S_PLAY_STND && player->powers[pw_super] && skins[player->skin]->sprites[SPR2_WAIT|FF_SPR2SUPER].numframes == 0) // if no super wait, don't wait at all mobj->tics = -1; else if (player->panim == PA_EDGE && (player->charflags & SF_FASTEDGE)) mobj->tics = 2; @@ -3292,7 +3292,7 @@ void P_MobjCheckWater(mobj_t *mobj) { // Water removes electric and non-water fire shields... if (electric) P_FlashPal(p, PAL_WHITE, 1); - + p->powers[pw_shield] = p->powers[pw_shield] & SH_STACK; } } @@ -10707,10 +10707,10 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) nummaprings++; break; case MT_METALSONIC_RACE: - mobj->skin = &skins[5]; + mobj->skin = skins[5]; /* FALLTHRU */ case MT_METALSONIC_BATTLE: - mobj->color = skins[5].prefcolor; + mobj->color = skins[5]->prefcolor; sc = 5; break; case MT_FANG: @@ -11381,7 +11381,7 @@ void P_SpawnPlayer(INT32 playernum) // set 'spritedef' override in mobj for player skins.. (see ProjectSprite) // (usefulness: when body mobj is detached from player (who respawns), // the dead body mobj retains the skin through the 'spritedef' override). - mobj->skin = &skins[p->skin]; + mobj->skin = skins[p->skin]; P_SetupStateAnimation(mobj, mobj->state); mobj->health = 1; @@ -11389,14 +11389,14 @@ void P_SpawnPlayer(INT32 playernum) p->bonustime = false; p->realtime = leveltime; - p->followitem = skins[p->skin].followitem; + p->followitem = skins[p->skin]->followitem; // Make sure player's stats are reset if they were in dashmode! if (p->dashmode) { p->dashmode = 0; - p->normalspeed = skins[p->skin].normalspeed; - p->jumpfactor = skins[p->skin].jumpfactor; + p->normalspeed = skins[p->skin]->normalspeed; + p->jumpfactor = skins[p->skin]->jumpfactor; } // Clear lastlinehit and lastsidehit @@ -11412,7 +11412,7 @@ void P_SpawnPlayer(INT32 playernum) P_FlashPal(p, 0, 0); // Resets // Set bounds accurately. - mobj->radius = FixedMul(skins[p->skin].radius, mobj->scale); + mobj->radius = FixedMul(skins[p->skin]->radius, mobj->scale); mobj->height = P_GetPlayerHeight(p); if (!leveltime && !p->spectator && ((maptol & TOL_NIGHTS) == TOL_NIGHTS) != (G_IsSpecialStage(gamemap))) // non-special NiGHTS stage or special non-NiGHTS stage diff --git a/src/p_saveg.c b/src/p_saveg.c index 770c641b9..24ee14583 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -76,11 +76,11 @@ static inline void P_ArchivePlayer(void) // Write skin names, so that loading skins in different orders // doesn't change who the save file is for! - WRITESTRINGN(save_p, skins[player->skin].name, SKINNAMESIZE); + WRITESTRINGN(save_p, skins[player->skin]->name, SKINNAMESIZE); if (botskin != 0) { - WRITESTRINGN(save_p, skins[botskin-1].name, SKINNAMESIZE); + WRITESTRINGN(save_p, skins[botskin-1]->name, SKINNAMESIZE); } else { @@ -1815,7 +1815,7 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type) if (diff2 & MD2_CVMEM) WRITEINT32(save_p, mobj->cvmem); if (diff2 & MD2_SKIN) - WRITEUINT8(save_p, (UINT8)((skin_t *)mobj->skin - skins)); + WRITEUINT8(save_p, (UINT8)(((skin_t *)mobj->skin)->skinnum)); if (diff2 & MD2_COLOR) WRITEUINT16(save_p, mobj->color); if (diff2 & MD2_EXTVAL1) @@ -2855,7 +2855,7 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker) if (diff2 & MD2_CVMEM) mobj->cvmem = READINT32(save_p); if (diff2 & MD2_SKIN) - mobj->skin = &skins[READUINT8(save_p)]; + mobj->skin = skins[READUINT8(save_p)]; if (diff2 & MD2_COLOR) mobj->color = READUINT16(save_p); if (diff2 & MD2_EXTVAL1) diff --git a/src/p_setup.c b/src/p_setup.c index 498759b73..2546dcb98 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -3235,7 +3235,7 @@ static void P_ConvertBinaryMap(void) lines[i].args[4] |= TMSC_BACKTOFRONTCEILING; lines[i].special = 720; break; - + case 900: //Translucent wall (10%) case 901: //Translucent wall (20%) case 902: //Translucent wall (30%) @@ -3622,19 +3622,19 @@ static void P_ForceCharacter(const char *forcecharskin) if (splitscreen) { SetPlayerSkin(secondarydisplayplayer, forcecharskin); - if ((unsigned)cv_playercolor2.value != skins[players[secondarydisplayplayer].skin].prefcolor) + if ((unsigned)cv_playercolor2.value != skins[players[secondarydisplayplayer].skin]->prefcolor) { - CV_StealthSetValue(&cv_playercolor2, skins[players[secondarydisplayplayer].skin].prefcolor); - players[secondarydisplayplayer].skincolor = skins[players[secondarydisplayplayer].skin].prefcolor; + CV_StealthSetValue(&cv_playercolor2, skins[players[secondarydisplayplayer].skin]->prefcolor); + players[secondarydisplayplayer].skincolor = skins[players[secondarydisplayplayer].skin]->prefcolor; } } SetPlayerSkin(consoleplayer, forcecharskin); // normal player colors in single player - if ((unsigned)cv_playercolor.value != skins[players[consoleplayer].skin].prefcolor) + if ((unsigned)cv_playercolor.value != skins[players[consoleplayer].skin]->prefcolor) { - CV_StealthSetValue(&cv_playercolor, skins[players[consoleplayer].skin].prefcolor); - players[consoleplayer].skincolor = skins[players[consoleplayer].skin].prefcolor; + CV_StealthSetValue(&cv_playercolor, skins[players[consoleplayer].skin]->prefcolor); + players[consoleplayer].skincolor = skins[players[consoleplayer].skin]->prefcolor; } } } @@ -3678,8 +3678,8 @@ static void P_LoadRecordGhosts(void) if (cv_ghost_bestscore.value == 1 && players[consoleplayer].skin != i) continue; - if (FIL_FileExists(va("%s-%s-score-best.lmp", gpath, skins[i].name))) - G_AddGhost(va("%s-%s-score-best.lmp", gpath, skins[i].name)); + if (FIL_FileExists(va("%s-%s-score-best.lmp", gpath, skins[i]->name))) + G_AddGhost(va("%s-%s-score-best.lmp", gpath, skins[i]->name)); } } @@ -3691,8 +3691,8 @@ static void P_LoadRecordGhosts(void) if (cv_ghost_besttime.value == 1 && players[consoleplayer].skin != i) continue; - if (FIL_FileExists(va("%s-%s-time-best.lmp", gpath, skins[i].name))) - G_AddGhost(va("%s-%s-time-best.lmp", gpath, skins[i].name)); + if (FIL_FileExists(va("%s-%s-time-best.lmp", gpath, skins[i]->name))) + G_AddGhost(va("%s-%s-time-best.lmp", gpath, skins[i]->name)); } } @@ -3704,8 +3704,8 @@ static void P_LoadRecordGhosts(void) if (cv_ghost_bestrings.value == 1 && players[consoleplayer].skin != i) continue; - if (FIL_FileExists(va("%s-%s-rings-best.lmp", gpath, skins[i].name))) - G_AddGhost(va("%s-%s-rings-best.lmp", gpath, skins[i].name)); + if (FIL_FileExists(va("%s-%s-rings-best.lmp", gpath, skins[i]->name))) + G_AddGhost(va("%s-%s-rings-best.lmp", gpath, skins[i]->name)); } } @@ -3717,8 +3717,8 @@ static void P_LoadRecordGhosts(void) if (cv_ghost_last.value == 1 && players[consoleplayer].skin != i) continue; - if (FIL_FileExists(va("%s-%s-last.lmp", gpath, skins[i].name))) - G_AddGhost(va("%s-%s-last.lmp", gpath, skins[i].name)); + if (FIL_FileExists(va("%s-%s-last.lmp", gpath, skins[i]->name))) + G_AddGhost(va("%s-%s-last.lmp", gpath, skins[i]->name)); } } @@ -3748,8 +3748,8 @@ static void P_LoadNightsGhosts(void) if (cv_ghost_bestscore.value == 1 && players[consoleplayer].skin != i) continue; - if (FIL_FileExists(va("%s-%s-score-best.lmp", gpath, skins[i].name))) - G_AddGhost(va("%s-%s-score-best.lmp", gpath, skins[i].name)); + if (FIL_FileExists(va("%s-%s-score-best.lmp", gpath, skins[i]->name))) + G_AddGhost(va("%s-%s-score-best.lmp", gpath, skins[i]->name)); } } @@ -3761,8 +3761,8 @@ static void P_LoadNightsGhosts(void) if (cv_ghost_besttime.value == 1 && players[consoleplayer].skin != i) continue; - if (FIL_FileExists(va("%s-%s-time-best.lmp", gpath, skins[i].name))) - G_AddGhost(va("%s-%s-time-best.lmp", gpath, skins[i].name)); + if (FIL_FileExists(va("%s-%s-time-best.lmp", gpath, skins[i]->name))) + G_AddGhost(va("%s-%s-time-best.lmp", gpath, skins[i]->name)); } } @@ -3774,8 +3774,8 @@ static void P_LoadNightsGhosts(void) if (cv_ghost_last.value == 1 && players[consoleplayer].skin != i) continue; - if (FIL_FileExists(va("%s-%s-last.lmp", gpath, skins[i].name))) - G_AddGhost(va("%s-%s-last.lmp", gpath, skins[i].name)); + if (FIL_FileExists(va("%s-%s-last.lmp", gpath, skins[i]->name))) + G_AddGhost(va("%s-%s-last.lmp", gpath, skins[i]->name)); } } @@ -4251,7 +4251,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) players[consoleplayer].lives = savedata.lives; players[consoleplayer].score = savedata.score; if ((botingame = ((botskin = savedata.botskin) != 0))) - botcolor = skins[botskin-1].prefcolor; + botcolor = skins[botskin-1]->prefcolor; emeralds = savedata.emeralds; savedata.lives = 0; } diff --git a/src/p_spec.c b/src/p_spec.c index 4b566acfb..34ef688c8 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1779,7 +1779,7 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller case 331: // continuous case 332: // each time case 333: // once - if (!(actor && actor->player && ((stricmp(triggerline->text, skins[actor->player->skin].name) == 0) ^ ((triggerline->flags & ML_NOCLIMB) == ML_NOCLIMB)))) + if (!(actor && actor->player && ((stricmp(triggerline->text, skins[actor->player->skin]->name) == 0) ^ ((triggerline->flags & ML_NOCLIMB) == ML_NOCLIMB)))) return false; break; case 334: // object dye - continuous diff --git a/src/p_user.c b/src/p_user.c index c3184b52f..d3fe4c454 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -682,8 +682,8 @@ static void P_DeNightserizePlayer(player_t *player) player->mo->flags &= ~MF_NOGRAVITY; - player->mo->skin = &skins[player->skin]; - player->followitem = skins[player->skin].followitem; + player->mo->skin = skins[player->skin]; + player->followitem = skins[player->skin]->followitem; player->mo->color = player->skincolor; G_GhostAddColor(GHC_RETURNSKIN); @@ -785,12 +785,12 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime) player->mo->height = P_GetPlayerHeight(player); // Just to make sure jumping into the drone doesn't result in a squashed hitbox. player->oldscale = player->mo->scale; - if (skins[player->skin].sprites[SPR2_NFLY].numframes == 0) // If you don't have a sprite for flying horizontally, use the default NiGHTS skin. + if (skins[player->skin]->sprites[SPR2_NFLY].numframes == 0) // If you don't have a sprite for flying horizontally, use the default NiGHTS skin. { - player->mo->skin = &skins[DEFAULTNIGHTSSKIN]; + player->mo->skin = skins[DEFAULTNIGHTSSKIN]; if (!(cv_debug || devparm) && !(netgame || multiplayer || demoplayback)) - player->mo->color = skins[DEFAULTNIGHTSSKIN].prefcolor; - player->followitem = skins[DEFAULTNIGHTSSKIN].followitem; + player->mo->color = skins[DEFAULTNIGHTSSKIN]->prefcolor; + player->followitem = skins[DEFAULTNIGHTSSKIN]->followitem; G_GhostAddColor(GHC_NIGHTSSKIN); } } @@ -4264,7 +4264,7 @@ static void P_DoSuperStuff(player_t *player) player->mo->color = (player->pflags & PF_GODMODE && cv_debug == 0) ? (SKINCOLOR_SUPERSILVER1 + 5*(((signed)leveltime >> 1) % 7)) // A wholesome easter egg. - : skins[player->skin].supercolor + abs( ( (player->powers[pw_super] >> 1) % 9) - 4); // This is where super flashing is handled. + : skins[player->skin]->supercolor + abs( ( (player->powers[pw_super] >> 1) % 9) - 4); // This is where super flashing is handled. G_GhostAddColor(GHC_SUPER); @@ -11469,7 +11469,7 @@ static void P_DoMetalJetFume(player_t *player, mobj_t *fume) P_SetThingPosition(fume); // If dashmode is high enough, spawn a trail - if (player->normalspeed >= skins[player->skin].normalspeed*2) + if (player->normalspeed >= skins[player->skin]->normalspeed*2) P_SpawnGhostMobj(fume); } @@ -12294,20 +12294,20 @@ void P_PlayerThink(player_t *player) { if (prevdashmode >= DASHMODE_THRESHOLD) { - player->normalspeed = skins[player->skin].normalspeed; // Reset to default if not capable of entering dash mode. - player->jumpfactor = skins[player->skin].jumpfactor; + player->normalspeed = skins[player->skin]->normalspeed; // Reset to default if not capable of entering dash mode. + player->jumpfactor = skins[player->skin]->jumpfactor; } } else if (P_IsObjectOnGround(player->mo)) // Activate dash mode if we're on the ground. { - if (player->normalspeed < skins[player->skin].normalspeed*2) // If the player normalspeed is not currently at normalspeed*2 in dash mode, add speed each tic + if (player->normalspeed < skins[player->skin]->normalspeed*2) // If the player normalspeed is not currently at normalspeed*2 in dash mode, add speed each tic player->normalspeed += FRACUNIT/5; // Enter Dash Mode smoothly. - if (player->jumpfactor < FixedMul(skins[player->skin].jumpfactor, 5*FRACUNIT/4)) // Boost jump height. + if (player->jumpfactor < FixedMul(skins[player->skin]->jumpfactor, 5*FRACUNIT/4)) // Boost jump height. player->jumpfactor += FRACUNIT/300; } - if (player->normalspeed >= skins[player->skin].normalspeed*2) + if (player->normalspeed >= skins[player->skin]->normalspeed*2) { mobj_t *ghost = P_SpawnGhostMobj(player->mo); // Spawns afterimages ghost->fuse = 2; // Makes the images fade quickly @@ -12319,8 +12319,8 @@ void P_PlayerThink(player_t *player) { if (dashmode >= DASHMODE_THRESHOLD) // catch getting the flag! { - player->normalspeed = skins[player->skin].normalspeed; - player->jumpfactor = skins[player->skin].jumpfactor; + player->normalspeed = skins[player->skin]->normalspeed; + player->jumpfactor = skins[player->skin]->jumpfactor; S_StartSound(player->mo, sfx_kc65); } dashmode = 0; diff --git a/src/r_draw.c b/src/r_draw.c index f0a19a462..7b0d88e7d 100644 --- a/src/r_draw.c +++ b/src/r_draw.c @@ -548,7 +548,7 @@ static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, U if (skinnum < 0 && skinnum > TC_DEFAULT) I_Error("Invalid translation colormap index %d.", skinnum); - starttranscolor = (skinnum != TC_DEFAULT) ? skins[skinnum].starttranscolor : DEFAULT_STARTTRANSCOLOR; + starttranscolor = (skinnum != TC_DEFAULT) ? skins[skinnum]->starttranscolor : DEFAULT_STARTTRANSCOLOR; if (starttranscolor >= NUM_PALETTE_ENTRIES) I_Error("Invalid startcolor #%d.", starttranscolor); diff --git a/src/r_picformats.c b/src/r_picformats.c index bd457b62f..5c2544a7f 100644 --- a/src/r_picformats.c +++ b/src/r_picformats.c @@ -1612,7 +1612,7 @@ static void R_ParseSpriteInfo(boolean spr2) for (i = 0; i < foundskins; i++) { size_t skinnum = skinnumbers[i]; - skin_t *skin = &skins[skinnum]; + skin_t *skin = skins[skinnum]; spriteinfo_t *sprinfo = skin->sprinfo; M_Memcpy(&sprinfo[spr2num], info, sizeof(spriteinfo_t)); } diff --git a/src/r_skins.c b/src/r_skins.c index fd2f1b17b..b0dcda035 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -33,7 +33,7 @@ #endif INT32 numskins = 0; -skin_t *skins = NULL; +skin_t **skins = NULL; // // P_GetSkinSprite2 @@ -100,7 +100,7 @@ static void Sk_SetDefaultValue(skin_t *skin) // memset(skin, 0, sizeof (skin_t)); snprintf(skin->name, - sizeof skin->name, "skin %u", (UINT32)(skin-skins)); + sizeof skin->name, "skin %u", (UINT32)(skin->skinnum)); skin->name[sizeof skin->name - 1] = '\0'; skin->wadnum = INT16_MAX; @@ -221,7 +221,7 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum) // Force 2. return true; } - + if (metalrecording && skinnum == 5) { // Force 3. @@ -268,7 +268,7 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum) } } -// returns true if the skin name is found (loaded from pwad) +// returns the skin number if the skin name is found (loaded from pwad) // warning return -1 if not found INT32 R_SkinAvailable(const char *name) { @@ -277,8 +277,8 @@ INT32 R_SkinAvailable(const char *name) for (i = 0; i < numskins; i++) { // search in the skin list - if (stricmp(skins[i].name,name)==0) - return i; + if (!stricmp(skins[i]->name,name)) + return skins[i]->skinnum; } return -1; } @@ -286,7 +286,7 @@ INT32 R_SkinAvailable(const char *name) // Auxillary function that actually sets the skin static void SetSkin(player_t *player, INT32 skinnum) { - skin_t *skin = &skins[skinnum]; + skin_t *skin = skins[skinnum]; UINT16 newcolor = 0; player->skin = skinnum; @@ -347,7 +347,7 @@ static void SetSkin(player_t *player, INT32 skinnum) fixed_t radius = FixedMul(skin->radius, player->mo->scale); if ((player->powers[pw_carry] == CR_NIGHTSMODE) && (skin->sprites[SPR2_NFLY].numframes == 0)) // If you don't have a sprite for flying horizontally, use the default NiGHTS skin. { - skin = &skins[DEFAULTNIGHTSSKIN]; + skin = skins[DEFAULTNIGHTSSKIN]; player->followitem = skin->followitem; if (!(cv_debug || devparm) && !(netgame || multiplayer || demoplayback)) newcolor = skin->prefcolor; // will be updated in thinker to flashing @@ -687,9 +687,10 @@ void R_AddSkins(UINT16 wadnum, boolean mainfile) buf2[size] = '\0'; // set defaults - skins = Z_Realloc(skins, sizeof(skin_t) * (numskins + 1), PU_STATIC, NULL); - skin = &skins[numskins]; + skins = Z_Realloc(skins, sizeof(skin_t*) * (numskins + 1), PU_STATIC, NULL); + skin = skins[numskins] = Z_Calloc(sizeof(skin_t), PU_STATIC, NULL); Sk_SetDefaultValue(skin); + skin->skinnum = numskins; skin->wadnum = wadnum; hudname = realname = false; // parse @@ -785,11 +786,6 @@ next_token: CONS_Printf(M_GetText("Added skin '%s'\n"), skin->name); numskins++; - -#ifdef HWRENDER - if (rendermode == render_opengl) - HWR_AddPlayerModel(numskins-1); -#endif } return; } @@ -858,7 +854,7 @@ void R_PatchSkins(UINT16 wadnum, boolean mainfile) strlwr(value); skinnum = R_SkinAvailable(value); if (skinnum != -1) - skin = &skins[skinnum]; + skin = skins[skinnum]; else { CONS_Debug(DBG_SETUP, "R_PatchSkins: unknown skin name in P_SKIN lump# %d(%s) in WAD %s\n", lump, W_CheckNameForNumPwad(wadnum,lump), wadfiles[wadnum]->filename); diff --git a/src/r_skins.h b/src/r_skins.h index c95060fa7..67f34aa66 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -32,6 +32,7 @@ typedef struct { char name[SKINNAMESIZE+1]; // INT16 descriptive name of the skin + UINT32 skinnum; UINT16 wadnum; skinflags_t flags; @@ -84,7 +85,7 @@ typedef struct /// Externs extern INT32 numskins; -extern skin_t *skins; +extern skin_t **skins; /// Function prototypes void R_InitSkins(void); diff --git a/src/r_things.c b/src/r_things.c index 0283712b8..69a9eb5e9 100644 --- a/src/r_things.c +++ b/src/r_things.c @@ -476,10 +476,6 @@ void R_AddSpriteDefs(UINT16 wadnum) if (R_AddSingleSpriteDef(sprnames[i], &sprites[i], wadnum, start, end)) { -#ifdef HWRENDER - if (rendermode == render_opengl) - HWR_AddSpriteModel(i); -#endif // if a new sprite was added (not just replaced) addsprites++; #ifndef ZDEBUG @@ -553,14 +549,10 @@ void R_InitSprites(void) } ST_ReloadSkinFaceGraphics(); - // - // check if all sprites have frames - // - /* - for (i = 0; i < numsprites; i++) - if (sprites[i].numframes < 1) - CONS_Debug(DBG_SETUP, "R_InitSprites: sprite %s has no frames at all\n", sprnames[i]); - */ +#ifdef HWRENDER + if (rendermode == render_opengl) + HWR_LoadModels(); +#endif } // @@ -772,8 +764,8 @@ UINT8 *R_GetSpriteTranslation(vissprite_t *vis) } else if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && vis->mobj->sprite == SPR_PLAY) // This thing is a player! { - size_t skinnum = (skin_t*)vis->mobj->skin-skins; - return R_GetTranslationColormap((INT32)skinnum, vis->mobj->color, GTC_CACHE); + INT32 skinnum = ((skin_t*)vis->mobj->skin)->skinnum; + return R_GetTranslationColormap(skinnum, vis->mobj->color, GTC_CACHE); } else // Use the defaults return R_GetTranslationColormap(TC_DEFAULT, vis->mobj->color, GTC_CACHE); diff --git a/src/st_stuff.c b/src/st_stuff.c index 130460f49..88affcf0a 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -345,14 +345,14 @@ void ST_LoadGraphics(void) // made separate so that skins code can reload custom face graphics void ST_LoadFaceGraphics(INT32 skinnum) { - if (skins[skinnum].sprites[SPR2_XTRA].numframes > XTRA_LIFEPIC) + if (skins[skinnum]->sprites[SPR2_XTRA].numframes > XTRA_LIFEPIC) { - spritedef_t *sprdef = &skins[skinnum].sprites[SPR2_XTRA]; + spritedef_t *sprdef = &skins[skinnum]->sprites[SPR2_XTRA]; spriteframe_t *sprframe = &sprdef->spriteframes[XTRA_LIFEPIC]; faceprefix[skinnum] = W_CachePatchNum(sprframe->lumppat[0], PU_HUDGFX); - if (skins[skinnum].sprites[(SPR2_XTRA|FF_SPR2SUPER)].numframes > XTRA_LIFEPIC) + if (skins[skinnum]->sprites[(SPR2_XTRA|FF_SPR2SUPER)].numframes > XTRA_LIFEPIC) { - sprdef = &skins[skinnum].sprites[SPR2_XTRA|FF_SPR2SUPER]; + sprdef = &skins[skinnum]->sprites[SPR2_XTRA|FF_SPR2SUPER]; sprframe = &sprdef->spriteframes[0]; superprefix[skinnum] = W_CachePatchNum(sprframe->lumppat[0], PU_HUDGFX); } @@ -960,14 +960,14 @@ static void ST_drawLivesArea(void) // name v_colmap |= (V_HUDTRANS|hudinfo[HUD_LIVES].f|V_PERPLAYER); - if (strlen(skins[stplyr->skin].hudname) <= 5) - V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y, v_colmap, skins[stplyr->skin].hudname); - else if (V_StringWidth(skins[stplyr->skin].hudname, v_colmap) <= 48) - V_DrawString(hudinfo[HUD_LIVES].x+18, hudinfo[HUD_LIVES].y, v_colmap, skins[stplyr->skin].hudname); - else if (V_ThinStringWidth(skins[stplyr->skin].hudname, v_colmap) <= 40) - V_DrawRightAlignedThinString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y, v_colmap, skins[stplyr->skin].hudname); + if (strlen(skins[stplyr->skin]->hudname) <= 5) + V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y, v_colmap, skins[stplyr->skin]->hudname); + else if (V_StringWidth(skins[stplyr->skin]->hudname, v_colmap) <= 48) + V_DrawString(hudinfo[HUD_LIVES].x+18, hudinfo[HUD_LIVES].y, v_colmap, skins[stplyr->skin]->hudname); + else if (V_ThinStringWidth(skins[stplyr->skin]->hudname, v_colmap) <= 40) + V_DrawRightAlignedThinString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y, v_colmap, skins[stplyr->skin]->hudname); else - V_DrawThinString(hudinfo[HUD_LIVES].x+18, hudinfo[HUD_LIVES].y, v_colmap, skins[stplyr->skin].hudname); + V_DrawThinString(hudinfo[HUD_LIVES].x+18, hudinfo[HUD_LIVES].y, v_colmap, skins[stplyr->skin]->hudname); // Power Stones collected if (G_RingSlingerGametype() && LUA_HudEnabled(hud_powerstones)) diff --git a/src/v_video.c b/src/v_video.c index de05df2d5..661938287 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -1041,9 +1041,9 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_ // void V_DrawContinueIcon(INT32 x, INT32 y, INT32 flags, INT32 skinnum, UINT16 skincolor) { - if (skinnum >= 0 && skinnum < numskins && skins[skinnum].sprites[SPR2_XTRA].numframes > XTRA_CONTINUE) + if (skinnum >= 0 && skinnum < numskins && skins[skinnum]->sprites[SPR2_XTRA].numframes > XTRA_CONTINUE) { - spritedef_t *sprdef = &skins[skinnum].sprites[SPR2_XTRA]; + spritedef_t *sprdef = &skins[skinnum]->sprites[SPR2_XTRA]; spriteframe_t *sprframe = &sprdef->spriteframes[XTRA_CONTINUE]; patch_t *patch = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH); const UINT8 *colormap = R_GetTranslationColormap(skinnum, skincolor, GTC_CACHE); diff --git a/src/y_inter.c b/src/y_inter.c index 6d876d7bd..8897d2b99 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -1358,22 +1358,22 @@ void Y_StartIntermission(void) else { // too long so just show "YOU GOT THROUGH THE ACT" - if (strlen(skins[players[consoleplayer].skin].realname) > 13) + if (strlen(skins[players[consoleplayer].skin]->realname) > 13) { strcpy(data.coop.passed1, "you got"); strcpy(data.coop.passed2, (mapheaderinfo[gamemap-1]->actnum) ? "through act" : "through the act"); } // long enough that "X GOT" won't fit so use "X PASSED THE ACT" - else if (strlen(skins[players[consoleplayer].skin].realname) > 8) + else if (strlen(skins[players[consoleplayer].skin]->realname) > 8) { - strcpy(data.coop.passed1, skins[players[consoleplayer].skin].realname); + strcpy(data.coop.passed1, skins[players[consoleplayer].skin]->realname); strcpy(data.coop.passed2, (mapheaderinfo[gamemap-1]->actnum) ? "passed act" : "passed the act"); } // length is okay for normal use else { snprintf(data.coop.passed1, sizeof data.coop.passed1, "%s got", - skins[players[consoleplayer].skin].realname); + skins[players[consoleplayer].skin]->realname); strcpy(data.coop.passed2, (mapheaderinfo[gamemap-1]->actnum) ? "through act" : "through the act"); } } @@ -1445,7 +1445,7 @@ void Y_StartIntermission(void) { snprintf(data.spec.passed1, sizeof data.spec.passed1, "%s", - skins[players[consoleplayer].skin].realname); + skins[players[consoleplayer].skin]->realname); data.spec.passed1[sizeof data.spec.passed1 - 1] = '\0'; strcpy(data.spec.passed2, "got them all!"); @@ -1454,17 +1454,17 @@ void Y_StartIntermission(void) strcpy(data.spec.passed3, "can now become"); snprintf(data.spec.passed4, sizeof data.spec.passed4, "Super %s", - skins[players[consoleplayer].skin].realname); + skins[players[consoleplayer].skin]->realname); data.spec.passed4[sizeof data.spec.passed4 - 1] = '\0'; } } else { - if (strlen(skins[players[consoleplayer].skin].realname) <= SKINNAMESIZE-5) + if (strlen(skins[players[consoleplayer].skin]->realname) <= SKINNAMESIZE-5) { snprintf(data.spec.passed1, sizeof data.spec.passed1, "%s got", - skins[players[consoleplayer].skin].realname); + skins[players[consoleplayer].skin]->realname); data.spec.passed1[sizeof data.spec.passed1 - 1] = '\0'; } else From 655b48b52e56f70449b88d66e05dda3927a17c02 Mon Sep 17 00:00:00 2001 From: Jaime Ita Passos Date: Tue, 10 Aug 2021 00:46:36 -0300 Subject: [PATCH 017/227] Refactor and optimize translation caching --- src/lua_infolib.c | 3 - src/m_menu.c | 6 +- src/r_draw.c | 157 +++++++++++++++++++++++++++------------------- src/r_draw.h | 2 +- src/st_stuff.c | 14 +---- 5 files changed, 97 insertions(+), 85 deletions(-) diff --git a/src/lua_infolib.c b/src/lua_infolib.c index af2d99a0c..bec2e488a 100644 --- a/src/lua_infolib.c +++ b/src/lua_infolib.c @@ -30,9 +30,6 @@ #include "lua_hud.h" // hud_running errors #include "lua_hook.h" // hook_cmd_running errors -extern CV_PossibleValue_t Color_cons_t[]; -extern UINT8 skincolor_modified[]; - boolean LUA_CallAction(enum actionnum actionnum, mobj_t *actor); state_t *astate; diff --git a/src/m_menu.c b/src/m_menu.c index ca4486bf6..083de2de1 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -9111,7 +9111,7 @@ static UINT16 M_SetupChoosePlayerDirect(INT32 choice) { INT32 skinnum, botskinnum; UINT16 i; - UINT16 firstvalid = 65535, lastvalid = 65535; + INT32 firstvalid = INT32_MAX, lastvalid = INT32_MAX; boolean allowed = false; (void)choice; @@ -9155,7 +9155,7 @@ static UINT16 M_SetupChoosePlayerDirect(INT32 choice) } // Handling order. - if (firstvalid == 65535) + if (firstvalid == INT32_MAX) firstvalid = i; else { @@ -9518,7 +9518,7 @@ static void M_ChoosePlayer(INT32 choice) UINT8 skinnum; // skip this if forcecharacter or no characters available - if (choice == 65535) + if (choice == INT32_MAX) { skinnum = botskin = 0; botingame = false; diff --git a/src/r_draw.c b/src/r_draw.c index 7b0d88e7d..a9dbb9358 100644 --- a/src/r_draw.c +++ b/src/r_draw.c @@ -122,54 +122,56 @@ float focallengthf, zeroheight; UINT32 nflatxshift, nflatyshift, nflatshiftup, nflatmask; // ========================================================================= -// TRANSLATION COLORMAP CODE +// TRANSLATION COLORMAP CODE // ========================================================================= -#define DEFAULT_TT_CACHE_INDEX MAXSKINS -#define BOSS_TT_CACHE_INDEX (MAXSKINS + 1) -#define METALSONIC_TT_CACHE_INDEX (MAXSKINS + 2) -#define ALLWHITE_TT_CACHE_INDEX (MAXSKINS + 3) -#define RAINBOW_TT_CACHE_INDEX (MAXSKINS + 4) -#define BLINK_TT_CACHE_INDEX (MAXSKINS + 5) -#define DASHMODE_TT_CACHE_INDEX (MAXSKINS + 6) -#define DEFAULT_STARTTRANSCOLOR 96 #define NUM_PALETTE_ENTRIES 256 +#define DEFAULT_STARTTRANSCOLOR 96 -static UINT8 **translationtablecache[MAXSKINS + 7] = {NULL}; -UINT8 skincolor_modified[MAXSKINCOLORS]; - -static INT32 SkinToCacheIndex(INT32 skinnum) +enum { - switch (skinnum) + DEFAULT_TT_CACHE_INDEX, + BOSS_TT_CACHE_INDEX, + METALSONIC_TT_CACHE_INDEX, + ALLWHITE_TT_CACHE_INDEX, + RAINBOW_TT_CACHE_INDEX, + BLINK_TT_CACHE_INDEX, + DASHMODE_TT_CACHE_INDEX, + + TT_CACHE_SIZE +}; + +static UINT8 **translationtablecache[TT_CACHE_SIZE] = {NULL}; +static UINT8 **skintranslationcache[NUM_PALETTE_ENTRIES] = {NULL}; + +boolean skincolor_modified[MAXSKINCOLORS]; + +static INT32 TranslationToCacheIndex(INT32 translation) +{ + switch (translation) { - case TC_DEFAULT: return DEFAULT_TT_CACHE_INDEX; case TC_BOSS: return BOSS_TT_CACHE_INDEX; case TC_METALSONIC: return METALSONIC_TT_CACHE_INDEX; case TC_ALLWHITE: return ALLWHITE_TT_CACHE_INDEX; case TC_RAINBOW: return RAINBOW_TT_CACHE_INDEX; case TC_BLINK: return BLINK_TT_CACHE_INDEX; case TC_DASHMODE: return DASHMODE_TT_CACHE_INDEX; - default: break; + default: return DEFAULT_TT_CACHE_INDEX; } - - return skinnum; } -static INT32 CacheIndexToSkin(INT32 ttc) +static INT32 CacheIndexToTranslation(INT32 index) { - switch (ttc) + switch (index) { - case DEFAULT_TT_CACHE_INDEX: return TC_DEFAULT; case BOSS_TT_CACHE_INDEX: return TC_BOSS; case METALSONIC_TT_CACHE_INDEX: return TC_METALSONIC; case ALLWHITE_TT_CACHE_INDEX: return TC_ALLWHITE; case RAINBOW_TT_CACHE_INDEX: return TC_RAINBOW; case BLINK_TT_CACHE_INDEX: return TC_BLINK; case DASHMODE_TT_CACHE_INDEX: return TC_DASHMODE; - default: break; + default: return TC_DEFAULT; } - - return ttc; } CV_PossibleValue_t Color_cons_t[MAXSKINCOLORS+1]; @@ -398,18 +400,18 @@ static void R_RainbowColormap(UINT8 *dest_colormap, UINT16 skincolor) RGBA_t color; UINT8 brightness; INT32 j; - UINT8 colorbrightnesses[16]; + UINT8 colorbrightnesses[COLORRAMPSIZE]; UINT16 brightdif; INT32 temp; // first generate the brightness of all the colours of that skincolour - for (i = 0; i < 16; i++) + for (i = 0; i < COLORRAMPSIZE; i++) { color = V_GetColor(skincolors[skincolor].ramp[i]); SETBRIGHTNESS(colorbrightnesses[i], color.s.red, color.s.green, color.s.blue); } - // next, for every colour in the palette, choose the transcolor that has the closest brightness + // next, for every colour in the palette, choose the translated colour that has the closest brightness for (i = 0; i < NUM_PALETTE_ENTRIES; i++) { if (i == 0 || i == 31) // pure black and pure white don't change @@ -420,7 +422,7 @@ static void R_RainbowColormap(UINT8 *dest_colormap, UINT16 skincolor) color = V_GetColor(i); SETBRIGHTNESS(brightness, color.s.red, color.s.green, color.s.blue); brightdif = 256; - for (j = 0; j < 16; j++) + for (j = 0; j < COLORRAMPSIZE; j++) { temp = abs((INT16)brightness - (INT16)colorbrightnesses[j]); if (temp < brightdif) @@ -436,28 +438,29 @@ static void R_RainbowColormap(UINT8 *dest_colormap, UINT16 skincolor) /** \brief Generates a translation colormap. - \param dest_colormap colormap to populate - \param skinnum skin number, or a translation mode - \param color translation color + \param dest_colormap colormap to populate + \param translation translation mode + \param color translation color + \param starttranscolor starting point of the translation \return void */ -static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, UINT16 color) +static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 translation, UINT16 color, INT32 starttranscolor) { - INT32 i, starttranscolor, skinramplength; + INT32 i, skinramplength; // Handle a couple of simple special cases - if (skinnum < TC_DEFAULT) + if (translation < TC_DEFAULT) { - switch (skinnum) + switch (translation) { case TC_ALLWHITE: memset(dest_colormap, 0, NUM_PALETTE_ENTRIES * sizeof(UINT8)); return; case TC_RAINBOW: if (color >= numskincolors) - I_Error("Invalid skin color #%hu.", (UINT16)color); - if (color != SKINCOLOR_NONE) + I_Error("Invalid skin color #%hu", (UINT16)color); + else if (color != SKINCOLOR_NONE) { R_RainbowColormap(dest_colormap, color); return; @@ -465,8 +468,8 @@ static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, U break; case TC_BLINK: if (color >= numskincolors) - I_Error("Invalid skin color #%hu.", (UINT16)color); - if (color != SKINCOLOR_NONE) + I_Error("Invalid skin color #%hu", (UINT16)color); + else if (color != SKINCOLOR_NONE) { memset(dest_colormap, skincolors[color].ramp[3], NUM_PALETTE_ENTRIES * sizeof(UINT8)); return; @@ -480,26 +483,28 @@ static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, U dest_colormap[i] = (UINT8)i; // White! - if (skinnum == TC_BOSS) + if (translation == TC_BOSS) { UINT8 *originalColormap = R_GetTranslationColormap(TC_DEFAULT, (skincolornum_t)color, GTC_CACHE); - for (i = 0; i < 16; i++) + if (starttranscolor >= NUM_PALETTE_ENTRIES) + I_Error("Invalid startcolor #%d", starttranscolor); + for (i = 0; i < COLORRAMPSIZE; i++) { - dest_colormap[DEFAULT_STARTTRANSCOLOR + i] = originalColormap[DEFAULT_STARTTRANSCOLOR + i]; + dest_colormap[starttranscolor + i] = originalColormap[starttranscolor + i]; dest_colormap[31-i] = i; } } - else if (skinnum == TC_METALSONIC) + else if (translation == TC_METALSONIC) { for (i = 0; i < 6; i++) { dest_colormap[skincolors[SKINCOLOR_BLUE].ramp[12-i]] = skincolors[SKINCOLOR_BLUE].ramp[i]; } dest_colormap[159] = dest_colormap[253] = dest_colormap[254] = 0; - for (i = 0; i < 16; i++) + for (i = 0; i < COLORRAMPSIZE; i++) dest_colormap[96+i] = dest_colormap[skincolors[SKINCOLOR_COBALT].ramp[i]]; } - else if (skinnum == TC_DASHMODE) // This is a long one, because MotorRoach basically hand-picked the indices + else if (translation == TC_DASHMODE) // This is a long one, because MotorRoach basically hand-picked the indices { // greens -> ketchups dest_colormap[96] = dest_colormap[97] = 48; @@ -543,26 +548,21 @@ static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, U } if (color >= numskincolors) - I_Error("Invalid skin color #%hu.", (UINT16)color); - - if (skinnum < 0 && skinnum > TC_DEFAULT) - I_Error("Invalid translation colormap index %d.", skinnum); - - starttranscolor = (skinnum != TC_DEFAULT) ? skins[skinnum]->starttranscolor : DEFAULT_STARTTRANSCOLOR; + I_Error("Invalid skin color #%hu", (UINT16)color); if (starttranscolor >= NUM_PALETTE_ENTRIES) - I_Error("Invalid startcolor #%d.", starttranscolor); + I_Error("Invalid startcolor #%d", starttranscolor); // Fill in the entries of the palette that are fixed for (i = 0; i < starttranscolor; i++) dest_colormap[i] = (UINT8)i; - i = starttranscolor + 16; + i = starttranscolor + COLORRAMPSIZE; if (i < NUM_PALETTE_ENTRIES) { for (i = (UINT8)i; i < NUM_PALETTE_ENTRIES; i++) dest_colormap[i] = (UINT8)i; - skinramplength = 16; + skinramplength = COLORRAMPSIZE; } else skinramplength = i - NUM_PALETTE_ENTRIES; // shouldn't this be NUM_PALETTE_ENTRIES - starttranscolor? @@ -575,7 +575,7 @@ static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, U /** \brief Retrieves a translation colormap from the cache. - \param skinnum number of skin, TC_DEFAULT or TC_BOSS + \param skinnum number of skin, or translation modes \param color translation color \param flags set GTC_CACHE to use the cache @@ -583,25 +583,47 @@ static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, U */ UINT8* R_GetTranslationColormap(INT32 skinnum, skincolornum_t color, UINT8 flags) { - UINT8* ret; - INT32 skintableindex = SkinToCacheIndex(skinnum); // Adjust if we want the default colormap - INT32 i; + UINT8 ***cache = NULL; + INT32 index, starttranscolor; + UINT8 *ret; + + // Adjust if we want the default colormap + if (skinnum >= numskins) + I_Error("Invalid skin number %d", skinnum); + else if (skinnum >= 0) + { + cache = skintranslationcache; + starttranscolor = index = skins[skinnum]->starttranscolor; + } + else if (skinnum <= TC_DEFAULT) + { + cache = translationtablecache; + starttranscolor = DEFAULT_STARTTRANSCOLOR; + index = TranslationToCacheIndex(skinnum); + } + else + I_Error("Invalid translation %d", skinnum); if (flags & GTC_CACHE) { // Allocate table for skin if necessary - if (!translationtablecache[skintableindex]) - translationtablecache[skintableindex] = Z_Calloc(MAXSKINCOLORS * sizeof(UINT8**), PU_STATIC, NULL); + if (!cache[index]) + cache[index] = Z_Calloc(MAXSKINCOLORS * sizeof(UINT8**), PU_STATIC, NULL); // Get colormap - ret = translationtablecache[skintableindex][color]; + ret = cache[index][color]; // Rebuild the cache if necessary if (skincolor_modified[color]) { - for (i = 0; i < (INT32)(sizeof(translationtablecache) / sizeof(translationtablecache[0])); i++) + INT32 i; + + for (i = 0; i < TT_CACHE_SIZE; i++) if (translationtablecache[i] && translationtablecache[i][color]) - R_GenerateTranslationColormap(translationtablecache[i][color], CacheIndexToSkin(i), color); + R_GenerateTranslationColormap(translationtablecache[i][color], CacheIndexToTranslation(i), color, starttranscolor); + for (i = 0; i < NUM_PALETTE_ENTRIES; i++) + if (skintranslationcache[i] && skintranslationcache[i][color]) + R_GenerateTranslationColormap(skintranslationcache[i][color], 0, color, i); skincolor_modified[color] = false; } @@ -612,11 +634,11 @@ UINT8* R_GetTranslationColormap(INT32 skinnum, skincolornum_t color, UINT8 flags if (!ret) { ret = Z_MallocAlign(NUM_PALETTE_ENTRIES, (flags & GTC_CACHE) ? PU_LEVEL : PU_STATIC, NULL, 8); - R_GenerateTranslationColormap(ret, skinnum, color); + R_GenerateTranslationColormap(ret, skinnum, color, starttranscolor); // Cache the colormap if desired if (flags & GTC_CACHE) - translationtablecache[skintableindex][color] = ret; + cache[index][color] = ret; } return ret; @@ -634,9 +656,12 @@ void R_FlushTranslationColormapCache(void) { INT32 i; - for (i = 0; i < (INT32)(sizeof(translationtablecache) / sizeof(translationtablecache[0])); i++) + for (i = 0; i < TT_CACHE_SIZE; i++) if (translationtablecache[i]) memset(translationtablecache[i], 0, MAXSKINCOLORS * sizeof(UINT8**)); + for (i = 0; i < NUM_PALETTE_ENTRIES; i++) + if (skintranslationcache[i]) + memset(skintranslationcache[i], 0, MAXSKINCOLORS * sizeof(UINT8**)); } UINT16 R_GetColorByName(const char *name) diff --git a/src/r_draw.h b/src/r_draw.h index 2173c7a5a..6d7e89f35 100644 --- a/src/r_draw.h +++ b/src/r_draw.h @@ -147,7 +147,7 @@ UINT8 *R_GetBlendTable(int style, INT32 alphalevel); boolean R_BlendLevelVisible(INT32 blendmode, INT32 alphalevel); // Color ramp modification should force a recache -extern UINT8 skincolor_modified[]; +extern boolean skincolor_modified[]; void R_InitViewBuffer(INT32 width, INT32 height); void R_InitViewBorder(void); diff --git a/src/st_stuff.c b/src/st_stuff.c index 88affcf0a..1db91da7a 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -128,8 +128,6 @@ static patch_t *gotrflag; static patch_t *gotbflag; static patch_t *fnshico; -static boolean facefreed[MAXPLAYERS]; - hudinfo_t hudinfo[NUMHUDITEMS] = { { 16, 176, V_SNAPTOLEFT|V_SNAPTOBOTTOM}, // HUD_LIVES @@ -361,17 +359,14 @@ void ST_LoadFaceGraphics(INT32 skinnum) } else faceprefix[skinnum] = superprefix[skinnum] = W_CachePatchName("MISSING", PU_HUDGFX); // ditto - facefreed[skinnum] = false; } void ST_ReloadSkinFaceGraphics(void) { INT32 i; - if (faceprefix) - Z_Free(faceprefix); - if (superprefix) - Z_Free(superprefix); + Z_Free(faceprefix); + Z_Free(superprefix); if (!numskins) return; @@ -421,11 +416,6 @@ lumpnum_t st_borderpatchnum; void ST_Init(void) { - INT32 i; - - for (i = 0; i < MAXPLAYERS; i++) - facefreed[i] = true; - if (dedicated) return; From 1a84f1bcba730c5924debce96473639f0ca7fb67 Mon Sep 17 00:00:00 2001 From: Jaime Ita Passos Date: Tue, 10 Aug 2021 15:17:22 -0300 Subject: [PATCH 018/227] Minor changes and fixes --- src/d_player.h | 4 +- src/deh_soc.c | 103 +++++++++++++++++++++++------------------ src/dehacked.c | 22 ++++++--- src/dehacked.h | 1 + src/doomdef.h | 7 +-- src/g_game.c | 2 +- src/hardware/hw_main.c | 2 +- src/hardware/hw_md2.c | 2 +- src/m_menu.h | 4 +- src/r_picformats.c | 9 ++-- src/r_skins.c | 2 +- src/r_skins.h | 4 +- src/r_things.c | 2 +- src/y_inter.c | 8 ++-- 14 files changed, 96 insertions(+), 76 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index 54ab34288..1418b7c52 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -375,10 +375,10 @@ typedef struct player_s UINT16 flashcount; UINT16 flashpal; - // Player skin colorshift, 0-15 for which color to draw player. + // Player skin colorshift, which color to draw player. UINT16 skincolor; - INT32 skin; + UINT8 skin; UINT32 availabilities; UINT32 score; // player score diff --git a/src/deh_soc.c b/src/deh_soc.c index 4c63a4d6a..2624d183c 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -187,7 +187,7 @@ void clear_levels(void) P_AllocMapHeader(gamemap-1); } -static boolean findFreeSlot(INT32 *num) +static boolean findCharacterSlot(INT32 *num) { if (description) { @@ -213,30 +213,43 @@ void readPlayer(MYFILE *f, INT32 num) char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); char *word; char *word2; - char *displayname = ZZ_Alloc(MAXLINELEN+1); - INT32 i; boolean slotfound = false; + boolean failure = false; + INT32 i; + + if (num < 0 || num >= MAXCHARACTERSLOTS) + { + deh_warning("Character %d out of range (0 - %d)", num, MAXCHARACTERSLOTS-1); + failure = true; + } + + #define FINDSLOT \ + if (!failure && !slotfound && (slotfound = findCharacterSlot(&num)) == false) { \ + failure = true; \ + deh_warning("Too many characters, ignoring"); \ + } #define SLOTFOUND \ - if (!slotfound && (slotfound = findFreeSlot(&num)) == false) \ - goto done; - - displayname[MAXLINELEN] = '\0'; + FINDSLOT \ + if (failure) \ + continue; do { if (myfgets(s, MAXLINELEN, f)) { + char stringvalue[MAXLINELEN]; + if (s[0] == '\n') break; - for (i = 0; i < MAXLINELEN-3; i++) + stringvalue[0] = '\0'; + + for (i = 0; i < MAXLINELEN-3 && !failure; i++) { - char *tmp; if (s[i] == '=') { - tmp = &s[i+2]; - strncpy(displayname, tmp, SKINNAMESIZE); + strlcpy(stringvalue, &s[i+2], sizeof stringvalue); break; } } @@ -251,7 +264,13 @@ void readPlayer(MYFILE *f, INT32 num) { char *playertext = NULL; - SLOTFOUND + FINDSLOT + + if (failure) + { + ignorelinesuntilhash(f); + continue; + } // A friendly neighborhood alias for brevity's sake #define NOTE_SIZE sizeof(description[num].notes) @@ -271,7 +290,7 @@ void readPlayer(MYFILE *f, INT32 num) myhashfgets(playertext, NOTE_SIZE, f), NOTE_SIZE); } else - strcpy(description[num].notes, ""); + description[num].notes[0] = '\0'; // For some reason, cutting the string did not work above. Most likely due to strcpy or strcat... // It works down here, though. @@ -300,37 +319,32 @@ void readPlayer(MYFILE *f, INT32 num) if (word2[strlen(word2)-1] == '\n') word2[strlen(word2)-1] = '\0'; - i = atoi(word2); if (fastcmp(word, "PICNAME")) { SLOTFOUND strncpy(description[num].picname, word2, 8); } - // new character select else if (fastcmp(word, "DISPLAYNAME")) { + char *cur = NULL; + SLOTFOUND - // replace '#' with line breaks - // (also remove any '\n') + + // Remove any line breaks + cur = strchr(stringvalue, '\n'); + if (cur) + *cur = '\0'; + + // Turn '#' into line breaks + cur = strchr(stringvalue, '#'); + while (cur) { - char *cur = NULL; - - // remove '\n' - cur = strchr(displayname, '\n'); - if (cur) - *cur = '\0'; - - // turn '#' into '\n' - cur = strchr(displayname, '#'); - while (cur) - { - *cur = '\n'; - cur = strchr(cur, '#'); - } + *cur = '\n'; + cur = strchr(cur, '#'); } - // copy final string - strncpy(description[num].displayname, displayname, SKINNAMESIZE); + + strlcpy(description[num].displayname, stringvalue, sizeof description[num].displayname); } else if (fastcmp(word, "OPPOSITECOLOR") || fastcmp(word, "OPPOSITECOLOUR")) { @@ -361,10 +375,12 @@ void readPlayer(MYFILE *f, INT32 num) Because of this, you are allowed to edit any previous entries you like, but only if you signal that you are purposely doing so by disabling and then reenabling the slot. */ - if (i && !slotfound && (slotfound = findFreeSlot(&num)) == false) - goto done; + i = atoi(word2); + if (i && !slotfound && (slotfound = findCharacterSlot(&num)) == false) + failure = true; - description[num].used = (!!i); + if (!failure) + description[num].used = (!!i); } else if (fastcmp(word, "SKINNAME")) { @@ -373,13 +389,12 @@ void readPlayer(MYFILE *f, INT32 num) strlcpy(description[num].skinname, word2, sizeof description[num].skinname); strlwr(description[num].skinname); } - else + else if (!failure) deh_warning("readPlayer %d: unknown word '%s'", num, word); } } while (!myfeof(f)); // finish when the line is empty + #undef FINDSLOT #undef SLOTFOUND -done: - Z_Free(displayname); Z_Free(s); } @@ -920,7 +935,7 @@ void readspriteinfo(MYFILE *f, INT32 num, boolean sprite2) INT32 value; #endif char *lastline; - INT32 *skinnumbers = NULL; + UINT8 *skinnumbers = NULL; INT32 foundskins = 0; // allocate a spriteinfo @@ -1010,8 +1025,8 @@ void readspriteinfo(MYFILE *f, INT32 num, boolean sprite2) } if (skinnumbers == NULL) - skinnumbers = Z_Malloc(sizeof(INT32) * numskins, PU_STATIC, NULL); - skinnumbers[foundskins] = skinnum; + skinnumbers = Z_Malloc(sizeof(UINT8) * numskins, PU_STATIC, NULL); + skinnumbers[foundskins] = (UINT8)skinnum; foundskins++; } else if (fastcmp(word, "DEFAULT")) @@ -1054,8 +1069,7 @@ void readspriteinfo(MYFILE *f, INT32 num, boolean sprite2) } for (i = 0; i < foundskins; i++) { - size_t skinnum = skinnumbers[i]; - skin_t *skin = skins[skinnum]; + skin_t *skin = skins[skinnumbers[i]]; spriteinfo_t *sprinfo = skin->sprinfo; M_Memcpy(&sprinfo[num], info, sizeof(spriteinfo_t)); } @@ -1121,7 +1135,6 @@ void readsprite2(MYFILE *f, INT32 num) Z_Free(s); } -// copypasted from readPlayer :] void readgametype(MYFILE *f, char *gtname) { char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); diff --git a/src/dehacked.c b/src/dehacked.c index 7a8e2e9d6..ac9a4a46a 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -169,6 +169,20 @@ static void ignorelines(MYFILE *f) Z_Free(s); } +void ignorelinesuntilhash(MYFILE *f) +{ + char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); + do + { + if (myfgets(s, MAXLINELEN, f)) + { + if (s[0] == '#') + break; + } + } while (!myfeof(f)); + Z_Free(s); +} + static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) { char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); @@ -226,13 +240,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) i = 0; if (fastcmp(word, "CHARACTER")) { - if (i >= 0 && i < MAXCHARACTERSLOTS) - readPlayer(f, i); - else - { - deh_warning("Character %d out of range (0 - 31)", i); - ignorelines(f); - } + readPlayer(f, i); continue; } else if (fastcmp(word, "EMBLEM")) diff --git a/src/dehacked.h b/src/dehacked.h index 1b200e246..23765f8b7 100644 --- a/src/dehacked.h +++ b/src/dehacked.h @@ -61,4 +61,5 @@ typedef struct #define myfeof(a) (a->data + a->size <= a->curpos) char *myfgets(char *buf, size_t bufsize, MYFILE *f); char *myhashfgets(char *buf, size_t bufsize, MYFILE *f); +void ignorelinesuntilhash(MYFILE *f); #endif diff --git a/src/doomdef.h b/src/doomdef.h index 5e7df3ecd..cf209bc8a 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -220,10 +220,11 @@ extern char logfilename[1024]; // NOTE: it needs more than this to increase the number of players... #define MAXPLAYERS 32 -#define MAXSKINS 256 -#define MAXCHARACTERSLOTS (MAXSKINS * 2) -#define PLAYERSMASK (MAXPLAYERS-1) #define MAXPLAYERNAME 21 +#define PLAYERSMASK (MAXPLAYERS-1) + +#define MAXSKINS 256 +#define MAXCHARACTERSLOTS (MAXSKINS * 3) #define COLORRAMPSIZE 16 #define MAXCOLORNAME 32 diff --git a/src/g_game.c b/src/g_game.c index 6b3354de7..2908b9f01 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2488,7 +2488,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) UINT8 laps; UINT8 mare; UINT16 skincolor; - INT32 skin; + UINT8 skin; UINT32 availabilities; tic_t jointime; tic_t quittime; diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 66df7d2cd..4913714b1 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -5359,7 +5359,7 @@ static void HWR_ProjectSprite(mobj_t *thing) } else if (thing->skin && thing->sprite == SPR_PLAY) // This thing is a player! { - INT32 skinnum = ((skin_t*)thing->skin)->skinnum; + UINT8 skinnum = ((skin_t*)thing->skin)->skinnum; vis->colormap = R_GetTranslationColormap(skinnum, thing->color, GTC_CACHE); } else diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c index b54e14089..06f2ecf34 100644 --- a/src/hardware/hw_md2.c +++ b/src/hardware/hw_md2.c @@ -1271,7 +1271,7 @@ boolean HWR_DrawModel(gl_vissprite_t *spr) // 2. draw model with correct position, rotation,... if (spr->mobj->skin && spr->mobj->sprite == SPR_PLAY) // Use the player MD2 list if the mobj has a skin and is using the player sprites { - INT32 skinnum = ((skin_t*)spr->mobj->skin)->skinnum; + UINT8 skinnum = ((skin_t*)spr->mobj->skin)->skinnum; md2 = &md2_playermodels[skinnum]; sprinfo = &((skin_t *)spr->mobj->skin)->sprinfo[spr->mobj->sprite2]; } diff --git a/src/m_menu.h b/src/m_menu.h index 3e2f6f9ae..b7c5c797b 100644 --- a/src/m_menu.h +++ b/src/m_menu.h @@ -375,10 +375,8 @@ typedef struct patch_t *charpic; UINT8 prev; UINT8 next; - - // new character select char displayname[SKINNAMESIZE+1]; - SINT8 skinnum[2]; + INT16 skinnum[2]; UINT16 oppositecolor; char nametag[8]; patch_t *namepic; diff --git a/src/r_picformats.c b/src/r_picformats.c index 5c2544a7f..3e1ea8eb9 100644 --- a/src/r_picformats.c +++ b/src/r_picformats.c @@ -1498,7 +1498,7 @@ static void R_ParseSpriteInfo(boolean spr2) spritenum_t sprnum = NUMSPRITES; playersprite_t spr2num = NUMPLAYERSPRITES; INT32 i; - INT32 *skinnumbers = NULL; + UINT8 *skinnumbers = NULL; INT32 foundskins = 0; // Sprite name @@ -1597,8 +1597,8 @@ static void R_ParseSpriteInfo(boolean spr2) I_Error("Error parsing SPRTINFO lump: Unknown skin \"%s\"", skinName); if (skinnumbers == NULL) - skinnumbers = Z_Malloc(sizeof(INT32) * numskins, PU_STATIC, NULL); - skinnumbers[foundskins] = skinnum; + skinnumbers = Z_Malloc(sizeof(UINT8) * numskins, PU_STATIC, NULL); + skinnumbers[foundskins] = (UINT8)skinnum; foundskins++; } else if (stricmp(sprinfoToken, "FRAME")==0) @@ -1611,8 +1611,7 @@ static void R_ParseSpriteInfo(boolean spr2) I_Error("Error parsing SPRTINFO lump: No skins specified in this sprite2 definition"); for (i = 0; i < foundskins; i++) { - size_t skinnum = skinnumbers[i]; - skin_t *skin = skins[skinnum]; + skin_t *skin = skins[skinnumbers[i]]; spriteinfo_t *sprinfo = skin->sprinfo; M_Memcpy(&sprinfo[spr2num], info, sizeof(spriteinfo_t)); } diff --git a/src/r_skins.c b/src/r_skins.c index b0dcda035..c4cac6ce2 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -278,7 +278,7 @@ INT32 R_SkinAvailable(const char *name) { // search in the skin list if (!stricmp(skins[i]->name,name)) - return skins[i]->skinnum; + return i; } return -1; } diff --git a/src/r_skins.h b/src/r_skins.h index 67f34aa66..8cf3f640b 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -31,8 +31,8 @@ /// The skin_t struct typedef struct { - char name[SKINNAMESIZE+1]; // INT16 descriptive name of the skin - UINT32 skinnum; + char name[SKINNAMESIZE+1]; // name of the skin + UINT8 skinnum; UINT16 wadnum; skinflags_t flags; diff --git a/src/r_things.c b/src/r_things.c index 69a9eb5e9..b4a45f966 100644 --- a/src/r_things.c +++ b/src/r_things.c @@ -764,7 +764,7 @@ UINT8 *R_GetSpriteTranslation(vissprite_t *vis) } else if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && vis->mobj->sprite == SPR_PLAY) // This thing is a player! { - INT32 skinnum = ((skin_t*)vis->mobj->skin)->skinnum; + UINT8 skinnum = ((skin_t*)vis->mobj->skin)->skinnum; return R_GetTranslationColormap(skinnum, vis->mobj->color, GTC_CACHE); } else // Use the defaults diff --git a/src/y_inter.c b/src/y_inter.c index 8897d2b99..8a8fcd425 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -1617,7 +1617,7 @@ static void Y_CalculateMatchWinners(void) { data.match.scores[data.match.numplayers] = players[i].score; data.match.color[data.match.numplayers] = &players[i].skincolor; - data.match.character[data.match.numplayers] = &players[i].skin; + data.match.character[data.match.numplayers] = players[i].skin; data.match.name[data.match.numplayers] = player_names[i]; data.match.spectator[data.match.numplayers] = players[i].spectator; data.match.num[data.match.numplayers] = i; @@ -1662,7 +1662,7 @@ static void Y_CalculateTimeRaceWinners(void) { data.match.scores[data.match.numplayers] = players[i].realtime; data.match.color[data.match.numplayers] = &players[i].skincolor; - data.match.character[data.match.numplayers] = &players[i].skin; + data.match.character[data.match.numplayers] = players[i].skin; data.match.name[data.match.numplayers] = player_names[i]; data.match.num[data.match.numplayers] = i; } @@ -1788,7 +1788,7 @@ static void Y_CalculateCompetitionWinners(void) strncpy(data.competition.name[data.competition.numplayers], tempname, 9); data.competition.color[data.competition.numplayers] = &players[winner].skincolor; - data.competition.character[data.competition.numplayers] = &players[winner].skin; + data.competition.character[data.competition.numplayers] = players[winner].skin; completed[winner] = true; data.competition.numplayers++; @@ -2110,7 +2110,7 @@ static void Y_AwardSpecialStageBonus(void) if (players[i].gotcontinue) data.spec.continues |= 0x80; data.spec.playercolor = &players[i].skincolor; - data.spec.playerchar = &players[i].skin; + data.spec.playerchar = players[i].skin; } } } From 290adb23726da907099f59936a8e8103677f6031 Mon Sep 17 00:00:00 2001 From: Jaime Ita Passos Date: Tue, 10 Aug 2021 17:02:43 -0300 Subject: [PATCH 019/227] Add a descriptive comment about the MAXSKINS macro --- src/doomdef.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/doomdef.h b/src/doomdef.h index cf209bc8a..43e25b064 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -223,8 +223,13 @@ extern char logfilename[1024]; #define MAXPLAYERNAME 21 #define PLAYERSMASK (MAXPLAYERS-1) +// Don't make MAXSKINS higher than 256, since skin numbers are used with an +// UINT8 in various parts of the codebase. If you do anyway, the data type +// of those variables will have to be changed into at least an UINT16. +// This change must affect code such as demo recording and playback, +// and the structure of some networking packets and commands. #define MAXSKINS 256 -#define MAXCHARACTERSLOTS (MAXSKINS * 3) +#define MAXCHARACTERSLOTS (MAXSKINS * 3) // Should be higher than MAXSKINS. #define COLORRAMPSIZE 16 #define MAXCOLORNAME 32 From 99c4cc246ed20c1281c396a2b068dc8d56734c6a Mon Sep 17 00:00:00 2001 From: GoldenTails Date: Sat, 14 Aug 2021 21:54:19 -0500 Subject: [PATCH 020/227] Rework `bind` command's argument list to accept additional arguments as arguments to the command being bound. --- src/console.c | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/src/console.c b/src/console.c index b3c413840..cce82a62c 100644 --- a/src/console.c +++ b/src/console.c @@ -209,13 +209,16 @@ static char *bindtable[NUMINPUTS]; static void CONS_Bind_f(void) { size_t na; + char *newcmd; + //size_t newlen = 0; + unsigned int i; INT32 key; na = COM_Argc(); - if (na != 2 && na != 3) + if (na < 2) { - CONS_Printf(M_GetText("bind []: create shortcut keys to command(s)\n")); + CONS_Printf(M_GetText("bind [] [] [...]: create shortcut keys to command(s)\n")); CONS_Printf("\x82%s", M_GetText("Bind table :\n")); na = 0; for (key = 0; key < NUMINPUTS; key++) @@ -239,8 +242,36 @@ static void CONS_Bind_f(void) Z_Free(bindtable[key]); bindtable[key] = NULL; - if (na == 3) - bindtable[key] = Z_StrDup(COM_Argv(2)); + if (na < 3) + return; + + for (i = 2; i < na; ++i) + { + const char *arg = COM_Argv(i); + + // on the second iteration, and after + if (i > 2) + { + size_t newlen = strlen(bindtable[key]) + strlen(arg) + 1; // new length, allow space for ' ' and '\0' + size_t curpos = newcmd - bindtable[key]; // offset from newcmd to original pointer + + newcmd = bindtable[key] = Z_Realloc(bindtable[key], newlen, PU_STATIC, NULL); + newcmd += curpos; // reapply offset + + newcmd[0] = ' '; // replace previous '\0' w/ ' ' + ++newcmd; // make sure later strcpy doesnt overwrite ' ' + } + // first iteration + else + // allocate space for argument and a ' ' or '\0' + newcmd = bindtable[key] = Z_Calloc(strlen(arg) + 1, PU_STATIC, NULL); + + // the copy + strcpy(newcmd, arg); + + // move window past copied argument for next iteration + newcmd += strlen(arg); + } } //====================================================================== From 9441bafb091fbc510e48e3360770c8b63782de2d Mon Sep 17 00:00:00 2001 From: GoldenTails Date: Sun, 5 Sep 2021 14:38:51 -0500 Subject: [PATCH 021/227] Restructure the code to be able to possibly support loadfile. --- src/blua/lbaselib.c | 2 +- src/lua_script.c | 88 +++++++++++++++++++++++++++++++++++---------- src/lua_script.h | 3 +- src/w_wad.c | 8 ++--- 4 files changed, 76 insertions(+), 25 deletions(-) diff --git a/src/blua/lbaselib.c b/src/blua/lbaselib.c index 644565c28..9d4e30eb1 100644 --- a/src/blua/lbaselib.c +++ b/src/blua/lbaselib.c @@ -282,7 +282,7 @@ static int luaB_dofile (lua_State *L) { if (lumpnum == INT16_MAX) luaL_error(L, "can't find script " LUA_QS, fullfilename); - LUA_LoadLump(numwadfiles - 1, lumpnum, false); + LUA_DoLump(numwadfiles - 1, lumpnum, false); return lua_gettop(L) - n; } diff --git a/src/lua_script.c b/src/lua_script.c index 9eb1912b3..1cc95306e 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -554,64 +554,114 @@ void LUA_ClearExtVars(void) INT32 lua_lumploading = 0; // Load a script from a MYFILE -static inline void LUA_LoadFile(MYFILE *f, char *name, boolean noresults) +static inline boolean LUA_LoadFile(MYFILE *f, char *name) { int errorhandlerindex; + boolean success; if (!name) name = wadfiles[f->wad]->filename; + CONS_Printf("Loading Lua script from %s\n", name); + if (!gL) // Lua needs to be initialized LUA_ClearState(); + lua_pushinteger(gL, f->wad); lua_setfield(gL, LUA_REGISTRYINDEX, "WAD"); + lua_pushcfunction(gL, LUA_GetErrorMessage); + errorhandlerindex = lua_gettop(gL); + + success = !luaL_loadbuffer(gL, f->data, f->size, va("@%s",name)); + + if (!success) { + CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1)); + lua_pop(gL,1); + } + + lua_gc(gL, LUA_GCCOLLECT, 0); + lua_remove(gL, errorhandlerindex); + + return success; +} + +// Runs a script loaded by LUA_LoadFile. +static inline void LUA_DoFile(boolean noresults) +{ + int errorhandlerindex; + + if (!gL) // LUA_LoadFile should've allocated gL for us! + return; + lua_lumploading++; // turn on loading flag lua_pushcfunction(gL, LUA_GetErrorMessage); - errorhandlerindex = lua_gettop(gL); - if (luaL_loadbuffer(gL, f->data, f->size, va("@%s",name)) || lua_pcall(gL, 0, noresults ? 0 : LUA_MULTRET, lua_gettop(gL) - 1)) { + lua_insert(gL, -2); // move the function we're calling to the top. + errorhandlerindex = lua_gettop(gL) - 1; + + if (lua_pcall(gL, 0, noresults ? 0 : LUA_MULTRET, lua_gettop(gL) - 1)) { CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1)); lua_pop(gL,1); } + lua_gc(gL, LUA_GCCOLLECT, 0); lua_remove(gL, errorhandlerindex); lua_lumploading--; // turn off again } -// Load a script from a lump -void LUA_LoadLump(UINT16 wad, UINT16 lump, boolean noresults) +static inline MYFILE *LUA_GetFile(UINT16 wad, UINT16 lump, char **name) { - MYFILE f; - char *name; + MYFILE *f = Z_Malloc(sizeof(MYFILE), PU_LUA, NULL); size_t len; - f.wad = wad; - f.size = W_LumpLengthPwad(wad, lump); - f.data = Z_Malloc(f.size, PU_LUA, NULL); - W_ReadLumpPwad(wad, lump, f.data); - f.curpos = f.data; + + f->wad = wad; + f->size = W_LumpLengthPwad(wad, lump); + f->data = Z_Malloc(f->size, PU_LUA, NULL); + W_ReadLumpPwad(wad, lump, f->data); + f->curpos = f->data; len = strlen(wadfiles[wad]->filename); // length of file name if (wadfiles[wad]->type == RET_LUA) { - name = malloc(len+1); - strcpy(name, wadfiles[wad]->filename); + *name = malloc(len+1); + strcpy(*name, wadfiles[wad]->filename); } else // If it's not a .lua file, copy the lump name in too. { lumpinfo_t *lump_p = &wadfiles[wad]->lumpinfo[lump]; len += 1 + strlen(lump_p->fullname); // length of file name, '|', and lump name - name = malloc(len+1); - sprintf(name, "%s|%s", wadfiles[wad]->filename, lump_p->fullname); - name[len] = '\0'; + *name = malloc(len+1); + sprintf(*name, "%s|%s", wadfiles[wad]->filename, lump_p->fullname); + (*name)[len] = '\0'; // annoying that index takes priority over dereference, but w/e } - LUA_LoadFile(&f, name, noresults); // actually load file! + return f; +} + +// Load a script from a lump +boolean LUA_LoadLump(UINT16 wad, UINT16 lump) +{ + char *name = NULL; + MYFILE *f = LUA_GetFile(wad, lump, &name); + boolean success = LUA_LoadFile(f, name); // actually load file! free(name); - Z_Free(f.data); + + Z_Free(f->data); + Z_Free(f); + + return success; +} + +void LUA_DoLump(UINT16 wad, UINT16 lump, boolean noresults) +{ + boolean success = LUA_LoadLump(wad, lump); + + if (success) + LUA_DoFile(noresults); // run it } #ifdef LUA_ALLOW_BYTECODE diff --git a/src/lua_script.h b/src/lua_script.h index e88256941..6b7c21124 100644 --- a/src/lua_script.h +++ b/src/lua_script.h @@ -45,7 +45,8 @@ extern INT32 lua_lumploading; // is LUA_LoadLump being called? int LUA_GetErrorMessage(lua_State *L); int LUA_Call(lua_State *L, int nargs, int nresults, int errorhandlerindex); -void LUA_LoadLump(UINT16 wad, UINT16 lump, boolean noresults); +boolean LUA_LoadLump(UINT16 wad, UINT16 lump); +void LUA_DoLump(UINT16 wad, UINT16 lump, boolean noresults); #ifdef LUA_ALLOW_BYTECODE void LUA_DumpFile(const char *filename); #endif diff --git a/src/w_wad.c b/src/w_wad.c index cbff5c67b..e3dc3a971 100644 --- a/src/w_wad.c +++ b/src/w_wad.c @@ -199,7 +199,7 @@ static inline void W_LoadDehackedLumpsPK3(UINT16 wadnum, boolean mainfile) posStart = W_CheckNumForFullNamePK3("Init.lua", wadnum, 0); if (posStart != INT16_MAX) { - LUA_LoadLump(wadnum, posStart, true); + LUA_DoLump(wadnum, posStart, true); } else { @@ -208,7 +208,7 @@ static inline void W_LoadDehackedLumpsPK3(UINT16 wadnum, boolean mainfile) { posEnd = W_CheckNumForFolderEndPK3("Lua/", wadnum, posStart); for (; posStart < posEnd; posStart++) - LUA_LoadLump(wadnum, posStart, true); + LUA_DoLump(wadnum, posStart, true); } } @@ -241,7 +241,7 @@ static inline void W_LoadDehackedLumps(UINT16 wadnum, boolean mainfile) lumpinfo_t *lump_p = wadfiles[wadnum]->lumpinfo; for (lump = 0; lump < wadfiles[wadnum]->numlumps; lump++, lump_p++) if (memcmp(lump_p->name,"LUA_",4)==0) - LUA_LoadLump(wadnum, lump, true); + LUA_DoLump(wadnum, lump, true); } { @@ -879,7 +879,7 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup) DEH_LoadDehackedLumpPwad(numwadfiles - 1, 0, mainfile); break; case RET_LUA: - LUA_LoadLump(numwadfiles - 1, 0, true); + LUA_DoLump(numwadfiles - 1, 0, true); break; default: break; From bd9118eae7b9f4c988b2d6e072c150d8a84db720 Mon Sep 17 00:00:00 2001 From: GoldenTails Date: Sun, 5 Sep 2021 14:52:40 -0500 Subject: [PATCH 022/227] Implement loadfile --- src/blua/lbaselib.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/blua/lbaselib.c b/src/blua/lbaselib.c index 9d4e30eb1..830043f30 100644 --- a/src/blua/lbaselib.c +++ b/src/blua/lbaselib.c @@ -287,6 +287,25 @@ static int luaB_dofile (lua_State *L) { return lua_gettop(L) - n; } +// Edited to load PK3 entries instead +static int luaB_loadfile (lua_State *L) { + const char *filename = luaL_checkstring(L, 1); + char fullfilename[256]; + UINT16 lumpnum; + int n = lua_gettop(L); + + if (wadfiles[numwadfiles - 1]->type != RET_PK3) + luaL_error(L, "loadfile() only works with PK3 files"); + + snprintf(fullfilename, sizeof(fullfilename), "Lua/%s", filename); + lumpnum = W_CheckNumForFullNamePK3(fullfilename, numwadfiles - 1, 0); + if (lumpnum == INT16_MAX) + luaL_error(L, "can't find script " LUA_QS, fullfilename); + + LUA_LoadLump(numwadfiles - 1, lumpnum); + + return 1; +} static int luaB_assert (lua_State *L) { luaL_checkany(L, 1); @@ -406,6 +425,7 @@ static const luaL_Reg base_funcs[] = { {"collectgarbage", luaB_collectgarbage}, {"error", luaB_error}, {"dofile", luaB_dofile}, + {"loadfile", luaB_loadfile}, {"gcinfo", luaB_gcinfo}, {"getfenv", luaB_getfenv}, {"getmetatable", luaB_getmetatable}, From 6f19615fabe216537d4d59b5a0de717c7df755b1 Mon Sep 17 00:00:00 2001 From: GoldenTails Date: Mon, 6 Sep 2021 15:05:34 -0500 Subject: [PATCH 023/227] Remove unused variable to fix warning in luaB_loadfile. --- src/blua/lbaselib.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/blua/lbaselib.c b/src/blua/lbaselib.c index 830043f30..d7bdc8204 100644 --- a/src/blua/lbaselib.c +++ b/src/blua/lbaselib.c @@ -292,7 +292,6 @@ static int luaB_loadfile (lua_State *L) { const char *filename = luaL_checkstring(L, 1); char fullfilename[256]; UINT16 lumpnum; - int n = lua_gettop(L); if (wadfiles[numwadfiles - 1]->type != RET_PK3) luaL_error(L, "loadfile() only works with PK3 files"); From 2fb1c5f1ef43c142eb5e4ecdb98ea7cac612c8f6 Mon Sep 17 00:00:00 2001 From: Hannu Hanhi Date: Sat, 30 Oct 2021 03:53:46 +0300 Subject: [PATCH 024/227] Wireframe mode for OpenGL --- src/hardware/hw_defs.h | 1 + src/hardware/hw_main.c | 24 ++++++++++++++++++++++++ src/hardware/hw_main.h | 2 ++ src/hardware/r_opengl/r_opengl.c | 7 +++++++ 4 files changed, 34 insertions(+) diff --git a/src/hardware/hw_defs.h b/src/hardware/hw_defs.h index 8df9b8916..139de66e2 100644 --- a/src/hardware/hw_defs.h +++ b/src/hardware/hw_defs.h @@ -295,6 +295,7 @@ enum hwdsetspecialstate HWD_SET_SHADERS, HWD_SET_TEXTUREFILTERMODE, HWD_SET_TEXTUREANISOTROPICMODE, + HWD_SET_WIREFRAME, HWD_NUMSTATE }; diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index e0851af85..198bb3992 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -178,6 +178,11 @@ static boolean HWR_UseShader(void) return (cv_glshaders.value && gl_shadersavailable); } +static boolean HWR_IsWireframeMode(void) +{ + return (cv_glwireframe.value && cv_debug); +} + void HWR_Lighting(FSurfaceInfo *Surface, INT32 light_level, extracolormap_t *colormap) { RGBA_t poly_color, tint_color, fade_color; @@ -5642,6 +5647,9 @@ void HWR_BuildSkyDome(void) static void HWR_DrawSkyBackground(player_t *player) { + if (HWR_IsWireframeMode()) + return; + HWD.pfnSetBlend(PF_Translucent|PF_NoDepthTest|PF_Modulated); if (cv_glskydome.value) @@ -5994,6 +6002,9 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player) // Reset the shader state. HWR_SetShaderState(); + if (HWR_IsWireframeMode()) + HWD.pfnSetSpecialState(HWD_SET_WIREFRAME, 1); + validcount++; if (cv_glbatching.value) @@ -6056,6 +6067,9 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player) HWR_CreateDrawNodes(); } + if (HWR_IsWireframeMode()) + HWD.pfnSetSpecialState(HWD_SET_WIREFRAME, 0); + HWD.pfnSetTransform(NULL); HWD.pfnUnSetShader(); @@ -6208,6 +6222,9 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player) // Reset the shader state. HWR_SetShaderState(); + if (HWR_IsWireframeMode()) + HWD.pfnSetSpecialState(HWD_SET_WIREFRAME, 1); + ps_numbspcalls = 0; ps_numpolyobjects = 0; ps_bsptime = I_GetPreciseTime(); @@ -6284,6 +6301,9 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player) HWR_CreateDrawNodes(); } + if (HWR_IsWireframeMode()) + HWD.pfnSetSpecialState(HWD_SET_WIREFRAME, 0); + HWD.pfnSetTransform(NULL); HWD.pfnUnSetShader(); @@ -6361,6 +6381,8 @@ consvar_t cv_glsolvetjoin = CVAR_INIT ("gr_solvetjoin", "On", 0, CV_OnOff, NULL) consvar_t cv_glbatching = CVAR_INIT ("gr_batching", "On", 0, CV_OnOff, NULL); +consvar_t cv_glwireframe = CVAR_INIT ("gr_wireframe", "Off", 0, CV_OnOff, NULL); + static void CV_glfiltermode_OnChange(void) { if (rendermode == render_opengl) @@ -6401,6 +6423,8 @@ void HWR_AddCommands(void) CV_RegisterVar(&cv_glbatching); + CV_RegisterVar(&cv_glwireframe); + #ifndef NEWCLIP CV_RegisterVar(&cv_glclipwalls); #endif diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h index b751b2a6e..b965ea247 100644 --- a/src/hardware/hw_main.h +++ b/src/hardware/hw_main.h @@ -106,6 +106,8 @@ extern consvar_t cv_glslopecontrast; extern consvar_t cv_glbatching; +extern consvar_t cv_glwireframe; + extern float gl_viewwidth, gl_viewheight, gl_baseviewwindowy; extern float gl_viewwindowx, gl_basewindowcentery; diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c index de0e8c6a6..a5a16a0c3 100644 --- a/src/hardware/r_opengl/r_opengl.c +++ b/src/hardware/r_opengl/r_opengl.c @@ -301,6 +301,8 @@ typedef void (APIENTRY * PFNglDisable) (GLenum cap); static PFNglDisable pglDisable; typedef void (APIENTRY * PFNglGetFloatv) (GLenum pname, GLfloat *params); static PFNglGetFloatv pglGetFloatv; +typedef void (APIENTRY * PFNglPolygonMode) (GLenum, GLenum); +static PFNglPolygonMode pglPolygonMode; /* Depth Buffer */ typedef void (APIENTRY * PFNglClearDepth) (GLclampd depth); @@ -475,6 +477,7 @@ boolean SetupGLfunc(void) GETOPENGLFUNC(pglGetFloatv, glGetFloatv) GETOPENGLFUNC(pglGetIntegerv, glGetIntegerv) GETOPENGLFUNC(pglGetString, glGetString) + GETOPENGLFUNC(pglPolygonMode, glPolygonMode) GETOPENGLFUNC(pglClearDepth, glClearDepth) GETOPENGLFUNC(pglDepthFunc, glDepthFunc) @@ -2467,6 +2470,10 @@ EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value) Flush(); //??? if we want to change filter mode by texture, remove this break; + case HWD_SET_WIREFRAME: + pglPolygonMode(GL_FRONT_AND_BACK, Value ? GL_LINE : GL_FILL); + break; + default: break; } From ad0c684d1d6b479d7d12daed5ba1c7045a73e024 Mon Sep 17 00:00:00 2001 From: GoldenTails Date: Sun, 7 Nov 2021 00:09:06 -0500 Subject: [PATCH 025/227] Expose a `constants` hashtable to Lua. Had to split the main comparison portion of lib_getenum to a new function getEnum to do so. --- src/deh_lua.c | 54 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/src/deh_lua.c b/src/deh_lua.c index fbeaae08c..4666a8946 100644 --- a/src/deh_lua.c +++ b/src/deh_lua.c @@ -216,14 +216,11 @@ static int lib_dummysuper(lua_State *L) return luaL_error(L, "Can't call super() outside of hardcode-replacing A_Action functions being called by state changes!"); // convoluted, I know. @_@;; } -static inline int lib_getenum(lua_State *L) +static inline int getEnum(lua_State *L, boolean mathlib, const char *word) { - const char *word, *p; + const char *p; fixed_t i; - boolean mathlib = lua_toboolean(L, lua_upvalueindex(1)); - if (lua_type(L,2) != LUA_TSTRING) - return 0; - word = lua_tostring(L,2); + if (strlen(word) == 1) { // Assume sprite frame if length 1. if (*word >= 'A' && *word <= '~') { @@ -545,6 +542,42 @@ static inline int lib_getenum(lua_State *L) return 1; } + return -1; +} + +static int constants_get(lua_State *L) +{ + const char *key; + int ret; + + if (!lua_isstring(L, 2)) + return 0; + + key = luaL_checkstring(L, 2); + + // In Lua, mathlib is always there + ret = getEnum(L, true, key); + + if (ret != -1) + return ret; + + return 0; +} + +static inline int lib_getenum(lua_State *L) +{ + const char *word; + int ret; + boolean mathlib = lua_toboolean(L, lua_upvalueindex(1)); + if (lua_type(L,2) != LUA_TSTRING) + return 0; + word = lua_tostring(L,2); + + ret = getEnum(L, mathlib, word); + + if (ret != -1) + return ret; + if (mathlib) return luaL_error(L, "constant '%s' could not be parsed.\n", word); // DYNAMIC variables too!! @@ -629,6 +662,15 @@ int LUA_SOCLib(lua_State *L) lua_setfield(L, -2, "__call"); lua_pop(L, 1); + // Allow access to constants without forcing the use of name comparison checks Lua-side + // This table will not access global variables, only constants + lua_newuserdata(L, 0); + lua_createtable(L, 0, 2); + lua_pushcfunction(L, constants_get); + lua_setfield(L, -2, "__index"); + lua_setmetatable(L, -2); + lua_setglobal(L, "constants"); + return 0; } From fa25cdad81969111ccace7774db915ae26abcd1b Mon Sep 17 00:00:00 2001 From: GoldenTails Date: Sun, 7 Nov 2021 01:27:59 -0500 Subject: [PATCH 026/227] Fix `constants' searching SOC's vars, deny A_* and super being in `constants'. --- src/deh_lua.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/deh_lua.c b/src/deh_lua.c index 4666a8946..5f6e8055b 100644 --- a/src/deh_lua.c +++ b/src/deh_lua.c @@ -555,11 +555,15 @@ static int constants_get(lua_State *L) key = luaL_checkstring(L, 2); - // In Lua, mathlib is always there - ret = getEnum(L, true, key); + // In Lua, mathlib is never there + ret = getEnum(L, false, key); if (ret != -1) - return ret; + // Don't allow A_* or super. + // All userdata is meant to be considered global variables, + // so no need to get more specific than "is it userdata?" + if (!lua_isuserdata(L, -1) && !lua_isfunction(L, -1)) + return ret; return 0; } From 9dc22a408621a28e2e5c9466439d2705796f77e0 Mon Sep 17 00:00:00 2001 From: John FrostFox Date: Sun, 12 Dec 2021 03:18:30 +0300 Subject: [PATCH 027/227] Fix for rare but possible menu unresponsiveness bug It may happen if SDL timers get wonky or you've been ruuning the game for so long that the global timer wraps around. --- src/d_clisrv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 78a3ebe6c..26dd89beb 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -5485,7 +5485,7 @@ void NetUpdate(void) } nowtime /= NEWTICRATERATIO; - if (nowtime > resptime) + if (nowtime != resptime) { resptime = nowtime; #ifdef HAVE_THREADS From 754a11f7f1b58d3f36155a46f072e123375f9e0c Mon Sep 17 00:00:00 2001 From: ashifolfi Date: Sun, 20 Nov 2022 17:39:46 -0500 Subject: [PATCH 028/227] add transparency flag support to V_DrawFill --- src/hardware/hw_draw.c | 11 ++++++++++- src/v_video.c | 40 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c index ada2a6bf8..8c307bd9f 100644 --- a/src/hardware/hw_draw.c +++ b/src/hardware/hw_draw.c @@ -1333,6 +1333,7 @@ void HWR_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color) FOutVector v[4]; FSurfaceInfo Surf; float fx, fy, fw, fh; + UINT8 alphalevel = ((color & V_ALPHAMASK) >> V_ALPHASHIFT); UINT8 perplayershuffle = 0; @@ -1499,8 +1500,16 @@ void HWR_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color) Surf.PolyColor = V_GetColor(color); + if (alphalevel) + { + if (alphalevel == 10) Surf.PolyColor.s.alpha = softwaretranstogl_lo[st_translucency]; // V_HUDTRANSHALF + else if (alphalevel == 11) Surf.PolyColor.s.alpha = softwaretranstogl[st_translucency]; // V_HUDTRANS + else if (alphalevel == 12) Surf.PolyColor.s.alpha = softwaretranstogl_hi[st_translucency]; // V_HUDTRANSDOUBLE + else Surf.PolyColor.s.alpha = softwaretranstogl[10-alphalevel]; + } + HWD.pfnDrawPolygon(&Surf, v, 4, - PF_Modulated|PF_NoTexture|PF_NoDepthTest); + PF_Modulated|PF_NoTexture|PF_NoDepthTest|PF_Translucent); } #ifdef HAVE_PNG diff --git a/src/v_video.c b/src/v_video.c index 84d7978cb..82d603b5f 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -1187,12 +1187,33 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c) { UINT8 *dest; const UINT8 *deststop; + UINT32 alphalevel = ((c & V_ALPHAMASK) >> V_ALPHASHIFT); + UINT32 blendmode = ((c & V_BLENDMASK) >> V_BLENDSHIFT); + INT32 u; UINT8 perplayershuffle = 0; if (rendermode == render_none) return; + v_translevel = NULL; + if (alphalevel || blendmode) + { + if (alphalevel == 10) // V_HUDTRANSHALF + alphalevel = hudminusalpha[st_translucency]; + else if (alphalevel == 11) // V_HUDTRANS + alphalevel = 10 - st_translucency; + else if (alphalevel == 12) // V_HUDTRANSDOUBLE + alphalevel = hudplusalpha[st_translucency]; + + if (alphalevel >= 10) + return; // invis + + if (alphalevel || blendmode) + v_translevel = R_GetBlendTable(blendmode+1, alphalevel); + } + + #ifdef HWRENDER //if (rendermode != render_soft && !con_startup) // Not this again if (rendermode == render_opengl) @@ -1202,6 +1223,8 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c) } #endif + + if (splitscreen && (c & V_PERPLAYER)) { fixed_t adjusty = ((c & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)>>1; @@ -1338,8 +1361,21 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c) c &= 255; - for (;(--h >= 0) && dest < deststop; dest += vid.width) - memset(dest, c, w * vid.bpp); + // borrowing this from jimitia's new hud drawing functions rq + if (alphalevel) + { + v_translevel += c<<8; + for (;(--h >= 0) && dest < deststop; dest += vid.width) + { + for (x = 0; x < w; x++) + dest[x] = v_translevel[dest[x]]; + } + } + else + { + for (;(--h >= 0) && dest < deststop; dest += vid.width) + memset(dest, c, w * vid.bpp); + } } #ifdef HWRENDER From 039df0d38bbae893a56eacd437f05034fdc9e1ea Mon Sep 17 00:00:00 2001 From: namishere Date: Fri, 10 Feb 2023 21:34:50 -0800 Subject: [PATCH 029/227] Implement perfstats options for LUAh_PreThinkFrame and LUAh_PostThinkFrame --- src/d_netcmd.c | 2 +- src/lua_hook.h | 2 + src/lua_hooklib.c | 31 ++++++++--- src/m_perfstats.c | 134 ++++++++++++++++++++++++++++++++++++++-------- src/m_perfstats.h | 4 ++ src/p_tick.c | 8 ++- 6 files changed, 151 insertions(+), 30 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index f63f38a74..8c122e56a 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -376,7 +376,7 @@ consvar_t cv_mute = CVAR_INIT ("mute", "Off", CV_NETVAR|CV_CALL, CV_OnOff, Mute_ consvar_t cv_sleep = CVAR_INIT ("cpusleep", "1", CV_SAVE, sleeping_cons_t, NULL); static CV_PossibleValue_t perfstats_cons_t[] = { - {0, "Off"}, {1, "Rendering"}, {2, "Logic"}, {3, "ThinkFrame"}, {0, NULL}}; + {0, "Off"}, {1, "Rendering"}, {2, "Logic"}, {3, "ThinkFrame"}, {4, "PreThinkFrame"}, {5, "PostThinkFrame"}, {0, NULL}}; consvar_t cv_perfstats = CVAR_INIT ("perfstats", "Off", CV_CALL, perfstats_cons_t, PS_PerfStats_OnChange); static CV_PossibleValue_t ps_samplesize_cons_t[] = { {1, "MIN"}, {1000, "MAX"}, {0, NULL}}; diff --git a/src/lua_hook.h b/src/lua_hook.h index 4fa3a1a17..fea234f2f 100644 --- a/src/lua_hook.h +++ b/src/lua_hook.h @@ -126,7 +126,9 @@ int LUA_HookPlayer(player_t *, int hook); int LUA_HookTiccmd(player_t *, ticcmd_t *, int hook); int LUA_HookKey(event_t *event, int hook); // Hooks for key events +void LUA_HookPreThinkFrame(void); void LUA_HookThinkFrame(void); +void LUA_HookPostThinkFrame(void); int LUA_HookMobjLineCollide(mobj_t *, line_t *); int LUA_HookTouchSpecial(mobj_t *special, mobj_t *toucher); int LUA_HookShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype); diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c index c4083c9ad..e00860dfb 100644 --- a/src/lua_hooklib.c +++ b/src/lua_hooklib.c @@ -671,10 +671,8 @@ void LUA_HookHUD(int hook_type, huddrawlist_h list) SPECIALIZED HOOKS ========================================================================= */ -void LUA_HookThinkFrame(void) +static void hook_think_frame(int type) { - const int type = HOOK(ThinkFrame); - // variables used by perf stats int hook_index = 0; precise_t time_taken = 0; @@ -692,7 +690,7 @@ void LUA_HookThinkFrame(void) { get_hook(&hook, map->ids, k); - if (cv_perfstats.value == 3) + if (cv_perfstats.value >= 3) { lua_pushvalue(gL, -1);/* need the function again */ time_taken = I_GetPreciseTime(); @@ -700,12 +698,18 @@ void LUA_HookThinkFrame(void) call_single_hook(&hook); - if (cv_perfstats.value == 3) + if (cv_perfstats.value >= 3) { lua_Debug ar; time_taken = I_GetPreciseTime() - time_taken; lua_getinfo(gL, ">S", &ar); - PS_SetThinkFrameHookInfo(hook_index, time_taken, ar.short_src); + if (type == 4) // sorry for magic numbers + PS_SetPreThinkFrameHookInfo(hook_index, time_taken, ar.short_src); + else if (type == 5) + PS_SetThinkFrameHookInfo(hook_index, time_taken, ar.short_src); + else if (type == 6) + PS_SetPostThinkFrameHookInfo(hook_index, time_taken, ar.short_src); + hook_index++; } } @@ -714,6 +718,21 @@ void LUA_HookThinkFrame(void) } } +void LUA_HookPreThinkFrame() +{ + hook_think_frame(HOOK(PreThinkFrame)); +} + +void LUA_HookThinkFrame() +{ + hook_think_frame(HOOK(ThinkFrame)); +} + +void LUA_HookPostThinkFrame() +{ + hook_think_frame(HOOK(PostThinkFrame)); +} + int LUA_HookMobjLineCollide(mobj_t *mobj, line_t *line) { Hook_State hook; diff --git a/src/m_perfstats.c b/src/m_perfstats.c index 9f65a7616..935df82ac 100644 --- a/src/m_perfstats.c +++ b/src/m_perfstats.c @@ -65,7 +65,10 @@ static ps_metric_t ps_removecount = {0}; ps_metric_t ps_checkposition_calls = {0}; +ps_metric_t ps_lua_prethinkframe_time = {0}; ps_metric_t ps_lua_thinkframe_time = {0}; +ps_metric_t ps_lua_postthinkframe_time = {0}; + ps_metric_t ps_lua_mobjhooks = {0}; ps_metric_t ps_otherlogictime = {0}; @@ -157,7 +160,9 @@ perfstatrow_t gamelogic_rows[] = { {" mobjs ", " Mobjs: ", &ps_thlist_times[THINK_MOBJ], PS_TIME|PS_LEVEL}, {" dynslop", " Dynamic slopes: ", &ps_thlist_times[THINK_DYNSLOPE], PS_TIME|PS_LEVEL}, {" precip ", " Precipitation: ", &ps_thlist_times[THINK_PRECIP], PS_TIME|PS_LEVEL}, + {" lprethinkf", " LUAh_PreThinkFrame:", &ps_lua_prethinkframe_time, PS_TIME|PS_LEVEL}, {" lthinkf", " LUAh_ThinkFrame:", &ps_lua_thinkframe_time, PS_TIME|PS_LEVEL}, + {" lpostthinkf", " LUAh_PostThinkFrame:", &ps_lua_postthinkframe_time, PS_TIME|PS_LEVEL}, {" other ", " Other: ", &ps_otherlogictime, PS_TIME|PS_LEVEL}, {0} }; @@ -192,10 +197,43 @@ int ps_frame_index = 0; int ps_tick_index = 0; // dynamically allocated resizeable array for thinkframe hook stats +ps_hookinfo_t *prethinkframe_hooks = NULL; +int prethinkframe_hooks_length = 0; +int prethinkframe_hooks_capacity = 16; + ps_hookinfo_t *thinkframe_hooks = NULL; int thinkframe_hooks_length = 0; int thinkframe_hooks_capacity = 16; +ps_hookinfo_t *postthinkframe_hooks = NULL; +int postthinkframe_hooks_length = 0; +int postthinkframe_hooks_capacity = 16; + +void PS_SetPreThinkFrameHookInfo(int index, precise_t time_taken, char* short_src) +{ + if (!prethinkframe_hooks) + { + // array needs to be initialized + prethinkframe_hooks = Z_Calloc(sizeof(ps_hookinfo_t) * prethinkframe_hooks_capacity, PU_STATIC, NULL); + } + if (index >= prethinkframe_hooks_capacity) + { + // array needs more space, realloc with double size + int new_capacity = prethinkframe_hooks_capacity * 2; + prethinkframe_hooks = Z_Realloc(prethinkframe_hooks, + sizeof(ps_hookinfo_t) * new_capacity, PU_STATIC, NULL); + // initialize new memory with zeros so the pointers in the structs are null + memset(&prethinkframe_hooks[prethinkframe_hooks_capacity], 0, + sizeof(ps_hookinfo_t) * prethinkframe_hooks_capacity); + prethinkframe_hooks_capacity = new_capacity; + } + prethinkframe_hooks[index].time_taken.value.p = time_taken; + memcpy(prethinkframe_hooks[index].short_src, short_src, LUA_IDSIZE * sizeof(char)); + // since the values are set sequentially from begin to end, the last call should leave + // the correct value to this variable + prethinkframe_hooks_length = index + 1; +} + void PS_SetThinkFrameHookInfo(int index, precise_t time_taken, char* short_src) { if (!thinkframe_hooks) @@ -221,6 +259,31 @@ void PS_SetThinkFrameHookInfo(int index, precise_t time_taken, char* short_src) thinkframe_hooks_length = index + 1; } +void PS_SetPostThinkFrameHookInfo(int index, precise_t time_taken, char* short_src) +{ + if (!postthinkframe_hooks) + { + // array needs to be initialized + postthinkframe_hooks = Z_Calloc(sizeof(ps_hookinfo_t) * postthinkframe_hooks_capacity, PU_STATIC, NULL); + } + if (index >= postthinkframe_hooks_capacity) + { + // array needs more space, realloc with double size + int new_capacity = postthinkframe_hooks_capacity * 2; + postthinkframe_hooks = Z_Realloc(postthinkframe_hooks, + sizeof(ps_hookinfo_t) * new_capacity, PU_STATIC, NULL); + // initialize new memory with zeros so the pointers in the structs are null + memset(&postthinkframe_hooks[postthinkframe_hooks_capacity], 0, + sizeof(ps_hookinfo_t) * postthinkframe_hooks_capacity); + postthinkframe_hooks_capacity = new_capacity; + } + postthinkframe_hooks[index].time_taken.value.p = time_taken; + memcpy(postthinkframe_hooks[index].short_src, short_src, LUA_IDSIZE * sizeof(char)); + // since the values are set sequentially from begin to end, the last call should leave + // the correct value to this variable + postthinkframe_hooks_length = index + 1; +} + static boolean PS_HighResolution(void) { return (vid.width >= 640 && vid.height >= 400); @@ -564,7 +627,9 @@ void PS_UpdateTickStats(void) ps_tictime.value.p - ps_playerthink_time.value.p - ps_thinkertime.value.p - - ps_lua_thinkframe_time.value.p; + ps_lua_prethinkframe_time.value.p - + ps_lua_thinkframe_time.value.p - + ps_lua_postthinkframe_time.value.p; PS_CountThinkers(); } @@ -576,21 +641,24 @@ void PS_UpdateTickStats(void) PS_UpdateRowHistories(misc_calls_rows, false); } } - if (cv_perfstats.value == 3 && cv_ps_samplesize.value > 1 && PS_IsLevelActive()) + if (cv_ps_samplesize.value > 1) { - int i; - for (i = 0; i < thinkframe_hooks_length; i++) + if(cv_perfstats.value >= 3 && PS_IsLevelActive()) { - PS_UpdateMetricHistory(&thinkframe_hooks[i].time_taken, true, false, false); + int i; + for (i = 0; i < thinkframe_hooks_length; i++) + { + PS_UpdateMetricHistory(&thinkframe_hooks[i].time_taken, true, false, false); + } + } + if (cv_perfstats.value) + { + ps_tick_index++; + if (ps_tick_index >= cv_ps_samplesize.value) + ps_tick_index = 0; + if (ps_tick_samples_left) + ps_tick_samples_left--; } - } - if (cv_perfstats.value && cv_ps_samplesize.value > 1) - { - ps_tick_index++; - if (ps_tick_index >= cv_ps_samplesize.value) - ps_tick_index = 0; - if (ps_tick_samples_left) - ps_tick_samples_left--; } } @@ -610,7 +678,7 @@ static void PS_DrawDescriptorHeader(void) int samples_left = max(ps_frame_samples_left, ps_tick_samples_left); int x, y; - if (cv_perfstats.value == 3) + if (cv_perfstats.value >= 3) { x = 2; y = 0; @@ -697,7 +765,7 @@ static void PS_DrawGameLogicStats(void) PS_DrawPerfRows(x, y, V_PURPLEMAP, misc_calls_rows); } -static void PS_DrawThinkFrameStats(void) +static void draw_think_frame_stats(int hook_length, ps_hookinfo_t *hook) { char s[100]; int i; @@ -711,7 +779,7 @@ static void PS_DrawThinkFrameStats(void) PS_DrawDescriptorHeader(); - for (i = 0; i < thinkframe_hooks_length; i++) + for (i = 0; i < hook_length; i++) { #define NEXT_ROW() \ @@ -724,7 +792,7 @@ if (y > 192) \ break; \ } - char* str = thinkframe_hooks[i].short_src; + char* str = hook[i].short_src; char* tempstr = tempbuffer; int len = (int)strlen(str); char* str_ptr; @@ -771,7 +839,7 @@ if (y > 192) \ if (len > 20) str += len - 20; snprintf(s, sizeof s - 1, "%20s: %d", str, - PS_GetMetricScreenValue(&thinkframe_hooks[i].time_taken, true)); + PS_GetMetricScreenValue(&hook[i].time_taken, true)); V_DrawSmallString(x, y, V_MONOSPACE | V_ALLOWLOWERCASE | text_color, s); NEXT_ROW() @@ -780,6 +848,21 @@ if (y > 192) \ } } +static void PS_DrawPreThinkFrameStats(void) +{ + draw_think_frame_stats(prethinkframe_hooks_length, prethinkframe_hooks); +} + +static void PS_DrawThinkFrameStats(void) +{ + draw_think_frame_stats(thinkframe_hooks_length, thinkframe_hooks); +} + +static void PS_DrawPostThinkFrameStats(void) +{ + draw_think_frame_stats(postthinkframe_hooks_length, postthinkframe_hooks); +} + void M_DrawPerfStats(void) { if (cv_perfstats.value == 1) // rendering @@ -793,7 +876,7 @@ void M_DrawPerfStats(void) // tics when frame skips happen PS_DrawGameLogicStats(); } - else if (cv_perfstats.value == 3) // lua thinkframe + else if (cv_perfstats.value >= 3) // lua thinkframe { if (!PS_IsLevelActive()) return; @@ -802,13 +885,22 @@ void M_DrawPerfStats(void) // Low resolutions can't really use V_DrawSmallString that is used by thinkframe stats. // A low-res version using V_DrawThinString could be implemented, // but it would have much less space for information. - V_DrawThinString(80, 92, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, "Perfstats 3 is not available"); + V_DrawThinString(80, 92, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, "Lua Perfstats is not available"); V_DrawThinString(80, 100, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, "for resolutions below 640x400."); + return; } - else + if (cv_perfstats.value == 3) { PS_DrawThinkFrameStats(); } + else if (cv_perfstats.value == 4) + { + PS_DrawPreThinkFrameStats(); + } + else if (cv_perfstats.value == 5) + { + PS_DrawPostThinkFrameStats(); + } } } diff --git a/src/m_perfstats.h b/src/m_perfstats.h index f6a7c1f74..c70e7ad30 100644 --- a/src/m_perfstats.h +++ b/src/m_perfstats.h @@ -43,12 +43,16 @@ extern ps_metric_t ps_thlist_times[]; extern ps_metric_t ps_checkposition_calls; +extern ps_metric_t ps_lua_prethinkframe_time; extern ps_metric_t ps_lua_thinkframe_time; +extern ps_metric_t ps_lua_postthinkframe_time; extern ps_metric_t ps_lua_mobjhooks; extern ps_metric_t ps_otherlogictime; +void PS_SetPreThinkFrameHookInfo(int index, precise_t time_taken, char* short_src); void PS_SetThinkFrameHookInfo(int index, precise_t time_taken, char* short_src); +void PS_SetPostThinkFrameHookInfo(int index, precise_t time_taken, char* short_src); void PS_UpdateTickStats(void); diff --git a/src/p_tick.c b/src/p_tick.c index fe6a4d33f..2db2ab59d 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -664,7 +664,9 @@ void P_Ticker(boolean run) ps_lua_mobjhooks.value.i = 0; ps_checkposition_calls.value.i = 0; - LUA_HOOK(PreThinkFrame); + PS_START_TIMING(ps_lua_prethinkframe_time); + LUA_HookPreThinkFrame(); + PS_STOP_TIMING(ps_lua_prethinkframe_time); PS_START_TIMING(ps_playerthink_time); for (i = 0; i < MAXPLAYERS; i++) @@ -768,7 +770,9 @@ void P_Ticker(boolean run) if (modeattacking) G_GhostTicker(); - LUA_HOOK(PostThinkFrame); + PS_START_TIMING(ps_lua_postthinkframe_time); + LUA_HookPostThinkFrame(); + PS_STOP_TIMING(ps_lua_postthinkframe_time); } if (run) From eaacb64d3fa4f9fa17159aea19fbc6da9c24f4d3 Mon Sep 17 00:00:00 2001 From: namishere Date: Sat, 11 Feb 2023 04:19:24 -0800 Subject: [PATCH 030/227] Update PS_UpdateTickStats to correctly update metric history for pre and post thinkframe hooks --- src/m_perfstats.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/m_perfstats.c b/src/m_perfstats.c index 935df82ac..ce5202e7d 100644 --- a/src/m_perfstats.c +++ b/src/m_perfstats.c @@ -646,9 +646,20 @@ void PS_UpdateTickStats(void) if(cv_perfstats.value >= 3 && PS_IsLevelActive()) { int i; - for (i = 0; i < thinkframe_hooks_length; i++) + if (cv_perfstats.value == 3) { - PS_UpdateMetricHistory(&thinkframe_hooks[i].time_taken, true, false, false); + for (i = 0; i < thinkframe_hooks_length; i++) + PS_UpdateMetricHistory(&thinkframe_hooks[i].time_taken, true, false, false); + } + else if (cv_perfstats.value == 4) + { + for (i = 0; i < prethinkframe_hooks_length; i++) + PS_UpdateMetricHistory(&prethinkframe_hooks[i].time_taken, true, false, false); + } + else if (cv_perfstats.value == 5) + { + for (i = 0; i < postthinkframe_hooks_length; i++) + PS_UpdateMetricHistory(&postthinkframe_hooks[i].time_taken, true, false, false); } } if (cv_perfstats.value) From 1a6e48de1eba6dde7df121b319524901bd94013e Mon Sep 17 00:00:00 2001 From: spherallic Date: Thu, 16 Mar 2023 12:12:58 +0100 Subject: [PATCH 031/227] Update mobj floorz/ceilingz after FOF destruction --- src/p_floor.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/p_floor.c b/src/p_floor.c index 869384b53..261ce997b 100644 --- a/src/p_floor.c +++ b/src/p_floor.c @@ -1212,6 +1212,19 @@ static boolean T_SectorHasEnemies(sector_t *sec) return false; } +static void T_UpdateMobjPlaneZ(sector_t *sec) +{ + msecnode_t *node = sec->touching_thinglist; // things touching this sector + mobj_t *mo; + while (node) + { + mo = node->m_thing; + mo->floorz = P_FloorzAtPos(mo->x, mo->y, mo->z, mo->height); + mo->ceilingz = P_CeilingzAtPos(mo->x, mo->y, mo->z, mo->height); + node = node->m_thinglist_next; + } +} + // // T_NoEnemiesThinker // @@ -1938,6 +1951,7 @@ void EV_CrumbleChain(sector_t *sec, ffloor_t *rover) // no longer exists (can't collide with again) rover->fofflags &= ~FOF_EXISTS; rover->master->frontsector->moved = true; + T_UpdateMobjPlaneZ(sec); // prevent objects from floating P_RecalcPrecipInSector(sec); } From cfb40d42132b72bcf06f776b5c4e0c9cb6c76a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Thu, 13 Apr 2023 19:28:54 +0200 Subject: [PATCH 032/227] Fix crash in R_PointToDist2 when passing -2147483648 --- src/m_fixed.h | 3 ++- src/r_main.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/m_fixed.h b/src/m_fixed.h index 4a5b7ce2a..b9586d653 100644 --- a/src/m_fixed.h +++ b/src/m_fixed.h @@ -34,6 +34,7 @@ */ typedef INT32 fixed_t; +typedef UINT32 ufixed_t; /*! \brief convert fixed_t into floating number @@ -198,7 +199,7 @@ FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FixedInt(fixed_t a) */ FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FixedDiv(fixed_t a, fixed_t b) { - if ((abs(a) >> (FRACBITS-2)) >= abs(b)) + if (((ufixed_t)abs(a) >> (FRACBITS-2)) >= (ufixed_t)abs(b)) return (a^b) < 0 ? INT32_MIN : INT32_MAX; return FixedDiv2(a, b); diff --git a/src/r_main.c b/src/r_main.c index ebf7a28bf..5265fe15d 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -364,7 +364,7 @@ angle_t R_PointToAngle2(fixed_t pviewx, fixed_t pviewy, fixed_t x, fixed_t y) fixed_t R_PointToDist2(fixed_t px2, fixed_t py2, fixed_t px1, fixed_t py1) { angle_t angle; - fixed_t dx, dy, dist; + ufixed_t dx, dy, dist; dx = abs(px1 - px2); dy = abs(py1 - py2); From 4967c296ab9c4056d617b401bfd37889967a0155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Sun, 30 Apr 2023 12:43:31 +0200 Subject: [PATCH 033/227] Fix segfaults when P_RemoveMobj is called from MobjSpawn hook --- src/b_bot.c | 3 + src/g_demo.c | 143 ++++---- src/p_enemy.c | 914 ++++++++++++++++++++++++++++++------------------ src/p_floor.c | 3 + src/p_inter.c | 261 ++++++++------ src/p_local.h | 2 +- src/p_map.c | 2 + src/p_mobj.c | 851 +++++++++++++++++++++++++++++--------------- src/p_polyobj.c | 2 + src/p_user.c | 439 ++++++++++++++--------- 10 files changed, 1670 insertions(+), 950 deletions(-) diff --git a/src/b_bot.c b/src/b_bot.c index d1465f891..aa9ccd33e 100644 --- a/src/b_bot.c +++ b/src/b_bot.c @@ -613,6 +613,9 @@ void B_HandleFlightIndicator(player_t *player) // otherwise, spawn it P_SetTarget(&tails->hnext, P_SpawnMobjFromMobj(tails, 0, 0, 0, MT_OVERLAY)); + if (P_MobjWasRemoved(tails->hnext)) + return; // we can't spawn one, so it can't exist + P_SetTarget(&tails->hnext->target, tails); P_SetTarget(&tails->hnext->hprev, tails); P_SetMobjState(tails->hnext, S_FLIGHTINDICATOR); diff --git a/src/g_demo.c b/src/g_demo.c index 0403da16d..d1d1a4a11 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -795,32 +795,40 @@ void G_GhostTicker(void) if (type == MT_GHOST) { mobj = P_SpawnGhostMobj(g->mo); // does a large portion of the work for us - mobj->frame = (mobj->frame & ~FF_FRAMEMASK)|tr_trans60<frame = (mobj->frame & ~FF_FRAMEMASK)|tr_trans60<mo, 0, 0, -FixedDiv(FixedMul(g->mo->info->height, g->mo->scale) - g->mo->height,3*FRACUNIT), MT_THOK); - mobj->sprite = states[mobjinfo[type].spawnstate].sprite; - mobj->frame = (states[mobjinfo[type].spawnstate].frame & FF_FRAMEMASK) | tr_trans60<color = g->mo->color; - mobj->skin = g->mo->skin; - P_SetScale(mobj, (mobj->destscale = g->mo->scale)); - - if (type == MT_THOK) // spintrail-specific modification for MT_THOK + if (!P_MobjWasRemoved(mobj)) { - mobj->frame = FF_TRANS80; - mobj->fuse = mobj->tics; + mobj->sprite = states[mobjinfo[type].spawnstate].sprite; + mobj->frame = (states[mobjinfo[type].spawnstate].frame & FF_FRAMEMASK) | tr_trans60<color = g->mo->color; + mobj->skin = g->mo->skin; + P_SetScale(mobj, (mobj->destscale = g->mo->scale)); + + if (type == MT_THOK) // spintrail-specific modification for MT_THOK + { + mobj->frame = FF_TRANS80; + mobj->fuse = mobj->tics; + } + mobj->tics = -1; // nope. } - mobj->tics = -1; // nope. } - mobj->floorz = mobj->z; - mobj->ceilingz = mobj->z+mobj->height; - P_UnsetThingPosition(mobj); - mobj->flags = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY; // make an ATTEMPT to curb crazy SOCs fucking stuff up... - P_SetThingPosition(mobj); - if (!mobj->fuse) - mobj->fuse = 8; - P_SetTarget(&mobj->target, g->mo); + + if (!P_MobjWasRemoved(mobj)) + { + mobj->floorz = mobj->z; + mobj->ceilingz = mobj->z+mobj->height; + P_UnsetThingPosition(mobj); + mobj->flags = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY; // make an ATTEMPT to curb crazy SOCs fucking stuff up... + P_SetThingPosition(mobj); + if (!mobj->fuse) + mobj->fuse = 8; + P_SetTarget(&mobj->target, g->mo); + } } } if (xziptic & EZT_HIT) @@ -844,6 +852,8 @@ void G_GhostTicker(void) || health != 0 || i >= 4) // only spawn for the first 4 hits per frame, to prevent ghosts from splode-spamming too bad. continue; poof = P_SpawnMobj(x, y, z, MT_GHOST); + if (P_MobjWasRemoved(poof)) + continue; poof->angle = angle; poof->flags = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY; // make an ATTEMPT to curb crazy SOCs fucking stuff up... poof->health = 0; @@ -889,19 +899,22 @@ void G_GhostTicker(void) if (follow) P_RemoveMobj(follow); P_SetTarget(&follow, P_SpawnMobjFromMobj(g->mo, 0, 0, 0, MT_GHOST)); - P_SetTarget(&follow->tracer, g->mo); - follow->tics = -1; - temp = READINT16(g->p)<height = FixedMul(follow->scale, temp); + if (!P_MobjWasRemoved(follow)) + { + P_SetTarget(&follow->tracer, g->mo); + follow->tics = -1; + temp = READINT16(g->p)<height = FixedMul(follow->scale, temp); - if (followtic & FZT_LINKDRAW) - follow->flags2 |= MF2_LINKDRAW; + if (followtic & FZT_LINKDRAW) + follow->flags2 |= MF2_LINKDRAW; - if (followtic & FZT_COLORIZED) - follow->colorized = true; + if (followtic & FZT_COLORIZED) + follow->colorized = true; - if (followtic & FZT_SKIN) - follow->skin = &skins[READUINT8(g->p)]; + if (followtic & FZT_SKIN) + follow->skin = &skins[READUINT8(g->p)]; + } } if (follow) { @@ -1094,28 +1107,35 @@ void G_ReadMetalTic(mobj_t *metal) else { mobj = P_SpawnMobjFromMobj(metal, 0, 0, -FixedDiv(FixedMul(metal->info->height, metal->scale) - metal->height,3*FRACUNIT), MT_THOK); - mobj->sprite = states[mobjinfo[type].spawnstate].sprite; - mobj->frame = states[mobjinfo[type].spawnstate].frame; - mobj->angle = metal->angle; - mobj->color = metal->color; - mobj->skin = metal->skin; - P_SetScale(mobj, (mobj->destscale = metal->scale)); - - if (type == MT_THOK) // spintrail-specific modification for MT_THOK + if (!P_MobjWasRemoved(mobj)) { - mobj->frame = FF_TRANS70; - mobj->fuse = mobj->tics; + mobj->sprite = states[mobjinfo[type].spawnstate].sprite; + mobj->frame = states[mobjinfo[type].spawnstate].frame; + mobj->angle = metal->angle; + mobj->color = metal->color; + mobj->skin = metal->skin; + P_SetScale(mobj, (mobj->destscale = metal->scale)); + + if (type == MT_THOK) // spintrail-specific modification for MT_THOK + { + mobj->frame = FF_TRANS70; + mobj->fuse = mobj->tics; + } + mobj->tics = -1; // nope. } - mobj->tics = -1; // nope. } - mobj->floorz = mobj->z; - mobj->ceilingz = mobj->z+mobj->height; - P_UnsetThingPosition(mobj); - mobj->flags = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY; // make an ATTEMPT to curb crazy SOCs fucking stuff up... - P_SetThingPosition(mobj); - if (!mobj->fuse) - mobj->fuse = 8; - P_SetTarget(&mobj->target, metal); + + if (!P_MobjWasRemoved(mobj)) + { + mobj->floorz = mobj->z; + mobj->ceilingz = mobj->z+mobj->height; + P_UnsetThingPosition(mobj); + mobj->flags = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY; // make an ATTEMPT to curb crazy SOCs fucking stuff up... + P_SetThingPosition(mobj); + if (!mobj->fuse) + mobj->fuse = 8; + P_SetTarget(&mobj->target, metal); + } } } if (xziptic & EZT_SPRITE) @@ -1137,19 +1157,22 @@ void G_ReadMetalTic(mobj_t *metal) if (follow) P_RemoveMobj(follow); P_SetTarget(&follow, P_SpawnMobjFromMobj(metal, 0, 0, 0, MT_GHOST)); - P_SetTarget(&follow->tracer, metal); - follow->tics = -1; - temp = READINT16(metal_p)<height = FixedMul(follow->scale, temp); + if (!P_MobjWasRemoved(follow)) + { + P_SetTarget(&follow->tracer, metal); + follow->tics = -1; + temp = READINT16(metal_p)<height = FixedMul(follow->scale, temp); - if (followtic & FZT_LINKDRAW) - follow->flags2 |= MF2_LINKDRAW; + if (followtic & FZT_LINKDRAW) + follow->flags2 |= MF2_LINKDRAW; - if (followtic & FZT_COLORIZED) - follow->colorized = true; + if (followtic & FZT_COLORIZED) + follow->colorized = true; - if (followtic & FZT_SKIN) - follow->skin = &skins[READUINT8(metal_p)]; + if (followtic & FZT_SKIN) + follow->skin = &skins[READUINT8(metal_p)]; + } } if (follow) { @@ -2218,7 +2241,9 @@ void G_AddGhost(char *defdemoname) { // A bit more complex than P_SpawnPlayer because ghosts aren't solid and won't just push themselves out of the ceiling. fixed_t z,f,c; fixed_t offset = mthing->z << FRACBITS; - gh->mo = P_SpawnMobj(mthing->x << FRACBITS, mthing->y << FRACBITS, 0, MT_GHOST); + P_SetTarget(&gh->mo, P_SpawnMobj(mthing->x << FRACBITS, mthing->y << FRACBITS, 0, MT_GHOST)); + if (P_MobjWasRemoved(gh->mo)) + return; gh->mo->angle = FixedAngle(mthing->angle << FRACBITS); f = gh->mo->floorz; c = gh->mo->ceilingz - mobjinfo[MT_PLAYER].height; diff --git a/src/p_enemy.c b/src/p_enemy.c index 63d430eb6..6cc68e744 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -1200,7 +1200,8 @@ static void P_SharpDust(mobj_t *actor, mobjtype_t type, angle_t ang) -P_ReturnThrustX(actor, ang, 16<angle, actor->radius), actor->height/3, MT_PARTICLE); + if (P_MobjWasRemoved(flume)) + return; + flume->destscale = actor->scale*3; P_SetScale(flume, flume->destscale); P_SetTarget(&flume->target, actor); @@ -1328,12 +1332,15 @@ void A_FaceStabHurl(mobj_t *actor) { if (!hwork->hnext) P_SetTarget(&hwork->hnext, P_SpawnMobjFromMobj(actor, 0, 0, 0, MT_FACESTABBERSPEAR)); - hwork = hwork->hnext; - hwork->angle = actor->angle + ANGLE_90; - hwork->destscale = FixedSqrt(step*basesize); - P_SetScale(hwork, hwork->destscale); - hwork->fuse = 2; - P_MoveOrigin(hwork, actor->x + xo*(15-step), actor->y + yo*(15-step), actor->z + (actor->height - hwork->height)/2 + (P_MobjFlip(actor)*(8<hnext)) + { + hwork = hwork->hnext; + hwork->angle = actor->angle + ANGLE_90; + hwork->destscale = FixedSqrt(step*basesize); + P_SetScale(hwork, hwork->destscale); + hwork->fuse = 2; + P_MoveOrigin(hwork, actor->x + xo*(15-step), actor->y + yo*(15-step), actor->z + (actor->height - hwork->height)/2 + (P_MobjFlip(actor)*(8<info->raisestate); + if (P_MobjWasRemoved(newchain)) + continue; P_SetTarget(&prevchain->target, newchain); prevchain = newchain; } @@ -2324,10 +2335,13 @@ static void P_VultureHoverParticle(mobj_t *actor) fixed_t pz = P_FloorzAtPos(px, py, actor->z, actor->height); dust = P_SpawnMobj(px, py, pz, MT_ARIDDUST); - P_SetMobjState(dust, (statenum_t)(dust->state - states + P_RandomRange(0, 2))); - P_Thrust(dust, angle, FixedDiv(12*FRACUNIT, max(FRACUNIT, fdist/2))); - dust->momx += actor->momx; - dust->momy += actor->momy; + if (!P_MobjWasRemoved(dust)) + { + P_SetMobjState(dust, (statenum_t)(dust->state - states + P_RandomRange(0, 2))); + P_Thrust(dust, angle, FixedDiv(12*FRACUNIT, max(FRACUNIT, fdist/2))); + dust->momx += actor->momx; + dust->momy += actor->momy; + } angle += ANGLE_45; } } @@ -2422,6 +2436,8 @@ void A_VultureBlast(mobj_t *actor) { angle_t fa = ((i*(angle_t)ANGLE_45) >> ANGLETOFINESHIFT) & FINEMASK; dust = P_SpawnMobj(actor->x + 48*FixedMul(FINECOSINE(fa), -faasin), actor->y + 48*FixedMul(FINECOSINE(fa), faacos), actor->z + actor->height/2 + 48*FINESINE(fa), MT_PARTICLE); + if (P_MobjWasRemoved(dust)) + continue; P_SetScale(dust, 4*FRACUNIT); dust->destscale = FRACUNIT; @@ -2491,10 +2507,13 @@ void A_VultureFly(mobj_t *actor) P_VultureHoverParticle(actor); dust = P_SpawnMobj(actor->x + P_RandomFixed() - FRACUNIT/2, actor->y + P_RandomFixed() - FRACUNIT/2, actor->z + actor->height/2 + P_RandomFixed() - FRACUNIT/2, MT_PARTICLE); - P_SetScale(dust, 2*FRACUNIT); - dust->destscale = FRACUNIT/3; - dust->scalespeed = FRACUNIT/40; - dust->fuse = TICRATE*2; + if (!P_MobjWasRemoved(dust)) + { + P_SetScale(dust, 2*FRACUNIT); + dust->destscale = FRACUNIT/3; + dust->scalespeed = FRACUNIT/40; + dust->fuse = TICRATE*2; + } actor->momx += FixedDiv(dx, dm)*2; actor->momy += FixedDiv(dy, dm)*2; @@ -2693,6 +2712,8 @@ void A_LobShot(mobj_t *actor) z = actor->z + FixedMul(locvar2*FRACUNIT, actor->scale); shot = P_SpawnMobj(actor->x, actor->y, z, locvar1); + if (P_MobjWasRemoved(shot)) + return; if (actor->type == MT_BLACKEGGMAN) { @@ -3074,15 +3095,21 @@ void A_Boss1Laser(mobj_t *actor) S_StartSound(actor, mobjinfo[locvar1].seesound); point = P_SpawnMobj(x + P_ReturnThrustX(actor, actor->angle, actor->radius), y + P_ReturnThrustY(actor, actor->angle, actor->radius), actor->z - actor->height / 2, MT_EGGMOBILE_TARGET); - point->angle = actor->angle; - point->fuse = dur+1; - P_SetTarget(&point->target, actor->target); - P_SetTarget(&actor->target, point); + if (!P_MobjWasRemoved(point)) + { + point->angle = actor->angle; + point->fuse = dur+1; + P_SetTarget(&point->target, actor->target); + P_SetTarget(&actor->target, point); + } } angle = R_PointToAngle2(z + (mobjinfo[locvar1].height>>1), 0, actor->target->z, R_PointToDist2(x, y, actor->target->x, actor->target->y)); point = P_SpawnMobj(x, y, z, locvar1); + if (P_MobjWasRemoved(point)) + return; + P_SetTarget(&point->target, actor); point->angle = actor->angle; speed = point->radius; @@ -3093,6 +3120,9 @@ void A_Boss1Laser(mobj_t *actor) for (i = 0; i < 256; i++) { mobj_t *mo = P_SpawnMobj(point->x, point->y, point->z, point->type); + if (P_MobjWasRemoved(mo)) + continue; + mo->angle = point->angle; mo->color = LASERCOLORS[((UINT8)(i + 3*dur) >> 2) % sizeof(LASERCOLORS)]; // codeing P_UnsetThingPosition(mo); @@ -3105,9 +3135,12 @@ void A_Boss1Laser(mobj_t *actor) if (mo->info->meleestate) { mobj_t *mo2 = P_SpawnMobjFromMobj(mo, 0, 0, 0, MT_PARTICLE); - mo2->flags2 |= MF2_LINKDRAW; - P_SetTarget(&mo2->tracer, actor); - P_SetMobjState(mo2, mo->info->meleestate); + if (!P_MobjWasRemoved(mo2)) + { + mo2->flags2 |= MF2_LINKDRAW; + P_SetTarget(&mo2->tracer, actor); + P_SetMobjState(mo2, mo->info->meleestate); + } } } @@ -3125,37 +3158,42 @@ void A_Boss1Laser(mobj_t *actor) if (z - floorz < mobjinfo[MT_EGGMOBILE_FIRE].height>>1 && dur & 1) { point = P_SpawnMobj(x, y, floorz, MT_EGGMOBILE_FIRE); - point->angle = actor->angle; - point->destscale = actor->scale; - P_SetScale(point, point->destscale); - P_SetTarget(&point->target, actor); - P_MobjCheckWater(point); - if (point->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER)) + if (!P_MobjWasRemoved(point)) { - for (i = 0; i < 2; i++) + point->angle = actor->angle; + point->destscale = actor->scale; + P_SetScale(point, point->destscale); + P_SetTarget(&point->target, actor); + P_MobjCheckWater(point); + if (point->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER)) { - UINT8 size = 3; - mobj_t *steam = P_SpawnMobj(x, y, point->watertop - size*mobjinfo[MT_DUST].height, MT_DUST); - P_SetScale(steam, size*actor->scale); - P_SetObjectMomZ(steam, FRACUNIT + 2*P_RandomFixed(), true); - P_InstaThrust(steam, FixedAngle(P_RandomKey(360)*FRACUNIT), 2*P_RandomFixed()); - if (point->info->painsound) - S_StartSound(steam, point->info->painsound); - } - } - else - { - fixed_t distx = P_ReturnThrustX(point, point->angle, point->radius); - fixed_t disty = P_ReturnThrustY(point, point->angle, point->radius); - if (P_TryMove(point, point->x + distx, point->y + disty, false) // prevents the sprite from clipping into the wall or dangling off ledges - && P_TryMove(point, point->x - 2*distx, point->y - 2*disty, false) - && P_TryMove(point, point->x + distx, point->y + disty, false)) - { - if (point->info->seesound) - S_StartSound(point, point->info->seesound); + for (i = 0; i < 2; i++) + { + UINT8 size = 3; + mobj_t *steam = P_SpawnMobj(x, y, point->watertop - size*mobjinfo[MT_DUST].height, MT_DUST); + if (P_MobjWasRemoved(steam)) + continue; + P_SetScale(steam, size*actor->scale); + P_SetObjectMomZ(steam, FRACUNIT + 2*P_RandomFixed(), true); + P_InstaThrust(steam, FixedAngle(P_RandomKey(360)*FRACUNIT), 2*P_RandomFixed()); + if (point->info->painsound) + S_StartSound(steam, point->info->painsound); + } } else - P_RemoveMobj(point); + { + fixed_t distx = P_ReturnThrustX(point, point->angle, point->radius); + fixed_t disty = P_ReturnThrustY(point, point->angle, point->radius); + if (P_TryMove(point, point->x + distx, point->y + disty, false) // prevents the sprite from clipping into the wall or dangling off ledges + && P_TryMove(point, point->x - 2*distx, point->y - 2*disty, false) + && P_TryMove(point, point->x + distx, point->y + disty, false)) + { + if (point->info->seesound) + S_StartSound(point, point->info->seesound); + } + else + P_RemoveMobj(point); + } } } @@ -3499,6 +3537,8 @@ void A_BossScream(mobj_t *actor) z = actor->z + FixedMul((P_RandomByte()<<(FRACBITS-2)) - 8*FRACUNIT, actor->scale); mo = P_SpawnMobj(x, y, z, explodetype); + if (P_MobjWasRemoved(mo)) + return; if (actor->eflags & MFE_VERTICALFLIP) mo->flags2 |= MF2_OBJECTFLIP; mo->destscale = actor->scale; @@ -3625,6 +3665,9 @@ void A_1upThinker(mobj_t *actor) if (!actor->tracer) { P_SetTarget(&actor->tracer, P_SpawnMobj(actor->x, actor->y, actor->z, MT_OVERLAY)); + if (P_MobjWasRemoved(actor->tracer)) + return; + P_SetTarget(&actor->tracer->target, actor); actor->tracer->skin = &skins[players[closestplayer].skin]; // required here to prevent spr2 default showing stand for a single frame P_SetMobjState(actor->tracer, actor->info->seestate); @@ -3686,31 +3729,40 @@ void A_MonitorPop(mobj_t *actor) return; } - newmobj = P_SpawnMobjFromMobj(actor, 0, 0, 13*FRACUNIT, item); - P_SetTarget(&newmobj->target, actor->target); // Transfer target - if (item == MT_1UP_ICON) { if (actor->tracer) // Remove the old lives icon. P_RemoveMobj(actor->tracer); + } - if (!newmobj->target - || !newmobj->target->player - || !newmobj->target->skin - || ((skin_t *)newmobj->target->skin)->sprites[SPR2_LIFE].numframes == 0) - {} // No lives icon for this player, use the default. - else - { // Spawn the lives icon. - mobj_t *livesico = P_SpawnMobjFromMobj(newmobj, 0, 0, 0, MT_OVERLAY); - P_SetTarget(&livesico->target, newmobj); - P_SetTarget(&newmobj->tracer, livesico); + newmobj = P_SpawnMobjFromMobj(actor, 0, 0, 13*FRACUNIT, item); + if (!P_MobjWasRemoved(newmobj)) + { + P_SetTarget(&newmobj->target, actor->target); // Transfer target - livesico->color = newmobj->target->player->mo->color; - livesico->skin = &skins[newmobj->target->player->skin]; - P_SetMobjState(livesico, newmobj->info->seestate); + if (item == MT_1UP_ICON) + { + if (!newmobj->target + || !newmobj->target->player + || !newmobj->target->skin + || ((skin_t *)newmobj->target->skin)->sprites[SPR2_LIFE].numframes == 0) + {} // No lives icon for this player, use the default. + else + { // Spawn the lives icon. + mobj_t *livesico = P_SpawnMobjFromMobj(newmobj, 0, 0, 0, MT_OVERLAY); + if (!P_MobjWasRemoved(livesico)) + { + P_SetTarget(&livesico->target, newmobj); + P_SetTarget(&newmobj->tracer, livesico); - // We're using the overlay, so use the overlay 1up sprite (no text) - newmobj->sprite = SPR_TV1P; + livesico->color = newmobj->target->player->mo->color; + livesico->skin = &skins[newmobj->target->player->skin]; + P_SetMobjState(livesico, newmobj->info->seestate); + } + + // We're using the overlay, so use the overlay 1up sprite (no text) + newmobj->sprite = SPR_TV1P; + } } } @@ -3773,30 +3825,36 @@ void A_GoldMonitorPop(mobj_t *actor) // Note: the icon spawns 1 fracunit higher newmobj = P_SpawnMobjFromMobj(actor, 0, 0, 14*FRACUNIT, item); - P_SetTarget(&newmobj->target, actor->target); // Transfer target - - if (item == MT_1UP_ICON) + if (!P_MobjWasRemoved(newmobj)) { - if (actor->tracer) // Remove the old lives icon. - P_RemoveMobj(actor->tracer); + P_SetTarget(&newmobj->target, actor->target); // Transfer target - if (!newmobj->target - || !newmobj->target->player - || !newmobj->target->skin - || ((skin_t *)newmobj->target->skin)->sprites[SPR2_LIFE].numframes == 0) - {} // No lives icon for this player, use the default. - else - { // Spawn the lives icon. - mobj_t *livesico = P_SpawnMobjFromMobj(newmobj, 0, 0, 0, MT_OVERLAY); - P_SetTarget(&livesico->target, newmobj); - P_SetTarget(&newmobj->tracer, livesico); + if (item == MT_1UP_ICON) + { + if (actor->tracer) // Remove the old lives icon. + P_RemoveMobj(actor->tracer); - livesico->color = newmobj->target->player->mo->color; - livesico->skin = &skins[newmobj->target->player->skin]; - P_SetMobjState(livesico, newmobj->info->seestate); + if (!newmobj->target + || !newmobj->target->player + || !newmobj->target->skin + || ((skin_t *)newmobj->target->skin)->sprites[SPR2_LIFE].numframes == 0) + {} // No lives icon for this player, use the default. + else + { // Spawn the lives icon. + mobj_t *livesico = P_SpawnMobjFromMobj(newmobj, 0, 0, 0, MT_OVERLAY); + if (!P_MobjWasRemoved(livesico)) + { + P_SetTarget(&livesico->target, newmobj); + P_SetTarget(&newmobj->tracer, livesico); - // We're using the overlay, so use the overlay 1up sprite (no text) - newmobj->sprite = SPR_TV1P; + livesico->color = newmobj->target->player->mo->color; + livesico->skin = &skins[newmobj->target->player->skin]; + P_SetMobjState(livesico, newmobj->info->seestate); + } + + // We're using the overlay, so use the overlay 1up sprite (no text) + newmobj->sprite = SPR_TV1P; + } } } @@ -3841,7 +3899,13 @@ void A_GoldMonitorSparkle(mobj_t *actor) yofs = FINECOSINE((ngangle>>ANGLETOFINESHIFT) & FINEMASK) * (actor->radius>>FRACBITS); for (i = FRACUNIT*2; i <= FRACUNIT*3; i += FRACUNIT/2) - P_SetObjectMomZ(P_SpawnMobjFromMobj(actor, xofs, yofs, 0, MT_BOXSPARKLE), i, false); + { + mobj_t *sparkle = P_SpawnMobjFromMobj(actor, xofs, yofs, 0, MT_BOXSPARKLE); + if (P_MobjWasRemoved(sparkle)) + continue; + + P_SetObjectMomZ(sparkle, i, false); + } } // Function: A_Explode @@ -3963,56 +4027,73 @@ static void P_DoBossVictory(mobj_t *mo) static void P_SpawnBoss1Junk(mobj_t *mo) { mobj_t *mo2 = P_SpawnMobjFromMobj(mo, - P_ReturnThrustX(mo, mo->angle - ANGLE_90, 32<angle - ANGLE_90, 32<angle = mo->angle; - P_InstaThrust(mo2, mo2->angle - ANGLE_90, 4*mo2->scale); - P_SetObjectMomZ(mo2, 4*FRACUNIT, false); - P_SetMobjState(mo2, S_BOSSEGLZ1); + P_ReturnThrustX(mo, mo->angle - ANGLE_90, 32<angle - ANGLE_90, 32<angle = mo->angle; + P_InstaThrust(mo2, mo2->angle - ANGLE_90, 4*mo2->scale); + P_SetObjectMomZ(mo2, 4*FRACUNIT, false); + P_SetMobjState(mo2, S_BOSSEGLZ1); + } mo2 = P_SpawnMobjFromMobj(mo, P_ReturnThrustX(mo, mo->angle + ANGLE_90, 32<angle + ANGLE_90, 32<angle = mo->angle; - P_InstaThrust(mo2, mo2->angle + ANGLE_90, 4*mo2->scale); - P_SetObjectMomZ(mo2, 4*FRACUNIT, false); - P_SetMobjState(mo2, S_BOSSEGLZ2); + if (!P_MobjWasRemoved(mo2)) + { + mo2->angle = mo->angle; + P_InstaThrust(mo2, mo2->angle + ANGLE_90, 4*mo2->scale); + P_SetObjectMomZ(mo2, 4*FRACUNIT, false); + P_SetMobjState(mo2, S_BOSSEGLZ2); + } } static void P_SpawnBoss2Junk(mobj_t *mo) { mobj_t *mo2 = P_SpawnMobjFromMobj(mo, - P_ReturnThrustX(mo, mo->angle - ANGLE_90, 32<angle - ANGLE_90, 32<angle = mo->angle; - P_InstaThrust(mo2, mo2->angle - ANGLE_90, 4*mo2->scale); - P_SetObjectMomZ(mo2, 4*FRACUNIT, false); - P_SetMobjState(mo2, S_BOSSTANK1); + P_ReturnThrustX(mo, mo->angle - ANGLE_90, 32<angle - ANGLE_90, 32<angle = mo->angle; + P_InstaThrust(mo2, mo2->angle - ANGLE_90, 4*mo2->scale); + P_SetObjectMomZ(mo2, 4*FRACUNIT, false); + P_SetMobjState(mo2, S_BOSSTANK1); + } mo2 = P_SpawnMobjFromMobj(mo, P_ReturnThrustX(mo, mo->angle + ANGLE_90, 32<angle + ANGLE_90, 32<angle = mo->angle; - P_InstaThrust(mo2, mo2->angle + ANGLE_90, 4*mo2->scale); - P_SetObjectMomZ(mo2, 4*FRACUNIT, false); - P_SetMobjState(mo2, S_BOSSTANK2); + if (!P_MobjWasRemoved(mo2)) + { + mo2->angle = mo->angle; + P_InstaThrust(mo2, mo2->angle + ANGLE_90, 4*mo2->scale); + P_SetObjectMomZ(mo2, 4*FRACUNIT, false); + P_SetMobjState(mo2, S_BOSSTANK2); + } mo2 = P_SpawnMobjFromMobj(mo, 0, 0, mobjinfo[MT_EGGMOBILE2].height + (32<angle = mo->angle; - P_SetObjectMomZ(mo2, 4*FRACUNIT, false); - mo2->momz += mo->momz; - P_SetMobjState(mo2, S_BOSSSPIGOT); + if (!P_MobjWasRemoved(mo2)) + { + mo2->angle = mo->angle; + P_SetObjectMomZ(mo2, 4*FRACUNIT, false); + mo2->momz += mo->momz; + P_SetMobjState(mo2, S_BOSSSPIGOT); + } } static void P_SpawnBoss3Junk(mobj_t *mo) { mobj_t *mo2 = P_SpawnMobjFromMobj(mo, 0, 0, 0, MT_BOSSJUNK); + if (P_MobjWasRemoved(mo2)) + return; mo2->angle = mo->angle; P_SetMobjState(mo2, S_BOSSSEBH1); } @@ -4052,26 +4133,36 @@ static void P_DoBoss5Death(mobj_t *mo) mo->tracer->y - P_ReturnThrustY(mo->tracer, mo->tracer->angle, speed*time), mo->tracer->floorz + (256+1)*FRACUNIT, MT_FSGNB); - P_SetTarget(&pole->tracer, P_SpawnMobj( - pole->x, pole->y, - pole->z - 256*FRACUNIT, - MT_FSGNB)); - P_SetTarget(&pole->tracer->tracer, P_SpawnMobj( - pole->x + P_ReturnThrustX(pole, mo->tracer->angle, FRACUNIT), - pole->y + P_ReturnThrustY(pole, mo->tracer->angle, FRACUNIT), - pole->z + 256*FRACUNIT, - MT_FSGNA)); - pole->tracer->flags |= MF_NOCLIPTHING; - P_SetScale(pole, (pole->destscale = 2*FRACUNIT)); - P_SetScale(pole->tracer, (pole->tracer->destscale = 2*FRACUNIT)); - pole->angle = pole->tracer->angle = mo->tracer->angle; - pole->tracer->tracer->angle = pole->angle - ANGLE_90; - pole->momx = P_ReturnThrustX(pole, pole->angle, speed); - pole->momy = P_ReturnThrustY(pole, pole->angle, speed); - pole->tracer->momx = pole->momx; - pole->tracer->momy = pole->momy; - pole->tracer->tracer->momx = pole->momx; - pole->tracer->tracer->momy = pole->momy; + if (!P_MobjWasRemoved(pole)) + { + P_SetScale(pole, (pole->destscale = 2*FRACUNIT)); + pole->momx = P_ReturnThrustX(pole, pole->angle, speed); + pole->momy = P_ReturnThrustY(pole, pole->angle, speed); + P_SetTarget(&pole->tracer, P_SpawnMobj( + pole->x, pole->y, + pole->z - 256*FRACUNIT, + MT_FSGNB)); + if (!P_MobjWasRemoved(pole->tracer)) + { + pole->tracer->flags |= MF_NOCLIPTHING; + P_SetScale(pole->tracer, (pole->tracer->destscale = 2*FRACUNIT)); + pole->angle = pole->tracer->angle = mo->tracer->angle; + pole->tracer->momx = pole->momx; + pole->tracer->momy = pole->momy; + + P_SetTarget(&pole->tracer->tracer, P_SpawnMobj( + pole->x + P_ReturnThrustX(pole, mo->tracer->angle, FRACUNIT), + pole->y + P_ReturnThrustY(pole, mo->tracer->angle, FRACUNIT), + pole->z + 256*FRACUNIT, + MT_FSGNA)); + if (!P_MobjWasRemoved(pole->tracer->tracer)) + { + pole->tracer->tracer->angle = pole->angle - ANGLE_90; + pole->tracer->tracer->momx = pole->momx; + pole->tracer->tracer->momy = pole->momy; + } + } + } } } else @@ -4731,9 +4822,12 @@ void A_AttractChase(mobj_t *actor) { mobj_t *newring; newring = P_SpawnMobj(actor->x, actor->y, actor->z, actor->info->reactiontime); - newring->momx = actor->momx; - newring->momy = actor->momy; - newring->momz = actor->momz; + if (!P_MobjWasRemoved(newring)) + { + newring->momx = actor->momx; + newring->momy = actor->momy; + newring->momz = actor->momz; + } P_RemoveMobj(actor); return; } @@ -4813,9 +4907,12 @@ void A_DropMine(mobj_t *actor) // Use raisestate instead of MT_MINE mine = P_SpawnMobj(actor->x, actor->y, z, (mobjtype_t)actor->info->raisestate); - if (actor->eflags & MFE_VERTICALFLIP) - mine->eflags |= MFE_VERTICALFLIP; - mine->momz = actor->momz + actor->pmomz; + if (!P_MobjWasRemoved(mine)) + { + if (actor->eflags & MFE_VERTICALFLIP) + mine->eflags |= MFE_VERTICALFLIP; + mine->momz = actor->momz + actor->pmomz; + } S_StartSound(actor, actor->info->attacksound); } @@ -5138,11 +5235,13 @@ void A_SignSpin(mobj_t *actor) if (actor->tracer == NULL || P_MobjWasRemoved(actor->tracer)) return; for (i = -1; i < 2; i += 2) { - P_SpawnMobjFromMobj(actor, + mobj_t *spinmobj = P_SpawnMobjFromMobj(actor, P_ReturnThrustX(actor, actor->tracer->angle, i * actor->radius), P_ReturnThrustY(actor, actor->tracer->angle, i * actor->radius), (actor->eflags & MFE_VERTICALFLIP) ? 0 : actor->height, - actor->info->painchance)->destscale >>= 1; + actor->info->painchance); + if (!P_MobjWasRemoved(spinmobj)) + spinmobj->destscale >>= 1; } } @@ -5202,8 +5301,11 @@ void A_SignPlayer(mobj_t *actor) if (actor->tracer->tracer == NULL || P_MobjWasRemoved(actor->tracer->tracer)) { ov = P_SpawnMobj(actor->x, actor->y, actor->z, MT_OVERLAY); - P_SetTarget(&ov->target, actor->tracer); - P_SetTarget(&actor->tracer->tracer, ov); + if (!P_MobjWasRemoved(ov)) + { + P_SetTarget(&ov->target, actor->tracer); + P_SetTarget(&actor->tracer->tracer, ov); + } } else ov = actor->tracer->tracer; @@ -5263,33 +5365,36 @@ void A_SignPlayer(mobj_t *actor) signcolor = skincolors[facecolor].invcolor; } - if (skin) - { - if (skin->sprites[SPR2_SIGN].numframes) // player face + if (!P_MobjWasRemoved(ov)) + { + if (skin) { - ov->color = facecolor; - ov->skin = skin; - if ((statenum_t)(ov->state-states) != actor->info->seestate) - P_SetMobjState(ov, actor->info->seestate); // S_PLAY_SIGN + if (skin->sprites[SPR2_SIGN].numframes) // player face + { + ov->color = facecolor; + ov->skin = skin; + if ((statenum_t)(ov->state-states) != actor->info->seestate) + P_SetMobjState(ov, actor->info->seestate); // S_PLAY_SIGN + } + else // CLEAR! sign + { + ov->color = SKINCOLOR_NONE; + ov->skin = NULL; // needs to be NULL in the case of SF_HIRES characters + if ((statenum_t)(ov->state-states) != actor->info->missilestate) + P_SetMobjState(ov, actor->info->missilestate); // S_CLEARSIGN + } } - else // CLEAR! sign + else // Eggman face { ov->color = SKINCOLOR_NONE; - ov->skin = NULL; // needs to be NULL in the case of SF_HIRES characters - if ((statenum_t)(ov->state-states) != actor->info->missilestate) - P_SetMobjState(ov, actor->info->missilestate); // S_CLEARSIGN + ov->skin = NULL; + if ((statenum_t)(ov->state-states) != actor->info->meleestate) + P_SetMobjState(ov, actor->info->meleestate); // S_EGGMANSIGN + if (!signcolor) + signcolor = SKINCOLOR_CARBON; + facecolor = signcolor; } - } - else // Eggman face - { - ov->color = SKINCOLOR_NONE; - ov->skin = NULL; - if ((statenum_t)(ov->state-states) != actor->info->meleestate) - P_SetMobjState(ov, actor->info->meleestate); // S_EGGMANSIGN - if (!signcolor) - signcolor = SKINCOLOR_CARBON; - facecolor = signcolor; - } + } actor->tracer->color = signcolor; if (signcolor && signcolor < numskincolors) @@ -5480,12 +5585,14 @@ void A_JetbThink(mobj_t *actor) // use raisestate instead of MT_MINE bomb = P_SpawnMobj(actor->x, actor->y, actor->z - FixedMul((32<scale), (mobjtype_t)actor->info->raisestate); - - P_SetTarget(&bomb->target, actor); - bomb->destscale = actor->scale; - P_SetScale(bomb, actor->scale); - actor->reactiontime = TICRATE; // one second - S_StartSound(actor, actor->info->attacksound); + if (!P_MobjWasRemoved(bomb)) + { + P_SetTarget(&bomb->target, actor); + bomb->destscale = actor->scale; + P_SetScale(bomb, actor->scale); + actor->reactiontime = TICRATE; // one second + S_StartSound(actor, actor->info->attacksound); + } } } else if (((actor->z - FixedMul((32<scale)) < thefloor) && !((thefloor + FixedMul((32<scale) + actor->height) > actor->ceilingz)) @@ -5631,9 +5738,12 @@ void A_MinusDigging(mobj_t *actor) } par = P_SpawnMobj(actor->x, actor->y, mz, MT_MINUSDIRT); - if (actor->eflags & MFE_VERTICALFLIP) - par->eflags |= MFE_VERTICALFLIP; - P_TryMove(par, x, y, false); + if (!P_MobjWasRemoved(par)) + { + if (actor->eflags & MFE_VERTICALFLIP) + par->eflags |= MFE_VERTICALFLIP; + P_TryMove(par, x, y, false); + } // If close enough, prepare to attack if (P_AproxDistance(actor->x - actor->target->x, actor->y - actor->target->y) < actor->radius*2) @@ -5644,6 +5754,8 @@ void A_MinusDigging(mobj_t *actor) // Spawn growing dirt pile. par = P_SpawnMobj(actor->x, actor->y, mz, MT_MINUSDIRT); + if (P_MobjWasRemoved(par)) + return; P_SetMobjState(par, actor->info->raisestate); P_SetScale(par, actor->scale*2); if (actor->eflags & MFE_VERTICALFLIP) @@ -5714,6 +5826,8 @@ void A_MinusPopup(mobj_t *actor) for (i = 1; i <= num; i++) { mobj_t *rock = P_SpawnMobjFromMobj(actor, 0, 0, actor->height/4, MT_ROCKCRUMBLE1); + if (P_MobjWasRemoved(rock)) + continue; P_Thrust(rock, ani*i, FRACUNIT); P_SetObjectMomZ(rock, 3*FRACUNIT, false); P_SetScale(rock, rock->scale/3); @@ -5751,6 +5865,8 @@ void A_MinusCheck(mobj_t *actor) for (i = 1; i <= num; i++) { mobj_t *rock = P_SpawnMobjFromMobj(actor, 0, 0, actor->height/4, MT_ROCKCRUMBLE1); + if (P_MobjWasRemoved(rock)) + continue; P_Thrust(rock, ani*i, FRACUNIT); P_SetObjectMomZ(rock, 3*FRACUNIT, false); P_SetScale(rock, rock->scale/3); @@ -6262,6 +6378,8 @@ void A_RockSpawn(mobj_t *actor) dist += actor->spawnpoint->args[2] ? P_RandomByte() * (FRACUNIT/32) : 0; // random oomph mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_FALLINGROCK); + if (P_MobjWasRemoved(mo)) + return; P_SetMobjState(mo, mobjinfo[type].spawnstate); mo->angle = FixedAngle(actor->spawnpoint->angle << FRACBITS); @@ -6298,6 +6416,8 @@ void A_SlingAppear(mobj_t *actor) actor->friction = 128; hprev = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SMALLGRABCHAIN); + if (P_MobjWasRemoved(hprev)) + return; P_SetTarget(&hprev->tracer, actor); P_SetTarget(&hprev->hprev, actor); P_SetTarget(&actor->hnext, hprev); @@ -6309,13 +6429,16 @@ void A_SlingAppear(mobj_t *actor) while (mlength > 0) { spawnee = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SMALLMACECHAIN); - P_SetTarget(&spawnee->tracer, actor); - P_SetTarget(&spawnee->hprev, hprev); - P_SetTarget(&hprev->hnext, spawnee); - hprev = spawnee; + if (!P_MobjWasRemoved(spawnee)) + { + P_SetTarget(&spawnee->tracer, actor); + P_SetTarget(&spawnee->hprev, hprev); + P_SetTarget(&hprev->hnext, spawnee); + hprev = spawnee; - spawnee->flags |= MF_NOCLIP|MF_NOCLIPHEIGHT; - spawnee->movecount = mlength; + spawnee->flags |= MF_NOCLIP|MF_NOCLIPHEIGHT; + spawnee->movecount = mlength; + } mlength--; } @@ -6575,6 +6698,8 @@ void A_OldRingExplode(mobj_t *actor) { const angle_t fa = (i*FINEANGLES/16) & FINEMASK; mo = P_SpawnMobjFromMobj(actor, 0, 0, 0, locvar1); + if (P_MobjWasRemoved(mo)) + continue; P_SetTarget(&mo->target, actor->target); // Transfer target so player gets the points mo->momx = FixedMul(FINECOSINE(fa),ns); @@ -6601,33 +6726,37 @@ void A_OldRingExplode(mobj_t *actor) { } mo = P_SpawnMobjFromMobj(actor, 0, 0, 0, locvar1); - - P_SetTarget(&mo->target, actor->target); - mo->momz = ns; - mo->flags2 |= MF2_DEBRIS; - mo->fuse = TICRATE/5; - - if (changecolor) + if (!P_MobjWasRemoved(mo)) { - if (!(gametyperules & GTR_TEAMS)) - mo->color = actor->target->color; //copy color - else if (actor->target->player->ctfteam == 2) - mo->color = skincolor_bluering; + P_SetTarget(&mo->target, actor->target); + mo->momz = ns; + mo->flags2 |= MF2_DEBRIS; + mo->fuse = TICRATE/5; + + if (changecolor) + { + if (!(gametyperules & GTR_TEAMS)) + mo->color = actor->target->color; //copy color + else if (actor->target->player->ctfteam == 2) + mo->color = skincolor_bluering; + } } mo = P_SpawnMobjFromMobj(actor, 0, 0, 0, locvar1); - - P_SetTarget(&mo->target, actor->target); - mo->momz = -ns; - mo->flags2 |= MF2_DEBRIS; - mo->fuse = TICRATE/5; - - if (changecolor) + if (!P_MobjWasRemoved(mo)) { - if (!(gametyperules & GTR_TEAMS)) - mo->color = actor->target->color; //copy color - else if (actor->target->player->ctfteam == 2) - mo->color = skincolor_bluering; + P_SetTarget(&mo->target, actor->target); + mo->momz = -ns; + mo->flags2 |= MF2_DEBRIS; + mo->fuse = TICRATE/5; + + if (changecolor) + { + if (!(gametyperules & GTR_TEAMS)) + mo->color = actor->target->color; //copy color + else if (actor->target->player->ctfteam == 2) + mo->color = skincolor_bluering; + } } } @@ -7263,23 +7392,26 @@ void A_Boss2Chase(mobj_t *actor) fa = (actor->movedir*FINEANGLES/8) & FINEMASK; goop = P_SpawnMobj(actor->x, actor->y, fz, actor->info->painchance); - goop->momx = FixedMul(FINECOSINE(fa),ns); - goop->momy = FixedMul(FINESINE(fa),ns); - goop->momz = FixedMul(4*FRACUNIT, actor->scale); - goop->fuse = 10*TICRATE; - - if (actor->info->attacksound) - S_StartAttackSound(actor, actor->info->attacksound); - - if (P_RandomChance(FRACUNIT/2)) + if (!P_MobjWasRemoved(goop)) { - goop->momx *= 2; - goop->momy *= 2; - } - else if (P_RandomChance(129*FRACUNIT/256)) - { - goop->momx *= 3; - goop->momy *= 3; + goop->momx = FixedMul(FINECOSINE(fa),ns); + goop->momy = FixedMul(FINESINE(fa),ns); + goop->momz = FixedMul(4*FRACUNIT, actor->scale); + goop->fuse = 10*TICRATE; + + if (actor->info->attacksound) + S_StartAttackSound(actor, actor->info->attacksound); + + if (P_RandomChance(FRACUNIT/2)) + { + goop->momx *= 2; + goop->momy *= 2; + } + else if (P_RandomChance(129*FRACUNIT/256)) + { + goop->momx *= 3; + goop->momy *= 3; + } } actor->flags2 |= MF2_JUSTATTACKED; @@ -7320,6 +7452,8 @@ void A_Boss2Pogo(mobj_t *actor) fa = (actor->movedir*FINEANGLES/8) & FINEMASK; goop = P_SpawnMobj(actor->x, actor->y, fz, actor->info->painchance); + if (P_MobjWasRemoved(goop)) + continue; goop->momx = FixedMul(FINECOSINE(fa),ns); goop->momy = FixedMul(FINESINE(fa),ns); goop->momz = FixedMul(4*FRACUNIT, actor->scale); @@ -7640,7 +7774,8 @@ void A_Boss2PogoTarget(mobj_t *actor) if (actor->info->missilestate) // spawn the pogo stick collision box { mobj_t *pogo = P_SpawnMobj(actor->x, actor->y, actor->z - mobjinfo[actor->info->missilestate].height, (mobjtype_t)actor->info->missilestate); - P_SetTarget(&pogo->target, actor); + if (!P_MobjWasRemoved(pogo)) + P_SetTarget(&pogo->target, actor); } actor->reactiontime = 1; @@ -8131,6 +8266,8 @@ void A_Boss1Spikeballs(mobj_t *actor) return; ball = P_SpawnMobj(actor->x, actor->y, actor->z, MT_EGGMOBILE_BALL); + if (P_MobjWasRemoved(ball)) + return; P_SetTarget(&ball->target, actor); ball->movedir = FixedAngle(FixedMul(FixedDiv(locvar1<threshold = ball->radius + actor->radius + ball->info->painchance; @@ -8315,19 +8452,22 @@ void A_Boss3ShockThink(mobj_t *actor) snew = P_SpawnMobj((x0 >> 1) + (x1 >> 1), (y0 >> 1) + (y1 >> 1), (actor->z >> 1) + (snext->z >> 1), actor->type); - snew->momx = (actor->momx + snext->momx) >> 1; - snew->momy = (actor->momy + snext->momy) >> 1; - snew->momz = (actor->momz + snext->momz) >> 1; // is this really needed? - snew->angle = (actor->angle + snext->angle) >> 1; - P_SetTarget(&snew->target, actor->target); - snew->fuse = actor->fuse; + if (!P_MobjWasRemoved(snew)) + { + snew->momx = (actor->momx + snext->momx) >> 1; + snew->momy = (actor->momy + snext->momy) >> 1; + snew->momz = (actor->momz + snext->momz) >> 1; // is this really needed? + snew->angle = (actor->angle + snext->angle) >> 1; + P_SetTarget(&snew->target, actor->target); + snew->fuse = actor->fuse; - P_SetScale(snew, actor->scale); - snew->destscale = actor->destscale; - snew->scalespeed = actor->scalespeed; + P_SetScale(snew, actor->scale); + snew->destscale = actor->destscale; + snew->scalespeed = actor->scalespeed; - P_SetTarget(&actor->hnext, snew); - P_SetTarget(&snew->hnext, snext); + P_SetTarget(&actor->hnext, snew); + P_SetTarget(&snew->hnext, snext); + } } } } @@ -8462,10 +8602,13 @@ void A_SmokeTrailer(mobj_t *actor) if (actor->eflags & MFE_VERTICALFLIP) { th = P_SpawnMobj(actor->x-actor->momx, actor->y-actor->momy, actor->z + actor->height - FixedMul(mobjinfo[locvar1].height, actor->scale), locvar1); - th->flags2 |= MF2_OBJECTFLIP; + if (!P_MobjWasRemoved(th)) + th->flags2 |= MF2_OBJECTFLIP; } else th = P_SpawnMobj(actor->x-actor->momx, actor->y-actor->momy, actor->z, locvar1); + if (P_MobjWasRemoved(th)) + return; P_SetObjectMomZ(th, FRACUNIT, false); th->destscale = actor->scale; P_SetScale(th, actor->scale); @@ -8502,6 +8645,8 @@ void A_SpawnObjectAbsolute(mobj_t *actor) type = (mobjtype_t)(locvar2&65535); mo = P_SpawnMobj(x<angle = actor->angle; @@ -8544,6 +8689,8 @@ void A_SpawnObjectRelative(mobj_t *actor) mo = P_SpawnMobj(actor->x + FixedMul(x<scale), actor->y + FixedMul(y<scale), (actor->eflags & MFE_VERTICALFLIP) ? ((actor->z + actor->height - mobjinfo[type].height) - FixedMul(z<scale)) : (actor->z + FixedMul(z<scale)), type); + if (P_MobjWasRemoved(mo)) + return; // Spawn objects with an angle matching the spawner's, rather than spawning Eastwards - Monster Iestyn mo->angle = actor->angle; @@ -9205,12 +9352,15 @@ void A_BossJetFume(mobj_t *actor) jetz = actor->z + FixedMul(38*FRACUNIT, actor->scale); filler = P_SpawnMobj(jetx, jety, jetz, MT_JETFUME1); - P_SetTarget(&filler->target, actor); - filler->destscale = actor->scale; - P_SetScale(filler, filler->destscale); - if (actor->eflags & MFE_VERTICALFLIP) - filler->flags2 |= MF2_OBJECTFLIP; - filler->fuse = 56; + if (!P_MobjWasRemoved(filler)) + { + P_SetTarget(&filler->target, actor); + filler->destscale = actor->scale; + P_SetScale(filler, filler->destscale); + if (actor->eflags & MFE_VERTICALFLIP) + filler->flags2 |= MF2_OBJECTFLIP; + filler->fuse = 56; + } if (actor->eflags & MFE_VERTICALFLIP) jetz = actor->z + actor->height - FixedMul(12*FRACUNIT + mobjinfo[MT_JETFUME1].height, actor->scale); @@ -9220,22 +9370,28 @@ void A_BossJetFume(mobj_t *actor) filler = P_SpawnMobj(jetx + P_ReturnThrustX(actor, actor->angle-ANGLE_90, FixedMul(24*FRACUNIT, actor->scale)), jety + P_ReturnThrustY(actor, actor->angle-ANGLE_90, FixedMul(24*FRACUNIT, actor->scale)), jetz, MT_JETFUME1); - P_SetTarget(&filler->target, actor); - filler->destscale = actor->scale; - P_SetScale(filler, filler->destscale); - if (actor->eflags & MFE_VERTICALFLIP) - filler->flags2 |= MF2_OBJECTFLIP; - filler->fuse = 57; + if (!P_MobjWasRemoved(filler)) + { + P_SetTarget(&filler->target, actor); + filler->destscale = actor->scale; + P_SetScale(filler, filler->destscale); + if (actor->eflags & MFE_VERTICALFLIP) + filler->flags2 |= MF2_OBJECTFLIP; + filler->fuse = 57; + } filler = P_SpawnMobj(jetx + P_ReturnThrustX(actor, actor->angle+ANGLE_90, FixedMul(24*FRACUNIT, actor->scale)), jety + P_ReturnThrustY(actor, actor->angle+ANGLE_90, FixedMul(24*FRACUNIT, actor->scale)), jetz, MT_JETFUME1); - P_SetTarget(&filler->target, actor); - filler->destscale = actor->scale; - P_SetScale(filler, filler->destscale); - if (actor->eflags & MFE_VERTICALFLIP) - filler->flags2 |= MF2_OBJECTFLIP; - filler->fuse = 58; + if (!P_MobjWasRemoved(filler)) + { + P_SetTarget(&filler->target, actor); + filler->destscale = actor->scale; + P_SetScale(filler, filler->destscale); + if (actor->eflags & MFE_VERTICALFLIP) + filler->flags2 |= MF2_OBJECTFLIP; + filler->fuse = 58; + } P_SetTarget(&actor->tracer, filler); } @@ -9263,14 +9419,17 @@ void A_BossJetFume(mobj_t *actor) else if (locvar1 == 2) // Metal Sonic jet fumes { filler = P_SpawnMobj(actor->x, actor->y, actor->z, MT_JETFUME1); - P_SetTarget(&filler->target, actor); - filler->fuse = 59; - P_SetTarget(&actor->tracer, filler); - P_SetScale(filler, (filler->destscale = actor->scale/3)); - if (actor->eflags & MFE_VERTICALFLIP) - filler->flags2 |= MF2_OBJECTFLIP; - filler->color = SKINCOLOR_ICY; - filler->colorized = true; + if (!P_MobjWasRemoved(filler)) + { + P_SetTarget(&filler->target, actor); + filler->fuse = 59; + P_SetTarget(&actor->tracer, filler); + P_SetScale(filler, (filler->destscale = actor->scale/3)); + if (actor->eflags & MFE_VERTICALFLIP) + filler->flags2 |= MF2_OBJECTFLIP; + filler->color = SKINCOLOR_ICY; + filler->colorized = true; + } } else if (locvar1 == 3) // Boss 4 jet flame { @@ -9280,12 +9439,15 @@ void A_BossJetFume(mobj_t *actor) else jetz = actor->z - 50*actor->scale; filler = P_SpawnMobj(actor->x, actor->y, jetz, MT_JETFLAME); - P_SetTarget(&filler->target, actor); - // Boss 4 already uses its tracer for other things - filler->destscale = actor->scale; - P_SetScale(filler, filler->destscale); - if (actor->eflags & MFE_VERTICALFLIP) - filler->flags2 |= MF2_OBJECTFLIP; + if (!P_MobjWasRemoved(filler)) + { + P_SetTarget(&filler->target, actor); + // Boss 4 already uses its tracer for other things + filler->destscale = actor->scale; + P_SetScale(filler, filler->destscale); + if (actor->eflags & MFE_VERTICALFLIP) + filler->flags2 |= MF2_OBJECTFLIP; + } } else if (locvar1 == 4) // Boss 4 Spectator Eggrobo jet flame { @@ -9300,12 +9462,15 @@ void A_BossJetFume(mobj_t *actor) jetx = actor->x + P_ReturnThrustX(actor, actor->angle+ANGLE_90, movefactor*actor->scale) - P_ReturnThrustX(actor, actor->angle, 19*actor->scale); jety = actor->y + P_ReturnThrustY(actor, actor->angle+ANGLE_90, movefactor*actor->scale) - P_ReturnThrustY(actor, actor->angle, 19*actor->scale); filler = P_SpawnMobj(jetx, jety, jetz, MT_EGGROBO1JET); - filler->movefactor = movefactor; - P_SetTarget(&filler->target, actor); - filler->destscale = actor->scale; - P_SetScale(filler, filler->destscale); - if (actor->eflags & MFE_VERTICALFLIP) - filler->flags2 |= MF2_OBJECTFLIP; + if (!P_MobjWasRemoved(filler)) + { + filler->movefactor = movefactor; + P_SetTarget(&filler->target, actor); + filler->destscale = actor->scale; + P_SetScale(filler, filler->destscale); + if (actor->eflags & MFE_VERTICALFLIP) + filler->flags2 |= MF2_OBJECTFLIP; + } if (movefactor <= 0) break; movefactor = -movefactor; @@ -11007,6 +11172,8 @@ void A_TrapShot(mobj_t *actor) type, frontoff, (INT16)(locvar2 & 65535), vertoff); missile = P_SpawnMobj(x, y, z, type); + if (P_MobjWasRemoved(missile)) + return; if (actor->eflags & MFE_VERTICALFLIP) missile->flags2 |= MF2_OBJECTFLIP; @@ -11077,18 +11244,21 @@ void A_VileTarget(mobj_t *actor) actor->target->y, actor->target->z + ((actor->target->eflags & MFE_VERTICALFLIP) ? actor->target->height - mobjinfo[fogtype].height : 0), fogtype); - if (actor->target->eflags & MFE_VERTICALFLIP) + if (!P_MobjWasRemoved(fog)) { - fog->eflags |= MFE_VERTICALFLIP; - fog->flags2 |= MF2_OBJECTFLIP; - } - fog->destscale = actor->target->scale; - P_SetScale(fog, fog->destscale); + if (actor->target->eflags & MFE_VERTICALFLIP) + { + fog->eflags |= MFE_VERTICALFLIP; + fog->flags2 |= MF2_OBJECTFLIP; + } + fog->destscale = actor->target->scale; + P_SetScale(fog, fog->destscale); - P_SetTarget(&actor->tracer, fog); - P_SetTarget(&fog->target, actor); - P_SetTarget(&fog->tracer, actor->target); - A_VileFire(fog); + P_SetTarget(&actor->tracer, fog); + P_SetTarget(&fog->target, actor); + P_SetTarget(&fog->tracer, actor->target); + A_VileFire(fog); + } } else { @@ -11108,19 +11278,22 @@ void A_VileTarget(mobj_t *actor) players[i].mo->y, players[i].mo->z + ((players[i].mo->eflags & MFE_VERTICALFLIP) ? players[i].mo->height - mobjinfo[fogtype].height : 0), fogtype); - if (players[i].mo->eflags & MFE_VERTICALFLIP) + if (!P_MobjWasRemoved(fog)) { - fog->eflags |= MFE_VERTICALFLIP; - fog->flags2 |= MF2_OBJECTFLIP; - } - fog->destscale = players[i].mo->scale; - P_SetScale(fog, fog->destscale); + if (players[i].mo->eflags & MFE_VERTICALFLIP) + { + fog->eflags |= MFE_VERTICALFLIP; + fog->flags2 |= MF2_OBJECTFLIP; + } + fog->destscale = players[i].mo->scale; + P_SetScale(fog, fog->destscale); - if (players[i].mo == actor->target) // We only care to track the fog targeting who we REALLY hate right now - P_SetTarget(&actor->tracer, fog); - P_SetTarget(&fog->target, actor); - P_SetTarget(&fog->tracer, players[i].mo); - A_VileFire(fog); + if (players[i].mo == actor->target) // We only care to track the fog targeting who we REALLY hate right now + P_SetTarget(&actor->tracer, fog); + P_SetTarget(&fog->target, actor); + P_SetTarget(&fog->tracer, players[i].mo); + A_VileFire(fog); + } } } } @@ -11590,6 +11763,8 @@ void A_BrakLobShot(mobj_t *actor) typeOfShot = MT_CANNONBALL; else typeOfShot = (mobjtype_t)locvar1; shot = P_SpawnMobj(actor->x, actor->y, actor->z + FixedMul(locvar2*FRACUNIT, actor->scale), typeOfShot); + if (P_MobjWasRemoved(shot)) + return; if (shot->info->seesound) S_StartSound(shot, shot->info->seesound); P_SetTarget(&shot->target, actor); // where it came from @@ -11656,6 +11831,8 @@ void A_NapalmScatter(mobj_t *actor) const angle_t fa = (i*FINEANGLES/numToShoot) & FINEMASK; mo = P_SpawnMobj(actor->x, actor->y, actor->z, typeOfShot); + if (P_MobjWasRemoved(mo)) + continue; P_SetTarget(&mo->target, actor->target); // Transfer target so Brak doesn't hit himself like an idiot mo->angle = fa << ANGLETOFINESHIFT; @@ -11681,6 +11858,8 @@ void A_SpawnFreshCopy(mobj_t *actor) return; newObject = P_SpawnMobjFromMobj(actor, 0, 0, 0, actor->type); + if (P_MobjWasRemoved(newObject)) + return; newObject->flags2 = actor->flags2 & MF2_AMBUSH; newObject->angle = actor->angle; newObject->color = actor->color; @@ -11716,6 +11895,8 @@ mobj_t *P_InternalFlickySpawn(mobj_t *actor, mobjtype_t flickytype, fixed_t momz } flicky = P_SpawnMobjFromMobj(actor, offsx, offsy, 0, flickytype); + if (P_MobjWasRemoved(flicky)) + return NULL; flicky->angle = actor->angle; if (flickytype == MT_SEED) @@ -11827,8 +12008,11 @@ void A_FlickyCenter(mobj_t *actor) if (!actor->tracer) { mobj_t *flicky = P_InternalFlickySpawn(actor, locvar1, 1, false, 0); - P_SetTarget(&flicky->target, actor); - P_SetTarget(&actor->tracer, flicky); + if (!P_MobjWasRemoved(flicky)) + { + P_SetTarget(&flicky->target, actor); + P_SetTarget(&actor->tracer, flicky); + } if (actor->spawnpoint) { @@ -11863,18 +12047,21 @@ void A_FlickyCenter(mobj_t *actor) locvar1 = flickytype; } - if (actor->flags & MF_GRENADEBOUNCE) // in-place - actor->tracer->fuse = 0; - else if (actor->flags & MF_SLIDEME) // aimless + if (!P_MobjWasRemoved(flicky)) { - actor->tracer->fuse = 0; // less than 2*TICRATE means move aimlessly. - actor->tracer->angle = P_RandomKey(180)*ANG2; - } - else //orbit - actor->tracer->fuse = FRACUNIT; + if (actor->flags & MF_GRENADEBOUNCE) // in-place + actor->tracer->fuse = 0; + else if (actor->flags & MF_SLIDEME) // aimless + { + actor->tracer->fuse = 0; // less than 2*TICRATE means move aimlessly. + actor->tracer->angle = P_RandomKey(180)*ANG2; + } + else //orbit + actor->tracer->fuse = FRACUNIT; - if (locvar1 == MT_FLICKY_08) - P_InternalFlickySetColor(actor->tracer, actor->extravalue2); + if (locvar1 == MT_FLICKY_08) + P_InternalFlickySetColor(actor->tracer, actor->extravalue2); + } actor->extravalue2 = 0; } @@ -11885,7 +12072,8 @@ void A_FlickyCenter(mobj_t *actor) fixed_t originy = actor->movefactor; fixed_t originz = actor->watertop; - actor->tracer->fuse = FRACUNIT; + if (!P_MobjWasRemoved(actor->tracer)) + actor->tracer->fuse = FRACUNIT; // Impose default home radius if flicky orbits around player if (!actor->extravalue1) @@ -11917,6 +12105,8 @@ void P_InternalFlickyBubble(mobj_t *actor) return; overlay = P_SpawnMobj(actor->x, actor->y, actor->z, MT_OVERLAY); + if (P_MobjWasRemoved(overlay)) + return; P_SetMobjStateNF(overlay, mobjinfo[actor->type].raisestate); P_SetTarget(&actor->tracer, overlay); P_SetTarget(&overlay->target, actor); @@ -12291,7 +12481,8 @@ void A_FlameParticle(mobj_t *actor) P_RandomRange(rad, -rad)<frame = actor->frame; if (!(locvar1 & 1)) @@ -12477,6 +12670,8 @@ void A_MineExplode(mobj_t *actor) actor->y+P_RandomRange(-dist, dist)*FRACUNIT, actor->z+P_RandomRange(((actor->eflags & MFE_UNDERWATER) ? -dist : 0), dist)*FRACUNIT, type); + if (P_MobjWasRemoved(b)) + continue; fixed_t dx = b->x - actor->x, dy = b->y - actor->y, dz = b->z - actor->z; fixed_t dm = P_AproxDistance(dz, P_AproxDistance(dy, dx)); b->momx = FixedDiv(dx, dm)*3; @@ -12605,6 +12800,8 @@ void A_SpawnParticleRelative(mobj_t *actor) mo = P_SpawnMobj(actor->x + FixedMul(x<scale), actor->y + FixedMul(y<scale), (actor->eflags & MFE_VERTICALFLIP) ? ((actor->z + actor->height - mobjinfo[MT_PARTICLE].height) - FixedMul(z<scale)) : (actor->z + FixedMul(z<scale)), MT_PARTICLE); + if (P_MobjWasRemoved(mo)) + return; // Spawn objects with an angle matching the spawner's, rather than spawning Eastwards - Monster Iestyn mo->angle = actor->angle; @@ -12997,11 +13194,14 @@ void A_DoNPCSkid(mobj_t *actor) if (!(leveltime % 3)) { mobj_t *particle = P_SpawnMobjFromMobj(actor, 0, 0, 0, MT_SPINDUST); - particle->tics = 10; + if (!P_MobjWasRemoved(particle)) + { + particle->tics = 10; - P_SetScale(particle, 2*actor->scale/3); - particle->destscale = actor->scale; - P_SetObjectMomZ(particle, FRACUNIT, false); + P_SetScale(particle, 2*actor->scale/3); + particle->destscale = actor->scale; + P_SetObjectMomZ(particle, FRACUNIT, false); + } } } @@ -13304,11 +13504,18 @@ void A_Boss5MakeJunk(mobj_t *actor) if (actor->extravalue2 > 10) { mobj_t *front = P_SpawnMobjFromMobj(actor, 0, 0, 0, MT_VWREF); - broked = P_SpawnMobjFromMobj(front, 0, 0, 0, MT_VWREB); - front->z = broked->z = front->z - broked->height; - P_SetObjectMomZ(front, (4<momz = front->momz; - broked->fuse = front->fuse = (actor->height+(2*front->height))/front->momz; + if (!P_MobjWasRemoved(front)) + { + P_SetObjectMomZ(front, (4<z -= broked->height; + broked->z = front->z; + broked->momz = front->momz; + broked->fuse = front->fuse = (actor->height+(2*front->height))/front->momz; + } + } } if (!(actor->colorized = !actor->colorized)) actor->frame |= FF_FULLBRIGHT; @@ -13322,6 +13529,8 @@ void A_Boss5MakeJunk(mobj_t *actor) while (i--) { broked = P_SpawnMobjFromMobj(actor, 0, 0, FRACUNIT, MT_BROKENROBOT); + if (P_MobjWasRemoved(broked)) + continue; if (locvar2 & 2) broked->fuse = TICRATE; else @@ -13339,13 +13548,17 @@ void A_Boss5MakeJunk(mobj_t *actor) if (locvar2 & 2) { broked = P_SpawnMobjFromMobj(actor, 0, 0, 64<fuse = states[S_FANG_INTRO12].tics+10; - P_SetMobjState(broked, S_ALART1); + if (!P_MobjWasRemoved(broked)) + { + S_StartSound(broked, sfx_alart); + broked->fuse = states[S_FANG_INTRO12].tics+10; + P_SetMobjState(broked, S_ALART1); + } } else if (locvar2 & 1) { - broked->z += broked->momz; + if (!P_MobjWasRemoved(broked)) + broked->z += broked->momz; S_StartSound(actor, sfx_s3kccs); actor->flags &= ~MF_NOCLIPTHING; } @@ -13404,6 +13617,8 @@ static void P_DustRing(mobjtype_t mobjtype, UINT32 div, fixed_t x, fixed_t y, fi z, mobjtype ); + if (P_MobjWasRemoved(dust)) + continue; dust->angle = ang*i + ANGLE_90; P_SetScale(dust, FixedMul(initscale, scale)); @@ -13556,9 +13771,12 @@ void A_DustDevilThink(mobj_t *actor) if (P_IsObjectOnGround(actor)) { angle_t dustang = ((P_RandomRange(0, 7)*ANGLE_45)>>ANGLETOFINESHIFT) & FINEMASK; mobj_t *dust = P_SpawnMobj(actor->x + 96 * FixedMul(scale, FINECOSINE(dustang)), actor->y + 96 * FixedMul(scale, FINESINE(dustang)), actor->z, MT_ARIDDUST); - P_SetMobjState(dust, dust->info->spawnstate + P_RandomRange(0, 2)); - dust->destscale = scale * 3; - P_SetScale(dust, dust->destscale); + if (!P_MobjWasRemoved(dust)) + { + P_SetMobjState(dust, dust->info->spawnstate + P_RandomRange(0, 2)); + dust->destscale = scale * 3; + P_SetScale(dust, dust->destscale); + } } actor->extravalue1++; @@ -13574,6 +13792,8 @@ void A_DustDevilThink(mobj_t *actor) fixed_t pz = actor->z; layer = P_SpawnMobj(px, py, pz, MT_DUSTLAYER); + if (P_MobjWasRemoved(layer)) + continue; layer->momz = 5 * scale; layer->angle = ANGLE_90 + ANGLE_90*i; layer->extravalue1 = TICRATE * 3; @@ -13759,6 +13979,8 @@ void A_DebrisRandom(mobj_t *actor) static mobj_t *P_TrainSeg(mobj_t *src, fixed_t x, fixed_t y, fixed_t z, angle_t ang, spritenum_t spr, UINT32 frame) { mobj_t *s = P_SpawnMobj(x, y, z, MT_TRAINSEG); + if (P_MobjWasRemoved(s)) + return NULL; s->fuse = 16*TICRATE; s->sprite = spr; s->frame = frame|FF_PAPERSPRITE; @@ -13911,7 +14133,7 @@ static void P_SnapperLegPlace(mobj_t *mo) // Move as many legs as available. seg = seg->tracer; - do + while (seg) { o1 = seg->extravalue1; o2 = seg->extravalue2; @@ -13936,7 +14158,7 @@ static void P_SnapperLegPlace(mobj_t *mo) seg->angle = R_PointToAngle2(mo->x, mo->y, seg->x, seg->y); seg = seg->tracer; - } while (seg); + } } // @@ -13960,6 +14182,12 @@ void A_SnapperSpawn(mobj_t *actor) // It spawns 1 head. seg = P_SpawnMobjFromMobj(actor, 0, 0, 0, headtype); + if (P_MobjWasRemoved(seg)) + { + // if we can't spawn the head, don't spawn the snapper at all + P_RemoveMobj(actor); + return; + } P_SetTarget(&ptr->tracer, seg); ptr = seg; @@ -13967,6 +14195,8 @@ void A_SnapperSpawn(mobj_t *actor) for (i = 1; i <= 4; i++) { seg = P_SpawnMobjFromMobj(actor, 0, 0, 0, legtype); + if (P_MobjWasRemoved(seg)) + continue; P_SetTarget(&ptr->tracer, seg); ptr = seg; @@ -14068,7 +14298,8 @@ void A_SnapperThinker(mobj_t *actor) if (actor->reactiontime < 4) { mobj_t *dust = P_SpawnMobj(x0, y0, actor->z, MT_SPINDUST); - P_Thrust(dust, ang + ANGLE_180 + FixedAngle(P_RandomRange(-20, 20)*FRACUNIT), speed*FRACUNIT); + if (!P_MobjWasRemoved(dust)) + P_Thrust(dust, ang + ANGLE_180 + FixedAngle(P_RandomRange(-20, 20)*FRACUNIT), speed*FRACUNIT); } if (actor->extravalue2 == 0) @@ -14178,6 +14409,8 @@ void A_MinecartSparkThink(mobj_t *actor) for (i = 1; i <= 8; i++) { mobj_t *trail = P_SpawnMobj(actor->x - dx*i, actor->y - dy*i, actor->z - dz*i, MT_PARTICLE); + if (P_MobjWasRemoved(trail)) + continue; trail->tics = 2; trail->sprite = actor->sprite; P_SetScale(trail, trail->scale/4); @@ -14263,7 +14496,8 @@ void A_LavafallLava(mobj_t *actor) return; lavafall = P_SpawnMobjFromMobj(actor, 0, 0, -8*FRACUNIT, MT_LAVAFALL_LAVA); - lavafall->momz = -P_MobjFlip(actor)*25*FRACUNIT; + if (!P_MobjWasRemoved(lavafall)) + lavafall->momz = -P_MobjFlip(actor)*25*FRACUNIT; } // Function: A_FallingLavaCheck @@ -14338,9 +14572,18 @@ void A_SpawnPterabytes(mobj_t *actor) c = FINECOSINE(fa); s = FINESINE(fa); waypoint = P_SpawnMobjFromMobj(actor, FixedMul(c, rad), FixedMul(s, rad), 0, MT_PTERABYTEWAYPOINT); + if (P_MobjWasRemoved(waypoint)) + continue; + waypoint->angle = ang + ANGLE_90; P_SetTarget(&waypoint->tracer, actor); ptera = P_SpawnMobjFromMobj(waypoint, 0, 0, 0, MT_PTERABYTE); + if (P_MobjWasRemoved(ptera)) + { + P_RemoveMobj(waypoint); + continue; + } + ptera->angle = waypoint->angle; P_SetTarget(&ptera->tracer, waypoint); ptera->extravalue1 = 0; @@ -14388,14 +14631,17 @@ void A_RolloutSpawn(mobj_t *actor) || P_MobjWasRemoved(actor->target) || P_AproxDistance(actor->x - actor->target->x, actor->y - actor->target->y) > locvar1) { - actor->target = P_SpawnMobj(actor->x, actor->y, actor->z, locvar2); - actor->target->flags2 |= (actor->flags2 & (MF2_AMBUSH | MF2_OBJECTFLIP)) | MF2_SLIDEPUSH; - actor->target->eflags |= (actor->eflags & MFE_VERTICALFLIP); - - if (actor->target->flags2 & MF2_AMBUSH) + P_SetTarget(&actor->target, P_SpawnMobj(actor->x, actor->y, actor->z, locvar2)); + if (!P_MobjWasRemoved(actor->target)) { - actor->target->color = SKINCOLOR_SUPERRUST3; - actor->target->colorized = true; + actor->target->flags2 |= (actor->flags2 & (MF2_AMBUSH | MF2_OBJECTFLIP)) | MF2_SLIDEPUSH; + actor->target->eflags |= (actor->eflags & MFE_VERTICALFLIP); + + if (actor->target->flags2 & MF2_AMBUSH) + { + actor->target->color = SKINCOLOR_SUPERRUST3; + actor->target->colorized = true; + } } } } @@ -14516,6 +14762,8 @@ void A_DragonbomberSpawn(mobj_t *actor) x = P_ReturnThrustX(mo, mo->angle, -mo->radius << 1); y = P_ReturnThrustY(mo, mo->angle, -mo->radius << 1); segment = P_SpawnMobjFromMobj(mo, x, y, 0, MT_DRAGONTAIL); + if (P_MobjWasRemoved(segment)) + continue; P_SetTarget(&segment->target, mo); P_SetTarget(&mo->tracer, segment); segment->angle = mo->angle; @@ -14524,6 +14772,8 @@ void A_DragonbomberSpawn(mobj_t *actor) for (i = 0; i < 2; i++) // spawn wings { mo = P_SpawnMobjFromMobj(actor, 0, 0, 0, MT_DRAGONWING); + if (P_MobjWasRemoved(mo)) + continue; P_SetTarget(&mo->target, actor); mo->movedir = ANGLE_90 + i * ANGLE_180; } diff --git a/src/p_floor.c b/src/p_floor.c index 9c24f5851..dcb96a3b2 100644 --- a/src/p_floor.c +++ b/src/p_floor.c @@ -1921,6 +1921,9 @@ void EV_CrumbleChain(sector_t *sec, ffloor_t *rover) for (c = topz; c > bottomz; c -= spacing) { spawned = P_SpawnMobj(a, b, c, type); + if (P_MobjWasRemoved(spawned)) + continue; + spawned->angle += P_RandomKey(36)*ANG10; // irrelevant for default objects but might make sense for some custom ones if (fromcenter) diff --git a/src/p_inter.c b/src/p_inter.c index 8bc5c95e4..73d6b707f 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -199,6 +199,9 @@ void P_DoNightsScore(player_t *player) player->linktimer = nightslinktics; } + if (P_MobjWasRemoved(dummymo)) + return; + if (player->linkcount < 10) { if (player->bonustime) @@ -1214,7 +1217,8 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) { // A flicky orbits us now mobj_t *flickyobj = P_SpawnMobj(toucher->x, toucher->y, toucher->z + toucher->info->height, MT_NIGHTOPIANHELPER); - P_SetTarget(&flickyobj->target, toucher); + if (!P_MobjWasRemoved(flickyobj)) + P_SetTarget(&flickyobj->target, toucher); player->powers[pw_nights_helper] = (UINT16)special->info->speed; } @@ -1225,7 +1229,8 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (playeringame[i] && players[i].mo && players[i].powers[pw_carry] == CR_NIGHTSMODE) { players[i].powers[pw_nights_helper] = (UINT16)special->info->speed; flickyobj = P_SpawnMobj(players[i].mo->x, players[i].mo->y, players[i].mo->z + players[i].mo->info->height, MT_NIGHTOPIANHELPER); - P_SetTarget(&flickyobj->target, players[i].mo); + if (!P_MobjWasRemoved(flickyobj)) + P_SetTarget(&flickyobj->target, players[i].mo); } if (special->info->deathsound != sfx_None) S_StartSound(NULL, special->info->deathsound); @@ -1734,9 +1739,12 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (!player->bot && player->bot != BOT_MPAI && special->fuse <= TICRATE && player->powers[pw_carry] != CR_MINECART && !(player->powers[pw_ignorelatch] & (1<<15))) { mobj_t *mcart = P_SpawnMobj(special->x, special->y, special->z, MT_MINECART); - P_SetTarget(&mcart->target, toucher); - mcart->angle = toucher->angle = player->drawangle = special->angle; - mcart->friction = FRACUNIT; + if (!P_MobjWasRemoved(mcart)) + { + P_SetTarget(&mcart->target, toucher); + mcart->angle = toucher->angle = player->drawangle = special->angle; + mcart->friction = FRACUNIT; + } P_ResetPlayer(player); player->pflags |= PF_JUMPDOWN; @@ -2506,7 +2514,8 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget break; } - P_SetMobjState(scoremobj, scorestate); + if (!P_MobjWasRemoved(scoremobj)) + P_SetMobjState(scoremobj, scorestate); source->player->scoreadd = locscoreadd; } @@ -2667,6 +2676,8 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget mo = P_SpawnMobj(inflictor->x + inflictor->momx, inflictor->y + inflictor->momy, inflictor->z + (inflictor->height / 2) + inflictor->momz, MT_EXTRALARGEBUBBLE); else mo = P_SpawnMobj(target->x, target->y, target->z, MT_EXTRALARGEBUBBLE); + if (P_MobjWasRemoved(mo)) + break; mo->destscale = target->scale; P_SetScale(mo, mo->destscale); P_SetMobjState(mo, mo->info->raisestate); @@ -2745,8 +2756,11 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget mo->angle = FixedAngle((P_RandomKey(36)*10)<angle = mo->angle; - P_SetMobjState(mo2, S_BOSSSEBH2); + if (!P_MobjWasRemoved(mo2)) + { + mo2->angle = mo->angle; + P_SetMobjState(mo2, S_BOSSSEBH2); + } if (++i == 2) // we've already removed 2 of these, let's stop now break; @@ -2845,7 +2859,10 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget if (flip) momz *= -1; #define makechunk(angtweak, xmov, ymov) \ + do {\ chunk = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_SPIKE);\ + if (P_MobjWasRemoved(chunk))\ + break;\ P_SetMobjState(chunk, target->info->xdeathstate);\ chunk->health = 0;\ chunk->angle = angtweak;\ @@ -2855,7 +2872,8 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget chunk->y += ymov;\ P_SetThingPosition(chunk);\ P_InstaThrust(chunk,chunk->angle, 4*scale);\ - chunk->momz = momz + chunk->momz = momz;\ + } while (0) makechunk(ang + ANGLE_180, -xoffs, -yoffs); makechunk(ang, xoffs, yoffs); @@ -2868,20 +2886,23 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget momz *= -1; chunk = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_SPIKE); - P_SetMobjState(chunk, target->info->deathstate); - chunk->health = 0; - chunk->angle = ang + ANGLE_180; - P_UnsetThingPosition(chunk); - chunk->flags = MF_NOCLIP; - chunk->x -= xoffs; - chunk->y -= yoffs; - if (flip) - chunk->z -= 12*scale; - else - chunk->z += 12*scale; - P_SetThingPosition(chunk); - P_InstaThrust(chunk, chunk->angle, 2*scale); - chunk->momz = momz; + if (!P_MobjWasRemoved(chunk)) + { + P_SetMobjState(chunk, target->info->deathstate); + chunk->health = 0; + chunk->angle = ang + ANGLE_180; + P_UnsetThingPosition(chunk); + chunk->flags = MF_NOCLIP; + chunk->x -= xoffs; + chunk->y -= yoffs; + if (flip) + chunk->z -= 12*scale; + else + chunk->z += 12*scale; + P_SetThingPosition(chunk); + P_InstaThrust(chunk, chunk->angle, 2*scale); + chunk->momz = momz; + } P_SetMobjState(target, target->info->deathstate); target->health = 0; @@ -2890,7 +2911,10 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget target->flags = MF_NOCLIP; target->x += xoffs; target->y += yoffs; - target->z = chunk->z; + if (flip) + target->z -= 12*scale; + else + target->z += 12*scale; P_SetThingPosition(target); P_InstaThrust(target, target->angle, 2*scale); target->momz = momz; @@ -2913,21 +2937,25 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget sprflip = P_RandomChance(FRACUNIT/2); #define makechunk(angtweak, xmov, ymov) \ - chunk = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_WALLSPIKE);\ - P_SetMobjState(chunk, target->info->xdeathstate);\ - chunk->health = 0;\ - chunk->angle = target->angle;\ - P_UnsetThingPosition(chunk);\ - chunk->flags = MF_NOCLIP;\ - chunk->x += xmov - forwardxoffs;\ - chunk->y += ymov - forwardyoffs;\ - P_SetThingPosition(chunk);\ - P_InstaThrust(chunk, angtweak, 4*scale);\ - chunk->momz = P_RandomRange(5, 7)*scale;\ - if (flip)\ - chunk->momz *= -1;\ - if (sprflip)\ - chunk->frame |= FF_VERTICALFLIP + do {\ + chunk = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_WALLSPIKE);\ + if (P_MobjWasRemoved(chunk))\ + break;\ + P_SetMobjState(chunk, target->info->xdeathstate);\ + chunk->health = 0;\ + chunk->angle = target->angle;\ + P_UnsetThingPosition(chunk);\ + chunk->flags = MF_NOCLIP;\ + chunk->x += xmov - forwardxoffs;\ + chunk->y += ymov - forwardyoffs;\ + P_SetThingPosition(chunk);\ + P_InstaThrust(chunk, angtweak, 4*scale);\ + chunk->momz = P_RandomRange(5, 7)*scale;\ + if (flip)\ + chunk->momz *= -1;\ + if (sprflip)\ + chunk->frame |= FF_VERTICALFLIP;\ + } while (0) makechunk(ang + ANGLE_180, -xoffs, -yoffs); sprflip = !sprflip; @@ -2939,21 +2967,23 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget sprflip = P_RandomChance(FRACUNIT/2); chunk = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_WALLSPIKE); - - P_SetMobjState(chunk, target->info->deathstate); - chunk->health = 0; - chunk->angle = target->angle; - P_UnsetThingPosition(chunk); - chunk->flags = MF_NOCLIP; - chunk->x += forwardxoffs - xoffs; - chunk->y += forwardyoffs - yoffs; - P_SetThingPosition(chunk); - P_InstaThrust(chunk, ang + ANGLE_180, 2*scale); - chunk->momz = P_RandomRange(5, 7)*scale; - if (flip) - chunk->momz *= -1; - if (sprflip) - chunk->frame |= FF_VERTICALFLIP; + if (!P_MobjWasRemoved(chunk)) + { + P_SetMobjState(chunk, target->info->deathstate); + chunk->health = 0; + chunk->angle = target->angle; + P_UnsetThingPosition(chunk); + chunk->flags = MF_NOCLIP; + chunk->x += forwardxoffs - xoffs; + chunk->y += forwardyoffs - yoffs; + P_SetThingPosition(chunk); + P_InstaThrust(chunk, ang + ANGLE_180, 2*scale); + chunk->momz = P_RandomRange(5, 7)*scale; + if (flip) + chunk->momz *= -1; + if (sprflip) + chunk->frame |= FF_VERTICALFLIP; + } P_SetMobjState(target, target->info->deathstate); target->health = 0; @@ -3829,6 +3859,8 @@ void P_PlayerRingBurst(player_t *player, INT32 num_rings) z += player->mo->height - mobjinfo[objType].height; mo = P_SpawnMobj(player->mo->x, player->mo->y, z, objType); + if (P_MobjWasRemoved(mo)) + continue; mo->fuse = 8*TICRATE; P_SetTarget(&mo->target, player->mo); @@ -3962,6 +3994,9 @@ void P_PlayerWeaponPanelBurst(player_t *player) z += player->mo->height - mobjinfo[weptype].height; mo = P_SpawnMobj(player->mo->x, player->mo->y, z, weptype); + if (P_MobjWasRemoved(mo)) + continue; + mo->reactiontime = ammoamt; mo->flags2 |= MF2_DONTRESPAWN; mo->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT); @@ -3995,13 +4030,13 @@ void P_PlayerWeaponAmmoBurst(player_t *player) mobj_t *mo; angle_t fa; fixed_t ns; - INT32 i = 0; + INT32 i; fixed_t z; mobjtype_t weptype = 0; powertype_t power = 0; - while (true) + for (i = 0;; i++) { if (player->powers[pw_bouncering]) { @@ -4046,6 +4081,8 @@ void P_PlayerWeaponAmmoBurst(player_t *player) z += player->mo->height - mobjinfo[weptype].height; mo = P_SpawnMobj(player->mo->x, player->mo->y, z, weptype); + if (P_MobjWasRemoved(mo)) + continue; mo->health = player->powers[power]; mo->flags2 |= MF2_DONTRESPAWN; mo->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT); @@ -4071,8 +4108,6 @@ void P_PlayerWeaponAmmoBurst(player_t *player) if (i & 1) P_SetObjectMomZ(mo, 3*FRACUNIT, true); - - i++; } } @@ -4097,45 +4132,50 @@ void P_PlayerWeaponPanelOrAmmoBurst(player_t *player) player->ringweapons &= ~rwflag; \ SETUP_DROP(pickup) \ mo = P_SpawnMobj(player->mo->x, player->mo->y, z, pickup); \ - mo->reactiontime = 0; \ - mo->flags2 |= MF2_DONTRESPAWN; \ - mo->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT); \ - P_SetTarget(&mo->target, player->mo); \ - mo->fuse = 12*TICRATE; \ - mo->destscale = player->mo->scale; \ - P_SetScale(mo, player->mo->scale); \ - mo->momx = FixedMul(FINECOSINE(fa),ns); \ - if (!(twodlevel || (player->mo->flags2 & MF2_TWOD))) \ - mo->momy = FixedMul(FINESINE(fa),ns); \ - P_SetObjectMomZ(mo, 4*FRACUNIT, false); \ - if (i & 1) \ - P_SetObjectMomZ(mo, 4*FRACUNIT, true); \ - if (player->mo->eflags & MFE_VERTICALFLIP) \ - mo->flags2 |= MF2_OBJECTFLIP; \ - ++i; \ + if (!P_MobjWasRemoved(mo)) \ + { \ + mo->reactiontime = 0; \ + mo->flags2 |= MF2_DONTRESPAWN; \ + mo->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT); \ + P_SetTarget(&mo->target, player->mo); \ + mo->fuse = 12*TICRATE; \ + mo->destscale = player->mo->scale; \ + P_SetScale(mo, player->mo->scale); \ + mo->momx = FixedMul(FINECOSINE(fa),ns); \ + if (!(twodlevel || (player->mo->flags2 & MF2_TWOD))) \ + mo->momy = FixedMul(FINESINE(fa),ns); \ + P_SetObjectMomZ(mo, 4*FRACUNIT, false); \ + if (i & 1) \ + P_SetObjectMomZ(mo, 4*FRACUNIT, true); \ + if (player->mo->eflags & MFE_VERTICALFLIP) \ + mo->flags2 |= MF2_OBJECTFLIP; \ + } \ } \ else if (player->powers[power] > 0) \ { \ SETUP_DROP(ammo) \ mo = P_SpawnMobj(player->mo->x, player->mo->y, z, ammo); \ - mo->health = player->powers[power]; \ - mo->flags2 |= MF2_DONTRESPAWN; \ - mo->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT); \ - P_SetTarget(&mo->target, player->mo); \ - mo->fuse = 12*TICRATE; \ - mo->destscale = player->mo->scale; \ - P_SetScale(mo, player->mo->scale); \ - mo->momx = FixedMul(FINECOSINE(fa),ns); \ - if (!(twodlevel || (player->mo->flags2 & MF2_TWOD))) \ - mo->momy = FixedMul(FINESINE(fa),ns); \ - P_SetObjectMomZ(mo, 3*FRACUNIT, false); \ - if (i & 1) \ - P_SetObjectMomZ(mo, 3*FRACUNIT, true); \ - if (player->mo->eflags & MFE_VERTICALFLIP) \ - mo->flags2 |= MF2_OBJECTFLIP; \ - player->powers[power] = 0; \ - ++i; \ - } + if (!P_MobjWasRemoved(mo)) \ + { \ + mo->health = player->powers[power]; \ + mo->flags2 |= MF2_DONTRESPAWN; \ + mo->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT); \ + P_SetTarget(&mo->target, player->mo); \ + mo->fuse = 12*TICRATE; \ + mo->destscale = player->mo->scale; \ + P_SetScale(mo, player->mo->scale); \ + mo->momx = FixedMul(FINECOSINE(fa),ns); \ + if (!(twodlevel || (player->mo->flags2 & MF2_TWOD))) \ + mo->momy = FixedMul(FINESINE(fa),ns); \ + P_SetObjectMomZ(mo, 3*FRACUNIT, false); \ + if (i & 1) \ + P_SetObjectMomZ(mo, 3*FRACUNIT, true); \ + if (player->mo->eflags & MFE_VERTICALFLIP) \ + mo->flags2 |= MF2_OBJECTFLIP; \ + player->powers[power] = 0; \ + } \ + } \ + ++i DROP_WEAPON(RW_BOUNCE, MT_BOUNCEPICKUP, MT_BOUNCERING, pw_bouncering); DROP_WEAPON(RW_RAIL, MT_RAILPICKUP, MT_RAILRING, pw_railring); @@ -4259,23 +4299,26 @@ void P_PlayerEmeraldBurst(player_t *player, boolean toss) momy = 0; mo = P_SpawnMobj(player->mo->x, player->mo->y, z, MT_FLINGEMERALD); - mo->health = 1; - mo->threshold = stoneflag; - mo->flags2 |= (MF2_DONTRESPAWN|MF2_SLIDEPUSH); - mo->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT); - P_SetTarget(&mo->target, player->mo); - mo->fuse = 12*TICRATE; - P_SetMobjState(mo, statenum); - - mo->momx = momx; - mo->momy = momy; - - P_SetObjectMomZ(mo, 3*FRACUNIT, false); - - if (player->mo->eflags & MFE_VERTICALFLIP) + if (!P_MobjWasRemoved(mo)) { - mo->momz = -mo->momz; - mo->flags2 |= MF2_OBJECTFLIP; + mo->health = 1; + mo->threshold = stoneflag; + mo->flags2 |= (MF2_DONTRESPAWN|MF2_SLIDEPUSH); + mo->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT); + P_SetTarget(&mo->target, player->mo); + mo->fuse = 12*TICRATE; + P_SetMobjState(mo, statenum); + + mo->momx = momx; + mo->momy = momy; + + P_SetObjectMomZ(mo, 3*FRACUNIT, false); + + if (player->mo->eflags & MFE_VERTICALFLIP) + { + mo->momz = -mo->momz; + mo->flags2 |= MF2_OBJECTFLIP; + } } if (toss) @@ -4303,6 +4346,8 @@ void P_PlayerFlagBurst(player_t *player, boolean toss) type = MT_BLUEFLAG; flag = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, type); + if (P_MobjWasRemoved(flag)) + return; if (player->mo->eflags & MFE_VERTICALFLIP) { diff --git a/src/p_local.h b/src/p_local.h index cc060e4ee..b9d07fe24 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -282,7 +282,7 @@ mobjtype_t P_GetMobjtype(UINT16 mthingtype); void P_RespawnSpecials(void); -mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type); +mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type, ...); void P_RecalcPrecipInSector(sector_t *sector); void P_PrecipitationEffects(void); diff --git a/src/p_map.c b/src/p_map.c index a7d1f4abd..fc91c8f49 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -2861,6 +2861,8 @@ boolean P_CheckMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff) { boolean moveok; mobj_t *hack = P_SpawnMobjFromMobj(thing, 0, 0, 0, MT_RAY); + if (P_MobjWasRemoved(hack)) + return false; hack->radius = thing->radius; hack->height = thing->height; diff --git a/src/p_mobj.c b/src/p_mobj.c index 9e7110de3..920b60272 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -914,29 +914,41 @@ void P_ExplodeMissile(mobj_t *mo) P_RadiusAttack(mo, mo, 96*FRACUNIT, 0, true); explodemo = P_SpawnMobj(mo->x, mo->y, mo->z, MT_EXPLODE); - P_SetScale(explodemo, mo->scale); - explodemo->destscale = mo->destscale; - explodemo->momx += (P_RandomByte() % 32) * FixedMul(FRACUNIT/8, explodemo->scale); - explodemo->momy += (P_RandomByte() % 32) * FixedMul(FRACUNIT/8, explodemo->scale); - S_StartSound(explodemo, sfx_pop); + if (!P_MobjWasRemoved(explodemo)) + { + P_SetScale(explodemo, mo->scale); + explodemo->destscale = mo->destscale; + explodemo->momx += (P_RandomByte() % 32) * FixedMul(FRACUNIT/8, explodemo->scale); + explodemo->momy += (P_RandomByte() % 32) * FixedMul(FRACUNIT/8, explodemo->scale); + S_StartSound(explodemo, sfx_pop); + } explodemo = P_SpawnMobj(mo->x, mo->y, mo->z, MT_EXPLODE); - P_SetScale(explodemo, mo->scale); - explodemo->destscale = mo->destscale; - explodemo->momx += (P_RandomByte() % 64) * FixedMul(FRACUNIT/8, explodemo->scale); - explodemo->momy -= (P_RandomByte() % 64) * FixedMul(FRACUNIT/8, explodemo->scale); - S_StartSound(explodemo, sfx_dmpain); + if (!P_MobjWasRemoved(explodemo)) + { + P_SetScale(explodemo, mo->scale); + explodemo->destscale = mo->destscale; + explodemo->momx += (P_RandomByte() % 64) * FixedMul(FRACUNIT/8, explodemo->scale); + explodemo->momy -= (P_RandomByte() % 64) * FixedMul(FRACUNIT/8, explodemo->scale); + S_StartSound(explodemo, sfx_dmpain); + } explodemo = P_SpawnMobj(mo->x, mo->y, mo->z, MT_EXPLODE); - P_SetScale(explodemo, mo->scale); - explodemo->destscale = mo->destscale; - explodemo->momx -= (P_RandomByte() % 128) * FixedMul(FRACUNIT/8, explodemo->scale); - explodemo->momy += (P_RandomByte() % 128) * FixedMul(FRACUNIT/8, explodemo->scale); - S_StartSound(explodemo, sfx_pop); + if (!P_MobjWasRemoved(explodemo)) + { + P_SetScale(explodemo, mo->scale); + explodemo->destscale = mo->destscale; + explodemo->momx -= (P_RandomByte() % 128) * FixedMul(FRACUNIT/8, explodemo->scale); + explodemo->momy += (P_RandomByte() % 128) * FixedMul(FRACUNIT/8, explodemo->scale); + S_StartSound(explodemo, sfx_pop); + } explodemo = P_SpawnMobj(mo->x, mo->y, mo->z, MT_EXPLODE); - P_SetScale(explodemo, mo->scale); - explodemo->destscale = mo->destscale; - explodemo->momx -= (P_RandomByte() % 96) * FixedMul(FRACUNIT/8, explodemo->scale); - explodemo->momy -= (P_RandomByte() % 96) * FixedMul(FRACUNIT/8, explodemo->scale); - S_StartSound(explodemo, sfx_cybdth); + if (!P_MobjWasRemoved(explodemo)) + { + P_SetScale(explodemo, mo->scale); + explodemo->destscale = mo->destscale; + explodemo->momx -= (P_RandomByte() % 96) * FixedMul(FRACUNIT/8, explodemo->scale); + explodemo->momy -= (P_RandomByte() % 96) * FixedMul(FRACUNIT/8, explodemo->scale); + S_StartSound(explodemo, sfx_cybdth); + } } mo->flags &= ~MF_MISSILE; @@ -3091,6 +3103,8 @@ boolean P_SceneryZMovement(mobj_t *mo) { prandom = P_RandomByte(); explodemo = P_SpawnMobj(mo->x, mo->y, mo->z, MT_SMALLBUBBLE); + if (P_MobjWasRemoved(explodemo)) + continue; explodemo->momx += ((prandom & 0x0F) << (FRACBITS-2)) * (i & 2 ? -1 : 1); explodemo->momy += ((prandom & 0xF0) << (FRACBITS-6)) * (i & 1 ? -1 : 1); explodemo->destscale = mo->scale; @@ -3362,13 +3376,19 @@ void P_MobjCheckWater(mobj_t *mobj) if (mobj->eflags & MFE_VERTICALFLIP) { splish = P_SpawnMobj(mobj->x, mobj->y, mobj->waterbottom-FixedMul(mobjinfo[splishtype].height, mobj->scale), splishtype); - splish->flags2 |= MF2_OBJECTFLIP; - splish->eflags |= MFE_VERTICALFLIP; + if (!P_MobjWasRemoved(splish)) + { + splish->flags2 |= MF2_OBJECTFLIP; + splish->eflags |= MFE_VERTICALFLIP; + } } else splish = P_SpawnMobj(mobj->x, mobj->y, mobj->watertop, splishtype); - splish->destscale = mobj->scale; - P_SetScale(splish, mobj->scale); + if (!P_MobjWasRemoved(splish)) + { + splish->destscale = mobj->scale; + P_SetScale(splish, mobj->scale); + } } // skipping stone! @@ -3398,13 +3418,19 @@ void P_MobjCheckWater(mobj_t *mobj) if (mobj->eflags & MFE_VERTICALFLIP) { splish = P_SpawnMobj(mobj->x, mobj->y, mobj->waterbottom-FixedMul(mobjinfo[splishtype].height, mobj->scale), splishtype); - splish->flags2 |= MF2_OBJECTFLIP; - splish->eflags |= MFE_VERTICALFLIP; + if (!P_MobjWasRemoved(splish)) + { + splish->flags2 |= MF2_OBJECTFLIP; + splish->eflags |= MFE_VERTICALFLIP; + } } else splish = P_SpawnMobj(mobj->x, mobj->y, mobj->watertop, splishtype); - splish->destscale = mobj->scale; - P_SetScale(splish, mobj->scale); + if (!P_MobjWasRemoved(splish)) + { + splish->destscale = mobj->scale; + P_SetScale(splish, mobj->scale); + } } } @@ -4414,11 +4440,14 @@ static void P_Boss3Thinker(mobj_t *mobj) } dummy = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->info->mass); - dummy->angle = mobj->angle; - dummy->threshold = way1; - P_SetTarget(&dummy->tracer, mobj); - dummy->movefactor = mobj->movefactor; - dummy->cusval = mobj->cusval; + if (!P_MobjWasRemoved(dummy)) + { + dummy->angle = mobj->angle; + dummy->threshold = way1; + P_SetTarget(&dummy->tracer, mobj); + dummy->movefactor = mobj->movefactor; + dummy->cusval = mobj->cusval; + } way2 = P_RandomKey(8-3); if (way2 >= curpath) @@ -4437,11 +4466,14 @@ static void P_Boss3Thinker(mobj_t *mobj) } dummy = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->info->mass); - dummy->angle = mobj->angle; - dummy->threshold = way2; - P_SetTarget(&dummy->tracer, mobj); - dummy->movefactor = mobj->movefactor; - dummy->cusval = mobj->cusval; + if (!P_MobjWasRemoved(dummy)) + { + dummy->angle = mobj->angle; + dummy->threshold = way2; + P_SetTarget(&dummy->tracer, mobj); + dummy->movefactor = mobj->movefactor; + dummy->cusval = mobj->cusval; + } CONS_Debug(DBG_GAMELOGIC, "Eggman path %d - Dummy selected paths %d and %d\n", way0, way1, way2); if (mobj->spawnpoint) @@ -4559,6 +4591,8 @@ static void P_Boss3Thinker(mobj_t *mobj) for (i = 0; i < numtospawn; i++) { shock = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_SHOCKWAVE); + if (P_MobjWasRemoved(shock)) + continue; P_SetTarget(&shock->target, mobj); shock->fuse = shock->info->painchance; @@ -4578,7 +4612,8 @@ static void P_Boss3Thinker(mobj_t *mobj) ang += interval; sprev = shock; } - S_StartSound(mobj, shock->info->seesound); + if (!P_MobjWasRemoved(shock)) + S_StartSound(mobj, shock->info->seesound); // look for a new target P_BossTargetPlayer(mobj, true); @@ -4828,12 +4863,17 @@ static void P_Boss4Thinker(mobj_t *mobj) for (arm = 0; arm <3 ; arm++) { seg = P_SpawnMobj(mobj->x, mobj->y, z, MT_EGGMOBILE4_MACE); + if (P_MobjWasRemoved(seg)) + continue; + P_SetTarget(&base->tracer, seg); base = seg; P_SetTarget(&seg->target, mobj); for (i = 0; i < 9; i++) { P_SetTarget(&seg->hnext, P_SpawnMobj(mobj->x, mobj->y, z, MT_EGGMOBILE4_MACE)); + if (P_MobjWasRemoved(seg->hnext)) + continue; P_SetTarget(&seg->hnext->hprev, seg); seg = seg->hnext; } @@ -5104,9 +5144,12 @@ static void P_Boss7Thinker(mobj_t *mobj) if (mobj->health >= mobj->info->spawnhealth && (leveltime & 14) == 0) { mobj_t *smoke = P_SpawnMobj(mobj->x, mobj->y, mobj->z + mobj->height, MT_SMOKE); - smoke->destscale = mobj->destscale; - P_SetScale(smoke, smoke->destscale); - smoke->momz = FixedMul(FRACUNIT, smoke->scale); + if (!P_MobjWasRemoved(smoke)) + { + smoke->destscale = mobj->destscale; + P_SetScale(smoke, smoke->destscale); + smoke->momz = FixedMul(FRACUNIT, smoke->scale); + } } if (mobj->state == &states[S_BLACKEGG_STND] && mobj->tics == mobj->state->tics) @@ -5380,6 +5423,8 @@ static void P_Boss7Thinker(mobj_t *mobj) y = mobj->y + FixedMul(FINECOSINE(fa),ns); mo2 = P_SpawnMobj(x, y, z, MT_EXPLODE); + if (P_MobjWasRemoved(mo2)) + continue; ns = 16 * FRACUNIT; mo2->momx = FixedMul(FINESINE(fa),ns); mo2->momy = FixedMul(FINECOSINE(fa),ns); @@ -5573,76 +5618,84 @@ static void P_Boss9Thinker(mobj_t *mobj) mobj_t *missile; missile = P_SpawnMissile(mobj, mobj->target, MT_MSGATHER); - S_StopSound(missile); - if (mobj->extravalue1 >= 2) - P_SetScale(missile, FRACUNIT>>1); - missile->destscale = missile->scale>>1; - missile->fuse = TICRATE/2; - missile->scalespeed = abs(missile->destscale - missile->scale)/missile->fuse; - missile->z -= missile->height/2; - missile->momx *= -1; - missile->momy *= -1; - missile->momz *= -1; + if (!P_MobjWasRemoved(missile)) + { + S_StopSound(missile); + if (mobj->extravalue1 >= 2) + P_SetScale(missile, FRACUNIT>>1); + missile->destscale = missile->scale>>1; + missile->fuse = TICRATE/2; + missile->scalespeed = abs(missile->destscale - missile->scale)/missile->fuse; + missile->z -= missile->height/2; + missile->momx *= -1; + missile->momy *= -1; + missile->momz *= -1; - if (mobj->extravalue1 == 2) - { - UINT8 i; - mobj_t *spread; - for (i = 0; i < 5; i++) + if (mobj->extravalue1 == 2) { - if (i == 2) - continue; - spread = P_SpawnMobj(missile->x, missile->y, missile->z, missile->type); - spread->angle = missile->angle+(ANGLE_11hh/2)*(i-2); - P_InstaThrust(spread,spread->angle,-spread->info->speed); - spread->momz = missile->momz; - P_SetScale(spread, missile->scale); - spread->destscale = missile->destscale; - spread->scalespeed = missile->scalespeed; - spread->fuse = missile->fuse; - P_UnsetThingPosition(spread); - spread->x -= spread->fuse*spread->momx; - spread->y -= spread->fuse*spread->momy; - spread->z -= spread->fuse*spread->momz; - P_SetThingPosition(spread); - } - P_InstaThrust(missile,missile->angle,-missile->info->speed); - } - else if (mobj->extravalue1 >= 3) - { - UINT8 i; - mobj_t *spread; - mobj->target->z -= (4*missile->height); - for (i = 0; i < 5; i++) - { - if (i != 2) + UINT8 i; + mobj_t *spread; + for (i = 0; i < 5; i++) { - spread = P_SpawnMissile(mobj, mobj->target, missile->type); + if (i == 2) + continue; + spread = P_SpawnMobj(missile->x, missile->y, missile->z, missile->type); + if (P_MobjWasRemoved(spread)) + continue; + + spread->angle = missile->angle+(ANGLE_11hh/2)*(i-2); + P_InstaThrust(spread,spread->angle,-spread->info->speed); + spread->momz = missile->momz; P_SetScale(spread, missile->scale); spread->destscale = missile->destscale; + spread->scalespeed = missile->scalespeed; spread->fuse = missile->fuse; - spread->z -= spread->height/2; - spread->momx *= -1; - spread->momy *= -1; - spread->momz *= -1; P_UnsetThingPosition(spread); spread->x -= spread->fuse*spread->momx; spread->y -= spread->fuse*spread->momy; spread->z -= spread->fuse*spread->momz; P_SetThingPosition(spread); } - mobj->target->z += missile->height*2; + P_InstaThrust(missile,missile->angle,-missile->info->speed); } - mobj->target->z -= (6*missile->height); + else if (mobj->extravalue1 >= 3) + { + UINT8 i; + mobj_t *spread; + mobj->target->z -= (4*missile->height); + for (i = 0; i < 5; i++) + { + if (i != 2) + { + spread = P_SpawnMissile(mobj, mobj->target, missile->type); + if (P_MobjWasRemoved(spread)) + continue; + P_SetScale(spread, missile->scale); + spread->destscale = missile->destscale; + spread->fuse = missile->fuse; + spread->z -= spread->height/2; + spread->momx *= -1; + spread->momy *= -1; + spread->momz *= -1; + P_UnsetThingPosition(spread); + spread->x -= spread->fuse*spread->momx; + spread->y -= spread->fuse*spread->momy; + spread->z -= spread->fuse*spread->momz; + P_SetThingPosition(spread); + } + mobj->target->z += missile->height*2; + } + mobj->target->z -= (6*missile->height); + } + + P_UnsetThingPosition(missile); + missile->x -= missile->fuse*missile->momx; + missile->y -= missile->fuse*missile->momy; + missile->z -= missile->fuse*missile->momz; + P_SetThingPosition(missile); + + S_StartSound(mobj, sfx_s3kb3); } - - P_UnsetThingPosition(missile); - missile->x -= missile->fuse*missile->momx; - missile->y -= missile->fuse*missile->momy; - missile->z -= missile->fuse*missile->momz; - P_SetThingPosition(missile); - - S_StartSound(mobj, sfx_s3kb3); } } } @@ -5660,29 +5713,32 @@ static void P_Boss9Thinker(mobj_t *mobj) if (spawner && dist) { mobj_t *missile = P_SpawnMissile(spawner, mobj, MT_MSGATHER); - missile->fuse = (dist/P_AproxDistance(missile->momx, missile->momy)); - if (missile->fuse <= 0) // Prevents a division by zero when calculating missile->scalespeed - missile->fuse = 1; + if (!P_MobjWasRemoved(missile)) + { + missile->fuse = (dist/P_AproxDistance(missile->momx, missile->momy)); + if (missile->fuse <= 0) // Prevents a division by zero when calculating missile->scalespeed + missile->fuse = 1; - if (missile->fuse > mobj->fuse) - { - P_RemoveMobj(missile); - } - else - { - if (mobj->health > mobj->info->damage) + if (missile->fuse > mobj->fuse) { - P_SetScale(missile, FRACUNIT/3); - missile->color = SKINCOLOR_MAGENTA; // sonic OVA/4 purple power + P_RemoveMobj(missile); } else { - P_SetScale(missile, FRACUNIT/5); - missile->color = SKINCOLOR_SUNSET; // sonic cd electric power + if (mobj->health > mobj->info->damage) + { + P_SetScale(missile, FRACUNIT/3); + missile->color = SKINCOLOR_MAGENTA; // sonic OVA/4 purple power + } + else + { + P_SetScale(missile, FRACUNIT/5); + missile->color = SKINCOLOR_SUNSET; // sonic cd electric power + } + missile->destscale = missile->scale*2; + missile->scalespeed = abs(missile->scale - missile->destscale)/missile->fuse; + missile->colorized = true; } - missile->destscale = missile->scale*2; - missile->scalespeed = abs(missile->scale - missile->destscale)/missile->fuse; - missile->colorized = true; } } @@ -5716,6 +5772,7 @@ static void P_Boss9Thinker(mobj_t *mobj) // threshold is used for attacks/maneuvers. if (mobj->threshold && mobj->movecount != 2) { + mobj_t *ghost; fixed_t speed = 20*FRACUNIT + FixedMul(40*FRACUNIT, FixedDiv((mobj->info->spawnhealth - mobj->health)<info->spawnhealth<target, mobj->info->speed); - if (mobj->extravalue1 >= 2) + if (!P_MobjWasRemoved(missile)) { - missile->destscale = FRACUNIT>>1; - P_SetScale(missile, missile->destscale); - } - missile->fuse = 3*TICRATE; - missile->z -= missile->height/2; - - if (mobj->extravalue1 == 2) - { - UINT8 i; - mobj_t *spread; - for (i = 0; i < 5; i++) + if (mobj->extravalue1 >= 2) { - if (i == 2) - continue; - spread = P_SpawnMobj(missile->x, missile->y, missile->z, missile->type); - spread->angle = missile->angle+(ANGLE_11hh/2)*(i-2); - P_InstaThrust(spread,spread->angle,spread->info->speed); - spread->momz = missile->momz; - spread->destscale = FRACUNIT>>1; - P_SetScale(spread, spread->destscale); - spread->fuse = missile->fuse; + missile->destscale = FRACUNIT>>1; + P_SetScale(missile, missile->destscale); } - P_InstaThrust(missile,missile->angle,missile->info->speed); - } - else if (mobj->extravalue1 >= 3) - { - UINT8 i; - mobj_t *spread; - mobj->target->z -= (2*missile->height); - for (i = 0; i < 5; i++) + missile->fuse = 3*TICRATE; + missile->z -= missile->height/2; + + if (mobj->extravalue1 == 2) { - if (i != 2) + UINT8 i; + mobj_t *spread; + for (i = 0; i < 5; i++) { - spread = P_SpawnMissile(mobj, mobj->target, missile->type); + if (i == 2) + continue; + spread = P_SpawnMobj(missile->x, missile->y, missile->z, missile->type); + if (P_MobjWasRemoved(spread)) + continue; + + spread->angle = missile->angle+(ANGLE_11hh/2)*(i-2); + P_InstaThrust(spread,spread->angle,spread->info->speed); + spread->momz = missile->momz; spread->destscale = FRACUNIT>>1; P_SetScale(spread, spread->destscale); spread->fuse = missile->fuse; - spread->z -= spread->height/2; } - mobj->target->z += missile->height; + P_InstaThrust(missile,missile->angle,missile->info->speed); + } + else if (mobj->extravalue1 >= 3) + { + UINT8 i; + mobj_t *spread; + mobj->target->z -= (2*missile->height); + for (i = 0; i < 5; i++) + { + if (i != 2) + { + spread = P_SpawnMissile(mobj, mobj->target, missile->type); + if (!P_MobjWasRemoved(spread)) + { + spread->destscale = FRACUNIT>>1; + P_SetScale(spread, spread->destscale); + spread->fuse = missile->fuse; + spread->z -= spread->height/2; + } + } + mobj->target->z += missile->height; + } + mobj->target->z -= (3*missile->height); } - mobj->target->z -= (3*missile->height); } } else @@ -5837,7 +5903,9 @@ static void P_Boss9Thinker(mobj_t *mobj) return; } - P_SpawnGhostMobj(mobj)->colorized = false; + ghost = P_SpawnGhostMobj(mobj); + if (!P_MobjWasRemoved(ghost)) + ghost->colorized = false; // Vector form dodge! mobj->angle += mobj->movedir; @@ -5935,8 +6003,11 @@ static void P_Boss9Thinker(mobj_t *mobj) if (mobj->health > mobj->info->damage) { // No more bubble if we're broken (pinch phase) mobj_t *shield = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_MSSHIELD_FRONT); - P_SetTarget(&mobj->hprev, shield); - P_SetTarget(&shield->target, mobj); + if (!P_MobjWasRemoved(shield)) + { + P_SetTarget(&mobj->hprev, shield); + P_SetTarget(&shield->target, mobj); + } // Attack 2: Energy shot! switch (mobj->health) @@ -6271,7 +6342,8 @@ void P_SpawnHoopOfSomething(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT finalz = z + v.z; mobj = P_SpawnMobj(finalx, finaly, finalz, type); - mobj->z -= mobj->height/2; + if (!P_MobjWasRemoved(mobj)) + mobj->z -= mobj->height/2; } } @@ -6312,6 +6384,8 @@ void P_SpawnParaloop(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32 numb finalz = z + v.z; mobj = P_SpawnMobj(finalx, finaly, finalz, type); + if (P_MobjWasRemoved(mobj)) + continue; mobj->z -= mobj->height>>1; @@ -6960,6 +7034,8 @@ static void P_KoopaThinker(mobj_t *koopa) { mobj_t *flame; flame = P_SpawnMobj(koopa->x - koopa->radius + FixedMul(5*FRACUNIT, koopa->scale), koopa->y, koopa->z + (P_RandomByte()<<(FRACBITS-2)), MT_KOOPAFLAME); + if (P_MobjWasRemoved(flame)) + return; flame->momx = -FixedMul(flame->info->speed, flame->scale); S_StartSound(flame, sfx_koopfr); } @@ -6967,6 +7043,8 @@ static void P_KoopaThinker(mobj_t *koopa) { mobj_t *hammer; hammer = P_SpawnMobj(koopa->x - koopa->radius, koopa->y, koopa->z + koopa->height, MT_HAMMER); + if (P_MobjWasRemoved(hammer)) + return; hammer->momx = FixedMul(-5*FRACUNIT, hammer->scale); hammer->momz = FixedMul(7*FRACUNIT, hammer->scale); } @@ -6985,6 +7063,9 @@ static void P_SpawnMinecartSegments(mobj_t *mobj, boolean mode) for (i = 0; i < 4; i++) { seg = P_SpawnMobj(x, y, z, MT_MINECARTSEG); + if (P_MobjWasRemoved(seg)) + continue; + P_SetMobjState(seg, (statenum_t)(S_MINECARTSEG_FRONT + i)); if (i >= 2) seg->extravalue1 = (i == 2) ? -18 : 18; // make -20/20 when papersprite projection fixed @@ -7040,6 +7121,8 @@ static void P_PyreFlyBurn(mobj_t *mobj, fixed_t hoffs, INT16 vrange, mobjtype_t fixed_t yoffs = FixedMul(FINESINE(fa), mobj->radius + hoffs); fixed_t zoffs = P_RandomRange(-vrange, vrange)*FRACUNIT; mobj_t *particle = P_SpawnMobjFromMobj(mobj, xoffs, yoffs, zoffs, mobjtype); + if (P_MobjWasRemoved(particle)) + return; particle->momz = momz; particle->flags2 |= MF2_LINKDRAW; P_SetTarget(&particle->tracer, mobj); @@ -7198,6 +7281,8 @@ static void P_FlameJetSceneryThink(mobj_t *mobj) mobj->fuse -= 2; flame = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_FLAMEJETFLAME); + if (P_MobjWasRemoved(flame)) + return; P_SetMobjState(flame, S_FLAMEJETFLAME4); flame->angle = mobj->angle; @@ -7236,6 +7321,8 @@ static void P_VerticalFlameJetSceneryThink(mobj_t *mobj) mobj->fuse--; flame = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_FLAMEJETFLAME); + if (P_MobjWasRemoved(flame)) + return; strength = (mobj->movedir ? mobj->movedir : 80)<<(FRACBITS-2); @@ -7309,13 +7396,16 @@ static boolean P_ParticleGenSceneryThink(mobj_t *mobj) mobj->y + FixedMul(FixedMul(mobj->friction, mobj->scale), FINESINE(mobj->angle >> ANGLETOFINESHIFT)), mobj->z, (mobjtype_t)mobj->threshold); - P_SetScale(spawn, mobj->scale); - spawn->momz = FixedMul(mobj->movefactor, spawn->scale); - spawn->destscale = spawn->scale/100; - spawn->scalespeed = spawn->scale/mobj->health; - spawn->tics = (tic_t)mobj->health; - spawn->flags2 |= (mobj->flags2 & MF2_OBJECTFLIP); - spawn->angle += P_RandomKey(36)*ANG10; // irrelevant for default objects but might make sense for some custom ones + if (!P_MobjWasRemoved(spawn)) + { + P_SetScale(spawn, mobj->scale); + spawn->momz = FixedMul(mobj->movefactor, spawn->scale); + spawn->destscale = spawn->scale/100; + spawn->scalespeed = spawn->scale/mobj->health; + spawn->tics = (tic_t)mobj->health; + spawn->flags2 |= (mobj->flags2 & MF2_OBJECTFLIP); + spawn->angle += P_RandomKey(36)*ANG10; // irrelevant for default objects but might make sense for some custom ones + } mobj->angle += mobj->movedir; } @@ -7527,13 +7617,16 @@ static void P_RosySceneryThink(mobj_t *mobj) if (makeheart) { mobj_t *cdlhrt = P_SpawnMobjFromMobj(mobj, 0, 0, mobj->height, MT_CDLHRT); - cdlhrt->destscale = (5*mobj->scale) >> 4; - P_SetScale(cdlhrt, cdlhrt->destscale); - cdlhrt->fuse = (5*TICRATE) >> 1; - cdlhrt->momz = mobj->scale; - P_SetTarget(&cdlhrt->target, mobj); - cdlhrt->extravalue1 = mobj->x; - cdlhrt->extravalue2 = mobj->y; + if (!P_MobjWasRemoved(cdlhrt)) + { + cdlhrt->destscale = (5*mobj->scale) >> 4; + P_SetScale(cdlhrt, cdlhrt->destscale); + cdlhrt->fuse = (5*TICRATE) >> 1; + cdlhrt->momz = mobj->scale; + P_SetTarget(&cdlhrt->target, mobj); + cdlhrt->extravalue1 = mobj->x; + cdlhrt->extravalue2 = mobj->y; + } } } } @@ -7660,13 +7753,16 @@ static void P_MobjSceneryThink(mobj_t *mobj) && */ (mobj->target->player->pflags & PF_SHIELDABILITY)) { mobj_t *whoosh = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_GHOST); // done here so the offset is correct - P_SetMobjState(whoosh, mobj->info->raisestate); - whoosh->destscale = whoosh->scale << 1; - whoosh->scalespeed = FixedMul(whoosh->scalespeed, whoosh->scale); - whoosh->height = 38*whoosh->scale; - whoosh->fuse = 10; - whoosh->flags |= MF_NOCLIPHEIGHT; - whoosh->momz = mobj->target->momz; // Stay reasonably centered for a few frames + if (!P_MobjWasRemoved(whoosh)) + { + P_SetMobjState(whoosh, mobj->info->raisestate); + whoosh->destscale = whoosh->scale << 1; + whoosh->scalespeed = FixedMul(whoosh->scalespeed, whoosh->scale); + whoosh->height = 38*whoosh->scale; + whoosh->fuse = 10; + whoosh->flags |= MF_NOCLIPHEIGHT; + whoosh->momz = mobj->target->momz; // Stay reasonably centered for a few frames + } mobj->target->player->pflags &= ~PF_SHIELDABILITY; // prevent eternal whoosh } /* FALLTHRU */ @@ -8007,8 +8103,11 @@ static boolean P_MobjBossThink(mobj_t *mobj) P_RandomRange(rad, -rad) << FRACBITS, P_RandomRange(hei / 2, hei) << FRACBITS, MT_SMOKE); - P_SetObjectMomZ(particle, 2 << FRACBITS, false); - particle->momz += mobj->momz; + if (!P_MobjWasRemoved(particle)) + { + P_SetObjectMomZ(particle, 2 << FRACBITS, false); + particle->momz += mobj->momz; + } } if (mobj->flags2 & MF2_SKULLFLY) #if 1 @@ -8017,8 +8116,11 @@ static boolean P_MobjBossThink(mobj_t *mobj) { mobj_t *spawnmobj; spawnmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->info->painchance); - P_SetTarget(&spawnmobj->target, mobj); - spawnmobj->color = SKINCOLOR_GREY; + if (!P_MobjWasRemoved(spawnmobj)) + { + P_SetTarget(&spawnmobj->target, mobj); + spawnmobj->color = SKINCOLOR_GREY; + } } #endif P_Boss1Thinker(mobj); @@ -8033,8 +8135,11 @@ static boolean P_MobjBossThink(mobj_t *mobj) P_RandomRange(rad, -rad) << FRACBITS, P_RandomRange(hei/2, hei) << FRACBITS, MT_SMOKE); - P_SetObjectMomZ(particle, 2 << FRACBITS, false); - particle->momz += mobj->momz; + if (!P_MobjWasRemoved(particle)) + { + P_SetObjectMomZ(particle, 2 << FRACBITS, false); + particle->momz += mobj->momz; + } } P_Boss2Thinker(mobj); break; @@ -8048,8 +8153,11 @@ static boolean P_MobjBossThink(mobj_t *mobj) P_RandomRange(rad, -rad) << FRACBITS, P_RandomRange(hei/2, hei) << FRACBITS, MT_SMOKE); - P_SetObjectMomZ(particle, 2 << FRACBITS, false); - particle->momz += mobj->momz; + if (!P_MobjWasRemoved(particle)) + { + P_SetObjectMomZ(particle, 2 << FRACBITS, false); + particle->momz += mobj->momz; + } } P_Boss3Thinker(mobj); break; @@ -8063,8 +8171,11 @@ static boolean P_MobjBossThink(mobj_t *mobj) P_RandomRange(rad, -rad) << FRACBITS, P_RandomRange(hei/2, hei) << FRACBITS, MT_SMOKE); - P_SetObjectMomZ(particle, 2 << FRACBITS, false); - particle->momz += mobj->momz; + if (!P_MobjWasRemoved(particle)) + { + P_SetObjectMomZ(particle, 2 << FRACBITS, false); + particle->momz += mobj->momz; + } } P_Boss4Thinker(mobj); break; @@ -8192,6 +8303,9 @@ static boolean P_MobjDeadThink(mobj_t *mobj) y = mobj->y + FixedMul(FINECOSINE(fa), ns); mo2 = P_SpawnMobj(x, y, z, MT_EXPLODE); + if (P_MobjWasRemoved(mo2)) + continue; + P_SetMobjStateNF(mo2, S_XPLD_EGGTRAP); // so the flickies don't lose their target if they spawn ns = 4*FRACUNIT; mo2->momx = FixedMul(FINESINE(fa), ns); @@ -8237,7 +8351,8 @@ static boolean P_MobjDeadThink(mobj_t *mobj) mobj->y + (P_RandomRange(r, -r) << FRACBITS), mobj->z + (P_RandomKey(mobj->height >> FRACBITS) << FRACBITS), MT_SONIC3KBOSSEXPLODE); - S_StartSound(explosion, sfx_s3kb4); + if (!P_MobjWasRemoved(explosion)) + S_StartSound(explosion, sfx_s3kb4); } if (mobj->movedir == DMG_DROWNED) P_SetObjectMomZ(mobj, -FRACUNIT/2, true); // slower fall from drowning @@ -8255,7 +8370,8 @@ static boolean P_MobjDeadThink(mobj_t *mobj) mobj->y + (P_RandomRange(r, -r) << FRACBITS), mobj->z + (P_RandomKey(mobj->height >> FRACBITS) << FRACBITS), MT_SONIC3KBOSSEXPLODE); - S_StartSound(explosion, sfx_s3kb4); + if (!P_MobjWasRemoved(explosion)) + S_StartSound(explosion, sfx_s3kb4); } P_SetObjectMomZ(mobj, -2*FRACUNIT/3, true); } @@ -8353,9 +8469,12 @@ static void P_ArrowThink(mobj_t *mobj) if (leveltime & 1) { mobj_t *dust = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_PARTICLE); - dust->tics = 18; - dust->scalespeed = 4096; - dust->destscale = FRACUNIT/32; + if (!P_MobjWasRemoved(dust)) + { + dust->tics = 18; + dust->scalespeed = 4096; + dust->destscale = FRACUNIT/32; + } } } else @@ -8958,6 +9077,9 @@ static boolean P_TurretThink(mobj_t *mobj) y = mobj->y + FixedMul(FINECOSINE(fa), ns); mo2 = P_SpawnMobj(x, y, z, MT_EXPLODE); + if (P_MobjWasRemoved(mo2)) + continue; + ns = FixedMul(16*FRACUNIT, mobj->scale); mo2->momx = FixedMul(FINESINE(fa), ns); mo2->momy = FixedMul(FINECOSINE(fa), ns); @@ -9179,10 +9301,13 @@ static void P_DragonbomberThink(mobj_t *mobj) if (segment != mobj) // found an unactivated segment? { mobj_t *mine = P_SpawnMobjFromMobj(segment, 0, 0, 0, segment->info->painchance); - mine->angle = segment->angle; - P_InstaThrust(mine, mobj->angle, P_AproxDistance(mobj->momx, mobj->momy) >> 1); - P_SetObjectMomZ(mine, -2*FRACUNIT, true); - S_StartSound(mine, mine->info->seesound); + if (!P_MobjWasRemoved(mine)) + { + mine->angle = segment->angle; + P_InstaThrust(mine, mobj->angle, P_AproxDistance(mobj->momx, mobj->momy) >> 1); + P_SetObjectMomZ(mine, -2*FRACUNIT, true); + S_StartSound(mine, mine->info->seesound); + } P_SetMobjState(segment, segment->info->raisestate); mobj->threshold = mobj->info->painchance; } @@ -9401,9 +9526,12 @@ static boolean P_MobjRegularThink(mobj_t *mobj) if (!mobj->threshold && !mobj->target && mobj->reactiontime) { mobj_t *emerald = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->reactiontime); - emerald->threshold = 42; - P_SetTarget(&mobj->target, emerald); - P_SetTarget(&emerald->target, mobj); + if (!P_MobjWasRemoved(emerald)) + { + emerald->threshold = 42; + P_SetTarget(&mobj->target, emerald); + P_SetTarget(&emerald->target, mobj); + } } } break; @@ -9745,6 +9873,8 @@ static boolean P_MobjRegularThink(mobj_t *mobj) case MT_TRAINDUSTSPAWNER: if (leveltime % 5 == 0) { mobj_t* traindust = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_PARTICLE); + if (P_MobjWasRemoved(traindust)) + break; traindust->flags = MF_SCENERY; P_SetMobjState(traindust, S_TRAINDUST); traindust->frame = P_RandomRange(0, 8)|FF_TRANS90; @@ -9758,6 +9888,8 @@ static boolean P_MobjRegularThink(mobj_t *mobj) case MT_TRAINSTEAMSPAWNER: if (leveltime % 5 == 0) { mobj_t *steam = P_SpawnMobj(mobj->x + FRACUNIT*P_SignedRandom()/2, mobj->y + FRACUNIT*P_SignedRandom()/2, mobj->z, MT_PARTICLE); + if (P_MobjWasRemoved(steam)) + break; P_SetMobjState(steam, S_TRAINSTEAM); steam->frame = P_RandomRange(0, 1)|FF_TRANS90; steam->tics = TICRATE*8; @@ -9959,7 +10091,8 @@ for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) s newmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->type); // Transfer flags2 (ambush, strongbox, objectflip) - newmobj->flags2 = mobj->flags2; + if (!P_MobjWasRemoved(newmobj)) + newmobj->flags2 = mobj->flags2; P_RemoveMobj(mobj); // make sure they disappear } @@ -9981,6 +10114,8 @@ static void P_FlagFuseThink(mobj_t *mobj) else z = ss->sector->floorheight + z; flagmo = P_SpawnMobj(x, y, z, mobj->type); + if (P_MobjWasRemoved(flagmo)) + return; flagmo->spawnpoint = mobj->spawnpoint; if (mobj->spawnpoint->options & MTF_OBJECTFLIP) { @@ -10418,12 +10553,15 @@ void P_PushableThinker(mobj_t *mobj) z = ss->sector->floorheight; spawnmo = P_SpawnMobj(x, y, z, mobj->type); - spawnmo->spawnpoint = mobj->spawnpoint; - P_UnsetThingPosition(spawnmo); - spawnmo->flags = mobj->flags; - P_SetThingPosition(spawnmo); - spawnmo->flags2 = mobj->flags2; - spawnmo->flags |= MF_PUSHABLE; + if (!P_MobjWasRemoved(spawnmo)) + { + spawnmo->spawnpoint = mobj->spawnpoint; + P_UnsetThingPosition(spawnmo); + spawnmo->flags = mobj->flags; + P_SetThingPosition(spawnmo); + spawnmo->flags2 = mobj->flags2; + spawnmo->flags |= MF_PUSHABLE; + } P_RemoveMobj(mobj); break; default: @@ -10575,27 +10713,17 @@ static fixed_t P_DefaultMobjShadowScale (mobj_t *thing) // // P_SpawnMobj // -mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) +mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type, ...) { const mobjinfo_t *info = &mobjinfo[type]; SINT8 sc = -1; state_t *st; mobj_t *mobj; + int status; + va_list args; if (type == MT_NULL) - { -#if 0 -#ifdef PARANOIA - I_Error("Tried to spawn MT_NULL\n"); -#endif return NULL; -#endif - // Hack: Some code assumes that P_SpawnMobj can never return NULL - // So replace MT_NULL with MT_RAY in the meantime - // Remove when dealt properly - CONS_Debug(DBG_GAMELOGIC, "Tried to spawn MT_NULL, using MT_RAY\n"); - type = MT_RAY; - } mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL); @@ -10687,9 +10815,27 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) // Set shadowscale here, before spawn hook so that Lua can change it mobj->shadowscale = P_DefaultMobjShadowScale(mobj); + if (!(mobj->flags & MF_NOTHINK)) + P_AddThinker(THINK_MOBJ, &mobj->thinker); + + + if (type == MT_PLAYER) + { + // when spawning MT_PLAYER, set mobj->player before calling MobjSpawn hook to prevent P_RemoveMobj from succeeding on player mobj. + va_start(args, type); + mobj->player = va_arg(args, player_t *); + va_end(args); + } + + // increment mobj reference, so we don't get a dangling reference in case MobjSpawn calls P_RemoveMobj + mobj->thinker.references++; + // DANGER! This can cause P_SpawnMobj to return NULL! // Avoid using P_RemoveMobj on the newly created mobj in "MobjSpawn" Lua hooks! - if (LUA_HookMobj(mobj, MOBJ_HOOK(MobjSpawn))) + status = LUA_HookMobj(mobj, MOBJ_HOOK(MobjSpawn)); + mobj->thinker.references--; + + if (status) { if (P_MobjWasRemoved(mobj)) return NULL; @@ -10711,6 +10857,9 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) case MT_BLACKEGGMAN: { mobj_t *spawn = P_SpawnMobj(mobj->x, mobj->z, mobj->z+mobj->height-16*FRACUNIT, MT_BLACKEGGMAN_HELPER); + if (P_MobjWasRemoved(spawn)) + break; + spawn->destscale = mobj->scale; P_SetScale(spawn, mobj->scale); P_SetTarget(&spawn->target, mobj); @@ -10726,6 +10875,9 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) case MT_EGGGUARD: { mobj_t *spawn = P_SpawnMobj(x, y, z, MT_EGGSHIELD); + if (P_MobjWasRemoved(spawn)) + break; + spawn->destscale = mobj->scale; P_SetScale(spawn, mobj->scale); P_SetTarget(&mobj->tracer, spawn); @@ -10741,6 +10893,9 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) for (i = 0; i < mobj->info->damage; i++) { ball = P_SpawnMobj(x, y, z, mobj->info->painchance); + if (P_MobjWasRemoved(ball)) + continue; + ball->destscale = mobj->scale; P_SetScale(ball, mobj->scale); P_SetTarget(&ball->target, mobj); @@ -10760,6 +10915,9 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) for (q = 0; q < mobj->info->painchance; q++) { ball = P_SpawnMobj(x, y, z, mobj->info->mass); + if (P_MobjWasRemoved(ball)) + continue; + ball->destscale = mobj->scale; P_SetScale(ball, mobj->scale); P_SetTarget(&lastball->tracer, ball); @@ -10771,18 +10929,24 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) case MT_CRUSHSTACEAN: { mobj_t *bigmeatyclaw = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_CRUSHCLAW); - bigmeatyclaw->angle = mobj->angle + ((mobj->flags2 & MF2_AMBUSH) ? ANGLE_90 : ANGLE_270);; - P_SetTarget(&mobj->tracer, bigmeatyclaw); - P_SetTarget(&bigmeatyclaw->tracer, mobj); + if (!P_MobjWasRemoved(bigmeatyclaw)) + { + bigmeatyclaw->angle = mobj->angle + ((mobj->flags2 & MF2_AMBUSH) ? ANGLE_90 : ANGLE_270); + P_SetTarget(&mobj->tracer, bigmeatyclaw); + P_SetTarget(&bigmeatyclaw->tracer, mobj); + } mobj->reactiontime >>= 1; } break; case MT_BANPYURA: { mobj_t *bigmeatyclaw = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_BANPSPRING); - bigmeatyclaw->angle = mobj->angle + ((mobj->flags2 & MF2_AMBUSH) ? ANGLE_90 : ANGLE_270);; - P_SetTarget(&mobj->tracer, bigmeatyclaw); - P_SetTarget(&bigmeatyclaw->tracer, mobj); + if (!P_MobjWasRemoved(bigmeatyclaw)) + { + bigmeatyclaw->angle = mobj->angle + ((mobj->flags2 & MF2_AMBUSH) ? ANGLE_90 : ANGLE_270); + P_SetTarget(&mobj->tracer, bigmeatyclaw); + P_SetTarget(&bigmeatyclaw->tracer, mobj); + } mobj->reactiontime >>= 1; } break; @@ -10797,6 +10961,9 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) for (i = 0; i <= 16; i++) // probably should be < but staying authentic to the Lua version { cur = P_SpawnMobjFromMobj(mobj, 0, 0, 0, ((mobj->type == MT_WAVINGFLAG1) ? MT_WAVINGFLAGSEG1 : MT_WAVINGFLAGSEG2));; + if (P_MobjWasRemoved(cur)) + continue; + P_SetTarget(&prev->tracer, cur); cur->extravalue1 = i; prev = cur; @@ -10834,11 +11001,17 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) { mobj_t *fire; fire = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_SPINBOBERT_FIRE1); - P_SetTarget(&fire->target, mobj); - P_SetTarget(&mobj->hnext, fire); + if (!P_MobjWasRemoved(fire)) + { + P_SetTarget(&fire->target, mobj); + P_SetTarget(&mobj->hnext, fire); + } fire = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_SPINBOBERT_FIRE2); - P_SetTarget(&fire->target, mobj); - P_SetTarget(&mobj->hprev, fire); + if (!P_MobjWasRemoved(fire)) + { + P_SetTarget(&fire->target, mobj); + P_SetTarget(&mobj->hprev, fire); + } } break; case MT_REDRING: // Make MT_REDRING red by default @@ -10855,8 +11028,8 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) case MT_EGGCAPSULE: mobj->reactiontime = 0; mobj->extravalue1 = mobj->cvmem =\ - mobj->cusval = mobj->movecount =\ - mobj->lastlook = mobj->extravalue2 = -1; + mobj->cusval = mobj->movecount =\ + mobj->lastlook = mobj->extravalue2 = -1; break; case MT_REDTEAMRING: mobj->color = skincolor_redteam; @@ -10892,6 +11065,8 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) case MT_OILLAMP: { mobj_t* overlay = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_OVERLAY); + if (P_MobjWasRemoved(overlay)) + break; P_SetTarget(&overlay->target, mobj); P_SetMobjState(overlay, S_OILLAMPFLARE); break; @@ -10901,13 +11076,19 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) mobj->flags2 |= MF2_INVERTAIMABLE; break; case MT_MINECARTEND: - P_SetTarget(&mobj->tracer, P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_MINECARTENDSOLID)); - mobj->tracer->angle = mobj->angle + ANGLE_90; + { + mobj_t *mcsolid = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_MINECARTENDSOLID); + if (P_MobjWasRemoved(mcsolid)) + break; + P_SetTarget(&mobj->tracer, mcsolid); + mcsolid->angle = mobj->angle + ANGLE_90; + } break; case MT_TORCHFLOWER: { mobj_t *fire = P_SpawnMobjFromMobj(mobj, 0, 0, 46*FRACUNIT, MT_FLAME); - P_SetTarget(&mobj->target, fire); + if (!P_MobjWasRemoved(fire)) + P_SetTarget(&mobj->target, fire); break; } case MT_PYREFLY: @@ -10916,10 +11097,16 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) mobj->fuse = 100; break; case MT_SIGN: - P_SetTarget(&mobj->tracer, P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_OVERLAY)); - P_SetTarget(&mobj->tracer->target, mobj); - P_SetMobjState(mobj->tracer, S_SIGNBOARD); - mobj->tracer->movedir = ANGLE_90; + { + mobj_t *sign = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_OVERLAY); + if (P_MobjWasRemoved(sign)) + break; + + P_SetTarget(&mobj->tracer, sign); + P_SetTarget(&sign->target, mobj); + P_SetMobjState(sign, S_SIGNBOARD); + sign->movedir = ANGLE_90; + } default: break; } @@ -10942,9 +11129,6 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) } } - if (!(mobj->flags & MF_NOTHINK)) - P_AddThinker(THINK_MOBJ, &mobj->thinker); - if (mobj->skin) // correct inadequecies above. { mobj->sprite2 = P_GetSkinSprite2(mobj->skin, (mobj->frame & FF_FRAMEMASK), NULL); @@ -11553,8 +11737,10 @@ void P_SpawnPlayer(INT32 playernum) if ((netgame || multiplayer) && ((gametyperules & GTR_SPAWNINVUL) || leveltime) && !p->spectator && !(maptol & TOL_NIGHTS)) p->powers[pw_flashing] = flashingtics-1; // Babysitting deterrent - mobj = P_SpawnMobj(0, 0, 0, MT_PLAYER); - (mobj->player = p)->mo = mobj; + // MT_PLAYER cannot be removed, so this shouldn't be able to return NULL. + mobj = P_SpawnMobj(0, 0, 0, MT_PLAYER, p); + I_Assert(mobj != NULL); + p->mo = mobj; mobj->angle = 0; @@ -11605,10 +11791,13 @@ void P_SpawnPlayer(INT32 playernum) if (p == players) // this is totally the wrong place to do this aaargh. { mobj_t *idya = P_SpawnMobjFromMobj(mobj, 0, 0, mobj->height, MT_GOTEMERALD); - idya->health = 0; // for identification - P_SetTarget(&idya->target, mobj); - P_SetMobjState(idya, mobjinfo[MT_GOTEMERALD].missilestate); - P_SetTarget(&mobj->tracer, idya); + if (!P_MobjWasRemoved(idya)) + { + idya->health = 0; // for identification + P_SetTarget(&idya->target, mobj); + P_SetMobjState(idya, mobjinfo[MT_GOTEMERALD].missilestate); + P_SetTarget(&mobj->tracer, idya); + } } } else if (sstimer) @@ -12357,8 +12546,10 @@ static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle) mphase = (FixedAngle(mphase << FRACBITS) >> ANGLETOFINESHIFT); mroll = (FixedAngle(mroll << FRACBITS) >> ANGLETOFINESHIFT); -#define makemace(mobjtype, dist, moreflags2) {\ +#define makemace(mobjtype, dist, moreflags2) do {\ spawnee = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobjtype);\ + if (P_MobjWasRemoved(spawnee))\ + break;\ P_SetTarget(&spawnee->tracer, mobj);\ spawnee->threshold = mphase;\ spawnee->friction = mroll;\ @@ -12371,7 +12562,7 @@ static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle) P_SetTarget(&hprev->hnext, spawnee);\ P_SetTarget(&spawnee->hprev, hprev);\ hprev = spawnee;\ -} +} while (0) mdosound = (mspeed && !(mthing->args[8] & TMM_SILENT)); mdocenter = (macetype && (mthing->args[8] & TMM_CENTERLINK)); @@ -12436,7 +12627,7 @@ static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle) if (!mwidth) { - if (mdosound && mnumspokes <= mmin) // Can it make a sound? + if (!P_MobjWasRemoved(spawnee) && mdosound && mnumspokes <= mmin) // Can it make a sound? spawnee->flags2 |= MF2_BOSSNOTRAP; } else @@ -12449,7 +12640,7 @@ static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle) while ((mwidthset -= widthfactor) > -mwidth) { makemace(firsttype, radiusfactor*mlengthset, MF2_AMBUSH); - if (mdosound && (mwidthset == msound) && mnumspokes <= mmin) // Can it make a sound? + if (!P_MobjWasRemoved(spawnee) && mdosound && (mwidthset == msound) && mnumspokes <= mmin) // Can it make a sound? spawnee->flags2 |= MF2_BOSSNOTRAP; } } @@ -12458,7 +12649,7 @@ static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle) while ((mwidthset += widthfactor) < -mwidth) { makemace(firsttype, radiusfactor*mlengthset, MF2_AMBUSH); - if (mdosound && (mwidthset == msound) && mnumspokes <= mmin) // Can it make a sound? + if (!P_MobjWasRemoved(spawnee) && mdosound && (mwidthset == msound) && mnumspokes <= mmin) // Can it make a sound? spawnee->flags2 |= MF2_BOSSNOTRAP; } } @@ -12640,15 +12831,21 @@ static boolean P_SetupNiGHTSDrone(mapthing_t *mthing, mobj_t *mobj) mobj_t *droneman = P_SpawnMobjFromMobj(mobj, 0, 0, dronemanoffset, MT_NIGHTSDRONE_MAN); P_SetTarget(&mobj->target, goalpost); - P_SetTarget(&goalpost->target, sparkle); - P_SetTarget(&goalpost->tracer, droneman); + if (!P_MobjWasRemoved(goalpost)) + { + P_SetTarget(&goalpost->target, sparkle); + P_SetTarget(&goalpost->tracer, droneman); + } // correct Z position if (flip) { - P_MoveOrigin(goalpost, goalpost->x, goalpost->y, mobj->z + goaloffset); - P_MoveOrigin(sparkle, sparkle->x, sparkle->y, mobj->z + sparkleoffset); - P_MoveOrigin(droneman, droneman->x, droneman->y, mobj->z + dronemanoffset); + if (!P_MobjWasRemoved(goalpost)) + P_MoveOrigin(goalpost, goalpost->x, goalpost->y, mobj->z + goaloffset); + if (!P_MobjWasRemoved(sparkle)) + P_MoveOrigin(sparkle, sparkle->x, sparkle->y, mobj->z + sparkleoffset); + if (!P_MobjWasRemoved(droneman)) + P_MoveOrigin(droneman, droneman->x, droneman->y, mobj->z + dronemanoffset); } // Remember position preference for later @@ -12670,9 +12867,12 @@ static boolean P_SetupNiGHTSDrone(mapthing_t *mthing, mobj_t *mobj) } // Remember old Z position and flags for correction detection - goalpost->movefactor = mobj->z; - goalpost->friction = mobj->height; - goalpost->threshold = mobj->flags & (MF_SLIDEME|MF_GRENADEBOUNCE); + if (!P_MobjWasRemoved(goalpost)) + { + goalpost->movefactor = mobj->z; + goalpost->friction = mobj->height; + goalpost->threshold = mobj->flags & (MF_SLIDEME|MF_GRENADEBOUNCE); + } } return true; } @@ -12690,30 +12890,54 @@ static boolean P_SetupBooster(mapthing_t* mthing, mobj_t* mobj, boolean strong) statenum_t rollerstate = strong ? S_REDBOOSTERROLLER : S_YELLOWBOOSTERROLLER; mobj_t *seg = P_SpawnMobjFromMobj(mobj, 26*x1, 26*y1, 0, MT_BOOSTERSEG); - seg->angle = angle - ANGLE_90; - P_SetMobjState(seg, facestate); + if (!P_MobjWasRemoved(seg)) + { + seg->angle = angle - ANGLE_90; + P_SetMobjState(seg, facestate); + } seg = P_SpawnMobjFromMobj(mobj, -26*x1, -26*y1, 0, MT_BOOSTERSEG); - seg->angle = angle + ANGLE_90; - P_SetMobjState(seg, facestate); + if (!P_MobjWasRemoved(seg)) + { + seg->angle = angle + ANGLE_90; + P_SetMobjState(seg, facestate); + } seg = P_SpawnMobjFromMobj(mobj, 21*x2, 21*y2, 0, MT_BOOSTERSEG); - seg->angle = angle; - P_SetMobjState(seg, leftstate); + if (!P_MobjWasRemoved(seg)) + { + seg->angle = angle; + P_SetMobjState(seg, leftstate); + } seg = P_SpawnMobjFromMobj(mobj, -21*x2, -21*y2, 0, MT_BOOSTERSEG); - seg->angle = angle; - P_SetMobjState(seg, rightstate); + if (!P_MobjWasRemoved(seg)) + { + seg->angle = angle; + P_SetMobjState(seg, rightstate); + } seg = P_SpawnMobjFromMobj(mobj, 13*(x1 + x2), 13*(y1 + y2), 0, MT_BOOSTERROLLER); - seg->angle = angle; - P_SetMobjState(seg, rollerstate); + if (!P_MobjWasRemoved(seg)) + { + seg->angle = angle; + P_SetMobjState(seg, rollerstate); + } seg = P_SpawnMobjFromMobj(mobj, 13*(x1 - x2), 13*(y1 - y2), 0, MT_BOOSTERROLLER); - seg->angle = angle; - P_SetMobjState(seg, rollerstate); + if (!P_MobjWasRemoved(seg)) + { + seg->angle = angle; + P_SetMobjState(seg, rollerstate); + } seg = P_SpawnMobjFromMobj(mobj, -13*(x1 + x2), -13*(y1 + y2), 0, MT_BOOSTERROLLER); - seg->angle = angle; - P_SetMobjState(seg, rollerstate); + if (!P_MobjWasRemoved(seg)) + { + seg->angle = angle; + P_SetMobjState(seg, rollerstate); + } seg = P_SpawnMobjFromMobj(mobj, -13*(x1 - x2), -13*(y1 - y2), 0, MT_BOOSTERROLLER); - seg->angle = angle; - P_SetMobjState(seg, rollerstate); + if (!P_MobjWasRemoved(seg)) + { + seg->angle = angle; + P_SetMobjState(seg, rollerstate); + } return true; } @@ -12721,6 +12945,9 @@ static boolean P_SetupBooster(mapthing_t* mthing, mobj_t* mobj, boolean strong) static mobj_t *P_MakeSoftwareCorona(mobj_t *mo, INT32 height) { mobj_t *corona = P_SpawnMobjFromMobj(mo, 0, 0, height<sprite = SPR_FLAM; corona->frame = (FF_FULLBRIGHT|FF_TRANS90|12); corona->tics = -1; @@ -12856,6 +13083,9 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean if (mthing->args[0]) { mobj_t *corona = P_MakeSoftwareCorona(mobj, 20); + if (P_MobjWasRemoved(corona)) + break; + P_SetScale(corona, (corona->destscale = mobj->scale*3)); P_SetTarget(&mobj->tracer, corona); } @@ -12864,11 +13094,17 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean if (!(mthing->args[0] & TMFH_NOFLAME)) // Spawn the fire { mobj_t *flame = P_SpawnMobjFromMobj(mobj, 0, 0, mobj->height, MT_FLAME); + if (P_MobjWasRemoved(flame)) + break; + P_SetTarget(&flame->target, mobj); flame->flags2 |= MF2_BOSSNOTRAP; if (mthing->args[0] & TMFH_CORONA) { mobj_t *corona = P_MakeSoftwareCorona(flame, 20); + if (P_MobjWasRemoved(corona)) + break; + P_SetScale(corona, (corona->destscale = flame->scale*3)); P_SetTarget(&flame->tracer, corona); } @@ -12885,6 +13121,8 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean if (!(mthing->args[0])) // take the torch out of the crafting recipe { mobj_t *overlay = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_OVERLAY); + if (P_MobjWasRemoved(overlay)) + break; P_SetTarget(&overlay->target, mobj); P_SetMobjState(overlay, mobj->info->raisestate); } @@ -12964,9 +13202,15 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean case MT_THZTREE: { // Spawn the branches angle_t mobjangle = FixedAngle((mthing->angle % 113) << FRACBITS); - P_SpawnMobjFromMobj(mobj, FRACUNIT, 0, 0, MT_THZTREEBRANCH)->angle = mobjangle + ANGLE_22h; - P_SpawnMobjFromMobj(mobj, 0, FRACUNIT, 0, MT_THZTREEBRANCH)->angle = mobjangle + ANGLE_157h; - P_SpawnMobjFromMobj(mobj, -FRACUNIT, 0, 0, MT_THZTREEBRANCH)->angle = mobjangle + ANGLE_270; + mobj_t *branch = P_SpawnMobjFromMobj(mobj, FRACUNIT, 0, 0, MT_THZTREEBRANCH); + if (!P_MobjWasRemoved(branch)) + branch->angle = mobjangle + ANGLE_22h; + branch = P_SpawnMobjFromMobj(mobj, 0, FRACUNIT, 0, MT_THZTREEBRANCH); + if (!P_MobjWasRemoved(branch)) + branch->angle = mobjangle + ANGLE_157h; + branch = P_SpawnMobjFromMobj(mobj, -FRACUNIT, 0, 0, MT_THZTREEBRANCH); + if (!P_MobjWasRemoved(branch)) + branch->angle = mobjangle + ANGLE_270; } break; case MT_TUTORIALPLANT: @@ -12976,26 +13220,34 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean for (i = 0; i < 6; i++) { segment = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_TUTORIALLEAF); + if (P_MobjWasRemoved(segment)) + continue; segment->angle = mobj->angle + FixedAngle(i*60*FRACUNIT); P_SetMobjState(segment, S_TUTORIALLEAF1 + mthing->args[0]); } for (i = 0; i < 3; i++) { segment = P_SpawnMobjFromMobj(mobj, 0, 0, 112*FRACUNIT, MT_TUTORIALFLOWER); + if (P_MobjWasRemoved(segment)) + continue; segment->angle = mobj->angle + FixedAngle(i*120*FRACUNIT); P_SetMobjState(segment, S_TUTORIALFLOWER1 + mthing->args[0]); } - P_SetMobjState(P_SpawnMobjFromMobj(mobj, 0, 0, 112*FRACUNIT, MT_TUTORIALFLOWERF), S_TUTORIALFLOWERF1 + mthing->args[0]); + segment = P_SpawnMobjFromMobj(mobj, 0, 0, 112*FRACUNIT, MT_TUTORIALFLOWERF); + if (!P_MobjWasRemoved(segment)) + P_SetMobjState(segment, S_TUTORIALFLOWERF1 + mthing->args[0]); } break; case MT_CEZPOLE1: case MT_CEZPOLE2: { // Spawn the banner angle_t mobjangle = FixedAngle(mthing->angle << FRACBITS); - P_SpawnMobjFromMobj(mobj, + mobj_t *banner = P_SpawnMobjFromMobj(mobj, P_ReturnThrustX(mobj, mobjangle, 4 << FRACBITS), P_ReturnThrustY(mobj, mobjangle, 4 << FRACBITS), - 0, ((mobj->type == MT_CEZPOLE1) ? MT_CEZBANNER1 : MT_CEZBANNER2))->angle = mobjangle + ANGLE_90; + 0, ((mobj->type == MT_CEZPOLE1) ? MT_CEZBANNER1 : MT_CEZBANNER2)); + if (!P_MobjWasRemoved(banner)) + banner->angle = mobjangle + ANGLE_90; } break; case MT_HHZTREE_TOP: @@ -13004,8 +13256,11 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean mobj_t* leaf; #define doleaf(x, y) \ leaf = P_SpawnMobjFromMobj(mobj, x, y, 0, MT_HHZTREE_PART);\ - leaf->angle = mobjangle;\ - P_SetMobjState(leaf, leaf->info->seestate);\ + if (!P_MobjWasRemoved(leaf))\ + {\ + leaf->angle = mobjangle;\ + P_SetMobjState(leaf, leaf->info->seestate);\ + }\ mobjangle += ANGLE_90 doleaf(FRACUNIT, 0); doleaf(0, FRACUNIT); @@ -13044,8 +13299,9 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean angle_t fa = (angle >> ANGLETOFINESHIFT) & FINEMASK; fixed_t xoffs = FINECOSINE(fa); fixed_t yoffs = FINESINE(fa); - mobj_t* leaf = P_SpawnMobjFromMobj(mobj, xoffs, yoffs, 0, MT_BIGFERNLEAF); - leaf->angle = angle; + mobj_t *leaf = P_SpawnMobjFromMobj(mobj, xoffs, yoffs, 0, MT_BIGFERNLEAF); + if (!P_MobjWasRemoved(leaf)) + leaf->angle = angle; angle += ANGLE_45; } break; @@ -13082,6 +13338,8 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean { mobj_t* elecmobj; elecmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_CYBRAKDEMON_ELECTRIC_BARRIER); + if (P_MobjWasRemoved(elecmobj)) + break; P_SetTarget(&elecmobj->target, mobj); elecmobj->angle = FixedAngle(mthing->angle << FRACBITS); elecmobj->destscale = mobj->scale*2; @@ -13136,6 +13394,12 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean mobj->x - P_ReturnThrustX(mobj, mobjangle, baseradius), mobj->y - P_ReturnThrustY(mobj, mobjangle, baseradius), mobj->z, MT_WALLSPIKEBASE); + if (P_MobjWasRemoved(base)) + { + // if we can't spawn the base, don't spawn the spike at all. + P_RemoveMobj(mobj); + return false; + } base->angle = mobjangle + ANGLE_90; base->destscale = mobj->destscale; P_SetScale(base, mobj->scale); @@ -13311,6 +13575,8 @@ static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y, boolean doangle = true; mobj = P_SpawnMobj(x, y, z, i); + if (mobj == NULL) + return NULL; mobj->spawnpoint = mthing; P_SetScale(mobj, FixedMul(mobj->scale, mthing->scale)); @@ -13405,6 +13671,9 @@ void P_SpawnHoop(mapthing_t *mthing) fixed_t z = P_GetMobjSpawnHeight(MT_HOOP, x, y, mthing->z << FRACBITS, 0, false, mthing->scale); hoopcenter = P_SpawnMobj(x, y, z, MT_HOOPCENTER); + if (P_MobjWasRemoved(hoopcenter)) + return; + hoopcenter->spawnpoint = mthing; hoopcenter->z -= hoopcenter->height/2; @@ -13435,6 +13704,8 @@ void P_SpawnHoop(mapthing_t *mthing) FV4_Copy(&v, FM_MultMatrixVec4(&yawmatrix, &v, &res)); mobj = P_SpawnMobj(x + v.x, y + v.y, z + v.z, MT_HOOP); + if (P_MobjWasRemoved(mobj)) + continue; mobj->z -= mobj->height/2; if (maptol & TOL_XMAS) @@ -13479,6 +13750,8 @@ void P_SpawnHoop(mapthing_t *mthing) FV4_Copy(&v, FM_MultMatrixVec4(&yawmatrix, &v, &res)); mobj = P_SpawnMobj(x + v.x, y + v.y, z + v.z, MT_HOOPCOLLIDE); + if (P_MobjWasRemoved(mobj)) + continue; mobj->z -= mobj->height/2; // Link all the collision sprites together. @@ -13752,6 +14025,8 @@ mobj_t *P_SpawnXYZMissile(mobj_t *source, mobj_t *dest, mobjtype_t type, z -= FixedMul(mobjinfo[type].height, source->scale); th = P_SpawnMobj(x, y, z, type); + if (P_MobjWasRemoved(th)) + return NULL; if (source->eflags & MFE_VERTICALFLIP) th->flags2 |= MF2_OBJECTFLIP; @@ -13814,6 +14089,8 @@ mobj_t *P_SpawnAlteredDirectionMissile(mobj_t *source, mobjtype_t type, fixed_t z -= FixedMul(mobjinfo[type].height, source->scale); th = P_SpawnMobj(x, y, z, type); + if (P_MobjWasRemoved(th)) + return NULL; if (source->eflags & MFE_VERTICALFLIP) th->flags2 |= MF2_OBJECTFLIP; @@ -13879,6 +14156,8 @@ mobj_t *P_SpawnPointMissile(mobj_t *source, fixed_t xa, fixed_t ya, fixed_t za, z -= FixedMul(mobjinfo[type].height, source->scale); th = P_SpawnMobj(x, y, z, type); + if (P_MobjWasRemoved(th)) + return NULL; if (source->eflags & MFE_VERTICALFLIP) th->flags2 |= MF2_OBJECTFLIP; @@ -13949,6 +14228,8 @@ mobj_t *P_SpawnMissile(mobj_t *source, mobj_t *dest, mobjtype_t type) z -= FixedMul(mobjinfo[type].height, source->scale); th = P_SpawnMobj(source->x, source->y, z, type); + if (P_MobjWasRemoved(th)) + return NULL; if (source->eflags & MFE_VERTICALFLIP) th->flags2 |= MF2_OBJECTFLIP; @@ -14050,6 +14331,8 @@ mobj_t *P_SPMAngle(mobj_t *source, mobjtype_t type, angle_t angle, UINT8 allowai z = source->z + source->height/3; th = P_SpawnMobj(x, y, z, type); + if (P_MobjWasRemoved(th)) + return NULL; if (source->eflags & MFE_VERTICALFLIP) th->flags2 |= MF2_OBJECTFLIP; diff --git a/src/p_polyobj.c b/src/p_polyobj.c index b207bb740..331bc5c7f 100644 --- a/src/p_polyobj.c +++ b/src/p_polyobj.c @@ -1243,6 +1243,8 @@ boolean Polyobj_rotate(polyobj_t *po, angle_t delta, boolean turnplayers, boolea // Returns NULL if no such polyobject exists. polyobj_t *Polyobj_GetForNum(INT32 id) { + if (numPolyObjects == 0) + return NULL; INT32 curidx = PolyObjects[id % numPolyObjects].first; while (curidx != numPolyObjects && PolyObjects[curidx].id != id) diff --git a/src/p_user.c b/src/p_user.c index d441a7e81..658a35711 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -394,6 +394,8 @@ void P_GiveFinishFlags(player_t *player) fixed_t xoffs = FINECOSINE(fa); fixed_t yoffs = FINESINE(fa); mobj_t* flag = P_SpawnMobjFromMobj(player->mo, xoffs, yoffs, 0, MT_FINISHFLAG); + if (P_MobjWasRemoved(flag)) + continue; flag->angle = angle; angle += FixedAngle(120*FRACUNIT); @@ -1826,6 +1828,9 @@ void P_SpawnShieldOrb(player_t *player) } shieldobj = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, orbtype); + if (P_MobjWasRemoved(shieldobj)) + return; + shieldobj->flags2 |= MF2_SHIELD; P_SetTarget(&shieldobj->target, player->mo); if ((player->powers[pw_shield] & SH_NOSTACK) == SH_PINK) @@ -1840,21 +1845,30 @@ void P_SpawnShieldOrb(player_t *player) if (shieldobj->info->seestate) { ov = P_SpawnMobj(shieldobj->x, shieldobj->y, shieldobj->z, MT_OVERLAY); - P_SetTarget(&ov->target, shieldobj); - P_SetMobjState(ov, shieldobj->info->seestate); - P_SetTarget(&shieldobj->tracer, ov); + if (!P_MobjWasRemoved(ov)) + { + P_SetTarget(&ov->target, shieldobj); + P_SetMobjState(ov, shieldobj->info->seestate); + P_SetTarget(&shieldobj->tracer, ov); + } } if (shieldobj->info->meleestate) { ov = P_SpawnMobj(shieldobj->x, shieldobj->y, shieldobj->z, MT_OVERLAY); - P_SetTarget(&ov->target, shieldobj); - P_SetMobjState(ov, shieldobj->info->meleestate); + if (!P_MobjWasRemoved(ov)) + { + P_SetTarget(&ov->target, shieldobj); + P_SetMobjState(ov, shieldobj->info->meleestate); + } } if (shieldobj->info->missilestate) { ov = P_SpawnMobj(shieldobj->x, shieldobj->y, shieldobj->z, MT_OVERLAY); - P_SetTarget(&ov->target, shieldobj); - P_SetMobjState(ov, shieldobj->info->missilestate); + if (!P_MobjWasRemoved(ov)) + { + P_SetTarget(&ov->target, shieldobj); + P_SetMobjState(ov, shieldobj->info->missilestate); + } } if (player->powers[pw_shield] & SH_FORCE) { @@ -1950,6 +1964,8 @@ void P_SetPower(player_t *player, powertype_t power, UINT16 value) mobj_t *P_SpawnGhostMobj(mobj_t *mobj) { mobj_t *ghost = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_GHOST); + if (P_MobjWasRemoved(ghost)) + return NULL; P_SetTarget(&ghost->target, mobj); @@ -1992,9 +2008,12 @@ mobj_t *P_SpawnGhostMobj(mobj_t *mobj) if (mobj->player && mobj->player->followmobj) { mobj_t *ghost2 = P_SpawnGhostMobj(mobj->player->followmobj); - P_SetTarget(&ghost2->tracer, ghost); - P_SetTarget(&ghost->tracer, ghost2); - ghost2->flags2 |= (mobj->player->followmobj->flags2 & MF2_LINKDRAW); + if (!P_MobjWasRemoved(ghost2)) + { + P_SetTarget(&ghost2->tracer, ghost); + P_SetTarget(&ghost->tracer, ghost2); + ghost2->flags2 |= (mobj->player->followmobj->flags2 & MF2_LINKDRAW); + } } // Copy interpolation data :) @@ -2043,6 +2062,8 @@ void P_SpawnThokMobj(player_t *player) zheight = player->mo->ceilingz - FixedMul(mobjinfo[type].height, player->mo->scale); mobj = P_SpawnMobj(player->mo->x, player->mo->y, zheight, type); + if (P_MobjWasRemoved(mobj)) + return; // set to player's angle, just in case mobj->angle = player->drawangle; @@ -2066,7 +2087,8 @@ void P_SpawnThokMobj(player_t *player) } } - P_SetTarget(&mobj->target, player->mo); // the one thing P_SpawnGhostMobj doesn't do + if (!P_MobjWasRemoved(mobj)) + P_SetTarget(&mobj->target, player->mo); // the one thing P_SpawnGhostMobj doesn't do G_GhostAddThok(); } @@ -2104,6 +2126,8 @@ void P_SpawnSpinMobj(player_t *player, mobjtype_t type) zheight = player->mo->ceilingz - FixedMul(mobjinfo[type].height, player->mo->scale); mobj = P_SpawnMobj(player->mo->x, player->mo->y, zheight, type); + if (P_MobjWasRemoved(mobj)) + return; // set to player's angle, just in case mobj->angle = player->drawangle; @@ -2128,7 +2152,8 @@ void P_SpawnSpinMobj(player_t *player, mobjtype_t type) } } - P_SetTarget(&mobj->target, player->mo); // the one thing P_SpawnGhostMobj doesn't do + if (!P_MobjWasRemoved(mobj)) + P_SetTarget(&mobj->target, player->mo); // the one thing P_SpawnGhostMobj doesn't do } /** Called when \p player finishes the level. @@ -2352,15 +2377,18 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff) while (i < 5) { missile = P_SpawnMobjFromMobj(player->mo, xo, yo, zo, type); - P_SetTarget(&missile->target, player->mo); - missile->angle = throwang + player->drawangle; - P_Thrust(missile, player->drawangle + ANGLE_90, - P_ReturnThrustY(missile, throwang, mu)); // side to side component - P_Thrust(missile, player->drawangle, mu2); // forward component - P_SetObjectMomZ(missile, (4 + ((i&1)<<1))*FRACUNIT, true); - missile->momz += player->mo->pmomz; - missile->fuse = TICRATE/2; - missile->extravalue2 = ev; + if (!P_MobjWasRemoved(missile)) + { + P_SetTarget(&missile->target, player->mo); + missile->angle = throwang + player->drawangle; + P_Thrust(missile, player->drawangle + ANGLE_90, + P_ReturnThrustY(missile, throwang, mu)); // side to side component + P_Thrust(missile, player->drawangle, mu2); // forward component + P_SetObjectMomZ(missile, (4 + ((i&1)<<1))*FRACUNIT, true); + missile->momz += player->mo->pmomz; + missile->fuse = TICRATE/2; + missile->extravalue2 = ev; + } i++; throwang += ANG30; @@ -2912,24 +2940,29 @@ static void P_CheckUnderwaterAndSpaceTimer(player_t *player) : player->mo->z + player->mo->height + FixedMul(8*FRACUNIT, FixedMul(player->mo->scale, player->shieldscale)); mobj_t *numbermobj = P_SpawnMobj(player->mo->x, player->mo->y, height, MT_DROWNNUMBERS); - - timeleft /= (2*TICRATE); // To be strictly accurate it'd need to be ((timeleft/TICRATE) - 1)/2, but integer division rounds down for us - - if (player->charflags & SF_MACHINE) + if (!P_MobjWasRemoved(numbermobj)) { - S_StartSound(player->mo, sfx_buzz1); - timeleft += 6; + timeleft /= (2*TICRATE); // To be strictly accurate it'd need to be ((timeleft/TICRATE) - 1)/2, but integer division rounds down for us + + if (player->charflags & SF_MACHINE) + { + S_StartSound(player->mo, sfx_buzz1); + timeleft += 6; + } + else + S_StartSound(player->mo, sfx_dwnind); + + if (!P_MobjWasRemoved(numbermobj)) + { + if (timeleft) // Don't waste time setting the state if the time is 0. + P_SetMobjState(numbermobj, numbermobj->info->spawnstate+timeleft); + + P_SetTarget(&numbermobj->target, player->mo); + numbermobj->threshold = 40; + numbermobj->destscale = player->mo->scale; + P_SetScale(numbermobj, player->mo->scale); + } } - else - S_StartSound(player->mo, sfx_dwnind); - - if (timeleft) // Don't waste time setting the state if the time is 0. - P_SetMobjState(numbermobj, numbermobj->info->spawnstate+timeleft); - - P_SetTarget(&numbermobj->target, player->mo); - numbermobj->threshold = 40; - numbermobj->destscale = player->mo->scale; - P_SetScale(numbermobj, player->mo->scale); } // Underwater timer runs out else if (timeleft == 1) @@ -2988,8 +3021,11 @@ static void P_CheckInvincibilityTimer(player_t *player) else if (leveltime % (TICRATE/7) == 0) { mobj_t *sparkle = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_IVSP); - sparkle->destscale = player->mo->scale; - P_SetScale(sparkle, player->mo->scale); + if (!P_MobjWasRemoved(sparkle)) + { + sparkle->destscale = player->mo->scale; + P_SetScale(sparkle, player->mo->scale); + } } // Resume normal music stuff. @@ -3044,7 +3080,8 @@ static void P_DoBubbleBreath(player_t *player) y += (P_RandomRange(r, -r)<mo->height>>FRACBITS)<mo->x + stirwaterx, player->mo->y + stirwatery, stirwaterz, MT_SMALLBUBBLE); - bubble->destscale = player->mo->scale; - P_SetScale(bubble,bubble->destscale); + if (!P_MobjWasRemoved(bubble)) + { + bubble->destscale = player->mo->scale; + P_SetScale(bubble,bubble->destscale); + } bubble = P_SpawnMobj( player->mo->x - stirwaterx, player->mo->y - stirwatery, stirwaterz, MT_SMALLBUBBLE); - bubble->destscale = player->mo->scale; - P_SetScale(bubble,bubble->destscale); + if (!P_MobjWasRemoved(bubble)) + { + bubble->destscale = player->mo->scale; + P_SetScale(bubble,bubble->destscale); + } } } @@ -3110,22 +3153,25 @@ static void P_DoPlayerHeadSigns(player_t *player) if (player->pflags & PF_TAGIT && !P_IsLocalPlayer(player)) { mobj_t* it = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_TAG); - it->x = player->mo->x; - it->y = player->mo->y; - it->z = player->mo->z; - it->old_x = player->mo->old_x; - it->old_y = player->mo->old_y; - it->old_z = player->mo->old_z; + if (!P_MobjWasRemoved(it)) + { + it->x = player->mo->x; + it->y = player->mo->y; + it->z = player->mo->z; + it->old_x = player->mo->old_x; + it->old_y = player->mo->old_y; + it->old_z = player->mo->old_z; - if (!(player->mo->eflags & MFE_VERTICALFLIP)) - { - it->z += player->mo->height; - it->old_z += player->mo->height; - } - else - { - it->z -= mobjinfo[MT_TAG].height; - it->old_z -= mobjinfo[MT_TAG].height; + if (!(player->mo->eflags & MFE_VERTICALFLIP)) + { + it->z += player->mo->height; + it->old_z += player->mo->height; + } + else + { + it->z -= mobjinfo[MT_TAG].height; + it->old_z -= mobjinfo[MT_TAG].height; + } } } } @@ -3150,22 +3196,25 @@ static void P_DoPlayerHeadSigns(player_t *player) } sign = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_GOTFLAG); - sign->x = player->mo->x; - sign->y = player->mo->y; - sign->z = player->mo->z + zofs; - sign->old_x = player->mo->old_x; - sign->old_y = player->mo->old_y; - sign->old_z = player->mo->old_z + zofs; - - if (player_is_flipped) + if (!P_MobjWasRemoved(sign)) { - sign->eflags |= MFE_VERTICALFLIP; - } + sign->x = player->mo->x; + sign->y = player->mo->y; + sign->z = player->mo->z + zofs; + sign->old_x = player->mo->old_x; + sign->old_y = player->mo->old_y; + sign->old_z = player->mo->old_z + zofs; - if (player->gotflag & GF_REDFLAG) - sign->frame = 1|FF_FULLBRIGHT; - else //if (player->gotflag & GF_BLUEFLAG) - sign->frame = 2|FF_FULLBRIGHT; + if (player_is_flipped) + { + sign->eflags |= MFE_VERTICALFLIP; + } + + if (player->gotflag & GF_REDFLAG) + sign->frame = 1|FF_FULLBRIGHT; + else //if (player->gotflag & GF_BLUEFLAG) + sign->frame = 2|FF_FULLBRIGHT; + } } } } @@ -4270,8 +4319,11 @@ static void P_DoSuperStuff(player_t *player) && !(leveltime % TICRATE) && (player->mo->momx || player->mo->momy)) { spark = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SUPERSPARK); - spark->destscale = player->mo->scale; - P_SetScale(spark, player->mo->scale); + if (!P_MobjWasRemoved(spark)) + { + spark->destscale = player->mo->scale; + P_SetScale(spark, player->mo->scale); + } } // Ran out of rings while super! @@ -4544,6 +4596,8 @@ static void P_DoSpinDashDust(player_t *player) INT32 prandom[3]; for (i = 0; i <= (leveltime%7)/2; i++) { // 1, 2, 3 or 4 particles particle = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_SPINDUST); + if (P_MobjWasRemoved(particle)) + return; if (player->mo->eflags & (MFE_TOUCHWATER|MFE_UNDERWATER)) // overrides fire version P_SetMobjState(particle, S_SPINDUST_BUBBLE1); @@ -4691,7 +4745,8 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd) if (P_IsLocalPlayer(player)) // Only display it on your own view. { mobj_t *visual = P_SpawnMobj(lockon->x, lockon->y, lockon->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker - P_SetTarget(&visual->target, lockon); + if (!P_MobjWasRemoved(visual)) + P_SetTarget(&visual->target, lockon); } } if ((cmd->buttons & BT_SPIN) && !(player->pflags & PF_SPINDOWN)) @@ -4832,10 +4887,13 @@ void P_DoJumpShield(player_t *player) for (i = 0; i < numangles; i++) { spark = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_THUNDERCOIN_SPARK); - P_InstaThrust(spark, travelangle + i*(ANGLE_MAX/numangles), FixedMul(4*FRACUNIT, spark->scale)); - if (i % 2) - P_SetObjectMomZ(spark, -4*FRACUNIT, false); - spark->fuse = 18; + if (!P_MobjWasRemoved(spark)) + { + P_InstaThrust(spark, travelangle + i*(ANGLE_MAX/numangles), FixedMul(4*FRACUNIT, spark->scale)); + if (i % 2) + P_SetObjectMomZ(spark, -4*FRACUNIT, false); + spark->fuse = 18; + } } #undef limitangle #undef numangles @@ -4942,14 +5000,17 @@ void P_TwinSpinRejuvenate(player_t *player, mobjtype_t type) yo, player->mo->height/2 + zo, type); - P_SetTarget(&missile->target, player->mo); - P_SetScale(missile, (missile->destscale >>= 1)); - missile->angle = ang + movang; - missile->fuse = TICRATE/2; - missile->extravalue2 = (99*FRACUNIT)/100; - missile->momx = xo; - missile->momy = yo; - missile->momz = zo; + if (!P_MobjWasRemoved(missile)) + { + P_SetTarget(&missile->target, player->mo); + P_SetScale(missile, (missile->destscale >>= 1)); + missile->angle = ang + movang; + missile->fuse = TICRATE/2; + missile->extravalue2 = (99*FRACUNIT)/100; + missile->momx = xo; + missile->momy = yo; + missile->momz = zo; + } ang += ANGLE_45; } @@ -5046,8 +5107,11 @@ static boolean P_PlayerShieldThink(player_t *player, ticcmd_t *cmd, mobj_t *lock if (dovis) { visual = P_SpawnMobj(lockonshield->x, lockonshield->y, lockonshield->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker - P_SetTarget(&visual->target, lockonshield); - P_SetMobjStateNF(visual, visual->info->spawnstate+1); + if (!P_MobjWasRemoved(visual)) + { + P_SetTarget(&visual->target, lockonshield); + P_SetMobjStateNF(visual, visual->info->spawnstate+1); + } } } } @@ -5153,7 +5217,8 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd) if (P_IsLocalPlayer(player)) // Only display it on your own view. { visual = P_SpawnMobj(lockonthok->x, lockonthok->y, lockonthok->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker - P_SetTarget(&visual->target, lockonthok); + if (!P_MobjWasRemoved(visual)) + P_SetTarget(&visual->target, lockonthok); } } @@ -6848,10 +6913,14 @@ static void P_DoNiGHTSCapsule(player_t *player) // Spawn a 'pop' for every 2 tics if (!((tictimer - firstpoptic) % 2)) - S_StartSound(P_SpawnMobj(player->capsule->x + ((P_SignedRandom()/2)<capsule->y + ((P_SignedRandom()/2)<capsule->z + (player->capsule->height/2) + ((P_SignedRandom()/2)<capsule->x + ((P_SignedRandom()/2)<capsule->y + ((P_SignedRandom()/2)<capsule->z + (player->capsule->height/2) + ((P_SignedRandom()/2)<mo, 0, 0, player->mo->height, MT_GOTEMERALD); - emmo->health = em; // for identification - P_SetTarget(&emmo->target, player->mo); - P_SetMobjState(emmo, mobjinfo[MT_GOTEMERALD].meleestate + em); - P_SetTarget(&player->mo->tracer, emmo); + if (!P_MobjWasRemoved(emmo)) + { + emmo->health = em; // for identification + P_SetTarget(&emmo->target, player->mo); + P_SetMobjState(emmo, mobjinfo[MT_GOTEMERALD].meleestate + em); + P_SetTarget(&player->mo->tracer, emmo); + } } // Okay, we're doing this down here because we're handling time weirdly for co-op special stages @@ -6934,19 +7006,22 @@ static void P_DoNiGHTSCapsule(player_t *player) P_InstaThrust(flicky, flicky->angle, 8*FRACUNIT); }*/ mobj_t *idya = P_SpawnMobjFromMobj(player->mo, 0, 0, player->mo->height, MT_GOTEMERALD); - idya->extravalue2 = player->mare/5; - idya->health = player->mare + 1; // for identification - P_SetTarget(&idya->target, player->mo); - P_SetMobjState(idya, mobjinfo[MT_GOTEMERALD].missilestate + ((player->mare + 1) % 5)); - - if (player->mo->tracer) + if (!P_MobjWasRemoved(idya)) { - P_SetTarget(&idya->hnext, player->mo->tracer); - idya->extravalue1 = (angle_t)(player->mo->tracer->extravalue1 - 72*ANG1); - if (idya->extravalue1 > player->mo->tracer->extravalue1) - idya->extravalue1 -= (72*ANG1)/idya->extravalue1; + idya->extravalue2 = player->mare/5; + idya->health = player->mare + 1; // for identification + P_SetTarget(&idya->target, player->mo); + P_SetMobjState(idya, mobjinfo[MT_GOTEMERALD].missilestate + ((player->mare + 1) % 5)); + + if (player->mo->tracer) + { + P_SetTarget(&idya->hnext, player->mo->tracer); + idya->extravalue1 = (angle_t)(player->mo->tracer->extravalue1 - 72*ANG1); + if (idya->extravalue1 > player->mo->tracer->extravalue1) + idya->extravalue1 -= (72*ANG1)/idya->extravalue1; + } + P_SetTarget(&player->mo->tracer, idya); } - P_SetTarget(&player->mo->tracer, idya); } for (i = 0; i < MAXPLAYERS; i++) if (playeringame[i] && players[i].mare == player->mare) @@ -7271,19 +7346,25 @@ static void P_NiGHTSMovement(player_t *player) z -= FixedMul(mobjinfo[MT_NIGHTSPARKLE].height, player->mo->scale); firstmobj = P_SpawnMobj(player->mo->x + P_ReturnThrustX(player->mo, player->mo->angle+ANGLE_90, spawndist), player->mo->y + P_ReturnThrustY(player->mo, player->mo->angle+ANGLE_90, spawndist), z, MT_NIGHTSPARKLE); - secondmobj = P_SpawnMobj(player->mo->x + P_ReturnThrustX(player->mo, player->mo->angle-ANGLE_90, spawndist), player->mo->y + P_ReturnThrustY(player->mo, player->mo->angle-ANGLE_90, spawndist), z, MT_NIGHTSPARKLE); - - firstmobj->destscale = secondmobj->destscale = player->mo->scale; - P_SetTarget(&firstmobj->target, player->mo); - P_SetScale(firstmobj, player->mo->scale); - P_SetTarget(&secondmobj->target, player->mo); - P_SetScale(secondmobj, player->mo->scale); - - // Superloop turns sparkles red - if (player->powers[pw_nights_superloop]) + if (!P_MobjWasRemoved(firstmobj)) { - P_SetMobjState(firstmobj, mobjinfo[MT_NIGHTSPARKLE].seestate); - P_SetMobjState(secondmobj, mobjinfo[MT_NIGHTSPARKLE].seestate); + firstmobj->destscale = player->mo->scale; + P_SetTarget(&firstmobj->target, player->mo); + P_SetScale(firstmobj, player->mo->scale); + // Superloop turns sparkles red + if (player->powers[pw_nights_superloop]) + P_SetMobjState(firstmobj, mobjinfo[MT_NIGHTSPARKLE].seestate); + } + secondmobj = P_SpawnMobj(player->mo->x + P_ReturnThrustX(player->mo, player->mo->angle-ANGLE_90, spawndist), player->mo->y + P_ReturnThrustY(player->mo, player->mo->angle-ANGLE_90, spawndist), z, MT_NIGHTSPARKLE); + if (!P_MobjWasRemoved(secondmobj)) + { + secondmobj->destscale = player->mo->scale; + P_SetTarget(&secondmobj->target, player->mo); + P_SetScale(secondmobj, player->mo->scale); + + // Superloop turns sparkles red + if (player->powers[pw_nights_superloop]) + P_SetMobjState(secondmobj, mobjinfo[MT_NIGHTSPARKLE].seestate); } } @@ -7291,9 +7372,12 @@ static void P_NiGHTSMovement(player_t *player) // It also spawns every tic to avoid failed paraloops { mobj_t *helpermobj = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_NIGHTSLOOPHELPER); - helpermobj->fuse = player->mo->fuse = leveltime; - P_SetTarget(&helpermobj->target, player->mo); - P_SetScale(helpermobj, player->mo->scale); + if (!P_MobjWasRemoved(helpermobj)) + { + helpermobj->fuse = player->mo->fuse = leveltime; + P_SetTarget(&helpermobj->target, player->mo); + P_SetScale(helpermobj, player->mo->scale); + } } if (player->bumpertime) @@ -7482,19 +7566,22 @@ static void P_NiGHTSMovement(player_t *player) mobjtype_t splishtype = (player->mo->eflags & MFE_TOUCHLAVA) ? MT_LAVASPLISH : MT_SPLISH; mobj_t *water = P_SpawnMobj(player->mo->x, player->mo->y, ((player->mo->eflags & MFE_VERTICALFLIP) ? player->mo->waterbottom - FixedMul(mobjinfo[splishtype].height, player->mo->scale) : player->mo->watertop), splishtype); - if (player->mo->eflags & MFE_GOOWATER) - S_StartSound(water, sfx_ghit); - else if (player->mo->eflags & MFE_TOUCHLAVA) - S_StartSound(water, sfx_splash); - else - S_StartSound(water, sfx_wslap); - if (player->mo->eflags & MFE_VERTICALFLIP) + if (!P_MobjWasRemoved(water)) { - water->flags2 |= MF2_OBJECTFLIP; - water->eflags |= MFE_VERTICALFLIP; + if (player->mo->eflags & MFE_GOOWATER) + S_StartSound(water, sfx_ghit); + else if (player->mo->eflags & MFE_TOUCHLAVA) + S_StartSound(water, sfx_splash); + else + S_StartSound(water, sfx_wslap); + if (player->mo->eflags & MFE_VERTICALFLIP) + { + water->flags2 |= MF2_OBJECTFLIP; + water->eflags |= MFE_VERTICALFLIP; + } + water->destscale = player->mo->scale; + P_SetScale(water, player->mo->scale); } - water->destscale = player->mo->scale; - P_SetScale(water, player->mo->scale); } if (player->mo->momx || player->mo->momy) @@ -7727,6 +7814,8 @@ void P_ElementalFire(player_t *player, boolean cropcircle) for (i = 0; i < numangles; i++) { flame = P_SpawnMobj(player->mo->x, player->mo->y, ground, MT_SPINFIRE); + if (P_MobjWasRemoved(flame)) + continue; flame->flags &= ~MF_NOGRAVITY; P_SetTarget(&flame->target, player->mo); flame->angle = travelangle + i*(ANGLE_MAX/numangles); @@ -7764,6 +7853,8 @@ void P_ElementalFire(player_t *player, boolean cropcircle) } flame = P_SpawnMobj(newx, newy, ground, MT_SPINFIRE); + if (P_MobjWasRemoved(flame)) + continue; P_SetTarget(&flame->target, player->mo); flame->angle = travelangle; flame->fuse = TICRATE*6; @@ -7805,6 +7896,8 @@ void P_SpawnSkidDust(player_t *player, fixed_t radius, boolean sound) mobj_t *particle; particle = P_SpawnMobjFromMobj(mo, 0, 0, 0, MT_SPINDUST); + if (P_MobjWasRemoved(particle)) + return; if (radius >>= FRACBITS) { P_UnsetThingPosition(particle); @@ -8391,19 +8484,22 @@ void P_MovePlayer(player_t *player) mobjtype_t splishtype = (player->mo->eflags & MFE_TOUCHLAVA) ? MT_LAVASPLISH : MT_SPLISH; mobj_t *water = P_SpawnMobj(player->mo->x - P_ReturnThrustX(NULL, player->mo->angle, player->mo->radius), player->mo->y - P_ReturnThrustY(NULL, player->mo->angle, player->mo->radius), ((player->mo->eflags & MFE_VERTICALFLIP) ? player->mo->waterbottom - FixedMul(mobjinfo[splishtype].height, player->mo->scale) : player->mo->watertop), splishtype); - if (player->mo->eflags & MFE_GOOWATER) - S_StartSound(water, sfx_ghit); - else if (player->mo->eflags & MFE_TOUCHLAVA) - S_StartSound(water, sfx_splash); - else - S_StartSound(water, sfx_wslap); - if (player->mo->eflags & MFE_VERTICALFLIP) + if (!P_MobjWasRemoved(water)) { - water->flags2 |= MF2_OBJECTFLIP; - water->eflags |= MFE_VERTICALFLIP; + if (player->mo->eflags & MFE_GOOWATER) + S_StartSound(water, sfx_ghit); + else if (player->mo->eflags & MFE_TOUCHLAVA) + S_StartSound(water, sfx_splash); + else + S_StartSound(water, sfx_wslap); + if (player->mo->eflags & MFE_VERTICALFLIP) + { + water->flags2 |= MF2_OBJECTFLIP; + water->eflags |= MFE_VERTICALFLIP; + } + water->destscale = player->mo->scale; + P_SetScale(water, player->mo->scale); } - water->destscale = player->mo->scale; - P_SetScale(water, player->mo->scale); } // Little water sound while touching water - just a nicety. @@ -10821,6 +10917,8 @@ static void P_SpawnSparks(mobj_t *mo, angle_t maindir) fixed_t fm = (maindir >> ANGLETOFINESHIFT) & FINEMASK; spark = P_SpawnMobj(mo->x - b2*s + b1*c, mo->y + b2*c + b1*s, mo->z, MT_MINECARTSPARK); + if (P_MobjWasRemoved(spark)) + return; spark->momx = mo->momx + r1 + 8*FINECOSINE(fm); spark->momy = mo->momy + r2 + 8*FINESINE(fm); spark->momz = mo->momz + r3; @@ -12066,17 +12164,20 @@ void P_PlayerThink(player_t *player) if ((player->powers[pw_super] || player->powers[pw_sneakers]) && (player->speed + abs(player->mo->momz)) > FixedMul(20*FRACUNIT,player->mo->scale)) { mobj_t *gmobj = P_SpawnGhostMobj(player->mo); - gmobj->fuse = 2; - if (gmobj->tracer) - gmobj->tracer->fuse = 2; - if (leveltime & 1) + if (!P_MobjWasRemoved(gmobj)) { - gmobj->frame &= ~FF_TRANSMASK; - gmobj->frame |= tr_trans70<fuse = 2; if (gmobj->tracer) + gmobj->tracer->fuse = 2; + if (leveltime & 1) { - gmobj->tracer->frame &= ~FF_TRANSMASK; - gmobj->tracer->frame |= tr_trans70<frame &= ~FF_TRANSMASK; + gmobj->frame |= tr_trans70<tracer) + { + gmobj->tracer->frame &= ~FF_TRANSMASK; + gmobj->tracer->frame |= tr_trans70<normalspeed >= skins[player->skin].normalspeed*2) { mobj_t *ghost = P_SpawnGhostMobj(player->mo); // Spawns afterimages - ghost->fuse = 2; // Makes the images fade quickly - if (ghost->tracer && !P_MobjWasRemoved(ghost->tracer)) - ghost->tracer->fuse = ghost->fuse; + if (!P_MobjWasRemoved(ghost)) + { + ghost->fuse = 2; // Makes the images fade quickly + if (ghost->tracer && !P_MobjWasRemoved(ghost->tracer)) + ghost->tracer->fuse = ghost->fuse; + } } } else if (dashmode) @@ -12889,15 +12993,18 @@ void P_PlayerAfterThink(player_t *player) if (!player->followmobj || P_MobjWasRemoved(player->followmobj)) { P_SetTarget(&player->followmobj, P_SpawnMobjFromMobj(player->mo, 0, 0, 0, player->followitem)); - P_SetTarget(&player->followmobj->tracer, player->mo); - switch (player->followmobj->type) + if (!P_MobjWasRemoved(player->followmobj)) { - case MT_METALJETFUME: - player->followmobj->colorized = true; - break; - default: - player->followmobj->flags2 |= MF2_LINKDRAW; - break; + P_SetTarget(&player->followmobj->tracer, player->mo); + switch (player->followmobj->type) + { + case MT_METALJETFUME: + player->followmobj->colorized = true; + break; + default: + player->followmobj->flags2 |= MF2_LINKDRAW; + break; + } } } From aa952051fdcf088a559bdfdb8f341a0596338090 Mon Sep 17 00:00:00 2001 From: Sara Sparks Date: Sat, 6 May 2023 20:46:46 -0400 Subject: [PATCH 034/227] Made filesearch aware of symbolic links (cherry picked from commit cbcbda1586d5a19cf17aabedb49bca3e5328fa4f) --- src/filesrch.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/filesrch.c b/src/filesrch.c index 3f901b695..2104d6af5 100644 --- a/src/filesrch.c +++ b/src/filesrch.c @@ -23,6 +23,11 @@ #include #endif #include +#ifndef IGNORE_SYMLINKS +#include +#include +#include +#endif #include #include "filesrch.h" @@ -417,6 +422,9 @@ filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *want } else if (!strcasecmp(searchname, dent->d_name)) { +#ifndef IGNORE_SYMLINKS + struct stat statbuf; +#endif switch (checkfilemd5(searchpath, wantedmd5sum)) { case FS_FOUND: @@ -424,6 +432,17 @@ filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *want strcpy(filename,searchpath); else strcpy(filename,dent->d_name); +#ifndef IGNORE_SYMLINKS + if (lstat(filename, &statbuf) != -1) { + if (S_ISLNK(statbuf.st_mode)) { + char *tempbuf = realpath(filename, NULL); + if (!tempbuf) + I_Error("Error parsing link %s: %s", filename, strerror(errno)); + strncpy(filename, tempbuf, MAX_WADPATH); + free(tempbuf); + } + } +#endif retval = FS_FOUND; found = 1; break; From 37ab3e84fc201963d308bfb9dba07f7c82377b28 Mon Sep 17 00:00:00 2001 From: Sara Sparks Date: Mon, 8 May 2023 21:36:09 -0400 Subject: [PATCH 035/227] Increase size of filename buffer --- src/d_netcmd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 7417b8bf7..ced743f68 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -3658,11 +3658,11 @@ static void Got_RequestAddfoldercmd(UINT8 **cp, INT32 playernum) static void Got_Addfilecmd(UINT8 **cp, INT32 playernum) { - char filename[241]; + char filename[MAX_WADPATH+1]; filestatus_t ncs = FS_NOTCHECKED; UINT8 md5sum[16]; - READSTRINGN(*cp, filename, 240); + READSTRINGN(*cp, filename, MAX_WADPATH); READMEM(*cp, md5sum, 16); if (playernum != serverplayer) From 95d7a486eeb37d3feef16fd29892c62cf966d01d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Sat, 25 Mar 2023 16:26:31 +0100 Subject: [PATCH 036/227] Implement support for native keyboard layout --- src/console.c | 19 ++++++++----------- src/d_event.h | 1 + src/d_main.c | 6 +++--- src/hu_stuff.c | 45 ++++++++++++++++++++++++++++++++------------- src/sdl/i_video.c | 16 ++++++++++++++++ 5 files changed, 60 insertions(+), 27 deletions(-) diff --git a/src/console.c b/src/console.c index 33d59046e..2a48ad80b 100644 --- a/src/console.c +++ b/src/console.c @@ -924,7 +924,7 @@ boolean CON_Responder(event_t *ev) return false; // let go keyup events, don't eat them - if (ev->type != ev_keydown && ev->type != ev_console) + if (ev->type != ev_keydown && ev->type != ev_text && ev->type != ev_console) { if (ev->key == gamecontrol[GC_CONSOLE][0] || ev->key == gamecontrol[GC_CONSOLE][1]) consdown = false; @@ -951,7 +951,7 @@ boolean CON_Responder(event_t *ev) // check other keys only if console prompt is active if (!consoleready && key < NUMINPUTS) // metzgermeister: boundary check!! { - if (! menuactive && bindtable[key]) + if (!menuactive && bindtable[key]) { COM_BufAddText(bindtable[key]); COM_BufAddText("\n"); @@ -968,6 +968,12 @@ boolean CON_Responder(event_t *ev) } } + if (ev->type == ev_text) + { + CON_InputAddChar(key); + return true; + } + // Always eat ctrl/shift/alt if console open, so the menu doesn't get ideas if (key == KEY_LSHIFT || key == KEY_RSHIFT || key == KEY_LCTRL || key == KEY_RCTRL @@ -1304,21 +1310,12 @@ boolean CON_Responder(event_t *ev) else if (key == KEY_KPADSLASH) key = '/'; - if (key >= 'a' && key <= 'z') - { - if (capslock ^ shiftdown) - key = shiftxform[key]; - } - else if (shiftdown) - key = shiftxform[key]; - // enter a char into the command prompt if (key < 32 || key > 127) return true; if (input_sel != input_cur) CON_InputDelSelection(); - CON_InputAddChar(key); return true; } diff --git a/src/d_event.h b/src/d_event.h index 5aa435060..7743d8609 100644 --- a/src/d_event.h +++ b/src/d_event.h @@ -22,6 +22,7 @@ typedef enum { ev_keydown, ev_keyup, + ev_text, ev_console, ev_mouse, ev_joystick, diff --git a/src/d_main.c b/src/d_main.c index 32f16a282..d660b732e 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -190,19 +190,19 @@ void D_ProcessEvents(void) ev = &events[eventtail]; // Set mouse buttons early in case event is eaten later - if (ev->type == ev_keydown || ev->type == ev_keyup) + if (ev->type == ev_keydown || ev->type == ev_keyup || ev->type == ev_text) { // Mouse buttons if ((UINT32)(ev->key - KEY_MOUSE1) < MOUSEBUTTONS) { - if (ev->type == ev_keydown) + if (ev->type == ev_keydown || ev->type == ev_text) mouse.buttons |= 1 << (ev->key - KEY_MOUSE1); else mouse.buttons &= ~(1 << (ev->key - KEY_MOUSE1)); } else if ((UINT32)(ev->key - KEY_2MOUSE1) < MOUSEBUTTONS) { - if (ev->type == ev_keydown) + if (ev->type == ev_keydown || ev->type == ev_text) mouse2.buttons |= 1 << (ev->key - KEY_2MOUSE1); else mouse2.buttons &= ~(1 << (ev->key - KEY_2MOUSE1)); diff --git a/src/hu_stuff.c b/src/hu_stuff.c index 23c63e98f..04d1642a1 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -77,6 +77,7 @@ patch_t *nto_font[NT_FONTSIZE]; static player_t *plr; boolean chat_on; // entering a chat message? +boolean chat_on_first_event; // blocker for first chat input event static char w_chat[HU_MAXMSGLEN + 1]; static size_t c_input = 0; // let's try to make the chat input less shitty. static boolean headsupactive = false; @@ -1047,7 +1048,7 @@ boolean HU_Responder(event_t *ev) INT32 c=0; #endif - if (ev->type != ev_keydown) + if (ev->type != ev_keydown && ev->type != ev_text) return false; // only KeyDown events now... @@ -1077,11 +1078,15 @@ boolean HU_Responder(event_t *ev) if (!chat_on) { + if (ev->type == ev_text) + return false; + // enter chat mode if ((ev->key == gamecontrol[GC_TALKKEY][0] || ev->key == gamecontrol[GC_TALKKEY][1]) && netgame && !OLD_MUTE) // check for old chat mute, still let the players open the chat incase they want to scroll otherwise. { chat_on = true; + chat_on_first_event = false; w_chat[0] = 0; teamtalk = false; chat_scrollmedown = true; @@ -1092,6 +1097,7 @@ boolean HU_Responder(event_t *ev) && netgame && !OLD_MUTE) { chat_on = true; + chat_on_first_event = false; w_chat[0] = 0; teamtalk = G_GametypeHasTeams(); // Don't teamtalk if we don't have teams. chat_scrollmedown = true; @@ -1101,6 +1107,31 @@ boolean HU_Responder(event_t *ev) } else // if chat_on { + if (!chat_on_first_event) + { + // since the text event is sent immediately after the keydown event, + // we need to make sure that nothing is displayed once the chat + // opens, otherwise a 't' would be outputted. + chat_on_first_event = true; + return true; + } + + if (ev->type == ev_text) + { + if ((c < HU_FONTSTART || c > HU_FONTEND || !hu_font[c-HU_FONTSTART]) + && c != ' ') // Allow spaces, of course + { + return false; + } + + if (CHAT_MUTE || strlen(w_chat) >= HU_MAXMSGLEN) + return true; + + memmove(&w_chat[c_input + 1], &w_chat[c_input], strlen(w_chat) - c_input + 1); + w_chat[c_input] = c; + c_input++; + return true; + } // Ignore modifier keys // Note that we do this here so users can still set @@ -1110,8 +1141,6 @@ boolean HU_Responder(event_t *ev) || ev->key == KEY_LALT || ev->key == KEY_RALT) return true; - c = (INT32)ev->key; - // I know this looks very messy but this works. If it ain't broke, don't fix it! // shift LETTERS to uppercase if we have capslock or are holding shift if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) @@ -1194,16 +1223,6 @@ boolean HU_Responder(event_t *ev) else c_input++; } - else if ((c >= HU_FONTSTART && c <= HU_FONTEND && hu_font[c-HU_FONTSTART]) - || c == ' ') // Allow spaces, of course - { - if (CHAT_MUTE || strlen(w_chat) >= HU_MAXMSGLEN) - return true; - - memmove(&w_chat[c_input + 1], &w_chat[c_input], strlen(w_chat) - c_input + 1); - w_chat[c_input] = c; - c_input++; - } else if (c == KEY_BACKSPACE) { if (CHAT_MUTE || c_input <= 0) diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c index d2fbb9006..c25de5fd3 100644 --- a/src/sdl/i_video.c +++ b/src/sdl/i_video.c @@ -686,6 +686,19 @@ static void Impl_HandleKeyboardEvent(SDL_KeyboardEvent evt, Uint32 type) if (event.key) D_PostEvent(&event); } +static void Impl_HandleTextEvent(SDL_TextInputEvent evt) +{ + event_t event; + event.type = ev_text; + if (evt.text[1] != '\0') + { + // limit ourselves to ASCII for now, we can add UTF-8 support later + return; + } + event.key = evt.text[0]; + D_PostEvent(&event); +} + static void Impl_HandleMouseMotionEvent(SDL_MouseMotionEvent evt) { static boolean firstmove = true; @@ -934,6 +947,9 @@ void I_GetEvent(void) case SDL_KEYDOWN: Impl_HandleKeyboardEvent(evt.key, evt.type); break; + case SDL_TEXTINPUT: + Impl_HandleTextEvent(evt.text); + break; case SDL_MOUSEMOTION: //if (!mouseMotionOnce) Impl_HandleMouseMotionEvent(evt.motion); From 6896080f4fdca25a764516c66692d52dd8c9063c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Sat, 25 Mar 2023 23:08:13 +0100 Subject: [PATCH 037/227] fixup! Implement support for native keyboard layout --- src/hu_stuff.c | 15 +--------- src/m_menu.c | 76 ++++++++++++++++++++++++++++---------------------- 2 files changed, 44 insertions(+), 47 deletions(-) diff --git a/src/hu_stuff.c b/src/hu_stuff.c index 04d1642a1..a5268f821 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -1141,21 +1141,8 @@ boolean HU_Responder(event_t *ev) || ev->key == KEY_LALT || ev->key == KEY_RALT) return true; - // I know this looks very messy but this works. If it ain't broke, don't fix it! - // shift LETTERS to uppercase if we have capslock or are holding shift - if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) - { - if (shiftdown ^ capslock) - c = shiftxform[c]; - } - else // if we're holding shift we should still shift non letter symbols - { - if (shiftdown) - c = shiftxform[c]; - } - // pasting. pasting is cool. chat is a bit limited, though :( - if ((c == 'v' || c == 'V') && ctrldown) + if (c == 'v' && ctrldown) { const char *paste; size_t chatlen; diff --git a/src/m_menu.c b/src/m_menu.c index e879e9c14..71e47bc69 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -3230,40 +3230,42 @@ boolean M_Responder(event_t *ev) } else if (menuactive) { - if (ev->type == ev_keydown) + if (ev->type == ev_keydown || ev->type == ev_text) { - keydown++; ch = ev->key; - - // added 5-2-98 remap virtual keys (mouse & joystick buttons) - switch (ch) + if (ev->type == ev_keydown) { - case KEY_MOUSE1: - case KEY_JOY1: - ch = KEY_ENTER; - break; - case KEY_JOY1 + 3: - ch = 'n'; - break; - case KEY_MOUSE1 + 1: - case KEY_JOY1 + 1: - ch = KEY_ESCAPE; - break; - case KEY_JOY1 + 2: - ch = KEY_BACKSPACE; - break; - case KEY_HAT1: - ch = KEY_UPARROW; - break; - case KEY_HAT1 + 1: - ch = KEY_DOWNARROW; - break; - case KEY_HAT1 + 2: - ch = KEY_LEFTARROW; - break; - case KEY_HAT1 + 3: - ch = KEY_RIGHTARROW; - break; + keydown++; + // added 5-2-98 remap virtual keys (mouse & joystick buttons) + switch (ch) + { + case KEY_MOUSE1: + case KEY_JOY1: + ch = KEY_ENTER; + break; + case KEY_JOY1 + 3: + ch = 'n'; + break; + case KEY_MOUSE1 + 1: + case KEY_JOY1 + 1: + ch = KEY_ESCAPE; + break; + case KEY_JOY1 + 2: + ch = KEY_BACKSPACE; + break; + case KEY_HAT1: + ch = KEY_UPARROW; + break; + case KEY_HAT1 + 1: + ch = KEY_DOWNARROW; + break; + case KEY_HAT1 + 2: + ch = KEY_LEFTARROW; + break; + case KEY_HAT1 + 3: + ch = KEY_RIGHTARROW; + break; + } } } else if (ev->type == ev_joystick && ev->key == 0 && joywait < I_GetTime()) @@ -3425,8 +3427,11 @@ boolean M_Responder(event_t *ev) // Handle menuitems which need a specific key handling if (routine && (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_KEYHANDLER) { - if (shiftdown && ch >= 32 && ch <= 127) - ch = shiftxform[ch]; + // ignore ev_keydown events if the key maps to a character, since + // the ev_text event will follow immediately after in that case. + if (ev->type == ev_keydown && ch >= 32 && ch <= 127) + return false; + routine(ch); return true; } @@ -3468,6 +3473,11 @@ boolean M_Responder(event_t *ev) { if ((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_STRING) { + // ignore ev_keydown events if the key maps to a character, since + // the ev_text event will follow immediately after in that case. + if (ev->type == ev_keydown && ch >= 32 && ch <= 127) + return false; + if (M_ChangeStringCvar(ch)) return true; else From b8f8cc145104d41298585a2c5c84abee7211ade7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Wed, 29 Mar 2023 18:58:18 +0200 Subject: [PATCH 038/227] fixup! fixup! Implement support for native keyboard layout --- src/console.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/console.c b/src/console.c index 2a48ad80b..5571f6bb9 100644 --- a/src/console.c +++ b/src/console.c @@ -951,7 +951,7 @@ boolean CON_Responder(event_t *ev) // check other keys only if console prompt is active if (!consoleready && key < NUMINPUTS) // metzgermeister: boundary check!! { - if (!menuactive && bindtable[key]) + if (ev->type == ev_keydown && !menuactive && bindtable[key]) { COM_BufAddText(bindtable[key]); COM_BufAddText("\n"); From afe8432c1e5e0fcf40cb5fee9e1e4593352821b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Tue, 23 May 2023 17:41:10 +0200 Subject: [PATCH 039/227] fixup! fixup! fixup! Implement support for native keyboard layout --- src/sdl/i_system.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index e328bedc2..65f16f0bd 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -601,6 +601,7 @@ void I_GetConsoleEvents(void) else { // push regular character + ev.type = ev_text; ev.key = tty_con.buffer[tty_con.cursor] = key; tty_con.cursor++; // print the current line (this is differential) From ba014b84a7acccc02d163bd8a57422ac384d10eb Mon Sep 17 00:00:00 2001 From: Zwip-Zwap Zapony Date: Sun, 28 May 2023 22:56:18 +0200 Subject: [PATCH 040/227] Rewrite M_DrawStaticBox --- src/m_menu.c | 96 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 81 insertions(+), 15 deletions(-) diff --git a/src/m_menu.c b/src/m_menu.c index 64a1c9404..ba11795c9 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -4167,31 +4167,97 @@ void M_DrawTextBox(INT32 x, INT32 y, INT32 width, INT32 boxlines) */ } -static fixed_t staticalong = 0; - +// +// Draw the TV static effect on unavailable map icons +// static void M_DrawStaticBox(fixed_t x, fixed_t y, INT32 flags, fixed_t w, fixed_t h) { - patch_t *patch; - fixed_t sw, pw; + patch_t *patch = W_CachePatchName("LSSTATIC", PU_PATCH); + static fixed_t staticx = 0, staticy = 0; // Keep track of where we are across function calls - patch = W_CachePatchName("LSSTATIC", PU_PATCH); - pw = patch->width - (sw = w*2); //FixedDiv(w, scale); -- for scale FRACUNIT/2 - /*if (pw > 0) -- model code for modders providing weird LSSTATIC + if (!patch->width || !patch->height) // Shouldn't be necessary, but I don't want to get in trouble! { - if (staticalong > pw) - staticalong -= pw; + W_UnlockCachedPatch(patch); + return; } - else - staticalong = 0;*/ + if (patch->width == 1) // Nothing to randomise or tile + { + // Just stretch the patch over the whole box - no need to draw it 160 times over + // Technically, we should crop and maybe tile and randomise the Y axis, but... it's not worth it here + V_DrawStretchyFixedPatch(x*FRACUNIT, y*FRACUNIT, (w*FRACUNIT) / patch->width, (h*FRACUNIT) / patch->height, flags, patch, NULL); + W_UnlockCachedPatch(patch); + return; + } + if (patch->width == 160) // Something to randomise or tile - but don't! + { + // If it's 160 pixels wide, the modder probably wants the patch fixed in place + // But instead of "just drawing" it, why not allow sequential frames of animation on the Y axis? + // For example, this could be used to make a vignette effect, whether animated or not + // This function is primarily called with a patch region of 160x100, so the frames must be 160x100 too + fixed_t temp = patch->height / 100; // Amount of 160x100 frames in the patch + if (temp) // Don't modulo by zero + temp = (gametic % temp) * h*2*FRACUNIT; // Which frame to draw - if (staticalong > pw) // simplified for base LSSTATIC - staticalong -= pw; + V_DrawCroppedPatch(x*FRACUNIT, y*FRACUNIT, (w*FRACUNIT) / 160, (h*FRACUNIT) / 100, flags, patch, NULL, 0, temp, w*2*FRACUNIT, h*2*FRACUNIT); + + W_UnlockCachedPatch(patch); + return; + } - V_DrawCroppedPatch(x<width*2); + + if (patch->height == h*2) // Is the patch 100 pixels tall? If so, reset "staticy"... + staticy = 0; // ...in case that one add-on would randomise it and a later add-on wouldn't + else // Otherwise, as we already make "staticx" near-sequential, I think that making "staticy"... + staticy = M_RandomRange(0, (patch->height*2) - 1); // ...fully random instead of sequential increases the... randomness + + // The drawing function calls can get a bit lengthy, so let's make a little shortcut +#define DRAWSTATIC(_x,_y,_sx,_sy,_w,_h) V_DrawCroppedPatch((x*FRACUNIT)+((_x)*FRACUNIT/4), (y*FRACUNIT)+((_y)*FRACUNIT/4),\ +FRACUNIT/2, FRACUNIT/2, flags, patch, NULL, (_sx)*FRACUNIT/2, (_sy)*FRACUNIT/2, (w*2*FRACUNIT)+((_w)*FRACUNIT/2), (h*2*FRACUNIT)+((_h)*FRACUNIT/2)) + + // And finally, let's draw it! Don't worry about "staticx" plus "w*2" potentially going off-patch + DRAWSTATIC(0, 0, staticx, staticy, 0, 0); // This gets drawn in all cases + + if ((patch->width*2) - staticx >= w*4) // No horizontal tiling + { + if ((patch->height*2) - staticy >= h*4) // Simplest-case scenario, no tiling at all + {} + else // Vertical tiling only + { + for (INT16 j = 2; ((patch->height*j) - staticy) < h*4; j += 2) + DRAWSTATIC(0, (patch->height*j) - staticy, staticx, 0, 0, staticy - (patch->height*j)); + } + } + else // Horizontal tiling + { + if ((patch->height*2) - staticy >= h*4) // Horizontal tiling only + { + for (INT16 i = 2; ((patch->width*i) - staticx) < w*4; i += 2) + DRAWSTATIC((patch->width*i) - staticx, 0, 0, staticy, staticx - (patch->width*i), 0); + } + else // Horizontal and vertical tiling + { + for (INT16 j = 2; ((patch->height*j) - staticy) < h*4; j += 2) + DRAWSTATIC(0, (patch->height*j) - staticy, staticx, 0, 0, staticy - (patch->height*j)); + + for (INT16 i = 2; ((patch->width*i) - staticx) < w*4; i += 2) + { + DRAWSTATIC((patch->width*i) - staticx, 0, 0, staticy, staticx - (patch->width*i), 0); + for (INT16 j = 2; ((patch->height*j) - staticy) < h*4; j += 2) + DRAWSTATIC((patch->width*i) - staticx, (patch->height*j) - staticy, 0, 0, staticx - (patch->width*i), staticy - (patch->height*j)); + } + } + } +#undef DRAWSTATIC + + // Now that we're done with the patch, it's time to say our goodbyes. Until next time, patch! W_UnlockCachedPatch(patch); } From e65bdf8d2fcaadf885d2d69b6ddf8e004cc081be Mon Sep 17 00:00:00 2001 From: SMS Alfredo <65426124+SMS-Alfredo@users.noreply.github.com> Date: Wed, 31 May 2023 22:45:00 -0500 Subject: [PATCH 041/227] Expose P_IsLocalPlayer to Lua --- src/lua_baselib.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 25fa38769..33f9c4fcc 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -1327,6 +1327,17 @@ static int lib_pSetObjectMomZ(lua_State *L) return 0; } +static int lib_pIsLocalPlayer(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, P_IsLocalPlayer(player)); + return 1; +} + static int lib_pPlayJingle(lua_State *L) { player_t *player = NULL; @@ -4085,6 +4096,7 @@ static luaL_Reg lib[] = { {"P_InQuicksand",lib_pInQuicksand}, {"P_InJumpFlipSector",lib_pInJumpFlipSector}, {"P_SetObjectMomZ",lib_pSetObjectMomZ}, + {"P_IsLocalPlayer",lib_pIsLocalPlayer}, {"P_PlayJingle",lib_pPlayJingle}, {"P_PlayJingleMusic",lib_pPlayJingleMusic}, {"P_RestoreMusic",lib_pRestoreMusic}, From 2ce0c248ed74d91c117c73a28b8bd038bbb549db Mon Sep 17 00:00:00 2001 From: SMS Alfredo <65426124+SMS-Alfredo@users.noreply.github.com> Date: Wed, 31 May 2023 23:55:03 -0500 Subject: [PATCH 042/227] Make Dead / NoClipHeight Objects Immune to Death Pits --- src/p_mobj.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index eeaf54776..32bfb542a 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -2474,7 +2474,8 @@ boolean P_ZMovement(mobj_t *mo) break; } - if (!mo->player && P_CheckDeathPitCollide(mo)) + if (!mo->player && P_CheckDeathPitCollide(mo) && mo->health + && !(mo->flags & MF_NOCLIPHEIGHT) && !(mo->flags2 & MF2_BOSSDEAD)) { switch (mo->type) { @@ -2488,10 +2489,7 @@ boolean P_ZMovement(mobj_t *mo) if (mo->flags & MF_ENEMY || mo->flags & MF_BOSS || mo->type == MT_MINECART) { // Kill enemies, bosses and minecarts that fall into death pits. - if (mo->health) - { - P_KillMobj(mo, NULL, NULL, 0); - } + P_KillMobj(mo, NULL, NULL, 0); return !P_MobjWasRemoved(mo); // allows explosion states to run } else @@ -3137,7 +3135,8 @@ boolean P_SceneryZMovement(mobj_t *mo) break; } - if (P_CheckDeathPitCollide(mo)) + if (!mo->player && P_CheckDeathPitCollide(mo) && mo->health + && !(mo->flags & MF_NOCLIPHEIGHT) && !(mo->flags2 & MF2_BOSSDEAD)) { P_RemoveMobj(mo); return false; From 88a088a561d414adaba56ba6d0a86b22f3583d57 Mon Sep 17 00:00:00 2001 From: SMS Alfredo <65426124+SMS-Alfredo@users.noreply.github.com> Date: Thu, 1 Jun 2023 00:25:14 -0500 Subject: [PATCH 043/227] Remove Redundant Pit Checks --- src/p_mobj.c | 134 +++++++++++++++------------------------------------ 1 file changed, 40 insertions(+), 94 deletions(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index 32bfb542a..84f2bc9fa 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -2342,6 +2342,38 @@ boolean P_ZMovement(mobj_t *mo) else if (!onground) P_SlopeLaunch(mo); } + + if (!mo->player && P_CheckDeathPitCollide(mo) && mo->health + && !(mo->flags & MF_NOCLIPHEIGHT) && !(mo->flags2 & MF2_BOSSDEAD)) + { + switch (mo->type) + { + case MT_GHOST: + case MT_METALSONIC_RACE: + case MT_EXPLODE: + case MT_BOSSEXPLODE: + case MT_SONIC3KBOSSEXPLODE: + break; + case MT_REDFLAG: + case MT_BLUEFLAG: + // Remove from death pits. DON'T FUCKING DESPAWN IT DAMMIT + mo->fuse = 1; + return false; + default: + if (mo->flags & MF_ENEMY || mo->flags & MF_BOSS || mo->type == MT_MINECART) + { + // Kill enemies, bosses and minecarts that fall into death pits. + P_KillMobj(mo, NULL, NULL, 0); + return !P_MobjWasRemoved(mo); // allows explosion states to run + } + else + { + P_RemoveMobj(mo); + return false; + } + break; + } + } switch (mo->type) { @@ -2369,19 +2401,7 @@ boolean P_ZMovement(mobj_t *mo) mo->flags |= MF_NOGRAVITY; } break; - case MT_SPINFIRE: - if (P_CheckDeathPitCollide(mo)) - { - P_RemoveMobj(mo); - return false; - } - break; case MT_GOOP: - if (P_CheckDeathPitCollide(mo)) - { - P_RemoveMobj(mo); - return false; - } if (mo->z <= mo->floorz && mo->momz) { P_SetMobjState(mo, mo->info->meleestate); @@ -2391,27 +2411,6 @@ boolean P_ZMovement(mobj_t *mo) S_StartSound(mo, mo->info->painsound); } break; - case MT_FALLINGROCK: - case MT_BIGTUMBLEWEED: - case MT_LITTLETUMBLEWEED: - case MT_SHELL: - // Remove stuff from death pits. - if (P_CheckDeathPitCollide(mo)) - { - P_RemoveMobj(mo); - return false; - } - break; - case MT_REDFLAG: - case MT_BLUEFLAG: - // Remove from death pits. DON'T FUCKING DESPAWN IT DAMMIT - if (P_CheckDeathPitCollide(mo)) - { - mo->fuse = 1; - return false; - } - break; - case MT_RING: // Ignore still rings case MT_COIN: case MT_BLUESPHERE: @@ -2425,15 +2424,6 @@ boolean P_ZMovement(mobj_t *mo) case MT_FLINGBLUESPHERE: case MT_FLINGNIGHTSCHIP: case MT_FLINGEMERALD: - // Remove flinged stuff from death pits. - if (P_CheckDeathPitCollide(mo)) - { - P_RemoveMobj(mo); - return false; - } - if (!(mo->momx || mo->momy || mo->momz)) - return true; - break; case MT_BOUNCERING: case MT_INFINITYRING: case MT_AUTOMATICRING: @@ -2447,12 +2437,6 @@ boolean P_ZMovement(mobj_t *mo) case MT_EXPLODEPICKUP: case MT_SCATTERPICKUP: case MT_GRENADEPICKUP: - // Remove flinged stuff from death pits. - if (P_CheckDeathPitCollide(mo) && (mo->flags2 & MF2_DONTRESPAWN)) - { - P_RemoveMobj(mo); - return false; - } if (!(mo->momx || mo->momy || mo->momz)) return true; break; @@ -2474,33 +2458,6 @@ boolean P_ZMovement(mobj_t *mo) break; } - if (!mo->player && P_CheckDeathPitCollide(mo) && mo->health - && !(mo->flags & MF_NOCLIPHEIGHT) && !(mo->flags2 & MF2_BOSSDEAD)) - { - switch (mo->type) - { - case MT_GHOST: - case MT_METALSONIC_RACE: - case MT_EXPLODE: - case MT_BOSSEXPLODE: - case MT_SONIC3KBOSSEXPLODE: - break; - default: - if (mo->flags & MF_ENEMY || mo->flags & MF_BOSS || mo->type == MT_MINECART) - { - // Kill enemies, bosses and minecarts that fall into death pits. - P_KillMobj(mo, NULL, NULL, 0); - return !P_MobjWasRemoved(mo); // allows explosion states to run - } - else - { - P_RemoveMobj(mo); - return false; - } - break; - } - } - if (P_MobjFlip(mo)*mo->momz < 0 && (mo->flags2 & MF2_CLASSICPUSH)) mo->momx = mo->momy = 0; @@ -2952,7 +2909,7 @@ void P_PlayerZMovement(mobj_t *mo) if (P_MobjFlip(mo)*mo->momz < 0) // falling { - boolean clipmomz = !(P_CheckDeathPitCollide(mo)); + boolean clipmomz; mo->pmomz = 0; // We're on a new floor, don't keep doing platform movement. @@ -3060,6 +3017,13 @@ boolean P_SceneryZMovement(mobj_t *mo) mo->eflags &= ~MFE_APPLYPMOMZ; } mo->z += mo->momz; + + if (!mo->player && P_CheckDeathPitCollide(mo) && mo->health + && !(mo->flags & MF_NOCLIPHEIGHT) && !(mo->flags2 & MF2_BOSSDEAD)) + { + P_RemoveMobj(mo); + return false; + } switch (mo->type) { @@ -3073,11 +3037,6 @@ boolean P_SceneryZMovement(mobj_t *mo) } break; case MT_MEDIUMBUBBLE: - if (P_CheckDeathPitCollide(mo)) // Don't split if you fell in a pit - { - P_RemoveMobj(mo); - return false; - } if ((!(mo->eflags & MFE_VERTICALFLIP) && mo->z <= mo->floorz) || (mo->eflags & MFE_VERTICALFLIP && mo->z+mo->height >= mo->ceilingz)) // Hit the floor, so split! { @@ -3110,11 +3069,6 @@ boolean P_SceneryZMovement(mobj_t *mo) } break; case MT_SEED: // now scenery - if (P_CheckDeathPitCollide(mo)) // No flowers for death pits - { - P_RemoveMobj(mo); - return false; - } // Soniccd seed turns into a flower! if ((!(mo->eflags & MFE_VERTICALFLIP) && mo->z <= mo->floorz) || (mo->eflags & MFE_VERTICALFLIP && mo->z+mo->height >= mo->ceilingz)) @@ -3135,13 +3089,6 @@ boolean P_SceneryZMovement(mobj_t *mo) break; } - if (!mo->player && P_CheckDeathPitCollide(mo) && mo->health - && !(mo->flags & MF_NOCLIPHEIGHT) && !(mo->flags2 & MF2_BOSSDEAD)) - { - P_RemoveMobj(mo); - return false; - } - // clip movement if (((mo->z <= mo->floorz && !(mo->eflags & MFE_VERTICALFLIP)) || (mo->z + mo->height >= mo->ceilingz && mo->eflags & MFE_VERTICALFLIP)) @@ -7764,7 +7711,6 @@ static void P_MobjSceneryThink(mobj_t *mobj) if (!(mobj->eflags & MFE_UNDERWATER) || (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z + mobj->height >= mobj->ceilingz) || (mobj->eflags & MFE_VERTICALFLIP && mobj->z <= mobj->floorz) - || (P_CheckDeathPitCollide(mobj)) || --mobj->fuse <= 0) // Bubbles eventually dissipate if they can't reach the surface. { // no playing sound: no point; the object is being removed From 8919b35b936b365730001e5d69ca050a75dc830f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Fri, 2 Jun 2023 22:19:30 +0200 Subject: [PATCH 044/227] Fix buffer overflow when linedef type 415 executes --- src/p_spec.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/p_spec.c b/src/p_spec.c index aa04a723e..d91129610 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2613,7 +2613,15 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) if (lumpnum == LUMPERROR || W_LumpLength(lumpnum) == 0) CONS_Debug(DBG_SETUP, "Line type 415 Executor: script lump %s not found/not valid.\n", line->stringargs[0]); else - COM_BufInsertText(W_CacheLumpNum(lumpnum, PU_CACHE)); + { + void *lump = W_CacheLumpNum(lumpnum, PU_CACHE); + size_t len = W_LumpLength(lumpnum); + char *text = Z_Malloc(len + 1, PU_CACHE, NULL); + memcpy(text, lump, len); + text[len] = '\0'; + COM_BufInsertText(text); + Z_Free(text); + } } break; From 610effb8ea568212d3d63c9296bac33cc8784327 Mon Sep 17 00:00:00 2001 From: SMS Alfredo <65426124+SMS-Alfredo@users.noreply.github.com> Date: Fri, 2 Jun 2023 23:04:18 -0500 Subject: [PATCH 045/227] Expose P_ResetCamera to Lua --- src/lua_baselib.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 25fa38769..dddbea0ee 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -1641,6 +1641,19 @@ static int lib_pHomingAttack(lua_State *L) return 1; } +static int lib_pResetCamera(lua_State *L) +{ + player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER)); + camera_t *cam = *((camera_t **)luaL_checkudata(L, 2, META_CAMERA)); + + if (!player) + return LUA_ErrInvalid(L, "player_t"); + if (!cam) + return LUA_ErrInvalid(L, "camera_t"); + P_ResetCamera(player, cam); + return 0; +} + static int lib_pSuperReady(lua_State *L) { player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER)); @@ -4109,6 +4122,7 @@ static luaL_Reg lib[] = { {"P_NukeEnemies",lib_pNukeEnemies}, {"P_Earthquake",lib_pEarthquake}, {"P_HomingAttack",lib_pHomingAttack}, + {"P_ResetCamera",lib_pResetCamera}, {"P_SuperReady",lib_pSuperReady}, {"P_DoJump",lib_pDoJump}, {"P_SpawnThokMobj",lib_pSpawnThokMobj}, From a0c7c08bb7e8a0cc6d0eb9950e0734a3d5688961 Mon Sep 17 00:00:00 2001 From: SMS Alfredo <65426124+SMS-Alfredo@users.noreply.github.com> Date: Sat, 3 Jun 2023 00:40:12 -0500 Subject: [PATCH 046/227] Expose camera->reset to Lua --- src/lua_hudlib.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c index 1a0599757..f38333ae7 100644 --- a/src/lua_hudlib.c +++ b/src/lua_hudlib.c @@ -167,6 +167,7 @@ enum cameraf { camera_x, camera_y, camera_z, + camera_reset, camera_angle, camera_subsector, camera_floorz, @@ -185,6 +186,7 @@ static const char *const camera_opt[] = { "x", "y", "z", + "reset", "angle", "subsector", "floorz", @@ -338,6 +340,9 @@ static int camera_get(lua_State *L) case camera_z: lua_pushinteger(L, cam->z); break; + case camera_reset: + lua_pushboolean(L, cam->reset); + break; case camera_angle: lua_pushinteger(L, cam->angle); break; @@ -384,6 +389,9 @@ static int camera_set(lua_State *L) case camera_x: case camera_y: return luaL_error(L, LUA_QL("camera_t") " field " LUA_QS " should not be set directly. Use " LUA_QL("P_TryCameraMove") " or " LUA_QL("P_TeleportCameraMove") " instead.", camera_opt[field]); + case camera_reset: + cam->reset = luaL_checkboolean(L, 3); + break; case camera_chase: { INT32 chase = luaL_checkboolean(L, 3); if (cam == &camera) From 84fe1dfc7de28dd79230fee9f4d21e2259894369 Mon Sep 17 00:00:00 2001 From: MascaraSnake Date: Fri, 23 Jun 2023 23:32:58 +0200 Subject: [PATCH 047/227] Texture scaling (incomplete) --- src/hardware/hw_main.c | 135 ++++++++++++++++++++++------------------- src/lua_maplib.c | 48 +++++++++++++++ src/p_setup.c | 29 +++++++++ src/r_defs.h | 3 + src/r_segs.c | 45 +++++++++++--- 5 files changed, 189 insertions(+), 71 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index c2390519e..d1ec05247 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -1063,6 +1063,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom fixed_t h, l; // 3D sides and 2s middle textures fixed_t hS, lS; + float xscale, yscale; gl_sidedef = gl_curline->sidedef; gl_linedef = gl_curline->linedef; @@ -1159,33 +1160,35 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom texturevpeg %= textureheight[gl_toptexture]; grTex = HWR_GetTexture(gl_toptexture); + xscale = FIXED_TO_FLOAT(gl_sidedef->scalex_top) * grTex->scaleX; + yscale = FIXED_TO_FLOAT(gl_sidedef->scaley_top) * grTex->scaleY; - wallVerts[3].t = wallVerts[2].t = texturevpeg * grTex->scaleY; - wallVerts[0].t = wallVerts[1].t = (texturevpeg + gl_frontsector->ceilingheight - gl_backsector->ceilingheight) * grTex->scaleY; - wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_top) * grTex->scaleX; - wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_top) * grTex->scaleX; + wallVerts[3].t = wallVerts[2].t = texturevpeg * yscale; + wallVerts[0].t = wallVerts[1].t = (texturevpeg + gl_frontsector->ceilingheight - gl_backsector->ceilingheight) * yscale; + wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_top) * xscale; + wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_top) * xscale; // Adjust t value for sloped walls if (!(gl_linedef->flags & ML_SKEWTD)) { // Unskewed - wallVerts[3].t -= (worldtop - gl_frontsector->ceilingheight) * grTex->scaleY; - wallVerts[2].t -= (worldtopslope - gl_frontsector->ceilingheight) * grTex->scaleY; - wallVerts[0].t -= (worldhigh - gl_backsector->ceilingheight) * grTex->scaleY; - wallVerts[1].t -= (worldhighslope - gl_backsector->ceilingheight) * grTex->scaleY; + wallVerts[3].t -= (worldtop - gl_frontsector->ceilingheight) * yscale; + wallVerts[2].t -= (worldtopslope - gl_frontsector->ceilingheight) * yscale; + wallVerts[0].t -= (worldhigh - gl_backsector->ceilingheight) * yscale; + wallVerts[1].t -= (worldhighslope - gl_backsector->ceilingheight) * yscale; } else if (gl_linedef->flags & ML_DONTPEGTOP) { // Skewed by top - wallVerts[0].t = (texturevpeg + worldtop - worldhigh) * grTex->scaleY; - wallVerts[1].t = (texturevpeg + worldtopslope - worldhighslope) * grTex->scaleY; + wallVerts[0].t = (texturevpeg + worldtop - worldhigh) * yscale; + wallVerts[1].t = (texturevpeg + worldtopslope - worldhighslope) * yscale; } else { // Skewed by bottom - wallVerts[0].t = wallVerts[1].t = (texturevpeg + worldtop - worldhigh) * grTex->scaleY; - wallVerts[3].t = wallVerts[0].t - (worldtop - worldhigh) * grTex->scaleY; - wallVerts[2].t = wallVerts[1].t - (worldtopslope - worldhighslope) * grTex->scaleY; + wallVerts[0].t = wallVerts[1].t = (texturevpeg + worldtop - worldhigh) * yscale; + wallVerts[3].t = wallVerts[0].t - (worldtop - worldhigh) * yscale; + wallVerts[2].t = wallVerts[1].t - (worldtopslope - worldhighslope) * yscale; } // set top/bottom coords @@ -1219,32 +1222,34 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom texturevpeg %= textureheight[gl_bottomtexture]; grTex = HWR_GetTexture(gl_bottomtexture); + xscale = FIXED_TO_FLOAT(gl_sidedef->scalex_bot) * grTex->scaleX; + yscale = FIXED_TO_FLOAT(gl_sidedef->scaley_bot) * grTex->scaleY; - wallVerts[3].t = wallVerts[2].t = texturevpeg * grTex->scaleY; - wallVerts[0].t = wallVerts[1].t = (texturevpeg + gl_backsector->floorheight - gl_frontsector->floorheight) * grTex->scaleY; - wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_bot) * grTex->scaleX; - wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_bot) * grTex->scaleX; + wallVerts[3].t = wallVerts[2].t = texturevpeg * yscale; + wallVerts[0].t = wallVerts[1].t = (texturevpeg + gl_backsector->floorheight - gl_frontsector->floorheight) * yscale; + wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_bot) * xscale; + wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_bot) * xscale; // Adjust t value for sloped walls if (!(gl_linedef->flags & ML_SKEWTD)) { // Unskewed - wallVerts[0].t -= (worldbottom - gl_frontsector->floorheight) * grTex->scaleY; - wallVerts[1].t -= (worldbottomslope - gl_frontsector->floorheight) * grTex->scaleY; - wallVerts[3].t -= (worldlow - gl_backsector->floorheight) * grTex->scaleY; - wallVerts[2].t -= (worldlowslope - gl_backsector->floorheight) * grTex->scaleY; + wallVerts[0].t -= (worldbottom - gl_frontsector->floorheight) * yscale; + wallVerts[1].t -= (worldbottomslope - gl_frontsector->floorheight) * yscale; + wallVerts[3].t -= (worldlow - gl_backsector->floorheight) * yscale; + wallVerts[2].t -= (worldlowslope - gl_backsector->floorheight) * yscale; } else if (gl_linedef->flags & ML_DONTPEGBOTTOM) { // Skewed by bottom - wallVerts[0].t = wallVerts[1].t = (texturevpeg + worldlow - worldbottom) * grTex->scaleY; - wallVerts[2].t = wallVerts[1].t - (worldlowslope - worldbottomslope) * grTex->scaleY; + wallVerts[0].t = wallVerts[1].t = (texturevpeg + worldlow - worldbottom) * yscale; + wallVerts[2].t = wallVerts[1].t - (worldlowslope - worldbottomslope) * yscale; } else { // Skewed by top - wallVerts[0].t = (texturevpeg + worldlow - worldbottom) * grTex->scaleY; - wallVerts[1].t = (texturevpeg + worldlowslope - worldbottomslope) * grTex->scaleY; + wallVerts[0].t = (texturevpeg + worldlow - worldbottom) * yscale; + wallVerts[1].t = (texturevpeg + worldlowslope - worldbottomslope) * yscale; } // set top/bottom coords @@ -1403,16 +1408,18 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom } grTex = HWR_GetTexture(gl_midtexture); + xscale = FIXED_TO_FLOAT(gl_sidedef->scalex_mid) * grTex->scaleX; + yscale = FIXED_TO_FLOAT(gl_sidedef->scaley_mid) * grTex->scaleY; // Left side - wallVerts[3].t = texturevpeg * grTex->scaleY; - wallVerts[0].t = (h - l + texturevpeg) * grTex->scaleY; - wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_mid) * grTex->scaleX; + wallVerts[3].t = texturevpeg * yscale; + wallVerts[0].t = (h - l + texturevpeg) * yscale; + wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_mid) * xscale; // Right side - wallVerts[2].t = texturevpegslope * grTex->scaleY; - wallVerts[1].t = (hS - lS + texturevpegslope) * grTex->scaleY; - wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_mid) * grTex->scaleX; + wallVerts[2].t = texturevpegslope * yscale; + wallVerts[1].t = (hS - lS + texturevpegslope) * yscale; + wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_mid) * xscale; // set top/bottom coords // Take the texture peg into account, rather than changing the offsets past @@ -1482,24 +1489,26 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom texturevpeg = gl_sidedef->rowoffset + gl_sidedef->offsety_mid; grTex = HWR_GetTexture(gl_midtexture); + xscale = FIXED_TO_FLOAT(gl_sidedef->scalex_mid) * grTex->scaleX; + yscale = FIXED_TO_FLOAT(gl_sidedef->scaley_mid) * grTex->scaleY; - 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 + gl_sidedef->offsetx_mid) * grTex->scaleX; - wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_mid) * grTex->scaleX; + wallVerts[3].t = wallVerts[2].t = texturevpeg * yscale; + wallVerts[0].t = wallVerts[1].t = (texturevpeg + gl_frontsector->ceilingheight - gl_frontsector->floorheight) * yscale; + wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_mid) * xscale; + wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_mid) * xscale; // Texture correction for slopes if (gl_linedef->flags & ML_NOSKEW) { - wallVerts[3].t += (gl_frontsector->ceilingheight - worldtop) * grTex->scaleY; - wallVerts[2].t += (gl_frontsector->ceilingheight - worldtopslope) * grTex->scaleY; - wallVerts[0].t += (gl_frontsector->floorheight - worldbottom) * grTex->scaleY; - wallVerts[1].t += (gl_frontsector->floorheight - worldbottomslope) * grTex->scaleY; + wallVerts[3].t += (gl_frontsector->ceilingheight - worldtop) * yscale; + wallVerts[2].t += (gl_frontsector->ceilingheight - worldtopslope) * yscale; + wallVerts[0].t += (gl_frontsector->floorheight - worldbottom) * yscale; + wallVerts[1].t += (gl_frontsector->floorheight - worldbottomslope) * yscale; } else if (gl_linedef->flags & ML_DONTPEGBOTTOM) { - wallVerts[3].t = wallVerts[0].t + (worldbottom-worldtop) * grTex->scaleY; - wallVerts[2].t = wallVerts[1].t + (worldbottomslope-worldtopslope) * grTex->scaleY; + wallVerts[3].t = wallVerts[0].t + (worldbottom-worldtop) * yscale; + wallVerts[2].t = wallVerts[1].t + (worldbottomslope-worldtopslope) * yscale; } else { - wallVerts[0].t = wallVerts[3].t - (worldbottom-worldtop) * grTex->scaleY; - wallVerts[1].t = wallVerts[2].t - (worldbottomslope-worldtopslope) * grTex->scaleY; + wallVerts[0].t = wallVerts[3].t - (worldbottom-worldtop) * yscale; + wallVerts[1].t = wallVerts[2].t - (worldbottomslope-worldtopslope) * yscale; } //Set textures properly on single sided walls that are sloped @@ -1646,34 +1655,36 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom } grTex = HWR_GetTexture(texnum); + xscale = FIXED_TO_FLOAT(gl_sidedef->scalex_mid) * grTex->scaleX; + yscale = FIXED_TO_FLOAT(gl_sidedef->scaley_mid) * grTex->scaleY; if (!slopeskew) // no skewing { if (attachtobottom) texturevpeg -= *rover->topheight - *rover->bottomheight; - wallVerts[3].t = (*rover->topheight - h + texturevpeg) * grTex->scaleY; - wallVerts[2].t = (*rover->topheight - hS + texturevpeg) * grTex->scaleY; - wallVerts[0].t = (*rover->topheight - l + texturevpeg) * grTex->scaleY; - wallVerts[1].t = (*rover->topheight - lS + texturevpeg) * grTex->scaleY; + wallVerts[3].t = (*rover->topheight - h + texturevpeg) * yscale; + wallVerts[2].t = (*rover->topheight - hS + texturevpeg) * yscale; + wallVerts[0].t = (*rover->topheight - l + texturevpeg) * yscale; + wallVerts[1].t = (*rover->topheight - lS + texturevpeg) * yscale; } else { if (!attachtobottom) // skew by top { - wallVerts[3].t = wallVerts[2].t = texturevpeg * grTex->scaleY; - wallVerts[0].t = (h - l + texturevpeg) * grTex->scaleY; - wallVerts[1].t = (hS - lS + texturevpeg) * grTex->scaleY; + wallVerts[3].t = wallVerts[2].t = texturevpeg * yscale; + wallVerts[0].t = (h - l + texturevpeg) * yscale; + wallVerts[1].t = (hS - lS + texturevpeg) * yscale; } else // skew by bottom { - wallVerts[0].t = wallVerts[1].t = texturevpeg * grTex->scaleY; - wallVerts[3].t = wallVerts[0].t - (h - l) * grTex->scaleY; - wallVerts[2].t = wallVerts[1].t - (hS - lS) * grTex->scaleY; + wallVerts[0].t = wallVerts[1].t = texturevpeg * yscale; + wallVerts[3].t = wallVerts[0].t - (h - l) * yscale; + wallVerts[2].t = wallVerts[1].t - (hS - lS) * yscale; } } - wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_mid) * grTex->scaleX; - wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_mid) * grTex->scaleX; + wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_mid) * xscale; + wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_mid) * xscale; } if (rover->fofflags & FOF_FOG) { @@ -1782,20 +1793,22 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom else { grTex = HWR_GetTexture(texnum); + xscale = FIXED_TO_FLOAT(gl_sidedef->scalex_mid) * grTex->scaleX; + yscale = FIXED_TO_FLOAT(gl_sidedef->scaley_mid) * grTex->scaleY; if (newline) { - wallVerts[3].t = wallVerts[2].t = (*rover->topheight - h + sides[newline->sidenum[0]].rowoffset + sides[newline->sidenum[0]].offsety_mid) * grTex->scaleY; - wallVerts[0].t = wallVerts[1].t = (h - l + (*rover->topheight - h + sides[newline->sidenum[0]].rowoffset) + sides[newline->sidenum[0]].offsety_mid) * grTex->scaleY; + wallVerts[3].t = wallVerts[2].t = (*rover->topheight - h + sides[newline->sidenum[0]].rowoffset + sides[newline->sidenum[0]].offsety_mid) * yscale; + wallVerts[0].t = wallVerts[1].t = (h - l + (*rover->topheight - h + sides[newline->sidenum[0]].rowoffset) + sides[newline->sidenum[0]].offsety_mid) * yscale; } else { - wallVerts[3].t = wallVerts[2].t = (*rover->topheight - h + sides[rover->master->sidenum[0]].rowoffset + sides[rover->master->sidenum[0]].offsety_mid) * grTex->scaleY; - wallVerts[0].t = wallVerts[1].t = (h - l + (*rover->topheight - h + sides[rover->master->sidenum[0]].rowoffset + sides[rover->master->sidenum[0]].offsety_mid)) * grTex->scaleY; + wallVerts[3].t = wallVerts[2].t = (*rover->topheight - h + sides[rover->master->sidenum[0]].rowoffset + sides[rover->master->sidenum[0]].offsety_mid) * yscale; + wallVerts[0].t = wallVerts[1].t = (h - l + (*rover->topheight - h + sides[rover->master->sidenum[0]].rowoffset + sides[rover->master->sidenum[0]].offsety_mid)) * yscale; } - wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_mid) * grTex->scaleX; - wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_mid) * grTex->scaleX; + wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_mid) * xscale; + wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_mid) * xscale; } if (rover->fofflags & FOF_FOG) diff --git a/src/lua_maplib.c b/src/lua_maplib.c index f73b3c7d2..942fdf45a 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -172,6 +172,12 @@ enum side_e { side_offsety_mid, side_offsetx_bot, side_offsety_bot, + side_scalex_top, + side_scaley_top, + side_scalex_mid, + side_scaley_mid, + side_scalex_bot, + side_scaley_bot, side_toptexture, side_bottomtexture, side_midtexture, @@ -192,6 +198,12 @@ static const char *const side_opt[] = { "offsety_mid", "offsetx_bot", "offsety_bot", + "scalex_top", + "scaley_top", + "scalex_mid", + "scaley_mid", + "scalex_bot", + "scaley_bot", "toptexture", "bottomtexture", "midtexture", @@ -1127,6 +1139,24 @@ static int side_get(lua_State *L) case side_offsety_bot: lua_pushfixed(L, side->offsety_bot); return 1; + case side_scalex_top: + lua_pushfixed(L, side->scalex_top); + return 1; + case side_scaley_top: + lua_pushfixed(L, side->scaley_top); + return 1; + case side_scalex_mid: + lua_pushfixed(L, side->scalex_mid); + return 1; + case side_scaley_mid: + lua_pushfixed(L, side->scaley_mid); + return 1; + case side_scalex_bot: + lua_pushfixed(L, side->scalex_bot); + return 1; + case side_scaley_bot: + lua_pushfixed(L, side->scaley_bot); + return 1; case side_toptexture: lua_pushinteger(L, side->toptexture); return 1; @@ -1202,6 +1232,24 @@ static int side_set(lua_State *L) case side_offsety_bot: side->offsety_bot = luaL_checkfixed(L, 3); break; + case side_scalex_top: + side->scalex_top = luaL_checkfixed(L, 3); + break; + case side_scaley_top: + side->scaley_top = luaL_checkfixed(L, 3); + break; + case side_scalex_mid: + side->scalex_mid = luaL_checkfixed(L, 3); + break; + case side_scaley_mid: + side->scaley_mid = luaL_checkfixed(L, 3); + break; + case side_scalex_bot: + side->scalex_bot = luaL_checkfixed(L, 3); + break; + case side_scaley_bot: + side->scaley_bot = luaL_checkfixed(L, 3); + break; case side_toptexture: side->toptexture = luaL_checkinteger(L, 3); break; diff --git a/src/p_setup.c b/src/p_setup.c index a10326986..07961b5c7 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -1243,6 +1243,9 @@ static void P_LoadSidedefs(UINT8 *data) sd->offsetx_top = sd->offsetx_mid = sd->offsetx_bot = 0; sd->offsety_top = sd->offsety_mid = sd->offsety_bot = 0; + sd->scalex_top = sd->scalex_mid = sd->scalex_bot = FRACUNIT; + sd->scaley_top = sd->scaley_mid = sd->scaley_bot = FRACUNIT; + P_SetSidedefSector(i, SHORT(msd->sector)); // Special info stored in texture fields! @@ -1792,6 +1795,18 @@ static void ParseTextmapSidedefParameter(UINT32 i, const char *param, const char sides[i].offsety_mid = atol(val) << FRACBITS; else if (fastcmp(param, "offsety_bottom")) sides[i].offsety_bot = atol(val) << FRACBITS; + else if (fastcmp(param, "scalex_top")) + sides[i].scalex_top = FLOAT_TO_FIXED(atof(val)); + else if (fastcmp(param, "scalex_mid")) + sides[i].scalex_mid = FLOAT_TO_FIXED(atof(val)); + else if (fastcmp(param, "scalex_bottom")) + sides[i].scalex_bot = FLOAT_TO_FIXED(atof(val)); + else if (fastcmp(param, "scaley_top")) + sides[i].scaley_top = FLOAT_TO_FIXED(atof(val)); + else if (fastcmp(param, "scaley_mid")) + sides[i].scaley_mid = FLOAT_TO_FIXED(atof(val)); + else if (fastcmp(param, "scaley_bottom")) + sides[i].scaley_bot = FLOAT_TO_FIXED(atof(val)); else if (fastcmp(param, "texturetop")) sides[i].toptexture = R_TextureNumForName(val); else if (fastcmp(param, "texturebottom")) @@ -2488,6 +2503,18 @@ static void P_WriteTextmap(void) fprintf(f, "offsetx_bottom = %d;\n", wsides[i].offsetx_bot >> FRACBITS); if (wsides[i].offsety_bot != 0) fprintf(f, "offsety_bottom = %d;\n", wsides[i].offsety_bot >> FRACBITS); + if (wsides[i].scalex_top != FRACUNIT) + fprintf(f, "scalex_top = %f;\n", FIXED_TO_FLOAT(wsides[i].scalex_top)); + if (wsides[i].scaley_top != FRACUNIT) + fprintf(f, "scaley_top = %f;\n", FIXED_TO_FLOAT(wsides[i].scaley_top)); + if (wsides[i].scalex_mid != FRACUNIT) + fprintf(f, "scalex_mid = %f;\n", FIXED_TO_FLOAT(wsides[i].scalex_mid)); + if (wsides[i].scaley_mid != FRACUNIT) + fprintf(f, "scaley_mid = %f;\n", FIXED_TO_FLOAT(wsides[i].scaley_mid)); + if (wsides[i].scalex_bot != FRACUNIT) + fprintf(f, "scalex_bottom = %f;\n", FIXED_TO_FLOAT(wsides[i].scalex_bot)); + if (wsides[i].scaley_bot != FRACUNIT) + fprintf(f, "scaley_bottom = %f;\n", FIXED_TO_FLOAT(wsides[i].scaley_bot)); if (wsides[i].toptexture > 0 && wsides[i].toptexture < numtextures) fprintf(f, "texturetop = \"%.*s\";\n", 8, textures[wsides[i].toptexture]->name); if (wsides[i].bottomtexture > 0 && wsides[i].bottomtexture < numtextures) @@ -2857,6 +2884,8 @@ static void P_LoadTextmap(void) sd->rowoffset = 0; sd->offsetx_top = sd->offsetx_mid = sd->offsetx_bot = 0; sd->offsety_top = sd->offsety_mid = sd->offsety_bot = 0; + sd->scalex_top = sd->scalex_mid = sd->scalex_bot = FRACUNIT; + sd->scaley_top = sd->scaley_mid = sd->scaley_bot = FRACUNIT; sd->toptexture = R_TextureNumForName("-"); sd->midtexture = R_TextureNumForName("-"); sd->bottomtexture = R_TextureNumForName("-"); diff --git a/src/r_defs.h b/src/r_defs.h index 963d655b1..7f440179f 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -563,6 +563,9 @@ typedef struct fixed_t offsetx_top, offsetx_mid, offsetx_bot; fixed_t offsety_top, offsety_mid, offsety_bot; + fixed_t scalex_top, scalex_mid, scalex_bot; + fixed_t scaley_top, scaley_mid, scaley_bot; + // Texture indices. // We do not maintain names here. INT32 toptexture, bottomtexture, midtexture; diff --git a/src/r_segs.c b/src/r_segs.c index 5acca9b17..0a23699d2 100644 --- a/src/r_segs.c +++ b/src/r_segs.c @@ -42,6 +42,10 @@ angle_t rw_normalangle; // angle to line origin angle_t rw_angle1; fixed_t rw_distance; +// Horizontal scaling hack shenanigans. +fixed_t rw_distance_scalex_top; +fixed_t rw_distance_scalex_mid; +fixed_t rw_distance_scalex_bot; // // regular wall @@ -69,6 +73,7 @@ static fixed_t *rw_bsilheight = NULL; static fixed_t pixhigh, pixlow, pixhighstep, pixlowstep; static fixed_t topfrac, topstep; static fixed_t bottomfrac, bottomstep; +static fixed_t topxscale, topyscale, midxscale, midyscale, botxscale, botyscale; static lighttable_t **walllights; static INT16 *maskedtexturecol; @@ -1093,6 +1098,9 @@ static void R_RenderSegLoop (void) INT32 mid; fixed_t texturecolumn = 0; + fixed_t texturecolumn_top = 0; + fixed_t texturecolumn_mid = 0; + fixed_t texturecolumn_bot = 0; fixed_t oldtexturecolumn = -1; INT32 top; INT32 bottom; @@ -1261,6 +1269,9 @@ static void R_RenderSegLoop (void) // calculate texture offset angle = (rw_centerangle + xtoviewangle[rw_x])>>ANGLETOFINESHIFT; texturecolumn = rw_offset-FixedMul(FINETANGENT(angle),rw_distance); + texturecolumn_top = (rw_offset_top - FixedMul(FINETANGENT(angle),rw_distance_scalex_top))>>FRACBITS; + texturecolumn_mid = (rw_offset_mid - FixedMul(FINETANGENT(angle),rw_distance_scalex_mid))>>FRACBITS; + texturecolumn_bot = (rw_offset_bot - FixedMul(FINETANGENT(angle),rw_distance_scalex_bot))>>FRACBITS; if (oldtexturecolumn != -1) { rw_bottomtexturemid += FixedMul(rw_bottomtextureslide, oldtexturecolumn-texturecolumn); @@ -1335,8 +1346,9 @@ static void R_RenderSegLoop (void) { dc_yl = yl; dc_yh = yh; - dc_texturemid = rw_midtexturemid; - dc_source = R_GetColumn(midtexture,texturecolumn + (rw_offset_mid>>FRACBITS)); + dc_texturemid = FixedMul(rw_midtexturemid, midyscale); + dc_iscale = FixedMul(0xffffffffu / (unsigned)rw_scale, midyscale); + dc_source = R_GetColumn(midtexture, texturecolumn_mid); dc_texheight = textureheight[midtexture]>>FRACBITS; //profile stuff --------------------------------------------------------- @@ -1396,8 +1408,9 @@ static void R_RenderSegLoop (void) { dc_yl = yl; dc_yh = mid; - dc_texturemid = rw_toptexturemid; - dc_source = R_GetColumn(toptexture,texturecolumn + (rw_offset_top>>FRACBITS)); + dc_texturemid = FixedMul(rw_toptexturemid, topyscale); + dc_iscale = FixedMul(0xffffffffu / (unsigned)rw_scale, topyscale); + dc_source = R_GetColumn(toptexture, texturecolumn_top); dc_texheight = textureheight[toptexture]>>FRACBITS; colfunc(); ceilingclip[rw_x] = (INT16)mid; @@ -1432,9 +1445,9 @@ static void R_RenderSegLoop (void) { dc_yl = mid; dc_yh = yh; - dc_texturemid = rw_bottomtexturemid; - dc_source = R_GetColumn(bottomtexture, - texturecolumn + (rw_offset_bot>>FRACBITS)); + dc_texturemid = FixedMul(rw_bottomtexturemid, botyscale); + dc_iscale = FixedMul(0xffffffffu / (unsigned)rw_scale, botyscale); + dc_source = R_GetColumn(bottomtexture, texturecolumn_bot); dc_texheight = textureheight[bottomtexture]>>FRACBITS; colfunc(); floorclip[rw_x] = (INT16)mid; @@ -2305,10 +2318,22 @@ void R_StoreWallRange(INT32 start, INT32 stop) /// don't use texture offset for splats rw_offset2 = rw_offset + curline->offset; + + // Per-texture scaling, offsetting. + topxscale = sidedef->scalex_top; + midxscale = sidedef->scalex_mid; + botxscale = sidedef->scalex_bot; + topyscale = sidedef->scaley_top; + midyscale = sidedef->scaley_mid; + botyscale = sidedef->scaley_bot; + rw_offset_top = FixedMul(rw_offset + curline->offset, topxscale) + sidedef->textureoffset + sidedef->offsetx_top; + rw_offset_mid = FixedMul(rw_offset + curline->offset, midxscale) + sidedef->textureoffset + sidedef->offsetx_mid; + rw_offset_bot = FixedMul(rw_offset + curline->offset, botxscale) + sidedef->textureoffset + sidedef->offsetx_bot; + rw_distance_scalex_top = FixedMul(rw_distance, topxscale); + rw_distance_scalex_mid = FixedMul(rw_distance, midxscale); + rw_distance_scalex_bot = FixedMul(rw_distance, botxscale); + rw_offset += sidedef->textureoffset + curline->offset; - rw_offset_top = sidedef->offsetx_top; - rw_offset_mid = sidedef->offsetx_mid; - rw_offset_bot = sidedef->offsetx_bot; rw_centerangle = ANGLE_90 + viewangle - rw_normalangle; // calculate light table From 5bee4e1cd881703d7727b6261c9a567db8b9f18f Mon Sep 17 00:00:00 2001 From: spherallic Date: Wed, 28 Jun 2023 18:44:23 +0200 Subject: [PATCH 048/227] Don't scale texture offsets in Software --- src/r_segs.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/r_segs.c b/src/r_segs.c index 0a23699d2..768c4be81 100644 --- a/src/r_segs.c +++ b/src/r_segs.c @@ -2326,9 +2326,9 @@ void R_StoreWallRange(INT32 start, INT32 stop) topyscale = sidedef->scaley_top; midyscale = sidedef->scaley_mid; botyscale = sidedef->scaley_bot; - rw_offset_top = FixedMul(rw_offset + curline->offset, topxscale) + sidedef->textureoffset + sidedef->offsetx_top; - rw_offset_mid = FixedMul(rw_offset + curline->offset, midxscale) + sidedef->textureoffset + sidedef->offsetx_mid; - rw_offset_bot = FixedMul(rw_offset + curline->offset, botxscale) + sidedef->textureoffset + sidedef->offsetx_bot; + rw_offset_top = FixedMul(rw_offset + curline->offset + sidedef->textureoffset + sidedef->offsetx_top, topxscale); + rw_offset_mid = FixedMul(rw_offset + curline->offset + sidedef->textureoffset + sidedef->offsetx_mid, midxscale); + rw_offset_bot = FixedMul(rw_offset + curline->offset + sidedef->textureoffset + sidedef->offsetx_bot, botxscale); rw_distance_scalex_top = FixedMul(rw_distance, topxscale); rw_distance_scalex_mid = FixedMul(rw_distance, midxscale); rw_distance_scalex_bot = FixedMul(rw_distance, botxscale); From 4c4584f9d1e95a63eb82d76d16182ed9b9d76bfb Mon Sep 17 00:00:00 2001 From: spherallic Date: Wed, 28 Jun 2023 21:04:34 +0200 Subject: [PATCH 049/227] Fix Y offsets on scaled textures in OpenGL --- src/hardware/hw_main.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index d1ec05247..a8992a28d 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -1146,18 +1146,20 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom // check TOP TEXTURE if ((worldhighslope < worldtopslope || worldhigh < worldtop) && gl_toptexture) { + fixed_t texheight = FixedDiv(textureheight[gl_toptexture], gl_sidedef->scaley_top); + // PEGGING if (gl_linedef->flags & ML_DONTPEGTOP) texturevpeg = 0; else if (gl_linedef->flags & ML_SKEWTD) - texturevpeg = worldhigh + textureheight[gl_toptexture] - worldtop; + texturevpeg = worldhigh + texheight - worldtop; else - texturevpeg = gl_backsector->ceilingheight + textureheight[gl_toptexture] - gl_frontsector->ceilingheight; + texturevpeg = gl_backsector->ceilingheight + texheight - gl_frontsector->ceilingheight; texturevpeg += gl_sidedef->rowoffset + gl_sidedef->offsety_top; // This is so that it doesn't overflow and screw up the wall, it doesn't need to go higher than the texture's height anyway - texturevpeg %= textureheight[gl_toptexture]; + texturevpeg %= texheight; grTex = HWR_GetTexture(gl_toptexture); xscale = FIXED_TO_FLOAT(gl_sidedef->scalex_top) * grTex->scaleX; @@ -1219,7 +1221,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom texturevpeg += gl_sidedef->rowoffset + gl_sidedef->offsety_bot; // This is so that it doesn't overflow and screw up the wall, it doesn't need to go higher than the texture's height anyway - texturevpeg %= textureheight[gl_bottomtexture]; + texturevpeg %= FixedDiv(textureheight[gl_bottomtexture], gl_sidedef->scaley_bot); grTex = HWR_GetTexture(gl_bottomtexture); xscale = FIXED_TO_FLOAT(gl_sidedef->scalex_bot) * grTex->scaleX; From cb30411e4962f6843be8cdd8c3b3fb31827e7ec6 Mon Sep 17 00:00:00 2001 From: spherallic Date: Thu, 29 Jun 2023 09:34:00 +0200 Subject: [PATCH 050/227] Fix broken two-sided midtextures in Software --- src/r_segs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/r_segs.c b/src/r_segs.c index 768c4be81..5bb4f4836 100644 --- a/src/r_segs.c +++ b/src/r_segs.c @@ -1466,7 +1466,7 @@ static void R_RenderSegLoop (void) { // save texturecol // for backdrawing of masked mid texture - maskedtexturecol[rw_x] = (INT16)(texturecolumn + (rw_offset_mid>>FRACBITS)); + maskedtexturecol[rw_x] = (INT16)texturecolumn_mid; if (maskedtextureheight != NULL) { maskedtextureheight[rw_x] = (curline->linedef->flags & ML_MIDPEG) ? From cc852ef0fe72dd13ea7e254b7a5018ad9899919f Mon Sep 17 00:00:00 2001 From: spherallic Date: Thu, 29 Jun 2023 11:46:12 +0200 Subject: [PATCH 051/227] Fix Y scaling on 2-sided midtextures in OpenGL --- src/hardware/hw_main.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index a8992a28d..2db109921 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -1272,6 +1272,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom if (gl_midtexture && HWR_BlendMidtextureSurface(&Surf)) { sector_t *front, *back; + fixed_t texheight = FixedDiv(textureheight[gl_midtexture], gl_sidedef->scaley_mid); INT32 repeats; if (gl_linedef->frontsector->heightsec != -1) @@ -1300,8 +1301,8 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom else low = back->floorheight; - repeats = (high - low) / textureheight[gl_midtexture]; - if ((high - low) % textureheight[gl_midtexture]) + repeats = (high - low) / texheight; + if ((high - low) % texheight) repeats++; // tile an extra time to fill the gap -- Monster Iestyn } else @@ -1324,7 +1325,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom popenbottom = popenbottomslope = back->floorheight; } else - { + { popentop = min(worldtop, worldhigh); popenbottom = max(worldbottom, worldlow); popentopslope = min(worldtopslope, worldhighslope); @@ -1332,7 +1333,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom } // Find the wall's coordinates - fixed_t midtexheight = textureheight[gl_midtexture] * repeats; + fixed_t midtexheight = texheight * repeats; // Texture is not skewed if (gl_linedef->flags & ML_NOSKEW) From cffce823711af827f411a89b49d064994cea52a3 Mon Sep 17 00:00:00 2001 From: spherallic Date: Thu, 29 Jun 2023 14:19:16 +0200 Subject: [PATCH 052/227] Support FOF texture scaling in OpenGL --- src/hardware/hw_main.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 2db109921..7ec0f76c2 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -1658,8 +1658,8 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom } grTex = HWR_GetTexture(texnum); - xscale = FIXED_TO_FLOAT(gl_sidedef->scalex_mid) * grTex->scaleX; - yscale = FIXED_TO_FLOAT(gl_sidedef->scaley_mid) * grTex->scaleY; + xscale = FIXED_TO_FLOAT(sides[rover->master->sidenum[0]].scalex_mid) * grTex->scaleX; + yscale = FIXED_TO_FLOAT(sides[rover->master->sidenum[0]].scaley_mid) * grTex->scaleY; if (!slopeskew) // no skewing { @@ -1796,8 +1796,8 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom else { grTex = HWR_GetTexture(texnum); - xscale = FIXED_TO_FLOAT(gl_sidedef->scalex_mid) * grTex->scaleX; - yscale = FIXED_TO_FLOAT(gl_sidedef->scaley_mid) * grTex->scaleY; + xscale = FIXED_TO_FLOAT(sides[rover->master->sidenum[0]].scalex_mid) * grTex->scaleX; + yscale = FIXED_TO_FLOAT(sides[rover->master->sidenum[0]].scaley_mid) * grTex->scaleY; if (newline) { From d844c06f9bbf2dadb05af00bc7d6e10757e10501 Mon Sep 17 00:00:00 2001 From: katsy Date: Sat, 15 Jul 2023 16:24:08 -0500 Subject: [PATCH 053/227] Fix roll braking assuming player angle is camera angle --- src/p_user.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_user.c b/src/p_user.c index 1adf6bc3f..6c2f0fe90 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -5949,7 +5949,7 @@ static void P_3dMovement(player_t *player) // Monster Iestyn - 04-11-13 // Quadrants are stupid, excessive and broken, let's do this a much simpler way! // Get delta angle from rmom angle and player angle first - dangle = R_PointToAngle2(0,0, player->rmomx, player->rmomy) - player->mo->angle; + dangle = R_PointToAngle2(0,0, player->rmomx, player->rmomy) - (cmd->angleturn<<16); if (dangle > ANGLE_180) //flip to keep to one side dangle = InvAngle(dangle); From 285f7acbed9346444cc496b32dd52c0b28506670 Mon Sep 17 00:00:00 2001 From: SMS Alfredo <65426124+SMS-Alfredo@users.noreply.github.com> Date: Tue, 25 Jul 2023 14:18:36 -0500 Subject: [PATCH 054/227] Enable Pandora and Level Select with Devmode / Always allow Level Select if unlocked --- src/d_netcmd.c | 7 ++++--- src/m_cond.c | 3 ++- src/m_menu.c | 21 ++++++++++++++++----- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 72d020c22..f81a6abde 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -1905,7 +1905,7 @@ static void Command_Map_f(void) return; } - option_force = COM_CheckPartialParm("-f"); + option_force = COM_CheckPartialParm("-f") || (cv_debug || devparm); option_gametype = COM_CheckPartialParm("-g"); newresetplayers = ! COM_CheckParm("-noresetplayers"); @@ -1913,7 +1913,8 @@ static void Command_Map_f(void) !( netgame || multiplayer ) && !( usedCheats ); - if (wouldSetCheats && !option_force) + if (wouldSetCheats && !option_force + && !M_SecretUnlocked(SECRET_LEVELSELECT, serverGamedata)) { /* May want to be more descriptive? */ CONS_Printf(M_GetText("Sorry, level change disabled in single player.\n")); @@ -1967,7 +1968,7 @@ static void Command_Map_f(void) return; } - if (wouldSetCheats && option_force) + if (wouldSetCheats) { G_SetUsedCheats(false); } diff --git a/src/m_cond.c b/src/m_cond.c index b21778c69..86bae4933 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -467,7 +467,8 @@ UINT8 M_SecretUnlocked(INT32 type, gamedata_t *data) UINT8 M_MapLocked(INT32 mapnum, gamedata_t *data) { - if (!mapheaderinfo[mapnum-1] || mapheaderinfo[mapnum-1]->unlockrequired < 0) + if (!mapheaderinfo[mapnum-1] || mapheaderinfo[mapnum-1]->unlockrequired < 0 + || cv_debug || devparm) { return false; } diff --git a/src/m_menu.c b/src/m_menu.c index 12003f945..7618a2c4a 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -3673,9 +3673,12 @@ void M_StartControlPanel(void) } else if (!(netgame || multiplayer)) // Single Player { + // Devmode unlocks Pandora's Box in the pause menu + boolean pandora = ((M_SecretUnlocked(SECRET_PANDORA, serverGamedata) || cv_debug || devparm) && !marathonmode); + if (gamestate != GS_LEVEL || ultimatemode) // intermission, so gray out stuff. { - SPauseMenu[spause_pandora].status = (M_SecretUnlocked(SECRET_PANDORA, serverGamedata)) ? (IT_GRAYEDOUT) : (IT_DISABLED); + SPauseMenu[spause_pandora].status = (pandora) ? (IT_GRAYEDOUT) : (IT_DISABLED); SPauseMenu[spause_retry].status = IT_GRAYEDOUT; } else @@ -3684,7 +3687,7 @@ void M_StartControlPanel(void) if (players[consoleplayer].playerstate != PST_LIVE) ++numlives; - SPauseMenu[spause_pandora].status = (M_SecretUnlocked(SECRET_PANDORA, serverGamedata) && !marathonmode) ? (IT_STRING | IT_CALL) : (IT_DISABLED); + SPauseMenu[spause_pandora].status = (pandora) ? (IT_STRING | IT_CALL) : (IT_DISABLED); // The list of things that can disable retrying is (was?) a little too complex // for me to want to use the short if statement syntax @@ -3695,7 +3698,11 @@ void M_StartControlPanel(void) } // We can always use level select though. :33 - SPauseMenu[spause_levelselect].status = (maplistoption != 0) ? (IT_STRING | IT_CALL) : (IT_DISABLED); + // Guarantee it if we have either it unlocked or devmode is enabled + if ((maplistoption != 0 || M_SecretUnlocked(SECRET_LEVELSELECT, serverGamedata) || cv_debug || devparm) && !marathonmode) + SPauseMenu[spause_levelselect].status = (IT_STRING | IT_CALL); + else + SPauseMenu[spause_levelselect].status = (IT_DISABLED); // And emblem hints. SPauseMenu[spause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS, clientGamedata) && !marathonmode) ? (IT_STRING | IT_CALL) : (IT_DISABLED); @@ -5091,7 +5098,8 @@ static boolean M_CanShowLevelOnPlatter(INT32 mapnum, INT32 gt) return false; case LLM_LEVELSELECT: - if (!(mapheaderinfo[mapnum]->levelselect & maplistoption)) + if (!(mapheaderinfo[mapnum]->levelselect & maplistoption) + && !(cv_debug || devparm)) //Allow ALL levels in devmode! return false; return true; @@ -7655,9 +7663,12 @@ static void M_PauseLevelSelect(INT32 choice) SP_PauseLevelSelectDef.prevMenu = currentMenu; levellistmode = LLM_LEVELSELECT; - // maplistoption is NOT specified, so that this + // maplistoption is only specified if not set already + // and we have the level select unlocked so that it // transfers the level select list from the menu // used to enter the game to the pause menu. + if (maplistoption == 0 && M_SecretUnlocked(SECRET_LEVELSELECT, serverGamedata)) + maplistoption = 1; if (!M_PrepareLevelPlatter(-1, true)) { From 4ea2887d7fa41cd753b46b05de6920b9d84d3fbc Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 4 Aug 2023 00:31:51 -0300 Subject: [PATCH 055/227] Lua colorlib: extracolormap support --- src/CMakeLists.txt | 1 + src/lua_baselib.c | 22 +++ src/lua_colorlib.c | 335 +++++++++++++++++++++++++++++++++++++++++++++ src/lua_libs.h | 2 + src/lua_maplib.c | 5 + src/lua_script.c | 1 + src/m_misc.c | 14 ++ src/m_misc.h | 3 + src/p_local.h | 4 + src/p_map.c | 32 +++++ src/r_data.c | 55 ++++---- src/r_data.h | 1 + src/r_things.c | 42 +----- 13 files changed, 457 insertions(+), 60 deletions(-) create mode 100644 src/lua_colorlib.c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b926b3b7a..92cc11637 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -105,6 +105,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 lua_blockmaplib.c lua_hudlib.c lua_hudlib_drawlist.c + lua_colorlib.c lua_inputlib.c ) diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 915669bb2..ecebae4ac 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -213,6 +213,7 @@ static const struct { {META_HUDINFO, "hudinfo_t"}, {META_PATCH, "patch_t"}, {META_COLORMAP, "colormap"}, + {META_EXTRACOLORMAP,"extracolormap_t"}, {META_CAMERA, "camera_t"}, {META_ACTION, "action"}, @@ -1928,6 +1929,26 @@ static int lib_pCeilingzAtPos(lua_State *L) return 1; } +static int lib_pGetSectorColormapAt(lua_State *L) +{ + sector_t *sector = NULL; + if (!lua_isnone(L, 1) && lua_isuserdata(L, 1)) + sector = *((sector_t **)luaL_checkudata(L, 1, META_SECTOR)); + fixed_t x = luaL_checkfixed(L, 2); + fixed_t y = luaL_checkfixed(L, 3); + fixed_t z = luaL_checkfixed(L, 4); + INLEVEL + if (!sector) + return LUA_ErrInvalid(L, "sector_t"); + extracolormap_t *exc; + if (sector) + exc = P_GetColormapFromSectorAt(sector, x, y, z); + else + exc = P_GetSectorColormapAt(x, y, z); + LUA_PushUserdata(L, exc, META_EXTRACOLORMAP); + return 1; +} + static int lib_pDoSpring(lua_State *L) { mobj_t *spring = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ)); @@ -4151,6 +4172,7 @@ static luaL_Reg lib[] = { {"P_RadiusAttack",lib_pRadiusAttack}, {"P_FloorzAtPos",lib_pFloorzAtPos}, {"P_CeilingzAtPos",lib_pCeilingzAtPos}, + {"P_GetSectorColormapAt",lib_pGetSectorColormapAt}, {"P_DoSpring",lib_pDoSpring}, {"P_TryCameraMove", lib_pTryCameraMove}, {"P_TeleportCameraMove", lib_pTeleportCameraMove}, diff --git a/src/lua_colorlib.c b/src/lua_colorlib.c new file mode 100644 index 000000000..ee7074e84 --- /dev/null +++ b/src/lua_colorlib.c @@ -0,0 +1,335 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2021-2022 by "Lactozilla". +// Copyright (C) 2014-2023 by Sonic Team Junior. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file lua_colorlib.c +/// \brief color and colormap libraries for Lua scripting + +#include "doomdef.h" +#include "fastcmp.h" +#include "r_data.h" + +#include "lua_script.h" +#include "lua_libs.h" + +static UINT8 GetRGBAColorsFromTable(lua_State *L, UINT32 index, UINT8 *rgba, boolean useAlpha) +{ + UINT8 num = 0; + + lua_pushnil(L); + + while (lua_next(L, index)) { + lua_Integer i = 0; + const char *field = NULL; + if (lua_isnumber(L, -2)) + i = lua_tointeger(L, -2); + else + field = luaL_checkstring(L, -2); + +#define CHECKFIELD(p, c) (i == p || (field && fastcmp(field, c))) +#define RGBASET(p, c) { \ + INT32 color = luaL_checkinteger(L, -1); \ + if (color < 0 || color > 255) \ + luaL_error(L, c " channel %d out of range (0 - 255)", color); \ + rgba[p] = (UINT8)color; \ + num++; \ + } + + if (CHECKFIELD(1, "r")) { + RGBASET(0, "red color"); + } else if (CHECKFIELD(2, "g")) { + RGBASET(1, "green color"); + } else if (CHECKFIELD(3, "b")) { + RGBASET(2, "blue color"); + } else if (useAlpha && CHECKFIELD(4, "a")) { + RGBASET(3, "alpha"); + } + +#undef CHECKFIELD +#undef RGBASET + + lua_pop(L, 1); + } + + return num; +} + +#define IS_HEX_CHAR(x) ((x >= '0' && x <= '9') || (x >= 'a' && x <= 'f') || (x >= 'A' && x <= 'F')) +#define ARE_HEX_CHARS(str, i) IS_HEX_CHAR(str[i]) && IS_HEX_CHAR(str[i + 1]) + +static UINT32 hex2int(char x) +{ + if (x >= '0' && x <= '9') + return x - '0'; + else if (x >= 'a' && x <= 'f') + return x - 'a' + 10; + else if (x >= 'A' && x <= 'F') + return x - 'A' + 10; + + return 0; +} + +static UINT8 ParseHTMLColor(const char *str, UINT8 *rgba, size_t numc) +{ + const char *hex = str; + + if (hex[0] == '#') + hex++; + else if (!IS_HEX_CHAR(hex[0])) + return 0; + + size_t len = strlen(hex); + + if (len == 3) + { + // Shorthand like #09C + for (unsigned i = 0; i < 3; i++) + { + if (!IS_HEX_CHAR(hex[i])) + return 0; + + UINT32 hx = hex2int(hex[i]); + *rgba++ = (hx * 16) + hx; + } + + return 3; + } + else if (len == 6 || len == 8) + { + if (numc != 4) + len = 6; + + // A triplet like #0099CC + for (unsigned i = 0; i < len; i += 2) + { + if (!ARE_HEX_CHARS(hex, i)) + return false; + + *rgba++ = (hex2int(hex[i]) * 16) + hex2int(hex[i + 1]); + } + + return len; + } + + return 0; +} + +///////////////////////// +// extracolormap userdata +///////////////////////// + +enum extracolormap_e { + extracolormap_r = 0, + extracolormap_g, + extracolormap_b, + extracolormap_a, + extracolormap_rgba, + extracolormap_fade_r, + extracolormap_fade_g, + extracolormap_fade_b, + extracolormap_fade_a, + extracolormap_fade_rgba, + extracolormap_fade_start, + extracolormap_fade_end, + extracolormap_colormap +}; + +static const char *const extracolormap_opt[] = { + "r", + "g", + "b", + "a", + "rgba", + "fade_r", + "fade_g", + "fade_b", + "fade_a", + "fade_rgba", + "fade_start", + "fade_end", + "colormap", + NULL}; + +#define ALPHA_SCALE_FACTOR 102 // (255 / 25) * 10 +#define SCALE_ALPHA_UP(alpha) (((alpha) * ALPHA_SCALE_FACTOR) / 10) +#define SCALE_ALPHA_DOWN(alpha) (((alpha) * 10) / ALPHA_SCALE_FACTOR) + +static int extracolormap_get(lua_State *L) +{ + extracolormap_t *exc = *((extracolormap_t **)luaL_checkudata(L, 1, META_EXTRACOLORMAP)); + enum extracolormap_e field = luaL_checkoption(L, 2, NULL, extracolormap_opt); + + switch (field) + { + case extracolormap_r: + lua_pushinteger(L, R_GetRgbaR(exc->rgba)); + break; + case extracolormap_g: + lua_pushinteger(L, R_GetRgbaG(exc->rgba)); + break; + case extracolormap_b: + lua_pushinteger(L, R_GetRgbaB(exc->rgba)); + break; + case extracolormap_a: + lua_pushinteger(L, SCALE_ALPHA_UP(R_GetRgbaA(exc->rgba))); + break; + case extracolormap_rgba: + lua_pushinteger(L, R_GetRgbaR(exc->rgba)); + lua_pushinteger(L, R_GetRgbaG(exc->rgba)); + lua_pushinteger(L, R_GetRgbaB(exc->rgba)); + lua_pushinteger(L, SCALE_ALPHA_UP(R_GetRgbaA(exc->rgba))); + return 4; + case extracolormap_fade_r: + lua_pushinteger(L, R_GetRgbaR(exc->fadergba)); + break; + case extracolormap_fade_g: + lua_pushinteger(L, R_GetRgbaG(exc->fadergba)); + break; + case extracolormap_fade_b: + lua_pushinteger(L, R_GetRgbaB(exc->fadergba)); + break; + case extracolormap_fade_a: + lua_pushinteger(L, SCALE_ALPHA_UP(R_GetRgbaA(exc->fadergba))); + break; + case extracolormap_fade_rgba: + lua_pushinteger(L, R_GetRgbaR(exc->fadergba)); + lua_pushinteger(L, R_GetRgbaG(exc->fadergba)); + lua_pushinteger(L, R_GetRgbaB(exc->fadergba)); + lua_pushinteger(L, SCALE_ALPHA_UP(R_GetRgbaA(exc->fadergba))); + return 4; + case extracolormap_fade_start: + lua_pushinteger(L, exc->fadestart); + break; + case extracolormap_fade_end: + lua_pushinteger(L, exc->fadeend); + break; + case extracolormap_colormap: + LUA_PushUserdata(L, exc->colormap, META_COLORMAP); + break; + } + return 1; +} + +static void GetExtraColormapRGBA(lua_State *L, UINT8 *rgba) +{ + rgba[3] = SCALE_ALPHA_UP(rgba[3]); + + if (lua_type(L, 3) == LUA_TSTRING) + { + const char *str = lua_tostring(L, 3); + UINT8 parsed = ParseHTMLColor(str, rgba, 4); + if (!parsed) + luaL_error(L, "Malformed HTML color '%s'", str); + } + else + GetRGBAColorsFromTable(L, 3, rgba, true); + + rgba[3] = SCALE_ALPHA_DOWN(rgba[3]); +} + +static int extracolormap_set(lua_State *L) +{ + extracolormap_t *exc = *((extracolormap_t **)luaL_checkudata(L, 1, META_EXTRACOLORMAP)); + enum extracolormap_e field = luaL_checkoption(L, 2, NULL, extracolormap_opt); + + UINT8 r = R_GetRgbaR(exc->rgba); + UINT8 g = R_GetRgbaG(exc->rgba); + UINT8 b = R_GetRgbaB(exc->rgba); + UINT8 a = R_GetRgbaA(exc->rgba); + + UINT8 fr = R_GetRgbaR(exc->fadergba); + UINT8 fg = R_GetRgbaG(exc->fadergba); + UINT8 fb = R_GetRgbaB(exc->fadergba); + UINT8 fa = R_GetRgbaA(exc->fadergba); + + UINT8 rgba[4]; + + INT32 old_rgba = exc->rgba, old_fade_rgba = exc->fadergba; // It's not unsigned? + UINT8 old_fade_start = exc->fadestart, old_fade_end = exc->fadeend; + +#define val luaL_checkinteger(L, 3) + + switch(field) + { + case extracolormap_r: + exc->rgba = R_PutRgbaRGBA(val, g, b, a); + break; + case extracolormap_g: + exc->rgba = R_PutRgbaRGBA(r, val, b, a); + break; + case extracolormap_b: + exc->rgba = R_PutRgbaRGBA(r, g, val, a); + break; + case extracolormap_a: + exc->rgba = R_PutRgbaRGBA(r, g, b, SCALE_ALPHA_DOWN(val)); + break; + case extracolormap_rgba: + rgba[0] = r; + rgba[1] = g; + rgba[2] = b; + rgba[3] = a; + GetExtraColormapRGBA(L, rgba); + exc->rgba = R_PutRgbaRGBA(rgba[0], rgba[1], rgba[2], rgba[3]); + break; + case extracolormap_fade_r: + exc->fadergba = R_PutRgbaRGBA(val, fg, fb, fa); + break; + case extracolormap_fade_g: + exc->fadergba = R_PutRgbaRGBA(fr, val, fb, fa); + break; + case extracolormap_fade_b: + exc->fadergba = R_PutRgbaRGBA(fr, fg, val, fa); + break; + case extracolormap_fade_a: + exc->fadergba = R_PutRgbaRGBA(fr, fg, fb, SCALE_ALPHA_DOWN(val)); + break; + case extracolormap_fade_rgba: + rgba[0] = fr; + rgba[1] = fg; + rgba[2] = fb; + rgba[3] = fa; + GetExtraColormapRGBA(L, rgba); + exc->fadergba = R_PutRgbaRGBA(rgba[0], rgba[1], rgba[2], rgba[3]); + break; + case extracolormap_fade_start: + if (val > 31) + return luaL_error(L, "fade start %d out of range (0 - 31)", val); + exc->fadestart = val; + break; + case extracolormap_fade_end: + if (val > 31) + return luaL_error(L, "fade end %d out of range (0 - 31)", val); + exc->fadeend = val; + break; + case extracolormap_colormap: + return luaL_error(L, LUA_QL("extracolormap_t") " field " LUA_QS " should not be set directly.", extracolormap_opt[field]); + } + +#undef val + + if (exc->rgba != old_rgba + || exc->fadergba != old_fade_rgba + || exc->fadestart != old_fade_start + || exc->fadeend != old_fade_end) + R_GenerateLightTable(exc, true); + + return 0; +} + +int LUA_ColorLib(lua_State *L) +{ + luaL_newmetatable(L, META_EXTRACOLORMAP); + lua_pushcfunction(L, extracolormap_get); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, extracolormap_set); + lua_setfield(L, -2, "__newindex"); + lua_pop(L, 1); + + return 0; +} diff --git a/src/lua_libs.h b/src/lua_libs.h index 7f8d21f38..b520bb983 100644 --- a/src/lua_libs.h +++ b/src/lua_libs.h @@ -84,6 +84,7 @@ extern boolean mousegrabbedbylua; #define META_HUDINFO "HUDINFO_T*" #define META_PATCH "PATCH_T*" #define META_COLORMAP "COLORMAP" +#define META_EXTRACOLORMAP "EXTRACOLORMAP_T" #define META_CAMERA "CAMERA_T*" #define META_ACTION "ACTIONF_T*" @@ -111,4 +112,5 @@ int LUA_TagLib(lua_State *L); int LUA_PolyObjLib(lua_State *L); int LUA_BlockmapLib(lua_State *L); int LUA_HudLib(lua_State *L); +int LUA_ColorLib(lua_State *L); int LUA_InputLib(lua_State *L); diff --git a/src/lua_maplib.c b/src/lua_maplib.c index 3d95cdb75..440d1fce4 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -57,6 +57,7 @@ enum sector_e { sector_ffloors, sector_fslope, sector_cslope, + sector_extracolormap, sector_flags, sector_specialflags, sector_damagetype, @@ -95,6 +96,7 @@ static const char *const sector_opt[] = { "ffloors", "f_slope", "c_slope", + "extracolormap", "flags", "specialflags", "damagetype", @@ -751,6 +753,9 @@ static int sector_get(lua_State *L) case sector_cslope: // c_slope LUA_PushUserdata(L, sector->c_slope, META_SLOPE); return 1; + case sector_extracolormap: // extra_colormap + LUA_PushUserdata(L, sector->extra_colormap, META_EXTRACOLORMAP); + return 1; case sector_flags: // flags lua_pushinteger(L, sector->flags); return 1; diff --git a/src/lua_script.c b/src/lua_script.c index 6a5982006..8b081c587 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -58,6 +58,7 @@ static lua_CFunction liblist[] = { LUA_PolyObjLib, // polyobj_t LUA_BlockmapLib, // blockmap stuff LUA_HudLib, // HUD stuff + LUA_ColorLib, // general color functions LUA_InputLib, // inputs NULL }; diff --git a/src/m_misc.c b/src/m_misc.c index f547f5c41..68bc52055 100644 --- a/src/m_misc.c +++ b/src/m_misc.c @@ -2805,3 +2805,17 @@ boolean M_IsStringEmpty(const char *s) return true; } + +// Rounds off floating numbers and checks for 0 - 255 bounds +int M_RoundUp(double number) +{ + if (number > 255.0l) + return 255; + if (number < 0.0l) + return 0; + + if ((int)number <= (int)(number - 0.5f)) + return (int)number + 1; + + return (int)number; +} diff --git a/src/m_misc.h b/src/m_misc.h index 8cad7ba9a..753991e70 100644 --- a/src/m_misc.h +++ b/src/m_misc.h @@ -112,6 +112,9 @@ boolean M_IsStringEmpty(const char *s); // counting bits, for weapon ammo code, usually FUNCMATH UINT8 M_CountBits(UINT32 num, UINT8 size); +// Rounds off floating numbers and checks for 0 - 255 bounds +int M_RoundUp(double number); + #include "w_wad.h" extern char configfile[MAX_WADPATH]; diff --git a/src/p_local.h b/src/p_local.h index 563e257d8..7110d3494 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -438,6 +438,10 @@ boolean PIT_PushableMoved(mobj_t *thing); boolean P_DoSpring(mobj_t *spring, mobj_t *object); +INT32 P_GetSectorLightAt(sector_t *sector, fixed_t x, fixed_t y, fixed_t z); +extracolormap_t *P_GetColormapFromSectorAt(sector_t *sector, fixed_t x, fixed_t y, fixed_t z); +extracolormap_t *P_GetSectorColormapAt(fixed_t x, fixed_t y, fixed_t z); + // // P_SETUP // diff --git a/src/p_map.c b/src/p_map.c index 132a3cf85..9ffe238e9 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -5067,3 +5067,35 @@ fixed_t P_CeilingzAtPos(fixed_t x, fixed_t y, fixed_t z, fixed_t height) return ceilingz; } + +INT32 P_GetSectorLightAt(sector_t *sector, fixed_t x, fixed_t y, fixed_t z) +{ + if (!sector->numlights) + return -1; + + INT32 light = sector->numlights - 1; + + // R_GetPlaneLight won't work on sloped lights! + for (INT32 lightnum = 1; lightnum < sector->numlights; lightnum++) { + fixed_t h = P_GetLightZAt(§or->lightlist[lightnum], x, y); + if (h <= z) { + light = lightnum - 1; + break; + } + } + + return light; +} + +extracolormap_t *P_GetColormapFromSectorAt(sector_t *sector, fixed_t x, fixed_t y, fixed_t z) +{ + if (sector->numlights) + return *sector->lightlist[P_GetSectorLightAt(sector, x, y, z)].extra_colormap; + else + return sector->extra_colormap; +} + +extracolormap_t *P_GetSectorColormapAt(fixed_t x, fixed_t y, fixed_t z) +{ + return P_GetColormapFromSectorAt(R_PointInSubsector(x, y)->sector, x, y, z); +} diff --git a/src/r_data.c b/src/r_data.c index 4b7492f90..1795e7727 100644 --- a/src/r_data.c +++ b/src/r_data.c @@ -692,9 +692,26 @@ extracolormap_t *R_ColormapForName(char *name) // static double deltas[256][3], map[256][3]; -static int RoundUp(double number); +static colorlookup_t lighttable_lut; + +static UINT8 LightTableNearest(UINT8 r, UINT8 g, UINT8 b) +{ + return NearestColor(r, g, b); +} + +static UINT8 LightTableNearest_LUT(UINT8 r, UINT8 g, UINT8 b) +{ + return GetColorLUT(&lighttable_lut, r, g, b); +} lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap) +{ + extra_colormap->colormap = Z_MallocAlign((256 * 34) + 10, PU_LEVEL, NULL, 8); + R_GenerateLightTable(extra_colormap, false); + return extra_colormap->colormap; +} + +void R_GenerateLightTable(extracolormap_t *extra_colormap, boolean uselookup) { double cmaskr, cmaskg, cmaskb, cdestr, cdestg, cdestb; double maskamt = 0, othermask = 0; @@ -711,7 +728,6 @@ lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap) UINT8 fadestart = extra_colormap->fadestart, fadedist = extra_colormap->fadeend - extra_colormap->fadestart; - lighttable_t *lighttable = NULL; size_t i; ///////////////////// @@ -753,6 +769,16 @@ lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap) int p; char *colormap_p; + UINT8 (*NearestColorFunc)(UINT8, UINT8, UINT8); + + if (uselookup) + { + InitColorLUT(&lighttable_lut, pMasterPalette, false); + NearestColorFunc = LightTableNearest_LUT; + } + else + NearestColorFunc = LightTableNearest; + // Initialise the map and delta arrays // map[i] stores an RGB color (as double) for index i, // which is then converted to SRB2's palette later @@ -783,8 +809,7 @@ lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap) // Now allocate memory for the actual colormap array itself! // aligned on 8 bit for asm code - colormap_p = Z_MallocAlign((256 * 34) + 10, PU_LEVEL, NULL, 8); - lighttable = (UINT8 *)colormap_p; + colormap_p = (char *)extra_colormap->colormap; // Calculate the palette index for each palette index, for each light level // (as well as the two unused colormap lines we inherited from Doom) @@ -792,9 +817,9 @@ lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap) { for (i = 0; i < 256; i++) { - *colormap_p = NearestColor((UINT8)RoundUp(map[i][0]), - (UINT8)RoundUp(map[i][1]), - (UINT8)RoundUp(map[i][2])); + *colormap_p = NearestColorFunc((UINT8)M_RoundUp(map[i][0]), + (UINT8)M_RoundUp(map[i][1]), + (UINT8)M_RoundUp(map[i][2])); colormap_p++; if ((UINT32)p < fadestart) @@ -818,8 +843,6 @@ lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap) } } } - - return lighttable; } extracolormap_t *R_CreateColormapFromLinedef(char *p1, char *p2, char *p3) @@ -1133,20 +1156,6 @@ UINT8 NearestPaletteColor(UINT8 r, UINT8 g, UINT8 b, RGBA_t *palette) return (UINT8)bestcolor; } -// Rounds off floating numbers and checks for 0 - 255 bounds -static int RoundUp(double number) -{ - if (number > 255.0l) - return 255; - if (number < 0.0l) - return 0; - - if ((int)number <= (int)(number - 0.5f)) - return (int)number + 1; - - return (int)number; -} - #ifdef EXTRACOLORMAPLUMPS const char *R_NameForColormap(extracolormap_t *extra_colormap) { diff --git a/src/r_data.h b/src/r_data.h index ef5c967e5..364f85b6d 100644 --- a/src/r_data.h +++ b/src/r_data.h @@ -92,6 +92,7 @@ typedef enum TMCF_OVERRIDE = 1<<13, } textmapcolormapflags_t; +void R_GenerateLightTable(extracolormap_t *extra_colormap, boolean uselookup); lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap); extracolormap_t * R_CreateColormapFromLinedef(char *p1, char *p2, char *p3); extracolormap_t* R_CreateColormap(INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 flags); diff --git a/src/r_things.c b/src/r_things.c index 90b80dda8..a0b49072e 100644 --- a/src/r_things.c +++ b/src/r_things.c @@ -1306,8 +1306,8 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale, patch_t *patch; fixed_t xscale, yscale, shadowxscale, shadowyscale, shadowskew, x1, x2; INT32 heightsec, phs; - INT32 light = 0; - fixed_t scalemul; UINT8 trans; + fixed_t scalemul; + UINT8 trans; fixed_t floordiff; fixed_t groundz; pslope_t *groundslope; @@ -1425,27 +1425,7 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale, if (thing->renderflags & RF_NOCOLORMAPS) shadow->extra_colormap = NULL; else - { - if (thing->subsector->sector->numlights) - { - INT32 lightnum; - light = thing->subsector->sector->numlights - 1; - - // R_GetPlaneLight won't work on sloped lights! - for (lightnum = 1; lightnum < thing->subsector->sector->numlights; lightnum++) { - fixed_t h = P_GetLightZAt(&thing->subsector->sector->lightlist[lightnum], interp.x, interp.y); - if (h <= shadow->gzt) { - light = lightnum - 1; - break; - } - } - } - - if (thing->subsector->sector->numlights) - shadow->extra_colormap = *thing->subsector->sector->lightlist[light].extra_colormap; - else - shadow->extra_colormap = thing->subsector->sector->extra_colormap; - } + shadow->extra_colormap = P_GetColormapFromSectorAt(thing->subsector->sector, interp.x, interp.y, shadow->gzt); shadow->transmap = R_GetTranslucencyTable(trans + 1); shadow->colormap = scalelight[0][0]; // full dark! @@ -2132,21 +2112,9 @@ static void R_ProjectSprite(mobj_t *thing) if (thing->subsector->sector->numlights) { - INT32 lightnum; - fixed_t top = (splat) ? gz : gzt; - light = thing->subsector->sector->numlights - 1; - - // R_GetPlaneLight won't work on sloped lights! - for (lightnum = 1; lightnum < thing->subsector->sector->numlights; lightnum++) { - fixed_t h = P_GetLightZAt(&thing->subsector->sector->lightlist[lightnum], interp.x, interp.y); - if (h <= top) { - light = lightnum - 1; - break; - } - } - //light = R_GetPlaneLight(thing->subsector->sector, gzt, false); - lightnum = (*thing->subsector->sector->lightlist[light].lightlevel >> LIGHTSEGSHIFT); + light = P_GetSectorLightAt(thing->subsector->sector, interp.x, interp.y, splat ? gz : gzt); + INT32 lightnum = (*thing->subsector->sector->lightlist[light].lightlevel >> LIGHTSEGSHIFT); if (lightnum < 0) spritelights = scalelight[0]; else if (lightnum >= LIGHTLEVELS) From b670f97ed1512655c7bd0463fc54b9f06106e40d Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 4 Aug 2023 00:36:11 -0300 Subject: [PATCH 056/227] Add lighttable_t support --- src/lua_baselib.c | 1 + src/lua_colorlib.c | 33 ++++++++++++++++++++++++++++++++- src/lua_libs.h | 3 ++- src/r_defs.h | 3 +++ src/r_draw.c | 2 -- 5 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/lua_baselib.c b/src/lua_baselib.c index ecebae4ac..cdda48e8f 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -214,6 +214,7 @@ static const struct { {META_PATCH, "patch_t"}, {META_COLORMAP, "colormap"}, {META_EXTRACOLORMAP,"extracolormap_t"}, + {META_LIGHTTABLE, "lighttable_t"}, {META_CAMERA, "camera_t"}, {META_ACTION, "action"}, diff --git a/src/lua_colorlib.c b/src/lua_colorlib.c index ee7074e84..a963a6a6b 100644 --- a/src/lua_colorlib.c +++ b/src/lua_colorlib.c @@ -209,7 +209,7 @@ static int extracolormap_get(lua_State *L) lua_pushinteger(L, exc->fadeend); break; case extracolormap_colormap: - LUA_PushUserdata(L, exc->colormap, META_COLORMAP); + LUA_PushUserdata(L, exc->colormap, META_LIGHTTABLE); break; } return 1; @@ -321,6 +321,29 @@ static int extracolormap_set(lua_State *L) return 0; } +static int lighttable_get(lua_State *L) +{ + void **userdata; + + lighttable_t *table = *((lighttable_t **)luaL_checkudata(L, 1, META_LIGHTTABLE)); + UINT32 row = luaL_checkinteger(L, 2); + if (row < 1 || row > 34) + return luaL_error(L, "lighttable row %d out of range (1 - %d)", row, 34); + + userdata = lua_newuserdata(L, sizeof(void *)); + *userdata = &table[256 * (row - 1)]; + luaL_getmetatable(L, META_COLORMAP); + lua_setmetatable(L, -2); + + return 1; +} + +static int lighttable_len(lua_State *L) +{ + lua_pushinteger(L, NUM_PALETTE_ENTRIES); + return 1; +} + int LUA_ColorLib(lua_State *L) { luaL_newmetatable(L, META_EXTRACOLORMAP); @@ -331,5 +354,13 @@ int LUA_ColorLib(lua_State *L) lua_setfield(L, -2, "__newindex"); lua_pop(L, 1); + luaL_newmetatable(L, META_LIGHTTABLE); + lua_pushcfunction(L, lighttable_get); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, lighttable_len); + lua_setfield(L, -2, "__len"); + lua_pop(L, 1); + return 0; } diff --git a/src/lua_libs.h b/src/lua_libs.h index b520bb983..65d5acb1b 100644 --- a/src/lua_libs.h +++ b/src/lua_libs.h @@ -84,7 +84,8 @@ extern boolean mousegrabbedbylua; #define META_HUDINFO "HUDINFO_T*" #define META_PATCH "PATCH_T*" #define META_COLORMAP "COLORMAP" -#define META_EXTRACOLORMAP "EXTRACOLORMAP_T" +#define META_EXTRACOLORMAP "EXTRACOLORMAP_T*" +#define META_LIGHTTABLE "LIGHTTABLE_T*" #define META_CAMERA "CAMERA_T*" #define META_ACTION "ACTIONF_T*" diff --git a/src/r_defs.h b/src/r_defs.h index a9b9a4a08..1edbba473 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -53,6 +53,9 @@ typedef struct // Could even use more than 32 levels. typedef UINT8 lighttable_t; +#define NUM_PALETTE_ENTRIES 256 +#define DEFAULT_STARTTRANSCOLOR 96 + #define CMF_FADEFULLBRIGHTSPRITES 1 #define CMF_FOG 4 diff --git a/src/r_draw.c b/src/r_draw.c index df9e1a460..671767b21 100644 --- a/src/r_draw.c +++ b/src/r_draw.c @@ -132,8 +132,6 @@ UINT32 nflatxshift, nflatyshift, nflatshiftup, nflatmask; #define RAINBOW_TT_CACHE_INDEX (MAXSKINS + 4) #define BLINK_TT_CACHE_INDEX (MAXSKINS + 5) #define DASHMODE_TT_CACHE_INDEX (MAXSKINS + 6) -#define DEFAULT_STARTTRANSCOLOR 96 -#define NUM_PALETTE_ENTRIES 256 static UINT8 **translationtablecache[MAXSKINS + 7] = {NULL}; UINT8 skincolor_modified[MAXSKINCOLORS]; From ec8757d716e196d5d0f226a1fbfdef6827d287b5 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 4 Aug 2023 05:00:46 -0300 Subject: [PATCH 057/227] Extend extracolormap alpha range --- src/hardware/hw_defs.h | 3 -- src/hardware/hw_main.c | 8 ++--- src/hardware/hw_md2.c | 3 -- src/hardware/r_opengl/r_opengl.c | 2 +- src/lua_colorlib.c | 20 ++++-------- src/r_data.c | 54 ++++++++++++++++---------------- 6 files changed, 38 insertions(+), 52 deletions(-) diff --git a/src/hardware/hw_defs.h b/src/hardware/hw_defs.h index 74c4ed7d2..b02f2824a 100644 --- a/src/hardware/hw_defs.h +++ b/src/hardware/hw_defs.h @@ -276,9 +276,6 @@ struct FSurfaceInfo }; typedef struct FSurfaceInfo FSurfaceInfo; -#define GL_DEFAULTMIX 0x00000000 -#define GL_DEFAULTFOG 0xFF000000 - //Hurdler: added for backward compatibility enum hwdsetspecialstate { diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 36ff86abd..568161425 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -186,8 +186,8 @@ void HWR_Lighting(FSurfaceInfo *Surface, INT32 light_level, extracolormap_t *col RGBA_t poly_color, tint_color, fade_color; poly_color.rgba = 0xFFFFFFFF; - tint_color.rgba = (colormap != NULL) ? (UINT32)colormap->rgba : GL_DEFAULTMIX; - fade_color.rgba = (colormap != NULL) ? (UINT32)colormap->fadergba : GL_DEFAULTFOG; + tint_color.rgba = (colormap != NULL) ? (UINT32)colormap->rgba : 0x00000000; + fade_color.rgba = (colormap != NULL) ? (UINT32)colormap->fadergba : 0xFF000000; // Crappy backup coloring if you can't do shaders if (!HWR_UseShader()) @@ -201,7 +201,7 @@ void HWR_Lighting(FSurfaceInfo *Surface, INT32 light_level, extracolormap_t *col blue = (float)poly_color.s.blue; // 48 is just an arbritrary value that looked relatively okay. - tint_alpha = (float)(sqrt(tint_color.s.alpha) * 48) / 255.0f; + tint_alpha = (float)(sqrt((float)tint_color.s.alpha / 10.2) * 48) / 255.0f; // 8 is roughly the brightness of the "close" color in Software, and 16 the brightness of the "far" color. // 8 is too bright for dark levels, and 16 is too dark for bright levels. @@ -242,7 +242,7 @@ UINT8 HWR_FogBlockAlpha(INT32 light, extracolormap_t *colormap) // Let's see if RGBA_t realcolor, surfcolor; INT32 alpha; - realcolor.rgba = (colormap != NULL) ? colormap->rgba : GL_DEFAULTMIX; + realcolor.rgba = (colormap != NULL) ? colormap->rgba : 0x00000000; if (cv_glshaders.value && gl_shadersavailable) { diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c index d005f0037..6d1f12664 100644 --- a/src/hardware/hw_md2.c +++ b/src/hardware/hw_md2.c @@ -1140,9 +1140,6 @@ static void HWR_GetBlendedTexture(patch_t *patch, patch_t *blendpatch, INT32 ski Z_ChangeTag(newMipmap->data, PU_HWRMODELTEXTURE_UNLOCKED); } -#define NORMALFOG 0x00000000 -#define FADEFOG 0x19000000 - static boolean HWR_AllowModel(mobj_t *mobj) { // Signpost overlay. Not needed. diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c index 71cb5ca70..6c0c90fc5 100644 --- a/src/hardware/r_opengl/r_opengl.c +++ b/src/hardware/r_opengl/r_opengl.c @@ -697,7 +697,7 @@ static GLRGBAFloat shader_defaultcolor = {1.0f, 1.0f, 1.0f, 1.0f}; #define GLSL_SOFTWARE_TINT_EQUATION \ "if (tint_color.a > 0.0) {\n" \ "float color_bright = sqrt((base_color.r * base_color.r) + (base_color.g * base_color.g) + (base_color.b * base_color.b));\n" \ - "float strength = sqrt(9.0 * tint_color.a);\n" \ + "float strength = sqrt(tint_color.a);\n" \ "final_color.r = clamp((color_bright * (tint_color.r * strength)) + (base_color.r * (1.0 - strength)), 0.0, 1.0);\n" \ "final_color.g = clamp((color_bright * (tint_color.g * strength)) + (base_color.g * (1.0 - strength)), 0.0, 1.0);\n" \ "final_color.b = clamp((color_bright * (tint_color.b * strength)) + (base_color.b * (1.0 - strength)), 0.0, 1.0);\n" \ diff --git a/src/lua_colorlib.c b/src/lua_colorlib.c index a963a6a6b..1aed69836 100644 --- a/src/lua_colorlib.c +++ b/src/lua_colorlib.c @@ -155,10 +155,6 @@ static const char *const extracolormap_opt[] = { "colormap", NULL}; -#define ALPHA_SCALE_FACTOR 102 // (255 / 25) * 10 -#define SCALE_ALPHA_UP(alpha) (((alpha) * ALPHA_SCALE_FACTOR) / 10) -#define SCALE_ALPHA_DOWN(alpha) (((alpha) * 10) / ALPHA_SCALE_FACTOR) - static int extracolormap_get(lua_State *L) { extracolormap_t *exc = *((extracolormap_t **)luaL_checkudata(L, 1, META_EXTRACOLORMAP)); @@ -176,13 +172,13 @@ static int extracolormap_get(lua_State *L) lua_pushinteger(L, R_GetRgbaB(exc->rgba)); break; case extracolormap_a: - lua_pushinteger(L, SCALE_ALPHA_UP(R_GetRgbaA(exc->rgba))); + lua_pushinteger(L, R_GetRgbaA(exc->rgba)); break; case extracolormap_rgba: lua_pushinteger(L, R_GetRgbaR(exc->rgba)); lua_pushinteger(L, R_GetRgbaG(exc->rgba)); lua_pushinteger(L, R_GetRgbaB(exc->rgba)); - lua_pushinteger(L, SCALE_ALPHA_UP(R_GetRgbaA(exc->rgba))); + lua_pushinteger(L, R_GetRgbaA(exc->rgba)); return 4; case extracolormap_fade_r: lua_pushinteger(L, R_GetRgbaR(exc->fadergba)); @@ -194,13 +190,13 @@ static int extracolormap_get(lua_State *L) lua_pushinteger(L, R_GetRgbaB(exc->fadergba)); break; case extracolormap_fade_a: - lua_pushinteger(L, SCALE_ALPHA_UP(R_GetRgbaA(exc->fadergba))); + lua_pushinteger(L, R_GetRgbaA(exc->fadergba)); break; case extracolormap_fade_rgba: lua_pushinteger(L, R_GetRgbaR(exc->fadergba)); lua_pushinteger(L, R_GetRgbaG(exc->fadergba)); lua_pushinteger(L, R_GetRgbaB(exc->fadergba)); - lua_pushinteger(L, SCALE_ALPHA_UP(R_GetRgbaA(exc->fadergba))); + lua_pushinteger(L, R_GetRgbaA(exc->fadergba)); return 4; case extracolormap_fade_start: lua_pushinteger(L, exc->fadestart); @@ -217,8 +213,6 @@ static int extracolormap_get(lua_State *L) static void GetExtraColormapRGBA(lua_State *L, UINT8 *rgba) { - rgba[3] = SCALE_ALPHA_UP(rgba[3]); - if (lua_type(L, 3) == LUA_TSTRING) { const char *str = lua_tostring(L, 3); @@ -228,8 +222,6 @@ static void GetExtraColormapRGBA(lua_State *L, UINT8 *rgba) } else GetRGBAColorsFromTable(L, 3, rgba, true); - - rgba[3] = SCALE_ALPHA_DOWN(rgba[3]); } static int extracolormap_set(lua_State *L) @@ -266,7 +258,7 @@ static int extracolormap_set(lua_State *L) exc->rgba = R_PutRgbaRGBA(r, g, val, a); break; case extracolormap_a: - exc->rgba = R_PutRgbaRGBA(r, g, b, SCALE_ALPHA_DOWN(val)); + exc->rgba = R_PutRgbaRGBA(r, g, b, val); break; case extracolormap_rgba: rgba[0] = r; @@ -286,7 +278,7 @@ static int extracolormap_set(lua_State *L) exc->fadergba = R_PutRgbaRGBA(fr, fg, val, fa); break; case extracolormap_fade_a: - exc->fadergba = R_PutRgbaRGBA(fr, fg, fb, SCALE_ALPHA_DOWN(val)); + exc->fadergba = R_PutRgbaRGBA(fr, fg, fb, val); break; case extracolormap_fade_rgba: rgba[0] = fr; diff --git a/src/r_data.c b/src/r_data.c index 1795e7727..e2b74da40 100644 --- a/src/r_data.c +++ b/src/r_data.c @@ -438,7 +438,7 @@ extracolormap_t *R_CreateDefaultColormap(boolean lighttable) exc->fadeend = 31; exc->flags = 0; exc->rgba = 0; - exc->fadergba = 0x19000000; + exc->fadergba = 0xFF000000; exc->colormap = lighttable ? R_CreateLightTable(exc) : NULL; #ifdef EXTRACOLORMAPLUMPS exc->lump = LUMPERROR; @@ -553,7 +553,7 @@ boolean R_CheckDefaultColormapByValues(boolean checkrgba, boolean checkfadergba, && !flags) ) && (!checkrgba ? true : rgba == 0) - && (!checkfadergba ? true : fadergba == 0x19000000) + && (!checkfadergba ? true : (unsigned)fadergba == 0xFF000000) #ifdef EXTRACOLORMAPLUMPS && lump == LUMPERROR && extra_colormap->lumpname[0] == 0 @@ -654,7 +654,7 @@ extracolormap_t *R_ColormapForName(char *name) if (lump == LUMPERROR) I_Error("R_ColormapForName: Cannot find colormap lump %.8s\n", name); - exc = R_GetColormapFromListByValues(0, 0x19000000, 0, 31, 0, lump); + exc = R_GetColormapFromListByValues(0, 0xFF000000, 0, 31, 0, lump); if (exc) return exc; @@ -674,7 +674,7 @@ extracolormap_t *R_ColormapForName(char *name) exc->fadeend = 31; exc->flags = 0; exc->rgba = 0; - exc->fadergba = 0x19000000; + exc->fadergba = 0xFF000000; R_AddColormapToList(exc); @@ -737,7 +737,7 @@ void R_GenerateLightTable(extracolormap_t *extra_colormap, boolean uselookup) cmaskg = cg; cmaskb = cb; - maskamt = (double)(ca/24.0l); + maskamt = (double)(ca/255.0l); othermask = 1 - maskamt; maskamt /= 0xff; @@ -753,7 +753,7 @@ void R_GenerateLightTable(extracolormap_t *extra_colormap, boolean uselookup) cdestb = cfb; // fade alpha unused in software - // maskamt = (double)(cfa/24.0l); + // maskamt = (double)(cfa/255.0l); // othermask = 1 - maskamt; // maskamt /= 0xff; @@ -851,7 +851,7 @@ extracolormap_t *R_CreateColormapFromLinedef(char *p1, char *p2, char *p3) UINT8 cr = 0, cg = 0, cb = 0, ca = 0, cfr = 0, cfg = 0, cfb = 0, cfa = 25; UINT32 fadestart = 0, fadeend = 31; UINT8 flags = 0; - INT32 rgba = 0, fadergba = 0x19000000; + INT32 rgba = 0, fadergba = 0xFF000000; #define HEX2INT(x) (UINT32)(x >= '0' && x <= '9' ? x - '0' : x >= 'a' && x <= 'f' ? x - 'a' + 10 : x >= 'A' && x <= 'F' ? x - 'A' + 10 : 0) #define ALPHA2INT(x) (x >= 'a' && x <= 'z' ? x - 'a' : x >= 'A' && x <= 'Z' ? x - 'A' : x >= '0' && x <= '9' ? 25 : 0) @@ -859,13 +859,13 @@ extracolormap_t *R_CreateColormapFromLinedef(char *p1, char *p2, char *p3) // Get base colormap value // First alpha-only, then full value if (p1[0] >= 'a' && p1[0] <= 'z' && !p1[1]) - ca = (p1[0] - 'a'); + ca = ((p1[0] - 'a') * 102) / 10; else if (p1[0] == '#' && p1[1] >= 'a' && p1[1] <= 'z' && !p1[2]) - ca = (p1[1] - 'a'); + ca = ((p1[1] - 'a') * 102) / 10; else if (p1[0] >= 'A' && p1[0] <= 'Z' && !p1[1]) - ca = (p1[0] - 'A'); + ca = ((p1[0] - 'A') * 102) / 10; else if (p1[0] == '#' && p1[1] >= 'A' && p1[1] <= 'Z' && !p1[2]) - ca = (p1[1] - 'A'); + ca = ((p1[1] - 'A') * 102) / 10; else if (p1[0] == '#') { // For each subsequent value, the value before it must exist @@ -881,20 +881,20 @@ extracolormap_t *R_CreateColormapFromLinedef(char *p1, char *p2, char *p3) cb = ((HEX2INT(p1[5]) * 16) + HEX2INT(p1[6])); if (p1[7] >= 'a' && p1[7] <= 'z') - ca = (p1[7] - 'a'); + ca = ((p1[7] - 'a') * 102) / 10; else if (p1[7] >= 'A' && p1[7] <= 'Z') - ca = (p1[7] - 'A'); + ca = ((p1[7] - 'A') * 102) / 10; else - ca = 25; + ca = 255; } else - ca = 25; + ca = 255; } else - ca = 25; + ca = 255; } else - ca = 25; + ca = 255; } #define NUMFROMCHAR(c) (c >= '0' && c <= '9' ? c - '0' : 0) @@ -924,13 +924,13 @@ extracolormap_t *R_CreateColormapFromLinedef(char *p1, char *p2, char *p3) // Get fade (dark) colormap value // First alpha-only, then full value if (p3[0] >= 'a' && p3[0] <= 'z' && !p3[1]) - cfa = (p3[0] - 'a'); + cfa = ((p3[0] - 'a') * 102) / 10; else if (p3[0] == '#' && p3[1] >= 'a' && p3[1] <= 'z' && !p3[2]) - cfa = (p3[1] - 'a'); + cfa = ((p3[1] - 'a') * 102) / 10; else if (p3[0] >= 'A' && p3[0] <= 'Z' && !p3[1]) - cfa = (p3[0] - 'A'); + cfa = ((p3[0] - 'A') * 102) / 10; else if (p3[0] == '#' && p3[1] >= 'A' && p3[1] <= 'Z' && !p3[2]) - cfa = (p3[1] - 'A'); + cfa = ((p3[1] - 'A') * 102) / 10; else if (p3[0] == '#') { // For each subsequent value, the value before it must exist @@ -946,20 +946,20 @@ extracolormap_t *R_CreateColormapFromLinedef(char *p1, char *p2, char *p3) cfb = ((HEX2INT(p3[5]) * 16) + HEX2INT(p3[6])); if (p3[7] >= 'a' && p3[7] <= 'z') - cfa = (p3[7] - 'a'); + cfa = ((p3[7] - 'a') * 102) / 10; else if (p3[7] >= 'A' && p3[7] <= 'Z') - cfa = (p3[7] - 'A'); + cfa = ((p3[7] - 'A') * 102) / 10; else - cfa = 25; + cfa = 255; } else - cfa = 25; + cfa = 255; } else - cfa = 25; + cfa = 255; } else - cfa = 25; + cfa = 255; } #undef ALPHA2INT #undef HEX2INT From a48030d02dc6d26cfc8a6a145cdbfa4a625631ce Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 4 Aug 2023 05:08:22 -0300 Subject: [PATCH 058/227] Support number as accepted value for "rgba" or "fade_rgba" fields --- src/lua_colorlib.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/lua_colorlib.c b/src/lua_colorlib.c index 1aed69836..312b32571 100644 --- a/src/lua_colorlib.c +++ b/src/lua_colorlib.c @@ -220,8 +220,26 @@ static void GetExtraColormapRGBA(lua_State *L, UINT8 *rgba) if (!parsed) luaL_error(L, "Malformed HTML color '%s'", str); } - else + else if (lua_type(L, 3) == LUA_TTABLE) GetRGBAColorsFromTable(L, 3, rgba, true); + else + { + UINT32 colors = lua_tointeger(L, 3); + if (colors > 0xFFFFFF) + { + rgba[0] = (colors >> 24) & 0xFF; + rgba[1] = (colors >> 16) & 0xFF; + rgba[2] = (colors >> 8) & 0xFF; + rgba[3] = colors & 0xFF; + } + else + { + rgba[0] = (colors >> 16) & 0xFF; + rgba[1] = (colors >> 8) & 0xFF; + rgba[2] = colors & 0xFF; + rgba[3] = 0xFF; + } + } } static int extracolormap_set(lua_State *L) From 0e2b6a94234ac0fe01263b4ede5f77bff6119faa Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 4 Aug 2023 05:13:56 -0300 Subject: [PATCH 059/227] Rename fields --- src/lua_colorlib.c | 83 ++++++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 40 deletions(-) diff --git a/src/lua_colorlib.c b/src/lua_colorlib.c index 312b32571..f4a641744 100644 --- a/src/lua_colorlib.c +++ b/src/lua_colorlib.c @@ -124,32 +124,32 @@ static UINT8 ParseHTMLColor(const char *str, UINT8 *rgba, size_t numc) ///////////////////////// enum extracolormap_e { - extracolormap_r = 0, - extracolormap_g, - extracolormap_b, - extracolormap_a, - extracolormap_rgba, - extracolormap_fade_r, - extracolormap_fade_g, - extracolormap_fade_b, - extracolormap_fade_a, - extracolormap_fade_rgba, + extracolormap_red = 0, + extracolormap_green, + extracolormap_blue, + extracolormap_alpha, + extracolormap_color, + extracolormap_fade_red, + extracolormap_fade_green, + extracolormap_fade_blue, + extracolormap_fade_alpha, + extracolormap_fade_color, extracolormap_fade_start, extracolormap_fade_end, extracolormap_colormap }; static const char *const extracolormap_opt[] = { - "r", - "g", - "b", - "a", - "rgba", - "fade_r", - "fade_g", - "fade_b", - "fade_a", - "fade_rgba", + "red", + "green", + "blue", + "alpha", + "color", + "fade_red", + "fade_green", + "fade_blue", + "fade_alpha", + "fade_color", "fade_start", "fade_end", "colormap", @@ -162,37 +162,37 @@ static int extracolormap_get(lua_State *L) switch (field) { - case extracolormap_r: + case extracolormap_red: lua_pushinteger(L, R_GetRgbaR(exc->rgba)); break; - case extracolormap_g: + case extracolormap_green: lua_pushinteger(L, R_GetRgbaG(exc->rgba)); break; - case extracolormap_b: + case extracolormap_blue: lua_pushinteger(L, R_GetRgbaB(exc->rgba)); break; - case extracolormap_a: + case extracolormap_alpha: lua_pushinteger(L, R_GetRgbaA(exc->rgba)); break; - case extracolormap_rgba: + case extracolormap_color: lua_pushinteger(L, R_GetRgbaR(exc->rgba)); lua_pushinteger(L, R_GetRgbaG(exc->rgba)); lua_pushinteger(L, R_GetRgbaB(exc->rgba)); lua_pushinteger(L, R_GetRgbaA(exc->rgba)); return 4; - case extracolormap_fade_r: + case extracolormap_fade_red: lua_pushinteger(L, R_GetRgbaR(exc->fadergba)); break; - case extracolormap_fade_g: + case extracolormap_fade_green: lua_pushinteger(L, R_GetRgbaG(exc->fadergba)); break; - case extracolormap_fade_b: + case extracolormap_fade_blue: lua_pushinteger(L, R_GetRgbaB(exc->fadergba)); break; - case extracolormap_fade_a: + case extracolormap_fade_alpha: lua_pushinteger(L, R_GetRgbaA(exc->fadergba)); break; - case extracolormap_fade_rgba: + case extracolormap_fade_color: lua_pushinteger(L, R_GetRgbaR(exc->fadergba)); lua_pushinteger(L, R_GetRgbaG(exc->fadergba)); lua_pushinteger(L, R_GetRgbaB(exc->fadergba)); @@ -205,6 +205,9 @@ static int extracolormap_get(lua_State *L) lua_pushinteger(L, exc->fadeend); break; case extracolormap_colormap: + // I'm not sure if making the colormap available makes sense. + // It's a read-only field, only used by one of the renderers, and + // the only way to manipulate it is by modifying the other fields. LUA_PushUserdata(L, exc->colormap, META_LIGHTTABLE); break; } @@ -266,19 +269,19 @@ static int extracolormap_set(lua_State *L) switch(field) { - case extracolormap_r: + case extracolormap_red: exc->rgba = R_PutRgbaRGBA(val, g, b, a); break; - case extracolormap_g: + case extracolormap_green: exc->rgba = R_PutRgbaRGBA(r, val, b, a); break; - case extracolormap_b: + case extracolormap_blue: exc->rgba = R_PutRgbaRGBA(r, g, val, a); break; - case extracolormap_a: + case extracolormap_alpha: exc->rgba = R_PutRgbaRGBA(r, g, b, val); break; - case extracolormap_rgba: + case extracolormap_color: rgba[0] = r; rgba[1] = g; rgba[2] = b; @@ -286,19 +289,19 @@ static int extracolormap_set(lua_State *L) GetExtraColormapRGBA(L, rgba); exc->rgba = R_PutRgbaRGBA(rgba[0], rgba[1], rgba[2], rgba[3]); break; - case extracolormap_fade_r: + case extracolormap_fade_red: exc->fadergba = R_PutRgbaRGBA(val, fg, fb, fa); break; - case extracolormap_fade_g: + case extracolormap_fade_green: exc->fadergba = R_PutRgbaRGBA(fr, val, fb, fa); break; - case extracolormap_fade_b: + case extracolormap_fade_blue: exc->fadergba = R_PutRgbaRGBA(fr, fg, val, fa); break; - case extracolormap_fade_a: + case extracolormap_fade_alpha: exc->fadergba = R_PutRgbaRGBA(fr, fg, fb, val); break; - case extracolormap_fade_rgba: + case extracolormap_fade_color: rgba[0] = fr; rgba[1] = fg; rgba[2] = fb; From a54d3a274a49297b22a8a3da95bd78a1914aa86c Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 4 Aug 2023 14:29:26 -0300 Subject: [PATCH 060/227] Remove assignment by table --- src/lua_colorlib.c | 59 +++++----------------------------------------- 1 file changed, 6 insertions(+), 53 deletions(-) diff --git a/src/lua_colorlib.c b/src/lua_colorlib.c index f4a641744..1e6e82333 100644 --- a/src/lua_colorlib.c +++ b/src/lua_colorlib.c @@ -17,48 +17,6 @@ #include "lua_script.h" #include "lua_libs.h" -static UINT8 GetRGBAColorsFromTable(lua_State *L, UINT32 index, UINT8 *rgba, boolean useAlpha) -{ - UINT8 num = 0; - - lua_pushnil(L); - - while (lua_next(L, index)) { - lua_Integer i = 0; - const char *field = NULL; - if (lua_isnumber(L, -2)) - i = lua_tointeger(L, -2); - else - field = luaL_checkstring(L, -2); - -#define CHECKFIELD(p, c) (i == p || (field && fastcmp(field, c))) -#define RGBASET(p, c) { \ - INT32 color = luaL_checkinteger(L, -1); \ - if (color < 0 || color > 255) \ - luaL_error(L, c " channel %d out of range (0 - 255)", color); \ - rgba[p] = (UINT8)color; \ - num++; \ - } - - if (CHECKFIELD(1, "r")) { - RGBASET(0, "red color"); - } else if (CHECKFIELD(2, "g")) { - RGBASET(1, "green color"); - } else if (CHECKFIELD(3, "b")) { - RGBASET(2, "blue color"); - } else if (useAlpha && CHECKFIELD(4, "a")) { - RGBASET(3, "alpha"); - } - -#undef CHECKFIELD -#undef RGBASET - - lua_pop(L, 1); - } - - return num; -} - #define IS_HEX_CHAR(x) ((x >= '0' && x <= '9') || (x >= 'a' && x <= 'f') || (x >= 'A' && x <= 'F')) #define ARE_HEX_CHARS(str, i) IS_HEX_CHAR(str[i]) && IS_HEX_CHAR(str[i + 1]) @@ -205,29 +163,24 @@ static int extracolormap_get(lua_State *L) lua_pushinteger(L, exc->fadeend); break; case extracolormap_colormap: - // I'm not sure if making the colormap available makes sense. - // It's a read-only field, only used by one of the renderers, and - // the only way to manipulate it is by modifying the other fields. LUA_PushUserdata(L, exc->colormap, META_LIGHTTABLE); break; } return 1; } -static void GetExtraColormapRGBA(lua_State *L, UINT8 *rgba) +static void GetExtraColormapRGBA(lua_State *L, UINT8 *rgba, int arg) { - if (lua_type(L, 3) == LUA_TSTRING) + if (lua_type(L, arg) == LUA_TSTRING) { - const char *str = lua_tostring(L, 3); + const char *str = lua_tostring(L, arg); UINT8 parsed = ParseHTMLColor(str, rgba, 4); if (!parsed) luaL_error(L, "Malformed HTML color '%s'", str); } - else if (lua_type(L, 3) == LUA_TTABLE) - GetRGBAColorsFromTable(L, 3, rgba, true); else { - UINT32 colors = lua_tointeger(L, 3); + UINT32 colors = lua_tointeger(L, arg); if (colors > 0xFFFFFF) { rgba[0] = (colors >> 24) & 0xFF; @@ -286,7 +239,7 @@ static int extracolormap_set(lua_State *L) rgba[1] = g; rgba[2] = b; rgba[3] = a; - GetExtraColormapRGBA(L, rgba); + GetExtraColormapRGBA(L, rgba, 3); exc->rgba = R_PutRgbaRGBA(rgba[0], rgba[1], rgba[2], rgba[3]); break; case extracolormap_fade_red: @@ -306,7 +259,7 @@ static int extracolormap_set(lua_State *L) rgba[1] = fg; rgba[2] = fb; rgba[3] = fa; - GetExtraColormapRGBA(L, rgba); + GetExtraColormapRGBA(L, rgba, 3); exc->fadergba = R_PutRgbaRGBA(rgba[0], rgba[1], rgba[2], rgba[3]); break; case extracolormap_fade_start: From 126fba4ddbe0e8c724dec690cb43315b276da90b Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 4 Aug 2023 15:10:25 -0300 Subject: [PATCH 061/227] Lua colorlib: color library --- src/lua_colorlib.c | 390 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 375 insertions(+), 15 deletions(-) diff --git a/src/lua_colorlib.c b/src/lua_colorlib.c index 1e6e82333..210d833df 100644 --- a/src/lua_colorlib.c +++ b/src/lua_colorlib.c @@ -13,10 +13,33 @@ #include "doomdef.h" #include "fastcmp.h" #include "r_data.h" +#include "v_video.h" #include "lua_script.h" #include "lua_libs.h" +#define COLORLIB_USE_LOOKUP + +#ifdef COLORLIB_USE_LOOKUP + static colorlookup_t colormix_lut; + #define GetNearestColor(r, g, b) GetColorLUT(&colormix_lut, r, g, b) +#else + #define GetNearestColor(r, g, b) NearestPaletteColor(r, g, b, pMasterPalette) +#endif + +//////////////// +// Color library +//////////////// + +static int lib_colorPaletteToRgb(lua_State *L) +{ + RGBA_t color = V_GetMasterColor((UINT8)luaL_checkinteger(L, 1)); + lua_pushinteger(L, color.s.red); + lua_pushinteger(L, color.s.green); + lua_pushinteger(L, color.s.blue); + return 3; +} + #define IS_HEX_CHAR(x) ((x >= '0' && x <= '9') || (x >= 'a' && x <= 'f') || (x >= 'A' && x <= 'F')) #define ARE_HEX_CHARS(str, i) IS_HEX_CHAR(str[i]) && IS_HEX_CHAR(str[i + 1]) @@ -32,6 +55,13 @@ static UINT32 hex2int(char x) return 0; } +static UINT8 GetHTMLColorLength(const char *str) +{ + if (str[0] == '#') + str++; + return strlen(str) >= 8 ? 4 : 3; +} + static UINT8 ParseHTMLColor(const char *str, UINT8 *rgba, size_t numc) { const char *hex = str; @@ -77,6 +107,348 @@ static UINT8 ParseHTMLColor(const char *str, UINT8 *rgba, size_t numc) return 0; } +static UINT8 GetPackedRGBA(UINT32 colors, UINT8 *rgba) +{ + if (colors > 0xFFFFFF) + { + rgba[0] = (colors >> 24) & 0xFF; + rgba[1] = (colors >> 16) & 0xFF; + rgba[2] = (colors >> 8) & 0xFF; + rgba[3] = colors & 0xFF; + return 4; + } + else + { + rgba[0] = (colors >> 16) & 0xFF; + rgba[1] = (colors >> 8) & 0xFF; + rgba[2] = colors & 0xFF; + rgba[3] = 0xFF; + return 3; + } +} + +static UINT8 GetArgsRGBA(lua_State *L, UINT8 index, INT32 *r, INT32 *g, INT32 *b, INT32 *a) +{ + UINT8 rgba[4] = { 0, 0, 0, 255 }; + UINT8 num = 0; + + if (lua_gettop(L) == 1 && lua_type(L, index) == LUA_TNUMBER) + { + num = GetPackedRGBA(luaL_checkinteger(L, 1), rgba); + + *r = rgba[0]; + *g = rgba[1]; + *b = rgba[2]; + if (a) + *a = rgba[3]; + } + else if (lua_type(L, index) == LUA_TSTRING) + { + const char *str = lua_tostring(L, index); + UINT8 parsed = ParseHTMLColor(str, rgba, GetHTMLColorLength(str)); + if (!parsed) + luaL_error(L, "Malformed HTML color '%s'", str); + + num = parsed == 8 ? 4 : 3; + + *r = rgba[0]; + *g = rgba[1]; + *b = rgba[2]; + if (a) + *a = rgba[3]; + } + else + { + INT32 temp; + +#define CHECKINT(i) luaL_checkinteger(L, i) +#define GETCOLOR(c, i, desc) { \ + temp = CHECKINT(i); \ + if (temp < 0 || temp > 255) \ + luaL_error(L, desc " channel %d out of range (0 - 255)", temp); \ + c = temp; \ + num++; \ + } + + GETCOLOR(*r, index + 0, "red color"); + GETCOLOR(*g, index + 1, "green color"); + GETCOLOR(*b, index + 2, "blue color"); +#undef CHECKINT +#define CHECKINT(i) luaL_optinteger(L, i, 255) + if (a) + GETCOLOR(*a, index + 3, "alpha"); +#undef CHECKINT +#undef GETCOLOR + + num = 3 + (lua_type(L, index + 3) == LUA_TNUMBER); + } + + return num; +} + +static int lib_colorRgbToPalette(lua_State *L) +{ + INT32 r, g, b; + GetArgsRGBA(L, 1, &r, &g, &b, NULL); + +#ifdef COLORLIB_USE_LOOKUP + InitColorLUT(&colormix_lut, pMasterPalette, false); +#endif + + lua_pushinteger(L, GetNearestColor(r, g, b)); + return 1; +} + +static void GetHSLFromTable(lua_State *L, UINT32 index, UINT8 *hsl) +{ + lua_pushnil(L); + + while (lua_next(L, index)) { + lua_Integer i = 0; + const char *field = NULL; + if (lua_isnumber(L, -2)) + i = lua_tointeger(L, -2); + else + field = luaL_checkstring(L, -2); + +#define CHECKFIELD(p, c) (i == p || (field && fastcmp(field, c))) +#define HSLSET(p, c) { \ + INT32 val = luaL_checkinteger(L, -1); \ + if (val < 0 || val > 255) \ + luaL_error(L, c " %d out of range (0 - 255)", val); \ + hsl[p] = (UINT8)val; \ + } + + if (CHECKFIELD(1, "h")) { + HSLSET(0, "hue"); + } else if (CHECKFIELD(2, "s")) { + HSLSET(1, "saturation"); + } else if (CHECKFIELD(3, "l")) { + HSLSET(2, "lightness"); + } + +#undef CHECKFIELD +#undef HSLSET + + lua_pop(L, 1); + } +} + +#define SCALE_UINT8_TO_FIXED(val) FixedDiv(val * FRACUNIT, 255 * FRACUNIT) +#define SCALE_FIXED_TO_UINT8(val) FixedRound(FixedMul(val, 255 * FRACUNIT)) / FRACUNIT + +static fixed_t hue2rgb(fixed_t p, fixed_t q, fixed_t t) +{ + if (t < 0) + t += FRACUNIT; + if (t > FRACUNIT) + t -= FRACUNIT; + + fixed_t out; + + if (t < FRACUNIT / 6) + out = p + FixedMul(FixedMul(q - p, 6 * FRACUNIT), t); + else if (t < FRACUNIT / 2) + out = q; + else if (t < 2 * FRACUNIT / 3) + out = p + FixedMul(FixedMul(q - p, 2 * FRACUNIT / 3 - t), 6 * FRACUNIT); + else + out = p; + + return out; +} + +static int lib_colorHslToRgb(lua_State *L) +{ + fixed_t h, s, l; + + if (lua_istable(L, 1)) + { + UINT8 hsl[3] = { 0, 0, 0 }; + GetHSLFromTable(L, 1, hsl); + h = hsl[0]; + s = hsl[1]; + l = hsl[2]; + } + else + { +#define GETHSL(c, i, desc) \ + c = luaL_checkinteger(L, i); \ + if (c < 0 || c > 255) \ + luaL_error(L, desc " %d out of range (0 - 255)", c) + + GETHSL(h, 1, "hue"); + GETHSL(s, 2, "saturation"); + GETHSL(l, 3, "value"); +#undef GETHSL + } + + if (!s) + { + lua_pushinteger(L, l); + lua_pushinteger(L, l); + lua_pushinteger(L, l); + } + else + { + h = SCALE_UINT8_TO_FIXED(h); + s = SCALE_UINT8_TO_FIXED(s); + l = SCALE_UINT8_TO_FIXED(l); + + fixed_t q, p; + + if (l < FRACUNIT/2) + q = FixedMul(l, FRACUNIT + s); + else + q = l + s - FixedMul(l, s); + + p = l * 2 - q; + + lua_pushinteger(L, SCALE_FIXED_TO_UINT8(hue2rgb(p, q, h + FRACUNIT/3))); + lua_pushinteger(L, SCALE_FIXED_TO_UINT8(hue2rgb(p, q, h))); + lua_pushinteger(L, SCALE_FIXED_TO_UINT8(hue2rgb(p, q, h - FRACUNIT/3))); + } + + return 3; +} + +static int lib_colorRgbToHsl(lua_State *L) +{ + INT32 ir, ig, ib; + GetArgsRGBA(L, 1, &ir, &ig, &ib, NULL); + + fixed_t r = SCALE_UINT8_TO_FIXED(ir); + fixed_t g = SCALE_UINT8_TO_FIXED(ig); + fixed_t b = SCALE_UINT8_TO_FIXED(ib); + + fixed_t cmin = min(min(r, g), b); + fixed_t cmax = max(max(r, g), b); + + fixed_t h, s, l = (cmax + cmin) / 2; + fixed_t delta = cmax - cmin; + + if (!delta) + h = s = 0; + else + { + if (l > FRACUNIT / 2) + s = FixedDiv(delta, (FRACUNIT * 2) - cmax - cmin); + else + s = FixedDiv(delta, cmax + cmin); + + if (r > g && r > b) + { + h = FixedDiv(g - b, delta); + + if (g < b) + h += FRACUNIT * 6; + } + else + { + h = FixedDiv(r - g, delta); + + if (g > b) + h += FRACUNIT * 2; + else + h += FRACUNIT * 4; + } + + h = FixedDiv(h, FRACUNIT * 6); + } + + lua_pushinteger(L, SCALE_FIXED_TO_UINT8(h)); + lua_pushinteger(L, SCALE_FIXED_TO_UINT8(s)); + lua_pushinteger(L, SCALE_FIXED_TO_UINT8(l)); + + return 3; +} + +static int lib_colorHexToRgb(lua_State *L) +{ + UINT8 rgba[4] = { 0, 0, 0, 255 }; + + const char *str = luaL_checkstring(L, 1); + UINT8 parsed = ParseHTMLColor(str, rgba, 4), num = 3; + if (!parsed) + luaL_error(L, "Malformed HTML color '%s'", str); + else if (parsed == 8) + num++; + + lua_pushinteger(L, rgba[0]); + lua_pushinteger(L, rgba[1]); + lua_pushinteger(L, rgba[2]); + if (num == 4) + lua_pushinteger(L, rgba[3]); + + return num; +} + +static int lib_colorRgbToHex(lua_State *L) +{ + INT32 r, g, b, a; + UINT8 num = GetArgsRGBA(L, 1, &r, &g, &b, &a); + + char buffer[10]; + if (num >= 4) + snprintf(buffer, sizeof buffer, "#%02X%02X%02X%02X", r, g, b, a); + else + snprintf(buffer, sizeof buffer, "#%02X%02X%02X", r, g, b); + + lua_pushstring(L, buffer); + return 1; +} + +static int lib_colorPackRgb(lua_State *L) +{ + INT32 r, g, b; + + GetArgsRGBA(L, 1, &r, &g, &b, NULL); + + UINT32 packed = ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF); + + lua_pushinteger(L, packed); + return 1; +} + +static int lib_colorPackRgba(lua_State *L) +{ + INT32 r, g, b, a; + + GetArgsRGBA(L, 1, &r, &g, &b, &a); + + UINT32 packed = ((r & 0xFF) << 24) | ((g & 0xFF) << 16) | ((b & 0xFF) << 8) | (a & 0xFF); + + lua_pushinteger(L, packed); + return 1; +} + +static int lib_colorUnpackRgba(lua_State *L) +{ + UINT8 rgba[4]; + + UINT8 num = GetPackedRGBA(lua_tointeger(L, 1), rgba); + + for (UINT8 i = 0; i < num; i++) + { + lua_pushinteger(L, rgba[i]); + } + + return num; +} + +static luaL_Reg color_lib[] = { + {"paletteToRgb", lib_colorPaletteToRgb}, + {"rgbToPalette", lib_colorRgbToPalette}, + {"hslToRgb", lib_colorHslToRgb}, + {"rgbToHsl", lib_colorRgbToHsl}, + {"hexToRgb", lib_colorHexToRgb}, + {"rgbToHex", lib_colorRgbToHex}, + {"packRgb", lib_colorPackRgb}, + {"packRgba", lib_colorPackRgba}, + {"unpackRgba", lib_colorUnpackRgba}, + {NULL, NULL} +}; + ///////////////////////// // extracolormap userdata ///////////////////////// @@ -180,21 +552,7 @@ static void GetExtraColormapRGBA(lua_State *L, UINT8 *rgba, int arg) } else { - UINT32 colors = lua_tointeger(L, arg); - if (colors > 0xFFFFFF) - { - rgba[0] = (colors >> 24) & 0xFF; - rgba[1] = (colors >> 16) & 0xFF; - rgba[2] = (colors >> 8) & 0xFF; - rgba[3] = colors & 0xFF; - } - else - { - rgba[0] = (colors >> 16) & 0xFF; - rgba[1] = (colors >> 8) & 0xFF; - rgba[2] = colors & 0xFF; - rgba[3] = 0xFF; - } + GetPackedRGBA(lua_tointeger(L, arg), rgba); } } @@ -328,5 +686,7 @@ int LUA_ColorLib(lua_State *L) lua_setfield(L, -2, "__len"); lua_pop(L, 1); + luaL_register(L, "color", color_lib); + return 0; } From 2e820ca83dac3a5143e7e41bbd4e2bfb6c110ed0 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 4 Aug 2023 15:26:12 -0300 Subject: [PATCH 062/227] Rename color.unpackRgba to color.unpackRgb --- src/lua_colorlib.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lua_colorlib.c b/src/lua_colorlib.c index 210d833df..931a45d37 100644 --- a/src/lua_colorlib.c +++ b/src/lua_colorlib.c @@ -422,7 +422,7 @@ static int lib_colorPackRgba(lua_State *L) return 1; } -static int lib_colorUnpackRgba(lua_State *L) +static int lib_colorUnpackRgb(lua_State *L) { UINT8 rgba[4]; @@ -445,7 +445,7 @@ static luaL_Reg color_lib[] = { {"rgbToHex", lib_colorRgbToHex}, {"packRgb", lib_colorPackRgb}, {"packRgba", lib_colorPackRgba}, - {"unpackRgba", lib_colorUnpackRgba}, + {"unpackRgb", lib_colorUnpackRgb}, {NULL, NULL} }; From 0c3aab9ac381db61a19678ecbdfd65876397c077 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 4 Aug 2023 15:27:05 -0300 Subject: [PATCH 063/227] Remove table argument support from color.hslToRgb --- src/lua_colorlib.c | 58 +++++----------------------------------------- 1 file changed, 6 insertions(+), 52 deletions(-) diff --git a/src/lua_colorlib.c b/src/lua_colorlib.c index 931a45d37..1ef21a41c 100644 --- a/src/lua_colorlib.c +++ b/src/lua_colorlib.c @@ -199,41 +199,6 @@ static int lib_colorRgbToPalette(lua_State *L) return 1; } -static void GetHSLFromTable(lua_State *L, UINT32 index, UINT8 *hsl) -{ - lua_pushnil(L); - - while (lua_next(L, index)) { - lua_Integer i = 0; - const char *field = NULL; - if (lua_isnumber(L, -2)) - i = lua_tointeger(L, -2); - else - field = luaL_checkstring(L, -2); - -#define CHECKFIELD(p, c) (i == p || (field && fastcmp(field, c))) -#define HSLSET(p, c) { \ - INT32 val = luaL_checkinteger(L, -1); \ - if (val < 0 || val > 255) \ - luaL_error(L, c " %d out of range (0 - 255)", val); \ - hsl[p] = (UINT8)val; \ - } - - if (CHECKFIELD(1, "h")) { - HSLSET(0, "hue"); - } else if (CHECKFIELD(2, "s")) { - HSLSET(1, "saturation"); - } else if (CHECKFIELD(3, "l")) { - HSLSET(2, "lightness"); - } - -#undef CHECKFIELD -#undef HSLSET - - lua_pop(L, 1); - } -} - #define SCALE_UINT8_TO_FIXED(val) FixedDiv(val * FRACUNIT, 255 * FRACUNIT) #define SCALE_FIXED_TO_UINT8(val) FixedRound(FixedMul(val, 255 * FRACUNIT)) / FRACUNIT @@ -262,26 +227,15 @@ static int lib_colorHslToRgb(lua_State *L) { fixed_t h, s, l; - if (lua_istable(L, 1)) - { - UINT8 hsl[3] = { 0, 0, 0 }; - GetHSLFromTable(L, 1, hsl); - h = hsl[0]; - s = hsl[1]; - l = hsl[2]; - } - else - { #define GETHSL(c, i, desc) \ - c = luaL_checkinteger(L, i); \ - if (c < 0 || c > 255) \ - luaL_error(L, desc " %d out of range (0 - 255)", c) + c = luaL_checkinteger(L, i); \ + if (c < 0 || c > 255) \ + luaL_error(L, desc " %d out of range (0 - 255)", c) - GETHSL(h, 1, "hue"); - GETHSL(s, 2, "saturation"); - GETHSL(l, 3, "value"); + GETHSL(h, 1, "hue"); + GETHSL(s, 2, "saturation"); + GETHSL(l, 3, "value"); #undef GETHSL - } if (!s) { From 8077a1db8f3f661bd9e991e49a0495f22e47d599 Mon Sep 17 00:00:00 2001 From: Zwip-Zwap Zapony Date: Mon, 14 Aug 2023 19:55:26 +0200 Subject: [PATCH 064/227] Always allow con_hudlines when using devmode --- src/console.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/console.c b/src/console.c index 21b608ce4..f2efe21aa 100644 --- a/src/console.c +++ b/src/console.c @@ -1885,9 +1885,9 @@ void CON_Drawer(void) if (con_curlines > 0) CON_DrawConsole(); - else if (gamestate == GS_LEVEL - || gamestate == GS_INTERMISSION || gamestate == GS_ENDING || gamestate == GS_CUTSCENE - || gamestate == GS_CREDITS || gamestate == GS_EVALUATION || gamestate == GS_WAITINGPLAYERS) + else if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_CREDITS + || gamestate == GS_EVALUATION || gamestate == GS_ENDING || gamestate == GS_CUTSCENE + || gamestate == GS_WAITINGPLAYERS || cv_debug) CON_DrawHudlines(); Unlock_state(); From 13ac83a2083440fafe62dd12928c4a647b0545b4 Mon Sep 17 00:00:00 2001 From: sphere Date: Mon, 5 Apr 2021 16:23:21 +0200 Subject: [PATCH 065/227] Floor portal test, ported Co-authored-by: Lactozilla --- src/deh_tables.c | 3 ++- src/info.c | 27 +++++++++++++++++++ src/info.h | 3 ++- src/p_mobj.c | 22 ++++++++++++++++ src/p_setup.c | 2 ++ src/r_bsp.c | 12 ++++----- src/r_defs.h | 3 +++ src/r_main.c | 4 +-- src/r_plane.c | 5 +++- src/r_plane.h | 3 ++- src/r_portal.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++-- src/r_portal.h | 10 ++++--- 12 files changed, 143 insertions(+), 19 deletions(-) diff --git a/src/deh_tables.c b/src/deh_tables.c index 3580e15c6..05f61f6e5 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -4283,8 +4283,9 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_POLYANCHOR", "MT_POLYSPAWN", - // Skybox objects + // Portal objects "MT_SKYBOX", + "MT_PORTALREFPOINT", // Debris "MT_SPARK", //spark diff --git a/src/info.c b/src/info.c index 11e9c9fe8..f71a2bd1c 100644 --- a/src/info.c +++ b/src/info.c @@ -20759,6 +20759,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_PORTALREFPOINT + 781, // doomednum + S_INVISIBLE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 12*FRACUNIT, // radius + 24*FRACUNIT, // height + 0, // display offset + 10, // mass + 0, // damage + sfx_None, // activesound + MF_SCENERY|MF_NOBLOCKMAP|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + { // MT_SPARK -1, // doomednum S_SPRK1, // spawnstate diff --git a/src/info.h b/src/info.h index 5c7a9f3fd..00822faea 100644 --- a/src/info.h +++ b/src/info.h @@ -5114,8 +5114,9 @@ typedef enum mobj_type MT_POLYANCHOR, MT_POLYSPAWN, - // Skybox objects + // Portal objects MT_SKYBOX, + MT_PORTALREFPOINT, // Debris MT_SPARK, //spark diff --git a/src/p_mobj.c b/src/p_mobj.c index de2c3a72c..f7ad7616d 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -12822,6 +12822,28 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean skyboxviewpnts[tag] = mobj; break; } + case MT_PORTALREFPOINT: + { + size_t i; + for (i = 0; i < numsectors; i++) + { + sector_t *targetsec = §ors[i]; + for (int j = 0; j < targetsec->tags.count; j++) + { + if ((mthing->extrainfo == targetsec->tags.tags[j]) && (GETSECSPECIAL(targetsec->special, 3) == 1)) + { + // origin + if (targetsec == mobj->subsector->sector) + targetsec->portals[1] = mobj; + // target + else + targetsec->portals[0] = mobj; + break; + } + } + } + } + break; case MT_EGGSTATUE: if (mthing->args[1]) { diff --git a/src/p_setup.c b/src/p_setup.c index e289b8346..51c14e147 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -989,6 +989,8 @@ static void P_InitializeSector(sector_t *ss) ss->floordata = NULL; ss->ceilingdata = NULL; ss->lightingdata = NULL; + ss->portals[0] = NULL; + ss->portals[1] = NULL; ss->fadecolormapdata = NULL; ss->heightsec = -1; diff --git a/src/r_bsp.c b/src/r_bsp.c index 42e050adf..70a9dbd94 100644 --- a/src/r_bsp.c +++ b/src/r_bsp.c @@ -908,7 +908,7 @@ static void R_Subsector(size_t num) || frontsector->floorpic == skyflatnum || (frontsector->heightsec != -1 && sectors[frontsector->heightsec].ceilingpic == skyflatnum)) { - floorplane = R_FindPlane(frontsector->floorheight, frontsector->floorpic, floorlightlevel, + floorplane = R_FindPlane(frontsector, frontsector->floorheight, frontsector->floorpic, floorlightlevel, frontsector->floorxoffset, frontsector->flooryoffset, frontsector->floorangle, floorcolormap, NULL, NULL, frontsector->f_slope); } else @@ -918,7 +918,7 @@ static void R_Subsector(size_t num) || frontsector->ceilingpic == skyflatnum || (frontsector->heightsec != -1 && sectors[frontsector->heightsec].floorpic == skyflatnum)) { - ceilingplane = R_FindPlane(frontsector->ceilingheight, frontsector->ceilingpic, + ceilingplane = R_FindPlane(frontsector, frontsector->ceilingheight, frontsector->ceilingpic, ceilinglightlevel, frontsector->ceilingxoffset, frontsector->ceilingyoffset, frontsector->ceilingangle, ceilingcolormap, NULL, NULL, frontsector->c_slope); } @@ -961,7 +961,7 @@ static void R_Subsector(size_t num) light = R_GetPlaneLight(frontsector, planecenterz, viewz < heightcheck); - ffloor[numffloors].plane = R_FindPlane(*rover->bottomheight, *rover->bottompic, + ffloor[numffloors].plane = R_FindPlane(rover->master->frontsector, *rover->bottomheight, *rover->bottompic, *frontsector->lightlist[light].lightlevel, *rover->bottomxoffs, *rover->bottomyoffs, *rover->bottomangle, *frontsector->lightlist[light].extra_colormap, rover, NULL, *rover->b_slope); @@ -990,7 +990,7 @@ static void R_Subsector(size_t num) { light = R_GetPlaneLight(frontsector, planecenterz, viewz < heightcheck); - ffloor[numffloors].plane = R_FindPlane(*rover->topheight, *rover->toppic, + ffloor[numffloors].plane = R_FindPlane(rover->master->frontsector, *rover->topheight, *rover->toppic, *frontsector->lightlist[light].lightlevel, *rover->topxoffs, *rover->topyoffs, *rover->topangle, *frontsector->lightlist[light].extra_colormap, rover, NULL, *rover->t_slope); @@ -1032,7 +1032,7 @@ static void R_Subsector(size_t num) && (viewz < polysec->floorheight)) { light = R_GetPlaneLight(frontsector, polysec->floorheight, viewz < polysec->floorheight); - ffloor[numffloors].plane = R_FindPlane(polysec->floorheight, polysec->floorpic, + ffloor[numffloors].plane = R_FindPlane(polysec, polysec->floorheight, polysec->floorpic, (light == -1 ? frontsector->lightlevel : *frontsector->lightlist[light].lightlevel), polysec->floorxoffset, polysec->flooryoffset, polysec->floorangle-po->angle, (light == -1 ? frontsector->extra_colormap : *frontsector->lightlist[light].extra_colormap), NULL, po, @@ -1056,7 +1056,7 @@ static void R_Subsector(size_t num) && (viewz > polysec->ceilingheight)) { light = R_GetPlaneLight(frontsector, polysec->floorheight, viewz < polysec->floorheight); - ffloor[numffloors].plane = R_FindPlane(polysec->ceilingheight, polysec->ceilingpic, + ffloor[numffloors].plane = R_FindPlane(polysec, polysec->ceilingheight, polysec->ceilingpic, (light == -1 ? frontsector->lightlevel : *frontsector->lightlist[light].lightlevel), polysec->ceilingxoffset, polysec->ceilingyoffset, polysec->ceilingangle-po->angle, (light == -1 ? frontsector->extra_colormap : *frontsector->lightlist[light].extra_colormap), NULL, po, NULL); // will ffloors be slopable eventually? diff --git a/src/r_defs.h b/src/r_defs.h index dfd2d6d70..61f9eaa4e 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -494,6 +494,9 @@ typedef struct sector_s // colormap structure extracolormap_t *spawn_extra_colormap; + + // floor portals + mobj_t *portals[2]; } sector_t; // diff --git a/src/r_main.c b/src/r_main.c index 952171405..8a0575d66 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -1513,10 +1513,8 @@ void R_RenderPlayerView(player_t *player) R_ClipSprites(drawsegs, NULL); PS_STOP_TIMING(ps_sw_spritecliptime); - // Add skybox portals caused by sky visplanes. - if (cv_skybox.value && skyboxmo[0]) - Portal_AddSkyboxPortals(); + Portal_AddSkyboxPortals(); // Portal rendering. Hijacks the BSP traversal. PS_START_TIMING(ps_sw_portaltime); diff --git a/src/r_plane.c b/src/r_plane.c index 29ce26b29..a24ea9d28 100644 --- a/src/r_plane.c +++ b/src/r_plane.c @@ -390,7 +390,7 @@ static visplane_t *new_visplane(unsigned hash) // Same height, same flattexture, same lightlevel. // If not, allocates another of them. // -visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel, +visplane_t *R_FindPlane(sector_t *sector, fixed_t height, INT32 picnum, INT32 lightlevel, fixed_t xoff, fixed_t yoff, angle_t plangle, extracolormap_t *planecolormap, ffloor_t *pfloor, polyobj_t *polyobj, pslope_t *slope) { @@ -447,6 +447,7 @@ visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel, && lightlevel == check->lightlevel && xoff == check->xoffs && yoff == check->yoffs && planecolormap == check->extra_colormap + && !pfloor && !check->ffloor && check->viewx == viewx && check->viewy == viewy && check->viewz == viewz && check->viewangle == viewangle && check->plangle == plangle @@ -477,6 +478,7 @@ visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel, check->viewz = viewz; check->viewangle = viewangle; check->plangle = plangle; + check->sector = sector; check->polyobj = polyobj; check->slope = slope; @@ -553,6 +555,7 @@ visplane_t *R_CheckPlane(visplane_t *pl, INT32 start, INT32 stop) new_pl->viewz = pl->viewz; new_pl->viewangle = pl->viewangle; new_pl->plangle = pl->plangle; + new_pl->sector = pl->sector; new_pl->polyobj = pl->polyobj; new_pl->slope = pl->slope; pl = new_pl; diff --git a/src/r_plane.h b/src/r_plane.h index 917e8b041..a3ae84f65 100644 --- a/src/r_plane.h +++ b/src/r_plane.h @@ -50,6 +50,7 @@ typedef struct visplane_s fixed_t xoffs, yoffs; // Scrolling flats. + sector_t *sector; struct ffloor_s *ffloor; polyobj_t *polyobj; pslope_t *slope; @@ -75,7 +76,7 @@ void R_ClearPlanes(void); void R_ClearFFloorClips (void); void R_DrawPlanes(void); -visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel, fixed_t xoff, fixed_t yoff, angle_t plangle, +visplane_t *R_FindPlane(sector_t *sector, fixed_t height, INT32 picnum, INT32 lightlevel, fixed_t xoff, fixed_t yoff, angle_t plangle, extracolormap_t *planecolormap, ffloor_t *ffloor, polyobj_t *polyobj, pslope_t *slope); visplane_t *R_CheckPlane(visplane_t *pl, INT32 start, INT32 stop); void R_ExpandPlane(visplane_t *pl, INT32 start, INT32 stop); diff --git a/src/r_portal.c b/src/r_portal.c index e594f960a..1a2b905f9 100644 --- a/src/r_portal.c +++ b/src/r_portal.c @@ -16,11 +16,13 @@ #include "r_main.h" #include "doomstat.h" #include "p_spec.h" // Skybox viewpoints +#include "p_local.h" #include "z_zone.h" #include "r_things.h" #include "r_sky.h" UINT8 portalrender; /**< When rendering a portal, it establishes the depth of the current BSP traversal. */ +UINT8 floorportalrender; /**< Same deal, but for floorportals. */ // Linked list for portals. portal_t *portal_base, *portal_cap; @@ -34,6 +36,7 @@ boolean portalline; // is curline a portal seg? void Portal_InitList (void) { portalrender = 0; + floorportalrender = 0; portal_base = portal_cap = NULL; } @@ -306,6 +309,45 @@ void Portal_AddSkybox (const visplane_t* plane) portal->clipline = -1; } +/** Creates a floor portal out of a visplane. + * + * Mostly the same as Portal_AddSkybox. + */ +void Portal_AddFloorPortal (const visplane_t* plane) +{ + INT16 start, end; + portal_t* portal; + sector_t *portalsector = plane->sector; + mobj_t *portalmobj = portalsector->portals[0]; + mobj_t *refmobj = portalsector->portals[1]; + fixed_t refx, refy; + + if (TrimVisplaneBounds(plane, &start, &end)) + return; + + portal = Portal_Add(start, end); + + Portal_ClipVisplane(plane, portal); + + if ((refmobj != NULL) && !(P_MobjWasRemoved(refmobj))) + { + refx = (refmobj->x - viewx); + refy = (refmobj->y - viewy); + } + else + { + refx = (portalsector->soundorg.x - viewx); + refy = (portalsector->soundorg.y - viewy); + } + + portal->viewx = portalmobj->x - refx; + portal->viewy = portalmobj->y - refy; + portal->viewz = portalmobj->z + viewz; + portal->viewangle = viewangle + portalmobj->angle; + + portal->clipline = -1; +} + /** Creates portals for the currently existing sky visplanes. * The visplanes are also removed and cleared from the list. */ @@ -319,13 +361,35 @@ void Portal_AddSkyboxPortals (void) { for (pl = visplanes[i]; pl; pl = pl->next) { + // true if added a portal for this visplane + boolean addedportal = false; + boolean floorportalpresent = (pl->sector->portals[0] != NULL && !P_MobjWasRemoved(pl->sector->portals[0])); + + // skybox portal if (pl->picnum == skyflatnum) { - Portal_AddSkybox(pl); + if (cv_skybox.value && skyboxmo[0]) + { + Portal_AddSkybox(pl); + addedportal = true; + } + } + // floor portal + else if (floorportalpresent) + { + if (floorportalrender < cv_maxportals.value) + { + Portal_AddFloorPortal(pl); + floorportalrender++; + addedportal = true; + } + } + // don't render this visplane anymore + if (addedportal) + { pl->minx = 0; pl->maxx = -1; - count++; } } diff --git a/src/r_portal.h b/src/r_portal.h index f90f05fbc..14b49f289 100644 --- a/src/r_portal.h +++ b/src/r_portal.h @@ -44,15 +44,17 @@ typedef struct portal_s extern portal_t* portal_base; extern portal_t* portal_cap; extern UINT8 portalrender; +extern UINT8 floorportalrender; extern line_t *portalclipline; extern sector_t *portalcullsector; extern INT32 portalclipstart, portalclipend; -void Portal_InitList (void); -void Portal_Remove (portal_t* portal); -void Portal_Add2Lines (const INT32 line1, const INT32 line2, const INT32 x1, const INT32 x2); -void Portal_AddSkybox (const visplane_t* plane); +void Portal_InitList (void); +void Portal_Remove (portal_t* portal); +void Portal_Add2Lines (const INT32 line1, const INT32 line2, const INT32 x1, const INT32 x2); +void Portal_AddSkybox (const visplane_t* plane); +void Portal_AddFloorPortal (const visplane_t* plane); void Portal_ClipRange (portal_t* portal); void Portal_ClipApply (const portal_t* portal); From 1506909a1f5ac3b943e7d068d06c01b298b0bb68 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Wed, 23 Aug 2023 04:24:06 -0300 Subject: [PATCH 066/227] Reimplement sector portals --- src/p_mobj.c | 22 ----------------- src/p_setup.c | 12 ++++++++-- src/p_spec.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++ src/r_bsp.c | 14 +++++------ src/r_defs.h | 14 +++++++++-- src/r_plane.c | 7 ++++-- src/r_plane.h | 3 ++- src/r_portal.c | 64 ++++++++++++++++++++------------------------------ src/r_portal.h | 2 +- 9 files changed, 121 insertions(+), 76 deletions(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index f7ad7616d..de2c3a72c 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -12822,28 +12822,6 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean skyboxviewpnts[tag] = mobj; break; } - case MT_PORTALREFPOINT: - { - size_t i; - for (i = 0; i < numsectors; i++) - { - sector_t *targetsec = §ors[i]; - for (int j = 0; j < targetsec->tags.count; j++) - { - if ((mthing->extrainfo == targetsec->tags.tags[j]) && (GETSECSPECIAL(targetsec->special, 3) == 1)) - { - // origin - if (targetsec == mobj->subsector->sector) - targetsec->portals[1] = mobj; - // target - else - targetsec->portals[0] = mobj; - break; - } - } - } - } - break; case MT_EGGSTATUE: if (mthing->args[1]) { diff --git a/src/p_setup.c b/src/p_setup.c index 51c14e147..c590bba4f 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -978,6 +978,13 @@ static void P_LoadVertices(UINT8 *data) } } +static void InitializeSectorPortal(sectorportal_t *secportal) +{ + secportal->target = NULL; + secportal->viewpoint.x = secportal->viewpoint.y = secportal->viewpoint.z = 0; + secportal->viewpoint.angle = 0; +} + static void P_InitializeSector(sector_t *ss) { memset(&ss->soundorg, 0, sizeof(ss->soundorg)); @@ -989,10 +996,11 @@ static void P_InitializeSector(sector_t *ss) ss->floordata = NULL; ss->ceilingdata = NULL; ss->lightingdata = NULL; - ss->portals[0] = NULL; - ss->portals[1] = NULL; ss->fadecolormapdata = NULL; + InitializeSectorPortal(&ss->portal_plane_floor); + InitializeSectorPortal(&ss->portal_plane_ceiling); + ss->heightsec = -1; ss->camsec = -1; diff --git a/src/p_spec.c b/src/p_spec.c index 28ecc60f4..4c34aaf05 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -6186,6 +6186,38 @@ fixed_t P_GetSectorGravityFactor(sector_t *sec) return sec->gravity; } +static void SetSectorPortal(sectorportal_t *secportal, sector_t *target_sector, INT32 viewpoint_tag) +{ + secportal->target = target_sector; + secportal->viewpoint.x = target_sector->soundorg.x; + secportal->viewpoint.y = target_sector->soundorg.y; + secportal->viewpoint.z = target_sector->ceilingheight; + secportal->viewpoint.angle = 0; + + if (viewpoint_tag <= 0) + return; + + for (thinker_t *th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) + { + if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) + continue; + + mobj_t *mo = (mobj_t *)th; + + if (mo->type != MT_PORTALREFPOINT || mo->spawnpoint == NULL) + continue; + + if (!Tag_Find(&mo->spawnpoint->tags, viewpoint_tag)) + continue; + + secportal->viewpoint.x = mo->x; + secportal->viewpoint.y = mo->y; + secportal->viewpoint.z = mo->z; + secportal->viewpoint.angle = mo->angle; + return; + } +} + /** After the map has loaded, scans for specials that spawn 3Dfloors and * thinkers. * @@ -6351,6 +6383,33 @@ void P_SpawnSpecials(boolean fromnetsave) P_AddCameraScanner(§ors[sec], §ors[s], R_PointToAngle2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y)); break; + case 6: // Sector portal + { + INT32 s1, s2; + TAG_ITER_SECTORS(lines[i].args[0], s1) // Target sector tag + { + TAG_ITER_SECTORS(lines[i].args[1], s2) // Sector tag to make a portal to + { + sector_t *target_sector = §ors[s2]; + boolean floor, ceiling; + + if (lines[i].args[2] == TMP_BOTH) + floor = ceiling = true; + else + { + floor = lines[i].args[2] == TMP_FLOOR; + ceiling = lines[i].args[2] == TMP_CEILING; + } + + if (floor) + SetSectorPortal(§ors[s1].portal_plane_floor, target_sector, lines[i].args[3]); + if (ceiling) + SetSectorPortal(§ors[s1].portal_plane_ceiling, target_sector, lines[i].args[3]); + } + } + break; + } + case 7: // Flat alignment - redone by toast { // Set calculated offsets such that line's v1 is the apparent origin diff --git a/src/r_bsp.c b/src/r_bsp.c index 70a9dbd94..5c9aa3ff9 100644 --- a/src/r_bsp.c +++ b/src/r_bsp.c @@ -909,7 +909,7 @@ static void R_Subsector(size_t num) || (frontsector->heightsec != -1 && sectors[frontsector->heightsec].ceilingpic == skyflatnum)) { floorplane = R_FindPlane(frontsector, frontsector->floorheight, frontsector->floorpic, floorlightlevel, - frontsector->floorxoffset, frontsector->flooryoffset, frontsector->floorangle, floorcolormap, NULL, NULL, frontsector->f_slope); + frontsector->floorxoffset, frontsector->flooryoffset, frontsector->floorangle, floorcolormap, NULL, NULL, frontsector->f_slope, frontsector->portal_plane_floor.target != NULL ? &frontsector->portal_plane_floor : NULL); } else floorplane = NULL; @@ -920,7 +920,7 @@ static void R_Subsector(size_t num) { ceilingplane = R_FindPlane(frontsector, frontsector->ceilingheight, frontsector->ceilingpic, ceilinglightlevel, frontsector->ceilingxoffset, frontsector->ceilingyoffset, frontsector->ceilingangle, - ceilingcolormap, NULL, NULL, frontsector->c_slope); + ceilingcolormap, NULL, NULL, frontsector->c_slope, frontsector->portal_plane_ceiling.target != NULL ? &frontsector->portal_plane_ceiling : NULL); } else ceilingplane = NULL; @@ -963,7 +963,7 @@ static void R_Subsector(size_t num) ffloor[numffloors].plane = R_FindPlane(rover->master->frontsector, *rover->bottomheight, *rover->bottompic, *frontsector->lightlist[light].lightlevel, *rover->bottomxoffs, - *rover->bottomyoffs, *rover->bottomangle, *frontsector->lightlist[light].extra_colormap, rover, NULL, *rover->b_slope); + *rover->bottomyoffs, *rover->bottomangle, *frontsector->lightlist[light].extra_colormap, rover, NULL, *rover->b_slope, NULL); ffloor[numffloors].slope = *rover->b_slope; @@ -992,7 +992,7 @@ static void R_Subsector(size_t num) ffloor[numffloors].plane = R_FindPlane(rover->master->frontsector, *rover->topheight, *rover->toppic, *frontsector->lightlist[light].lightlevel, *rover->topxoffs, *rover->topyoffs, *rover->topangle, - *frontsector->lightlist[light].extra_colormap, rover, NULL, *rover->t_slope); + *frontsector->lightlist[light].extra_colormap, rover, NULL, *rover->t_slope, NULL); ffloor[numffloors].slope = *rover->t_slope; @@ -1036,12 +1036,11 @@ static void R_Subsector(size_t num) (light == -1 ? frontsector->lightlevel : *frontsector->lightlist[light].lightlevel), polysec->floorxoffset, polysec->flooryoffset, polysec->floorangle-po->angle, (light == -1 ? frontsector->extra_colormap : *frontsector->lightlist[light].extra_colormap), NULL, po, - NULL); // will ffloors be slopable eventually? + NULL, NULL); ffloor[numffloors].height = polysec->floorheight; ffloor[numffloors].polyobj = po; ffloor[numffloors].slope = NULL; - //ffloor[numffloors].ffloor = rover; po->visplane = ffloor[numffloors].plane; numffloors++; } @@ -1059,12 +1058,11 @@ static void R_Subsector(size_t num) ffloor[numffloors].plane = R_FindPlane(polysec, polysec->ceilingheight, polysec->ceilingpic, (light == -1 ? frontsector->lightlevel : *frontsector->lightlist[light].lightlevel), polysec->ceilingxoffset, polysec->ceilingyoffset, polysec->ceilingangle-po->angle, (light == -1 ? frontsector->extra_colormap : *frontsector->lightlist[light].extra_colormap), NULL, po, - NULL); // will ffloors be slopable eventually? + NULL, NULL); ffloor[numffloors].polyobj = po; ffloor[numffloors].height = polysec->ceilingheight; ffloor[numffloors].slope = NULL; - //ffloor[numffloors].ffloor = rover; po->visplane = ffloor[numffloors].plane; numffloors++; } diff --git a/src/r_defs.h b/src/r_defs.h index 61f9eaa4e..d9fe6193d 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -208,6 +208,15 @@ typedef enum BT_STRONG, } busttype_e; +typedef struct sectorportal_s +{ + struct sector_s *target; + struct { + fixed_t x, y, z; + angle_t angle; + } viewpoint; +} sectorportal_t; + typedef struct ffloor_s { fixed_t *topheight; @@ -495,8 +504,9 @@ typedef struct sector_s // colormap structure extracolormap_t *spawn_extra_colormap; - // floor portals - mobj_t *portals[2]; + // portals + sectorportal_t portal_plane_floor; + sectorportal_t portal_plane_ceiling; } sector_t; // diff --git a/src/r_plane.c b/src/r_plane.c index a24ea9d28..5cd791023 100644 --- a/src/r_plane.c +++ b/src/r_plane.c @@ -392,7 +392,7 @@ static visplane_t *new_visplane(unsigned hash) // visplane_t *R_FindPlane(sector_t *sector, fixed_t height, INT32 picnum, INT32 lightlevel, fixed_t xoff, fixed_t yoff, angle_t plangle, extracolormap_t *planecolormap, - ffloor_t *pfloor, polyobj_t *polyobj, pslope_t *slope) + ffloor_t *pfloor, polyobj_t *polyobj, pslope_t *slope, sectorportal_t *portalsector) { visplane_t *check; unsigned hash; @@ -451,7 +451,8 @@ visplane_t *R_FindPlane(sector_t *sector, fixed_t height, INT32 picnum, INT32 li && check->viewx == viewx && check->viewy == viewy && check->viewz == viewz && check->viewangle == viewangle && check->plangle == plangle - && check->slope == slope) + && check->slope == slope + && check->portalsector == portalsector) { return check; } @@ -479,6 +480,7 @@ visplane_t *R_FindPlane(sector_t *sector, fixed_t height, INT32 picnum, INT32 li check->viewangle = viewangle; check->plangle = plangle; check->sector = sector; + check->portalsector = portalsector; check->polyobj = polyobj; check->slope = slope; @@ -558,6 +560,7 @@ visplane_t *R_CheckPlane(visplane_t *pl, INT32 start, INT32 stop) new_pl->sector = pl->sector; new_pl->polyobj = pl->polyobj; new_pl->slope = pl->slope; + new_pl->portalsector = pl->portalsector; pl = new_pl; pl->minx = start; pl->maxx = stop; diff --git a/src/r_plane.h b/src/r_plane.h index a3ae84f65..e33267798 100644 --- a/src/r_plane.h +++ b/src/r_plane.h @@ -54,6 +54,7 @@ typedef struct visplane_s struct ffloor_s *ffloor; polyobj_t *polyobj; pslope_t *slope; + sectorportal_t *portalsector; } visplane_t; extern visplane_t *visplanes[MAXVISPLANES]; @@ -77,7 +78,7 @@ void R_ClearFFloorClips (void); void R_DrawPlanes(void); visplane_t *R_FindPlane(sector_t *sector, fixed_t height, INT32 picnum, INT32 lightlevel, fixed_t xoff, fixed_t yoff, angle_t plangle, - extracolormap_t *planecolormap, ffloor_t *ffloor, polyobj_t *polyobj, pslope_t *slope); + extracolormap_t *planecolormap, ffloor_t *ffloor, polyobj_t *polyobj, pslope_t *slope, sectorportal_t *portalsector); visplane_t *R_CheckPlane(visplane_t *pl, INT32 start, INT32 stop); void R_ExpandPlane(visplane_t *pl, INT32 start, INT32 stop); void R_PlaneBounds(visplane_t *plane); diff --git a/src/r_portal.c b/src/r_portal.c index 1a2b905f9..b4328a56e 100644 --- a/src/r_portal.c +++ b/src/r_portal.c @@ -309,41 +309,38 @@ void Portal_AddSkybox (const visplane_t* plane) portal->clipline = -1; } -/** Creates a floor portal out of a visplane. +/** Creates a sector portal out of a visplane. * * Mostly the same as Portal_AddSkybox. */ -void Portal_AddFloorPortal (const visplane_t* plane) +void Portal_AddSectorPortal (const visplane_t* plane) { INT16 start, end; - portal_t* portal; - sector_t *portalsector = plane->sector; - mobj_t *portalmobj = portalsector->portals[0]; - mobj_t *refmobj = portalsector->portals[1]; - fixed_t refx, refy; + sector_t *source = plane->sector; + sectorportal_t *target = plane->portalsector; if (TrimVisplaneBounds(plane, &start, &end)) return; - portal = Portal_Add(start, end); + portal_t* portal = Portal_Add(start, end); Portal_ClipVisplane(plane, portal); - if ((refmobj != NULL) && !(P_MobjWasRemoved(refmobj))) + fixed_t refx = source->soundorg.x - viewx; + fixed_t refy = source->soundorg.y - viewy; + + if (target->viewpoint.angle) { - refx = (refmobj->x - viewx); - refy = (refmobj->y - viewy); - } - else - { - refx = (portalsector->soundorg.x - viewx); - refy = (portalsector->soundorg.y - viewy); + fixed_t x = refx, y = refy; + angle_t ang = target->viewpoint.angle >> ANGLETOFINESHIFT; + refx = FixedMul(x, FINECOSINE(ang)) - FixedMul(y, FINESINE(ang)); + refy = FixedMul(x, FINESINE(ang)) + FixedMul(y, FINECOSINE(ang)); } - portal->viewx = portalmobj->x - refx; - portal->viewy = portalmobj->y - refy; - portal->viewz = portalmobj->z + viewz; - portal->viewangle = viewangle + portalmobj->angle; + portal->viewx = target->viewpoint.x - refx; + portal->viewy = target->viewpoint.y - refy; + portal->viewz = target->viewpoint.z + viewz; + portal->viewangle = target->viewpoint.angle + viewangle; portal->clipline = -1; } @@ -354,16 +351,12 @@ void Portal_AddFloorPortal (const visplane_t* plane) void Portal_AddSkyboxPortals (void) { visplane_t *pl; - INT32 i; - UINT16 count = 0; - for (i = 0; i < MAXVISPLANES; i++, pl++) + for (INT32 i = 0; i < MAXVISPLANES; i++, pl++) { for (pl = visplanes[i]; pl; pl = pl->next) { - // true if added a portal for this visplane - boolean addedportal = false; - boolean floorportalpresent = (pl->sector->portals[0] != NULL && !P_MobjWasRemoved(pl->sector->portals[0])); + boolean added_portal = false; // skybox portal if (pl->picnum == skyflatnum) @@ -371,29 +364,24 @@ void Portal_AddSkyboxPortals (void) if (cv_skybox.value && skyboxmo[0]) { Portal_AddSkybox(pl); - addedportal = true; + added_portal = true; } } + // floor portal - else if (floorportalpresent) + if (pl->portalsector && pl->portalsector->target && floorportalrender < cv_maxportals.value) { - if (floorportalrender < cv_maxportals.value) - { - Portal_AddFloorPortal(pl); - floorportalrender++; - addedportal = true; - } + Portal_AddSectorPortal(pl); + floorportalrender++; + added_portal = true; } // don't render this visplane anymore - if (addedportal) + if (added_portal) { pl->minx = 0; pl->maxx = -1; - count++; } } } - - CONS_Debug(DBG_RENDER, "Skybox portals: %d\n", count); } diff --git a/src/r_portal.h b/src/r_portal.h index 14b49f289..6e2a6dedb 100644 --- a/src/r_portal.h +++ b/src/r_portal.h @@ -54,7 +54,7 @@ void Portal_InitList (void); void Portal_Remove (portal_t* portal); void Portal_Add2Lines (const INT32 line1, const INT32 line2, const INT32 x1, const INT32 x2); void Portal_AddSkybox (const visplane_t* plane); -void Portal_AddFloorPortal (const visplane_t* plane); +void Portal_AddSectorPortal (const visplane_t* plane); void Portal_ClipRange (portal_t* portal); void Portal_ClipApply (const portal_t* portal); From 2b64698c4eedd14ab80d94e866d8962aba5aa545 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Wed, 23 Aug 2023 04:34:35 -0300 Subject: [PATCH 067/227] Use different default viewpoint heights so that ceiling portals make more sense --- src/p_spec.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/p_spec.c b/src/p_spec.c index 4c34aaf05..5dc99ee5b 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -6186,12 +6186,12 @@ fixed_t P_GetSectorGravityFactor(sector_t *sec) return sec->gravity; } -static void SetSectorPortal(sectorportal_t *secportal, sector_t *target_sector, INT32 viewpoint_tag) +static void SetSectorPortal(sectorportal_t *secportal, sector_t *target_sector, fixed_t default_z, INT32 viewpoint_tag) { secportal->target = target_sector; secportal->viewpoint.x = target_sector->soundorg.x; secportal->viewpoint.y = target_sector->soundorg.y; - secportal->viewpoint.z = target_sector->ceilingheight; + secportal->viewpoint.z = default_z; secportal->viewpoint.angle = 0; if (viewpoint_tag <= 0) @@ -6402,9 +6402,9 @@ void P_SpawnSpecials(boolean fromnetsave) } if (floor) - SetSectorPortal(§ors[s1].portal_plane_floor, target_sector, lines[i].args[3]); + SetSectorPortal(§ors[s1].portal_plane_floor, target_sector, target_sector->ceilingheight, lines[i].args[3]); if (ceiling) - SetSectorPortal(§ors[s1].portal_plane_ceiling, target_sector, lines[i].args[3]); + SetSectorPortal(§ors[s1].portal_plane_ceiling, target_sector, target_sector->floorheight, lines[i].args[3]); } } break; From 03daf721effb5d99254f8d6e86fbc697ed79bf67 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Wed, 23 Aug 2023 13:43:02 -0300 Subject: [PATCH 068/227] Allow sector portals to be displayed properly on sky sectors --- src/hardware/hw_main.c | 18 +++++++----------- src/p_setup.c | 10 +++++----- src/p_spec.c | 27 ++++++++++++++++----------- src/p_spec.h | 2 ++ src/r_bsp.c | 19 ++++++++++++++----- src/r_bsp.h | 6 ++++-- src/r_defs.h | 8 ++++---- src/r_plane.c | 3 +-- src/r_portal.c | 39 +++++++++++++++++---------------------- src/r_portal.h | 1 - src/r_segs.c | 20 +++----------------- 11 files changed, 73 insertions(+), 80 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index bc66955fc..2340b9e8b 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -1117,8 +1117,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom INT32 gl_toptexture = 0, gl_bottomtexture = 0; fixed_t texturevpeg; - boolean bothceilingssky = false; // turned on if both back and front ceilings are sky - boolean bothfloorssky = false; // likewise, but for floors + bothceilingssky = bothfloorssky = false; SLOPEPARAMS(gl_backsector->c_slope, worldhigh, worldhighslope, gl_backsector->ceilingheight) SLOPEPARAMS(gl_backsector->f_slope, worldlow, worldlowslope, gl_backsector->floorheight) @@ -1854,12 +1853,6 @@ static boolean CheckClip(seg_t * seg, sector_t * afrontsector, sector_t * abacks { fixed_t frontf1,frontf2, frontc1, frontc2; // front floor/ceiling ends fixed_t backf1, backf2, backc1, backc2; // back floor ceiling ends - boolean bothceilingssky = false, bothfloorssky = false; - - if (abacksector->ceilingpic == skyflatnum && afrontsector->ceilingpic == skyflatnum) - bothceilingssky = true; - if (abacksector->floorpic == skyflatnum && afrontsector->floorpic == skyflatnum) - bothfloorssky = true; // GZDoom method of sloped line clipping @@ -2354,13 +2347,16 @@ static void HWR_AddLine(seg_t * line) } else { - boolean bothceilingssky = false, bothfloorssky = false; + bothceilingssky = bothfloorssky = false; gl_backsector = R_FakeFlat(gl_backsector, &tempsec, NULL, NULL, true); - if (gl_backsector->ceilingpic == skyflatnum && gl_frontsector->ceilingpic == skyflatnum) + if (gl_backsector->ceilingpic == skyflatnum && gl_frontsector->ceilingpic == skyflatnum + && !(gl_backsector->portal_ceiling.exists || gl_frontsector->portal_ceiling.exists)) bothceilingssky = true; - if (gl_backsector->floorpic == skyflatnum && gl_frontsector->floorpic == skyflatnum) + + if (gl_backsector->floorpic == skyflatnum && gl_frontsector->floorpic == skyflatnum + && !(gl_backsector->portal_floor.exists || gl_frontsector->portal_floor.exists)) bothfloorssky = true; if (bothceilingssky && bothfloorssky) // everything's sky? let's save us a bit of time then diff --git a/src/p_setup.c b/src/p_setup.c index c590bba4f..b9c2b5939 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -980,9 +980,9 @@ static void P_LoadVertices(UINT8 *data) static void InitializeSectorPortal(sectorportal_t *secportal) { - secportal->target = NULL; - secportal->viewpoint.x = secportal->viewpoint.y = secportal->viewpoint.z = 0; - secportal->viewpoint.angle = 0; + secportal->exists = false; + secportal->target.x = secportal->target.y = secportal->target.z = 0; + secportal->target.angle = 0; } static void P_InitializeSector(sector_t *ss) @@ -998,8 +998,8 @@ static void P_InitializeSector(sector_t *ss) ss->lightingdata = NULL; ss->fadecolormapdata = NULL; - InitializeSectorPortal(&ss->portal_plane_floor); - InitializeSectorPortal(&ss->portal_plane_ceiling); + InitializeSectorPortal(&ss->portal_floor); + InitializeSectorPortal(&ss->portal_ceiling); ss->heightsec = -1; ss->camsec = -1; diff --git a/src/p_spec.c b/src/p_spec.c index 5dc99ee5b..57653416a 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -6186,13 +6186,18 @@ fixed_t P_GetSectorGravityFactor(sector_t *sec) return sec->gravity; } +boolean P_CompareSectorPortals(sectorportal_t *a, sectorportal_t *b) +{ + return !memcmp(a, b, sizeof(sectorportal_t)); +} + static void SetSectorPortal(sectorportal_t *secportal, sector_t *target_sector, fixed_t default_z, INT32 viewpoint_tag) { - secportal->target = target_sector; - secportal->viewpoint.x = target_sector->soundorg.x; - secportal->viewpoint.y = target_sector->soundorg.y; - secportal->viewpoint.z = default_z; - secportal->viewpoint.angle = 0; + secportal->exists = target_sector; + secportal->target.x = target_sector->soundorg.x; + secportal->target.y = target_sector->soundorg.y; + secportal->target.z = default_z; + secportal->target.angle = 0; if (viewpoint_tag <= 0) return; @@ -6210,10 +6215,10 @@ static void SetSectorPortal(sectorportal_t *secportal, sector_t *target_sector, if (!Tag_Find(&mo->spawnpoint->tags, viewpoint_tag)) continue; - secportal->viewpoint.x = mo->x; - secportal->viewpoint.y = mo->y; - secportal->viewpoint.z = mo->z; - secportal->viewpoint.angle = mo->angle; + secportal->target.x = mo->x; + secportal->target.y = mo->y; + secportal->target.z = mo->z; + secportal->target.angle = mo->angle; return; } } @@ -6402,9 +6407,9 @@ void P_SpawnSpecials(boolean fromnetsave) } if (floor) - SetSectorPortal(§ors[s1].portal_plane_floor, target_sector, target_sector->ceilingheight, lines[i].args[3]); + SetSectorPortal(§ors[s1].portal_floor, target_sector, target_sector->ceilingheight, lines[i].args[3]); if (ceiling) - SetSectorPortal(§ors[s1].portal_plane_ceiling, target_sector, target_sector->floorheight, lines[i].args[3]); + SetSectorPortal(§ors[s1].portal_ceiling, target_sector, target_sector->floorheight, lines[i].args[3]); } } break; diff --git a/src/p_spec.h b/src/p_spec.h index 50ab6410f..71c9dcf3a 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -521,6 +521,8 @@ INT32 P_FindMinSurroundingLight(sector_t *sector, INT32 max); void P_SetupSignExit(player_t *player); boolean P_IsFlagAtBase(mobjtype_t flag); +boolean P_CompareSectorPortals(sectorportal_t *a, sectorportal_t *b); + boolean P_IsMobjTouchingSectorPlane(mobj_t *mo, sector_t *sec); boolean P_IsMobjTouching3DFloor(mobj_t *mo, ffloor_t *ffloor, sector_t *sec); boolean P_IsMobjTouchingPolyobj(mobj_t *mo, polyobj_t *po, sector_t *polysec); diff --git a/src/r_bsp.c b/src/r_bsp.c index 5c9aa3ff9..70f08e127 100644 --- a/src/r_bsp.c +++ b/src/r_bsp.c @@ -36,6 +36,9 @@ drawseg_t *curdrawsegs = NULL; /**< This is used to handle multiple lists for ma drawseg_t *drawsegs = NULL; drawseg_t *ds_p = NULL; +boolean bothceilingssky = false; // turned on if both back and front ceilings are sky +boolean bothfloorssky = false; // likewise, but for floors + // indicates doors closed wrt automap bugfix: INT32 doorclosed; @@ -391,7 +394,6 @@ static void R_AddLine(seg_t *line) INT32 x1, x2; angle_t angle1, angle2, span, tspan; static sector_t tempsec; - boolean bothceilingssky = false, bothfloorssky = false; portalline = false; @@ -481,10 +483,17 @@ static void R_AddLine(seg_t *line) backsector = R_FakeFlat(backsector, &tempsec, NULL, NULL, true); doorclosed = 0; + bothceilingssky = bothfloorssky = false; - if (backsector->ceilingpic == skyflatnum && frontsector->ceilingpic == skyflatnum) + // hack to allow height changes in outdoor areas + // This is what gets rid of the upper textures if there should be sky + if (backsector->ceilingpic == skyflatnum && frontsector->ceilingpic == skyflatnum + && !(backsector->portal_ceiling.exists || frontsector->portal_ceiling.exists)) bothceilingssky = true; - if (backsector->floorpic == skyflatnum && frontsector->floorpic == skyflatnum) + + // likewise, but for floors and upper textures + if (backsector->floorpic == skyflatnum && frontsector->floorpic == skyflatnum + && !(backsector->portal_floor.exists || frontsector->portal_floor.exists)) bothfloorssky = true; if (bothceilingssky && bothfloorssky) // everything's sky? let's save us a bit of time then @@ -909,7 +918,7 @@ static void R_Subsector(size_t num) || (frontsector->heightsec != -1 && sectors[frontsector->heightsec].ceilingpic == skyflatnum)) { floorplane = R_FindPlane(frontsector, frontsector->floorheight, frontsector->floorpic, floorlightlevel, - frontsector->floorxoffset, frontsector->flooryoffset, frontsector->floorangle, floorcolormap, NULL, NULL, frontsector->f_slope, frontsector->portal_plane_floor.target != NULL ? &frontsector->portal_plane_floor : NULL); + frontsector->floorxoffset, frontsector->flooryoffset, frontsector->floorangle, floorcolormap, NULL, NULL, frontsector->f_slope, frontsector->portal_floor.exists ? &frontsector->portal_floor : NULL); } else floorplane = NULL; @@ -920,7 +929,7 @@ static void R_Subsector(size_t num) { ceilingplane = R_FindPlane(frontsector, frontsector->ceilingheight, frontsector->ceilingpic, ceilinglightlevel, frontsector->ceilingxoffset, frontsector->ceilingyoffset, frontsector->ceilingangle, - ceilingcolormap, NULL, NULL, frontsector->c_slope, frontsector->portal_plane_ceiling.target != NULL ? &frontsector->portal_plane_ceiling : NULL); + ceilingcolormap, NULL, NULL, frontsector->c_slope, frontsector->portal_ceiling.exists ? &frontsector->portal_ceiling : NULL); } else ceilingplane = NULL; diff --git a/src/r_bsp.h b/src/r_bsp.h index 55199405a..f36f7e64d 100644 --- a/src/r_bsp.h +++ b/src/r_bsp.h @@ -25,13 +25,15 @@ extern sector_t *frontsector; extern sector_t *backsector; extern boolean portalline; // is curline a portal seg? -// drawsegs are allocated on the fly... see r_segs.c - extern INT32 checkcoord[12][4]; extern drawseg_t *curdrawsegs; extern drawseg_t *drawsegs; extern drawseg_t *ds_p; + +extern boolean bothceilingssky; +extern boolean bothfloorssky; + extern INT32 doorclosed; // BSP? diff --git a/src/r_defs.h b/src/r_defs.h index d9fe6193d..d435f69ab 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -210,11 +210,11 @@ typedef enum typedef struct sectorportal_s { - struct sector_s *target; + boolean exists; struct { fixed_t x, y, z; angle_t angle; - } viewpoint; + } target; } sectorportal_t; typedef struct ffloor_s @@ -505,8 +505,8 @@ typedef struct sector_s extracolormap_t *spawn_extra_colormap; // portals - sectorportal_t portal_plane_floor; - sectorportal_t portal_plane_ceiling; + sectorportal_t portal_floor; + sectorportal_t portal_ceiling; } sector_t; // diff --git a/src/r_plane.c b/src/r_plane.c index 5cd791023..d9051ae05 100644 --- a/src/r_plane.c +++ b/src/r_plane.c @@ -441,8 +441,6 @@ visplane_t *R_FindPlane(sector_t *sector, fixed_t height, INT32 picnum, INT32 li hash = visplane_hash(picnum, lightlevel, height); for (check = visplanes[hash]; check; check = check->next) { - if (polyobj != check->polyobj) - continue; if (height == check->height && picnum == check->picnum && lightlevel == check->lightlevel && xoff == check->xoffs && yoff == check->yoffs @@ -452,6 +450,7 @@ visplane_t *R_FindPlane(sector_t *sector, fixed_t height, INT32 picnum, INT32 li && check->viewangle == viewangle && check->plangle == plangle && check->slope == slope + && check->polyobj == polyobj && check->portalsector == portalsector) { return check; diff --git a/src/r_portal.c b/src/r_portal.c index b4328a56e..136eb4fac 100644 --- a/src/r_portal.c +++ b/src/r_portal.c @@ -22,7 +22,6 @@ #include "r_sky.h" UINT8 portalrender; /**< When rendering a portal, it establishes the depth of the current BSP traversal. */ -UINT8 floorportalrender; /**< Same deal, but for floorportals. */ // Linked list for portals. portal_t *portal_base, *portal_cap; @@ -36,7 +35,6 @@ boolean portalline; // is curline a portal seg? void Portal_InitList (void) { portalrender = 0; - floorportalrender = 0; portal_base = portal_cap = NULL; } @@ -317,7 +315,7 @@ void Portal_AddSectorPortal (const visplane_t* plane) { INT16 start, end; sector_t *source = plane->sector; - sectorportal_t *target = plane->portalsector; + sectorportal_t *secportal = plane->portalsector; if (TrimVisplaneBounds(plane, &start, &end)) return; @@ -329,18 +327,19 @@ void Portal_AddSectorPortal (const visplane_t* plane) fixed_t refx = source->soundorg.x - viewx; fixed_t refy = source->soundorg.y - viewy; - if (target->viewpoint.angle) + // Rotate the X/Y to match the target angle + if (secportal->target.angle) { fixed_t x = refx, y = refy; - angle_t ang = target->viewpoint.angle >> ANGLETOFINESHIFT; + angle_t ang = secportal->target.angle >> ANGLETOFINESHIFT; refx = FixedMul(x, FINECOSINE(ang)) - FixedMul(y, FINESINE(ang)); refy = FixedMul(x, FINESINE(ang)) + FixedMul(y, FINECOSINE(ang)); } - portal->viewx = target->viewpoint.x - refx; - portal->viewy = target->viewpoint.y - refy; - portal->viewz = target->viewpoint.z + viewz; - portal->viewangle = target->viewpoint.angle + viewangle; + portal->viewx = secportal->target.x - refx; + portal->viewy = secportal->target.y - refy; + portal->viewz = secportal->target.z + viewz; + portal->viewangle = secportal->target.angle + viewangle; portal->clipline = -1; } @@ -358,21 +357,17 @@ void Portal_AddSkyboxPortals (void) { boolean added_portal = false; - // skybox portal - if (pl->picnum == skyflatnum) - { - if (cv_skybox.value && skyboxmo[0]) - { - Portal_AddSkybox(pl); - added_portal = true; - } - } - - // floor portal - if (pl->portalsector && pl->portalsector->target && floorportalrender < cv_maxportals.value) + // Render sector portal if recursiveness limit hasn't been reached + if (pl->portalsector && portalrender < cv_maxportals.value) { Portal_AddSectorPortal(pl); - floorportalrender++; + added_portal = true; + } + + // Render skybox portal + if (!added_portal && pl->picnum == skyflatnum && cv_skybox.value && skyboxmo[0]) + { + Portal_AddSkybox(pl); added_portal = true; } diff --git a/src/r_portal.h b/src/r_portal.h index 6e2a6dedb..1e4bbfb12 100644 --- a/src/r_portal.h +++ b/src/r_portal.h @@ -44,7 +44,6 @@ typedef struct portal_s extern portal_t* portal_base; extern portal_t* portal_cap; extern UINT8 portalrender; -extern UINT8 floorportalrender; extern line_t *portalclipline; extern sector_t *portalcullsector; diff --git a/src/r_segs.c b/src/r_segs.c index 9af83f0c7..a8fb635d1 100644 --- a/src/r_segs.c +++ b/src/r_segs.c @@ -1787,8 +1787,7 @@ void R_StoreWallRange(INT32 start, INT32 stop) else { // two sided line - boolean bothceilingssky = false; // turned on if both back and front ceilings are sky - boolean bothfloorssky = false; // likewise, but for floors + bothceilingssky = bothfloorssky = false; SLOPEPARAMS(backsector->c_slope, worldhigh, worldhighslope, backsector->ceilingheight) SLOPEPARAMS(backsector->f_slope, worldlow, worldlowslope, backsector->floorheight) @@ -1797,21 +1796,6 @@ void R_StoreWallRange(INT32 start, INT32 stop) worldlow -= viewz; worldlowslope -= viewz; - // hack to allow height changes in outdoor areas - // This is what gets rid of the upper textures if there should be sky - if (frontsector->ceilingpic == skyflatnum - && backsector->ceilingpic == skyflatnum) - { - bothceilingssky = true; - } - - // likewise, but for floors and upper textures - if (frontsector->floorpic == skyflatnum - && backsector->floorpic == skyflatnum) - { - bothfloorssky = true; - } - ds_p->sprtopclip = ds_p->sprbottomclip = NULL; ds_p->silhouette = 0; @@ -1909,6 +1893,7 @@ void R_StoreWallRange(INT32 start, INT32 stop) || backsector->floorlightsec != frontsector->floorlightsec //SoM: 4/3/2000: Check for colormaps || frontsector->extra_colormap != backsector->extra_colormap + || !P_CompareSectorPortals(&frontsector->portal_floor, &backsector->portal_floor) || (frontsector->ffloors != backsector->ffloors && !Tag_Compare(&frontsector->tags, &backsector->tags))) { markfloor = true; @@ -1942,6 +1927,7 @@ void R_StoreWallRange(INT32 start, INT32 stop) || backsector->ceilinglightsec != frontsector->ceilinglightsec //SoM: 4/3/2000: Check for colormaps || frontsector->extra_colormap != backsector->extra_colormap + || !P_CompareSectorPortals(&frontsector->portal_ceiling, &backsector->portal_ceiling) || (frontsector->ffloors != backsector->ffloors && !Tag_Compare(&frontsector->tags, &backsector->tags))) { markceiling = true; From 5b387ec94ac96180a99d98677d48a5e9cc871954 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Wed, 23 Aug 2023 15:04:50 -0300 Subject: [PATCH 069/227] Make linedef type 6 closer to ZDoom's Sector_SetPortal --- src/deh_tables.c | 1 - src/hardware/hw_main.c | 4 +- src/info.c | 27 ------- src/info.h | 1 - src/p_setup.c | 4 +- src/p_spec.c | 165 ++++++++++++++++++++++++++++++++--------- src/p_spec.h | 2 + src/r_bsp.c | 8 +- src/r_defs.h | 24 ++++-- src/r_portal.c | 106 ++++++++++++++++++-------- src/r_segs.c | 2 +- 11 files changed, 236 insertions(+), 108 deletions(-) diff --git a/src/deh_tables.c b/src/deh_tables.c index 05f61f6e5..34cf7e5ff 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -4285,7 +4285,6 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t // Portal objects "MT_SKYBOX", - "MT_PORTALREFPOINT", // Debris "MT_SPARK", //spark diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 2340b9e8b..12a2d54ee 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -2352,11 +2352,11 @@ static void HWR_AddLine(seg_t * line) gl_backsector = R_FakeFlat(gl_backsector, &tempsec, NULL, NULL, true); if (gl_backsector->ceilingpic == skyflatnum && gl_frontsector->ceilingpic == skyflatnum - && !(gl_backsector->portal_ceiling.exists || gl_frontsector->portal_ceiling.exists)) + && !(P_SectorHasCeilingPortal(gl_backsector) || P_SectorHasCeilingPortal(gl_frontsector))) bothceilingssky = true; if (gl_backsector->floorpic == skyflatnum && gl_frontsector->floorpic == skyflatnum - && !(gl_backsector->portal_floor.exists || gl_frontsector->portal_floor.exists)) + && !(P_SectorHasFloorPortal(gl_backsector) || P_SectorHasFloorPortal(gl_frontsector))) bothfloorssky = true; if (bothceilingssky && bothfloorssky) // everything's sky? let's save us a bit of time then diff --git a/src/info.c b/src/info.c index f71a2bd1c..11e9c9fe8 100644 --- a/src/info.c +++ b/src/info.c @@ -20759,33 +20759,6 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, - { // MT_PORTALREFPOINT - 781, // doomednum - S_INVISIBLE, // spawnstate - 1000, // spawnhealth - S_NULL, // seestate - sfx_None, // seesound - 8, // reactiontime - sfx_None, // attacksound - S_NULL, // painstate - 0, // painchance - sfx_None, // painsound - S_NULL, // meleestate - S_NULL, // missilestate - S_NULL, // deathstate - S_NULL, // xdeathstate - sfx_None, // deathsound - 0, // speed - 12*FRACUNIT, // radius - 24*FRACUNIT, // height - 0, // display offset - 10, // mass - 0, // damage - sfx_None, // activesound - MF_SCENERY|MF_NOBLOCKMAP|MF_NOGRAVITY, // flags - S_NULL // raisestate - }, - { // MT_SPARK -1, // doomednum S_SPRK1, // spawnstate diff --git a/src/info.h b/src/info.h index 00822faea..602d12f28 100644 --- a/src/info.h +++ b/src/info.h @@ -5116,7 +5116,6 @@ typedef enum mobj_type // Portal objects MT_SKYBOX, - MT_PORTALREFPOINT, // Debris MT_SPARK, //spark diff --git a/src/p_setup.c b/src/p_setup.c index b9c2b5939..79c919a4f 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -980,9 +980,7 @@ static void P_LoadVertices(UINT8 *data) static void InitializeSectorPortal(sectorportal_t *secportal) { - secportal->exists = false; - secportal->target.x = secportal->target.y = secportal->target.z = 0; - secportal->target.angle = 0; + memset(secportal, 0, sizeof(*secportal)); } static void P_InitializeSector(sector_t *ss) diff --git a/src/p_spec.c b/src/p_spec.c index 57653416a..c4c254a82 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -6186,22 +6186,54 @@ fixed_t P_GetSectorGravityFactor(sector_t *sec) return sec->gravity; } -boolean P_CompareSectorPortals(sectorportal_t *a, sectorportal_t *b) +static boolean P_IsSectorPortalValid(sectorportal_t *secportal) { - return !memcmp(a, b, sizeof(sectorportal_t)); + switch (secportal->type) + { + case SECPORTAL_LINE: + case SECPORTAL_FLOOR: + case SECPORTAL_CEILING: + return true; + case SECPORTAL_OBJECT: + return secportal->mobj && !P_MobjWasRemoved(secportal->mobj); + case SECPORTAL_SKYBOX: + return skyboxmo[0] && !P_MobjWasRemoved(skyboxmo[0]); + default: + return false; + } } -static void SetSectorPortal(sectorportal_t *secportal, sector_t *target_sector, fixed_t default_z, INT32 viewpoint_tag) +boolean P_SectorHasFloorPortal(sector_t *sector) { - secportal->exists = target_sector; - secportal->target.x = target_sector->soundorg.x; - secportal->target.y = target_sector->soundorg.y; - secportal->target.z = default_z; - secportal->target.angle = 0; + return P_IsSectorPortalValid(§or->portal_floor); +} - if (viewpoint_tag <= 0) - return; +boolean P_SectorHasCeilingPortal(sector_t *sector) +{ + return P_IsSectorPortalValid(§or->portal_ceiling); +} +boolean P_CompareSectorPortals(sectorportal_t *a, sectorportal_t *b) +{ + if (a->type != b->type) + return false; + + switch (a->type) + { + case SECPORTAL_LINE: + return a->line.start == b->line.start && a->line.dest == b->line.dest; + case SECPORTAL_FLOOR: + case SECPORTAL_CEILING: + return a->sector == b->sector; + case SECPORTAL_OBJECT: + return a->mobj == b->mobj; + default: + return true; + } +} + +static mobj_t *GetSectorPortalObject(INT32 tag) +{ for (thinker_t *th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) { if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) @@ -6209,18 +6241,14 @@ static void SetSectorPortal(sectorportal_t *secportal, sector_t *target_sector, mobj_t *mo = (mobj_t *)th; - if (mo->type != MT_PORTALREFPOINT || mo->spawnpoint == NULL) + if (mo->spawnpoint == NULL) continue; - if (!Tag_Find(&mo->spawnpoint->tags, viewpoint_tag)) - continue; - - secportal->target.x = mo->x; - secportal->target.y = mo->y; - secportal->target.z = mo->z; - secportal->target.angle = mo->angle; - return; + if (Tag_Find(&mo->spawnpoint->tags, tag)) + return mo; } + + return NULL; } /** After the map has loaded, scans for specials that spawn 3Dfloors and @@ -6390,26 +6418,97 @@ void P_SpawnSpecials(boolean fromnetsave) case 6: // Sector portal { - INT32 s1, s2; - TAG_ITER_SECTORS(lines[i].args[0], s1) // Target sector tag + int target_sector_tag = lines[i].args[0]; + int portal_type = lines[i].args[1]; + int plane_type = lines[i].args[2]; + int misc = lines[i].args[3]; + + boolean floor, ceiling; + if (plane_type == TMP_BOTH) + floor = ceiling = true; + else { - TAG_ITER_SECTORS(lines[i].args[1], s2) // Sector tag to make a portal to + floor = plane_type == TMP_FLOOR; + ceiling = plane_type == TMP_CEILING; + } + + INT32 s1 = -1; + + TAG_ITER_SECTORS(target_sector_tag, s1) // Target sector tag + { + sectorportal_t *floorportal = §ors[s1].portal_floor; + sectorportal_t *ceilportal = §ors[s1].portal_ceiling; + + // Line portal + if (portal_type == 0) { - sector_t *target_sector = §ors[s2]; - boolean floor, ceiling; - - if (lines[i].args[2] == TMP_BOTH) - floor = ceiling = true; - else + INT32 linenum = -1; + TAG_ITER_LINES(misc, linenum) { - floor = lines[i].args[2] == TMP_FLOOR; - ceiling = lines[i].args[2] == TMP_CEILING; + if (lines[linenum].special == 6 + && lines[linenum].args[0] == target_sector_tag + && lines[linenum].args[1] == portal_type + && lines[linenum].args[2] == plane_type + && lines[linenum].args[3] == 1) + { + if (floor) + { + floorportal->type = SECPORTAL_LINE; + floorportal->line.start = &lines[i]; + floorportal->line.dest = &lines[linenum]; + } + if (ceiling) + { + ceilportal->type = SECPORTAL_LINE; + ceilportal->line.start = &lines[i]; + ceilportal->line.dest = &lines[linenum]; + } + } } - + } + // Skybox portal + else if (portal_type == 2) + { if (floor) - SetSectorPortal(§ors[s1].portal_floor, target_sector, target_sector->ceilingheight, lines[i].args[3]); + floorportal->type = SECPORTAL_SKYBOX; if (ceiling) - SetSectorPortal(§ors[s1].portal_ceiling, target_sector, target_sector->floorheight, lines[i].args[3]); + ceilportal->type = SECPORTAL_SKYBOX; + } + // Plane portal + else if (portal_type == 7) + { + INT32 s2 = -1; + TAG_ITER_SECTORS(misc, s2) // Sector tag to make a portal to + { + sector_t *target_sector = §ors[s2]; + if (floor) + { + floorportal->type = SECPORTAL_CEILING; + floorportal->sector = target_sector; + } + if (ceiling) + { + ceilportal->type = SECPORTAL_FLOOR; + ceilportal->sector = target_sector; + } + } + } + // Use mobj as viewpoint + else if (portal_type == 8) + { + mobj_t *mobj = GetSectorPortalObject(misc); + if (!mobj) + break; + if (floor) + { + floorportal->type = SECPORTAL_OBJECT; + floorportal->mobj = mobj; + } + if (ceiling) + { + ceilportal->type = SECPORTAL_OBJECT; + ceilportal->mobj = mobj; + } } } break; diff --git a/src/p_spec.h b/src/p_spec.h index 71c9dcf3a..63ae8b504 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -521,6 +521,8 @@ INT32 P_FindMinSurroundingLight(sector_t *sector, INT32 max); void P_SetupSignExit(player_t *player); boolean P_IsFlagAtBase(mobjtype_t flag); +boolean P_SectorHasFloorPortal(sector_t *sector); +boolean P_SectorHasCeilingPortal(sector_t *sector); boolean P_CompareSectorPortals(sectorportal_t *a, sectorportal_t *b); boolean P_IsMobjTouchingSectorPlane(mobj_t *mo, sector_t *sec); diff --git a/src/r_bsp.c b/src/r_bsp.c index 70f08e127..f902f2a83 100644 --- a/src/r_bsp.c +++ b/src/r_bsp.c @@ -488,12 +488,12 @@ static void R_AddLine(seg_t *line) // hack to allow height changes in outdoor areas // This is what gets rid of the upper textures if there should be sky if (backsector->ceilingpic == skyflatnum && frontsector->ceilingpic == skyflatnum - && !(backsector->portal_ceiling.exists || frontsector->portal_ceiling.exists)) + && !(P_SectorHasCeilingPortal(backsector) || P_SectorHasCeilingPortal(frontsector))) bothceilingssky = true; // likewise, but for floors and upper textures if (backsector->floorpic == skyflatnum && frontsector->floorpic == skyflatnum - && !(backsector->portal_floor.exists || frontsector->portal_floor.exists)) + && !(P_SectorHasFloorPortal(backsector) || P_SectorHasFloorPortal(frontsector))) bothfloorssky = true; if (bothceilingssky && bothfloorssky) // everything's sky? let's save us a bit of time then @@ -918,7 +918,7 @@ static void R_Subsector(size_t num) || (frontsector->heightsec != -1 && sectors[frontsector->heightsec].ceilingpic == skyflatnum)) { floorplane = R_FindPlane(frontsector, frontsector->floorheight, frontsector->floorpic, floorlightlevel, - frontsector->floorxoffset, frontsector->flooryoffset, frontsector->floorangle, floorcolormap, NULL, NULL, frontsector->f_slope, frontsector->portal_floor.exists ? &frontsector->portal_floor : NULL); + frontsector->floorxoffset, frontsector->flooryoffset, frontsector->floorangle, floorcolormap, NULL, NULL, frontsector->f_slope, P_SectorHasFloorPortal(frontsector) ? &frontsector->portal_floor : NULL); } else floorplane = NULL; @@ -929,7 +929,7 @@ static void R_Subsector(size_t num) { ceilingplane = R_FindPlane(frontsector, frontsector->ceilingheight, frontsector->ceilingpic, ceilinglightlevel, frontsector->ceilingxoffset, frontsector->ceilingyoffset, frontsector->ceilingangle, - ceilingcolormap, NULL, NULL, frontsector->c_slope, frontsector->portal_ceiling.exists ? &frontsector->portal_ceiling : NULL); + ceilingcolormap, NULL, NULL, frontsector->c_slope, P_SectorHasCeilingPortal(frontsector) ? &frontsector->portal_ceiling : NULL); } else ceilingplane = NULL; diff --git a/src/r_defs.h b/src/r_defs.h index d435f69ab..2e58a1bb8 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -208,13 +208,27 @@ typedef enum BT_STRONG, } busttype_e; +typedef enum +{ + SECPORTAL_NONE, + SECPORTAL_LINE, + SECPORTAL_OBJECT, + SECPORTAL_SKYBOX, + SECPORTAL_FLOOR, + SECPORTAL_CEILING, +} secportaltype_e; + typedef struct sectorportal_s { - boolean exists; - struct { - fixed_t x, y, z; - angle_t angle; - } target; + secportaltype_e type; + union { + struct { + struct line_s *start; + struct line_s *dest; + } line; + struct sector_s *sector; + struct mobj_s *mobj; + }; } sectorportal_t; typedef struct ffloor_s diff --git a/src/r_portal.c b/src/r_portal.c index 136eb4fac..b6a430b67 100644 --- a/src/r_portal.c +++ b/src/r_portal.c @@ -141,30 +141,17 @@ void Portal_Remove (portal_t* portal) Z_Free(portal); } -/** Creates a portal out of two lines and a determined screen range. - * - * line1 determines the entrance, and line2 the exit. - * x1 and x2 determine the screen's column bounds. - - * The view's offset from the entry line center is obtained, - * and then rotated&translated to the exit line's center. - * When the portal renders, it will create the illusion of - * the two lines being seamed together. - */ -void Portal_Add2Lines (const INT32 line1, const INT32 line2, const INT32 x1, const INT32 x2) +static void Portal_GetViewpointForLine(portal_t *portal, line_t *start, line_t *dest) { - portal_t* portal = Portal_Add(x1, x2); - // Offset the portal view by the linedef centers - line_t* start = &lines[line1]; - line_t* dest = &lines[line2]; - angle_t dangle = R_PointToAngle2(0,0,dest->dx,dest->dy) - R_PointToAngle2(start->dx,start->dy,0,0); fixed_t disttopoint; angle_t angtopoint; - vertex_t dest_c, start_c; + struct { + fixed_t x, y; + } dest_c, start_c; // looking glass center start_c.x = (start->v1->x + start->v2->x) / 2; @@ -182,6 +169,26 @@ void Portal_Add2Lines (const INT32 line1, const INT32 line2, const INT32 x1, con portal->viewy = dest_c.y + FixedMul(FINESINE(angtopoint>>ANGLETOFINESHIFT), disttopoint); portal->viewz = viewz + dest->frontsector->floorheight - start->frontsector->floorheight; portal->viewangle = viewangle + dangle; +} + +/** Creates a portal out of two lines and a determined screen range. + * + * line1 determines the entrance, and line2 the exit. + * x1 and x2 determine the screen's column bounds. + + * The view's offset from the entry line center is obtained, + * and then rotated&translated to the exit line's center. + * When the portal renders, it will create the illusion of + * the two lines being seamed together. + */ +void Portal_Add2Lines (const INT32 line1, const INT32 line2, const INT32 x1, const INT32 x2) +{ + portal_t* portal = Portal_Add(x1, x2); + + line_t* start = &lines[line1]; + line_t* dest = &lines[line2]; + + Portal_GetViewpointForLine(portal, start, dest); portal->clipline = line2; @@ -314,9 +321,17 @@ void Portal_AddSkybox (const visplane_t* plane) void Portal_AddSectorPortal (const visplane_t* plane) { INT16 start, end; - sector_t *source = plane->sector; + fixed_t x, y, z, angle; sectorportal_t *secportal = plane->portalsector; + if (secportal->type == SECPORTAL_NONE) + return; + else if (secportal->type == SECPORTAL_SKYBOX) + { + Portal_AddSkybox(plane); + return; + } + if (TrimVisplaneBounds(plane, &start, &end)) return; @@ -324,24 +339,53 @@ void Portal_AddSectorPortal (const visplane_t* plane) Portal_ClipVisplane(plane, portal); - fixed_t refx = source->soundorg.x - viewx; - fixed_t refy = source->soundorg.y - viewy; + portal->clipline = -1; - // Rotate the X/Y to match the target angle - if (secportal->target.angle) + switch (secportal->type) { - fixed_t x = refx, y = refy; - angle_t ang = secportal->target.angle >> ANGLETOFINESHIFT; - refx = FixedMul(x, FINECOSINE(ang)) - FixedMul(y, FINESINE(ang)); - refy = FixedMul(x, FINESINE(ang)) + FixedMul(y, FINECOSINE(ang)); + case SECPORTAL_LINE: + Portal_GetViewpointForLine(portal, secportal->line.start, secportal->line.dest); + return; + case SECPORTAL_OBJECT: + if (!secportal->mobj || P_MobjWasRemoved(secportal->mobj)) + return; + x = secportal->mobj->x; + y = secportal->mobj->y; + z = secportal->mobj->z; + angle = secportal->mobj->angle; + break; + case SECPORTAL_FLOOR: + x = secportal->sector->soundorg.x; + y = secportal->sector->soundorg.y; + z = secportal->sector->floorheight; + angle = 0; + break; + case SECPORTAL_CEILING: + x = secportal->sector->soundorg.x; + y = secportal->sector->soundorg.y; + z = secportal->sector->ceilingheight; + angle = 0; + break; + default: + return; } - portal->viewx = secportal->target.x - refx; - portal->viewy = secportal->target.y - refy; - portal->viewz = secportal->target.z + viewz; - portal->viewangle = secportal->target.angle + viewangle; + fixed_t refx = plane->sector->soundorg.x - viewx; + fixed_t refy = plane->sector->soundorg.y - viewy; - portal->clipline = -1; + // Rotate the X/Y to match the target angle + if (angle != 0) + { + fixed_t tr_x = refx, tr_y = refy; + angle_t ang = angle >> ANGLETOFINESHIFT; + refx = FixedMul(tr_x, FINECOSINE(ang)) - FixedMul(tr_y, FINESINE(ang)); + refy = FixedMul(tr_x, FINESINE(ang)) + FixedMul(tr_y, FINECOSINE(ang)); + } + + portal->viewx = x - refx; + portal->viewy = y - refy; + portal->viewz = z + viewz; + portal->viewangle = angle + viewangle; } /** Creates portals for the currently existing sky visplanes. diff --git a/src/r_segs.c b/src/r_segs.c index a8fb635d1..81fca64e1 100644 --- a/src/r_segs.c +++ b/src/r_segs.c @@ -1930,7 +1930,7 @@ void R_StoreWallRange(INT32 start, INT32 stop) || !P_CompareSectorPortals(&frontsector->portal_ceiling, &backsector->portal_ceiling) || (frontsector->ffloors != backsector->ffloors && !Tag_Compare(&frontsector->tags, &backsector->tags))) { - markceiling = true; + markceiling = true; } else { From 395e9bdd20934fed7303ea60c86d6bdc7d1d0de9 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Wed, 23 Aug 2023 15:29:16 -0300 Subject: [PATCH 070/227] Implement sector portal copying --- src/p_spec.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 6 deletions(-) diff --git a/src/p_spec.c b/src/p_spec.c index c4c254a82..af1a04434 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -6232,7 +6232,7 @@ boolean P_CompareSectorPortals(sectorportal_t *a, sectorportal_t *b) } } -static mobj_t *GetSectorPortalObject(INT32 tag) +static mobj_t *P_GetMobjByTag(INT32 tag) { for (thinker_t *th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) { @@ -6251,6 +6251,27 @@ static mobj_t *GetSectorPortalObject(INT32 tag) return NULL; } +static void P_CopySectorPortal(sectorportal_t *dest, sectorportal_t *src) +{ + if (src->type == SECPORTAL_NONE) + return; + + memcpy(dest, src, sizeof(sectorportal_t)); +} + +static void P_DoPortalCopyFromLine(sectorportal_t *floorportal, sectorportal_t *ceilportal, int tag) +{ + INT32 secnum = -1; + TAG_ITER_SECTORS(tag, secnum) + { + sector_t *src_sector = §ors[secnum]; + if (floorportal) + P_CopySectorPortal(floorportal, &src_sector->portal_floor); + if (ceilportal) + P_CopySectorPortal(ceilportal, &src_sector->portal_ceiling); + } +} + /** After the map has loaded, scans for specials that spawn 3Dfloors and * thinkers. * @@ -6496,7 +6517,7 @@ void P_SpawnSpecials(boolean fromnetsave) // Use mobj as viewpoint else if (portal_type == 8) { - mobj_t *mobj = GetSectorPortalObject(misc); + mobj_t *mobj = P_GetMobjByTag(misc); if (!mobj) break; if (floor) @@ -7289,10 +7310,6 @@ void P_SpawnSpecials(boolean fromnetsave) } } - - - - // Allocate each list for (i = 0; i < numsectors; i++) if(secthinkers[i].thinkers) @@ -7321,6 +7338,48 @@ void P_SpawnSpecials(boolean fromnetsave) } } + // Copy portals + for (i = 0; i < numlines; i++) + { + if (lines[i].special != 6) + continue; + + int portal_type = lines[i].args[1]; + if (portal_type != 1) + continue; + + int target_sector_tag = lines[i].args[0]; + int plane_type = lines[i].args[2]; + int tag_to_copy = lines[i].args[3]; + + boolean floor, ceiling; + if (plane_type == TMP_BOTH) + floor = ceiling = true; + else + { + floor = plane_type == TMP_FLOOR; + ceiling = plane_type == TMP_CEILING; + } + + if (target_sector_tag == 0) + { + sectorportal_t *floorportal = floor ? &lines[i].frontsector->portal_floor : NULL; + sectorportal_t *ceilportal = ceiling ? &lines[i].frontsector->portal_ceiling : NULL; + P_DoPortalCopyFromLine(floorportal, ceilportal, tag_to_copy); + } + else + { + INT32 s1 = -1; + TAG_ITER_SECTORS(target_sector_tag, s1) + { + sectorportal_t *floorportal = floor ? §ors[s1].portal_floor : NULL; + sectorportal_t *ceilportal = ceiling ? §ors[s1].portal_ceiling : NULL; + P_DoPortalCopyFromLine(floorportal, ceilportal, tag_to_copy); + } + } + break; + } + if (!fromnetsave) P_RunLevelLoadExecutors(); } From e230d38aad68eac9ce69e5dac211fa747e5d7751 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Wed, 23 Aug 2023 15:46:48 -0300 Subject: [PATCH 071/227] Ensure sector portals are rendered when they would otherwise be missed --- src/p_spec.c | 11 ++++++++++- src/p_spec.h | 1 + src/r_bsp.c | 8 +++++++- src/r_plane.c | 2 +- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/p_spec.c b/src/p_spec.c index af1a04434..734bada68 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -6203,6 +6203,11 @@ static boolean P_IsSectorPortalValid(sectorportal_t *secportal) } } +boolean P_SectorHasPortal(sector_t *sector) +{ + return P_SectorHasFloorPortal(sector) || P_SectorHasCeilingPortal(sector); +} + boolean P_SectorHasFloorPortal(sector_t *sector) { return P_IsSectorPortalValid(§or->portal_floor); @@ -6215,7 +6220,11 @@ boolean P_SectorHasCeilingPortal(sector_t *sector) boolean P_CompareSectorPortals(sectorportal_t *a, sectorportal_t *b) { - if (a->type != b->type) + if (a == NULL && b == NULL) + return true; + else if (!a || !b) + return false; + else if (a->type != b->type) return false; switch (a->type) diff --git a/src/p_spec.h b/src/p_spec.h index 63ae8b504..0c4ce4f32 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -521,6 +521,7 @@ INT32 P_FindMinSurroundingLight(sector_t *sector, INT32 max); void P_SetupSignExit(player_t *player); boolean P_IsFlagAtBase(mobjtype_t flag); +boolean P_SectorHasPortal(sector_t *sector); boolean P_SectorHasFloorPortal(sector_t *sector); boolean P_SectorHasCeilingPortal(sector_t *sector); boolean P_CompareSectorPortals(sectorportal_t *a, sectorportal_t *b); diff --git a/src/r_bsp.c b/src/r_bsp.c index f902f2a83..6f115a923 100644 --- a/src/r_bsp.c +++ b/src/r_bsp.c @@ -357,6 +357,11 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel, boolean R_IsEmptyLine(seg_t *line, sector_t *front, sector_t *back) { + if (P_SectorHasPortal(front) && !P_SectorHasPortal(back)) + return false; + else if (!P_SectorHasPortal(front) && P_SectorHasPortal(back)) + return false; + return ( !line->polyseg && back->ceilingpic == front->ceilingpic @@ -578,7 +583,6 @@ static void R_AddLine(seg_t *line) // Reject empty lines used for triggers and special events. // Identical floor and ceiling on both sides, identical light levels on both sides, // and no middle texture. - if (R_IsEmptyLine(line, frontsector, backsector)) return; @@ -915,6 +919,7 @@ static void R_Subsector(size_t num) if (P_GetSectorFloorZAt(frontsector, viewx, viewy) < viewz || frontsector->floorpic == skyflatnum + || P_SectorHasFloorPortal(frontsector) || (frontsector->heightsec != -1 && sectors[frontsector->heightsec].ceilingpic == skyflatnum)) { floorplane = R_FindPlane(frontsector, frontsector->floorheight, frontsector->floorpic, floorlightlevel, @@ -925,6 +930,7 @@ static void R_Subsector(size_t num) if (P_GetSectorCeilingZAt(frontsector, viewx, viewy) > viewz || frontsector->ceilingpic == skyflatnum + || P_SectorHasCeilingPortal(frontsector) || (frontsector->heightsec != -1 && sectors[frontsector->heightsec].floorpic == skyflatnum)) { ceilingplane = R_FindPlane(frontsector, frontsector->ceilingheight, frontsector->ceilingpic, diff --git a/src/r_plane.c b/src/r_plane.c index d9051ae05..4695034bd 100644 --- a/src/r_plane.c +++ b/src/r_plane.c @@ -451,7 +451,7 @@ visplane_t *R_FindPlane(sector_t *sector, fixed_t height, INT32 picnum, INT32 li && check->plangle == plangle && check->slope == slope && check->polyobj == polyobj - && check->portalsector == portalsector) + && P_CompareSectorPortals(check->portalsector, portalsector)) { return check; } From 50506254e8ec12664ad64a141c7f6fe33faa815b Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Wed, 23 Aug 2023 16:31:03 -0300 Subject: [PATCH 072/227] Support plane type 3 in the "copy portal" line special SRB2 doesn't implement sector portals the way ZDoom does, but this ensures plane type 3 acts the same as plane type 2. --- src/p_spec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_spec.c b/src/p_spec.c index 734bada68..ccd72e594 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -7362,7 +7362,7 @@ void P_SpawnSpecials(boolean fromnetsave) int tag_to_copy = lines[i].args[3]; boolean floor, ceiling; - if (plane_type == TMP_BOTH) + if (plane_type == TMP_BOTH || plane_type == 3) floor = ceiling = true; else { From 74ea880ca046e6a9b945e1cc7ce64fea590ea5e7 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Wed, 23 Aug 2023 17:49:29 -0300 Subject: [PATCH 073/227] Refactor --- src/p_setup.c | 10 ++-- src/p_spec.c | 136 +++++++++++++++++++++++++++++++++++-------------- src/p_spec.h | 10 ++++ src/r_bsp.c | 4 +- src/r_defs.h | 7 ++- src/r_portal.c | 7 +-- src/r_segs.c | 4 +- 7 files changed, 120 insertions(+), 58 deletions(-) diff --git a/src/p_setup.c b/src/p_setup.c index 79c919a4f..565a44eae 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -978,11 +978,6 @@ static void P_LoadVertices(UINT8 *data) } } -static void InitializeSectorPortal(sectorportal_t *secportal) -{ - memset(secportal, 0, sizeof(*secportal)); -} - static void P_InitializeSector(sector_t *ss) { memset(&ss->soundorg, 0, sizeof(ss->soundorg)); @@ -996,8 +991,8 @@ static void P_InitializeSector(sector_t *ss) ss->lightingdata = NULL; ss->fadecolormapdata = NULL; - InitializeSectorPortal(&ss->portal_floor); - InitializeSectorPortal(&ss->portal_ceiling); + ss->portal_floor = UINT32_MAX; + ss->portal_ceiling = UINT32_MAX; ss->heightsec = -1; ss->camsec = -1; @@ -7827,6 +7822,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) P_InitThinkers(); R_InitMobjInterpolators(); P_InitCachedActions(); + P_InitSectorPortals(); // internal game map maplumpname = G_BuildMapName(gamemap); diff --git a/src/p_spec.c b/src/p_spec.c index ccd72e594..0c798b976 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -52,6 +52,10 @@ mobj_t *skyboxmo[2]; // current skybox mobjs: 0 = viewpoint, 1 = centerpoint mobj_t *skyboxviewpnts[16]; // array of MT_SKYBOX viewpoint mobjs mobj_t *skyboxcenterpnts[16]; // array of MT_SKYBOX centerpoint mobjs +size_t secportalcount; +size_t secportalcapacity; +sectorportal_t *secportals; + /** Animated texture descriptor * This keeps track of an animated texture or an animated flat. * \sa P_UpdateSpecials, P_InitPicAnims, animdef_t @@ -6186,8 +6190,33 @@ fixed_t P_GetSectorGravityFactor(sector_t *sec) return sec->gravity; } +void P_InitSectorPortals(void) +{ + secportalcount = 0; + secportalcapacity = 0; + secportals = NULL; +} + +UINT32 P_NewSectorPortal(void) +{ + size_t i = secportalcount++; + if (i == UINT32_MAX) + I_Error("Too many sector portals"); + + if (secportalcapacity == 0 || secportalcount == secportalcapacity) + { + secportalcapacity = secportalcapacity ? (secportalcapacity * 2) : 16; + secportals = Z_Realloc(secportals, secportalcapacity * sizeof(sectorportal_t), PU_LEVEL, NULL); + } + + return (UINT32)i; +} + static boolean P_IsSectorPortalValid(sectorportal_t *secportal) { + if (secportal == NULL) + return false; + switch (secportal->type) { case SECPORTAL_LINE: @@ -6208,14 +6237,32 @@ boolean P_SectorHasPortal(sector_t *sector) return P_SectorHasFloorPortal(sector) || P_SectorHasCeilingPortal(sector); } +sectorportal_t *P_SectorGetFloorPortal(sector_t *sector) +{ + UINT32 num = sector->portal_floor; + if (num >= secportalcount) + return NULL; + + return &secportals[num]; +} + +sectorportal_t *P_SectorGetCeilingPortal(sector_t *sector) +{ + UINT32 num = sector->portal_ceiling; + if (num >= secportalcount) + return NULL; + + return &secportals[num]; +} + boolean P_SectorHasFloorPortal(sector_t *sector) { - return P_IsSectorPortalValid(§or->portal_floor); + return P_IsSectorPortalValid(P_SectorGetFloorPortal(sector)); } boolean P_SectorHasCeilingPortal(sector_t *sector) { - return P_IsSectorPortalValid(§or->portal_ceiling); + return P_IsSectorPortalValid(P_SectorGetCeilingPortal(sector)); } boolean P_CompareSectorPortals(sectorportal_t *a, sectorportal_t *b) @@ -6260,27 +6307,43 @@ static mobj_t *P_GetMobjByTag(INT32 tag) return NULL; } -static void P_CopySectorPortal(sectorportal_t *dest, sectorportal_t *src) -{ - if (src->type == SECPORTAL_NONE) - return; - - memcpy(dest, src, sizeof(sectorportal_t)); -} - -static void P_DoPortalCopyFromLine(sectorportal_t *floorportal, sectorportal_t *ceilportal, int tag) +static void P_DoPortalCopyFromLine(sector_t *dest_sector, int plane_type, int tag) { INT32 secnum = -1; TAG_ITER_SECTORS(tag, secnum) { sector_t *src_sector = §ors[secnum]; - if (floorportal) - P_CopySectorPortal(floorportal, &src_sector->portal_floor); - if (ceilportal) - P_CopySectorPortal(ceilportal, &src_sector->portal_ceiling); + if (plane_type == TMP_FLOOR || plane_type == TMP_BOTH) + dest_sector->portal_floor = src_sector->portal_floor; + if (plane_type == TMP_CEILING || plane_type == TMP_BOTH) + dest_sector->portal_ceiling = src_sector->portal_ceiling; } } +static sectorportal_t *P_SectorGetFloorPortalOrCreate(sector_t *sector) +{ + sectorportal_t *secportal = P_SectorGetFloorPortal(sector); + if (secportal == NULL) + { + sector->portal_floor = P_NewSectorPortal(); + return &secportals[sector->portal_floor]; + } + + return secportal; +} + +static sectorportal_t *P_SectorGetCeilingPortalOrCreate(sector_t *sector) +{ + sectorportal_t *secportal = P_SectorGetCeilingPortal(sector); + if (secportal == NULL) + { + sector->portal_ceiling = P_NewSectorPortal(); + return &secportals[sector->portal_ceiling]; + } + + return secportal; +} + /** After the map has loaded, scans for specials that spawn 3Dfloors and * thinkers. * @@ -6466,8 +6529,7 @@ void P_SpawnSpecials(boolean fromnetsave) TAG_ITER_SECTORS(target_sector_tag, s1) // Target sector tag { - sectorportal_t *floorportal = §ors[s1].portal_floor; - sectorportal_t *ceilportal = §ors[s1].portal_ceiling; + sector_t *target_sector = §ors[s1]; // Line portal if (portal_type == 0) @@ -6483,12 +6545,14 @@ void P_SpawnSpecials(boolean fromnetsave) { if (floor) { + sectorportal_t *floorportal = P_SectorGetFloorPortalOrCreate(target_sector); floorportal->type = SECPORTAL_LINE; floorportal->line.start = &lines[i]; floorportal->line.dest = &lines[linenum]; } if (ceiling) { + sectorportal_t *ceilportal = P_SectorGetCeilingPortalOrCreate(target_sector); ceilportal->type = SECPORTAL_LINE; ceilportal->line.start = &lines[i]; ceilportal->line.dest = &lines[linenum]; @@ -6500,9 +6564,15 @@ void P_SpawnSpecials(boolean fromnetsave) else if (portal_type == 2) { if (floor) + { + sectorportal_t *floorportal = P_SectorGetFloorPortalOrCreate(target_sector); floorportal->type = SECPORTAL_SKYBOX; + } if (ceiling) + { + sectorportal_t *ceilportal = P_SectorGetCeilingPortalOrCreate(target_sector); ceilportal->type = SECPORTAL_SKYBOX; + } } // Plane portal else if (portal_type == 7) @@ -6510,16 +6580,18 @@ void P_SpawnSpecials(boolean fromnetsave) INT32 s2 = -1; TAG_ITER_SECTORS(misc, s2) // Sector tag to make a portal to { - sector_t *target_sector = §ors[s2]; + sector_t *view_sector = §ors[s2]; if (floor) { + sectorportal_t *floorportal = P_SectorGetFloorPortalOrCreate(target_sector); floorportal->type = SECPORTAL_CEILING; - floorportal->sector = target_sector; + floorportal->sector = view_sector; } if (ceiling) { + sectorportal_t *ceilportal = P_SectorGetCeilingPortalOrCreate(target_sector); ceilportal->type = SECPORTAL_FLOOR; - ceilportal->sector = target_sector; + ceilportal->sector = view_sector; } } } @@ -6531,11 +6603,13 @@ void P_SpawnSpecials(boolean fromnetsave) break; if (floor) { + sectorportal_t *floorportal = P_SectorGetFloorPortalOrCreate(target_sector); floorportal->type = SECPORTAL_OBJECT; floorportal->mobj = mobj; } if (ceiling) { + sectorportal_t *ceilportal = P_SectorGetCeilingPortalOrCreate(target_sector); ceilportal->type = SECPORTAL_OBJECT; ceilportal->mobj = mobj; } @@ -7361,30 +7435,16 @@ void P_SpawnSpecials(boolean fromnetsave) int plane_type = lines[i].args[2]; int tag_to_copy = lines[i].args[3]; - boolean floor, ceiling; - if (plane_type == TMP_BOTH || plane_type == 3) - floor = ceiling = true; - else - { - floor = plane_type == TMP_FLOOR; - ceiling = plane_type == TMP_CEILING; - } + if (plane_type == 3) + plane_type = TMP_BOTH; if (target_sector_tag == 0) - { - sectorportal_t *floorportal = floor ? &lines[i].frontsector->portal_floor : NULL; - sectorportal_t *ceilportal = ceiling ? &lines[i].frontsector->portal_ceiling : NULL; - P_DoPortalCopyFromLine(floorportal, ceilportal, tag_to_copy); - } + P_DoPortalCopyFromLine(lines[i].frontsector, plane_type, tag_to_copy); else { INT32 s1 = -1; TAG_ITER_SECTORS(target_sector_tag, s1) - { - sectorportal_t *floorportal = floor ? §ors[s1].portal_floor : NULL; - sectorportal_t *ceilportal = ceiling ? §ors[s1].portal_ceiling : NULL; - P_DoPortalCopyFromLine(floorportal, ceilportal, tag_to_copy); - } + P_DoPortalCopyFromLine(§ors[s1], plane_type, tag_to_copy); } break; } diff --git a/src/p_spec.h b/src/p_spec.h index 0c4ce4f32..bb995a97a 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -21,6 +21,10 @@ extern mobj_t *skyboxmo[2]; // current skybox mobjs: 0 = viewpoint, 1 = centerpo extern mobj_t *skyboxviewpnts[16]; // array of MT_SKYBOX viewpoint mobjs extern mobj_t *skyboxcenterpnts[16]; // array of MT_SKYBOX centerpoint mobjs +extern size_t secportalcount; +extern size_t secportalcapacity; +extern sectorportal_t *secportals; + // Amount (dx, dy) vector linedef is shifted right to get scroll amount #define SCROLL_SHIFT 5 @@ -521,6 +525,12 @@ INT32 P_FindMinSurroundingLight(sector_t *sector, INT32 max); void P_SetupSignExit(player_t *player); boolean P_IsFlagAtBase(mobjtype_t flag); +void P_InitSectorPortals(void); +UINT32 P_NewSectorPortal(void); + +sectorportal_t *P_SectorGetFloorPortal(sector_t *sector); +sectorportal_t *P_SectorGetCeilingPortal(sector_t *sector); + boolean P_SectorHasPortal(sector_t *sector); boolean P_SectorHasFloorPortal(sector_t *sector); boolean P_SectorHasCeilingPortal(sector_t *sector); diff --git a/src/r_bsp.c b/src/r_bsp.c index 6f115a923..8ab5998c4 100644 --- a/src/r_bsp.c +++ b/src/r_bsp.c @@ -923,7 +923,7 @@ static void R_Subsector(size_t num) || (frontsector->heightsec != -1 && sectors[frontsector->heightsec].ceilingpic == skyflatnum)) { floorplane = R_FindPlane(frontsector, frontsector->floorheight, frontsector->floorpic, floorlightlevel, - frontsector->floorxoffset, frontsector->flooryoffset, frontsector->floorangle, floorcolormap, NULL, NULL, frontsector->f_slope, P_SectorHasFloorPortal(frontsector) ? &frontsector->portal_floor : NULL); + frontsector->floorxoffset, frontsector->flooryoffset, frontsector->floorangle, floorcolormap, NULL, NULL, frontsector->f_slope, P_SectorGetFloorPortal(frontsector)); } else floorplane = NULL; @@ -935,7 +935,7 @@ static void R_Subsector(size_t num) { ceilingplane = R_FindPlane(frontsector, frontsector->ceilingheight, frontsector->ceilingpic, ceilinglightlevel, frontsector->ceilingxoffset, frontsector->ceilingyoffset, frontsector->ceilingangle, - ceilingcolormap, NULL, NULL, frontsector->c_slope, P_SectorHasCeilingPortal(frontsector) ? &frontsector->portal_ceiling : NULL); + ceilingcolormap, NULL, NULL, frontsector->c_slope, P_SectorGetCeilingPortal(frontsector)); } else ceilingplane = NULL; diff --git a/src/r_defs.h b/src/r_defs.h index 2e58a1bb8..a8addfe0b 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -210,12 +210,11 @@ typedef enum typedef enum { - SECPORTAL_NONE, SECPORTAL_LINE, SECPORTAL_OBJECT, SECPORTAL_SKYBOX, SECPORTAL_FLOOR, - SECPORTAL_CEILING, + SECPORTAL_CEILING } secportaltype_e; typedef struct sectorportal_s @@ -519,8 +518,8 @@ typedef struct sector_s extracolormap_t *spawn_extra_colormap; // portals - sectorportal_t portal_floor; - sectorportal_t portal_ceiling; + UINT32 portal_floor; + UINT32 portal_ceiling; } sector_t; // diff --git a/src/r_portal.c b/src/r_portal.c index b6a430b67..c485aa033 100644 --- a/src/r_portal.c +++ b/src/r_portal.c @@ -315,8 +315,6 @@ void Portal_AddSkybox (const visplane_t* plane) } /** Creates a sector portal out of a visplane. - * - * Mostly the same as Portal_AddSkybox. */ void Portal_AddSectorPortal (const visplane_t* plane) { @@ -324,9 +322,8 @@ void Portal_AddSectorPortal (const visplane_t* plane) fixed_t x, y, z, angle; sectorportal_t *secportal = plane->portalsector; - if (secportal->type == SECPORTAL_NONE) - return; - else if (secportal->type == SECPORTAL_SKYBOX) + // Shortcut + if (secportal->type == SECPORTAL_SKYBOX) { Portal_AddSkybox(plane); return; diff --git a/src/r_segs.c b/src/r_segs.c index 81fca64e1..2a86cc41a 100644 --- a/src/r_segs.c +++ b/src/r_segs.c @@ -1893,7 +1893,7 @@ void R_StoreWallRange(INT32 start, INT32 stop) || backsector->floorlightsec != frontsector->floorlightsec //SoM: 4/3/2000: Check for colormaps || frontsector->extra_colormap != backsector->extra_colormap - || !P_CompareSectorPortals(&frontsector->portal_floor, &backsector->portal_floor) + || !P_CompareSectorPortals(P_SectorGetFloorPortal(frontsector), P_SectorGetFloorPortal(backsector)) || (frontsector->ffloors != backsector->ffloors && !Tag_Compare(&frontsector->tags, &backsector->tags))) { markfloor = true; @@ -1927,7 +1927,7 @@ void R_StoreWallRange(INT32 start, INT32 stop) || backsector->ceilinglightsec != frontsector->ceilinglightsec //SoM: 4/3/2000: Check for colormaps || frontsector->extra_colormap != backsector->extra_colormap - || !P_CompareSectorPortals(&frontsector->portal_ceiling, &backsector->portal_ceiling) + || !P_CompareSectorPortals(P_SectorGetCeilingPortal(frontsector), P_SectorGetCeilingPortal(backsector)) || (frontsector->ffloors != backsector->ffloors && !Tag_Compare(&frontsector->tags, &backsector->tags))) { markceiling = true; From 6d812b4a4343c134669bfc527a73645055334c6b Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Thu, 24 Aug 2023 00:51:52 -0300 Subject: [PATCH 074/227] Implement Eternity Engine's plane portal and horizon portal types --- src/hardware/hw_main.c | 5 +- src/p_spec.c | 30 +++++++-- src/p_spec.h | 15 +++++ src/r_bsp.c | 142 ++++++++++++++++++++++++++++++----------- src/r_bsp.h | 3 + src/r_defs.h | 12 ++-- src/r_main.c | 15 ++++- src/r_portal.c | 23 ++++++- src/r_portal.h | 4 ++ src/r_segs.c | 6 +- 10 files changed, 194 insertions(+), 61 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 12a2d54ee..30f9c4073 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -1117,8 +1117,6 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom INT32 gl_toptexture = 0, gl_bottomtexture = 0; fixed_t texturevpeg; - bothceilingssky = bothfloorssky = false; - SLOPEPARAMS(gl_backsector->c_slope, worldhigh, worldhighslope, gl_backsector->ceilingheight) SLOPEPARAMS(gl_backsector->f_slope, worldlow, worldlowslope, gl_backsector->floorheight) @@ -2339,6 +2337,7 @@ static void HWR_AddLine(seg_t * line) #endif gl_backsector = line->backsector; + bothceilingssky = bothfloorssky = false; #ifdef NEWCLIP if (!line->backsector) @@ -2347,8 +2346,6 @@ static void HWR_AddLine(seg_t * line) } else { - bothceilingssky = bothfloorssky = false; - gl_backsector = R_FakeFlat(gl_backsector, &tempsec, NULL, NULL, true); if (gl_backsector->ceilingpic == skyflatnum && gl_frontsector->ceilingpic == skyflatnum diff --git a/src/p_spec.c b/src/p_spec.c index 0c798b976..3334b184b 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -6227,6 +6227,9 @@ static boolean P_IsSectorPortalValid(sectorportal_t *secportal) return secportal->mobj && !P_MobjWasRemoved(secportal->mobj); case SECPORTAL_SKYBOX: return skyboxmo[0] && !P_MobjWasRemoved(skyboxmo[0]); + case SECPORTAL_PLANE: + case SECPORTAL_HORIZON: + return true; default: return false; } @@ -6525,6 +6528,23 @@ void P_SpawnSpecials(boolean fromnetsave) ceiling = plane_type == TMP_CEILING; } + // Eternity's floor and horizon portal types + if (portal_type == TMSECPORTAL_PLANE || portal_type == TMSECPORTAL_HORIZON) + { + secportaltype_e type = portal_type == TMSECPORTAL_HORIZON ? SECPORTAL_HORIZON : SECPORTAL_PLANE; + if (floor) + { + sectorportal_t *floorportal = P_SectorGetFloorPortalOrCreate(lines[i].frontsector); + floorportal->type = type; + } + if (ceiling) + { + sectorportal_t *ceilportal = P_SectorGetCeilingPortalOrCreate(lines[i].frontsector); + ceilportal->type = type; + } + break; + } + INT32 s1 = -1; TAG_ITER_SECTORS(target_sector_tag, s1) // Target sector tag @@ -6532,7 +6552,7 @@ void P_SpawnSpecials(boolean fromnetsave) sector_t *target_sector = §ors[s1]; // Line portal - if (portal_type == 0) + if (portal_type == TMSECPORTAL_NORMAL) { INT32 linenum = -1; TAG_ITER_LINES(misc, linenum) @@ -6561,7 +6581,7 @@ void P_SpawnSpecials(boolean fromnetsave) } } // Skybox portal - else if (portal_type == 2) + else if (portal_type == TMSECPORTAL_SKYBOX) { if (floor) { @@ -6575,7 +6595,7 @@ void P_SpawnSpecials(boolean fromnetsave) } } // Plane portal - else if (portal_type == 7) + else if (portal_type == TMSECPORTAL_SECTOR) { INT32 s2 = -1; TAG_ITER_SECTORS(misc, s2) // Sector tag to make a portal to @@ -6596,7 +6616,7 @@ void P_SpawnSpecials(boolean fromnetsave) } } // Use mobj as viewpoint - else if (portal_type == 8) + else if (portal_type == TMSECPORTAL_OBJECT) { mobj_t *mobj = P_GetMobjByTag(misc); if (!mobj) @@ -7428,7 +7448,7 @@ void P_SpawnSpecials(boolean fromnetsave) continue; int portal_type = lines[i].args[1]; - if (portal_type != 1) + if (portal_type != TMSECPORTAL_COPIED) continue; int target_sector_tag = lines[i].args[0]; diff --git a/src/p_spec.h b/src/p_spec.h index bb995a97a..6ceec1cae 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -476,6 +476,21 @@ typedef enum TMB_MODULATE = 4, } textmapblendmodes_t; +typedef enum +{ + TMSECPORTAL_NORMAL, + TMSECPORTAL_COPIED, + TMSECPORTAL_SKYBOX, + TMSECPORTAL_PLANE, + TMSECPORTAL_HORIZON, + // The two portal types below are unimplemented + TMSECPORTAL_COPY_PORTAL_TO_LINE, + TMSECPORTAL_INTERACTIVE, + // The two portal types below are new to SRB2 + TMSECPORTAL_SECTOR, + TMSECPORTAL_OBJECT +} textmapsecportaltype_t; + // GETSECSPECIAL (specialval, section) // // Pulls out the special # from a particular section. diff --git a/src/r_bsp.c b/src/r_bsp.c index 8ab5998c4..0486cd699 100644 --- a/src/r_bsp.c +++ b/src/r_bsp.c @@ -39,6 +39,8 @@ drawseg_t *ds_p = NULL; boolean bothceilingssky = false; // turned on if both back and front ceilings are sky boolean bothfloorssky = false; // likewise, but for floors +boolean horizonline = false; + // indicates doors closed wrt automap bugfix: INT32 doorclosed; @@ -456,6 +458,8 @@ static void R_AddLine(seg_t *line) return; backsector = line->backsector; + horizonline = line->linedef->special == HORIZONSPECIAL; + bothceilingssky = bothfloorssky = false; // Portal line if (line->linedef->special == 40 && line->side == 0) @@ -488,7 +492,6 @@ static void R_AddLine(seg_t *line) backsector = R_FakeFlat(backsector, &tempsec, NULL, NULL, true); doorclosed = 0; - bothceilingssky = bothfloorssky = false; // hack to allow height changes in outdoor areas // This is what gets rid of the upper textures if there should be sky @@ -844,6 +847,51 @@ static void R_AddPolyObjects(subsector_t *sub) drawseg_t *firstseg; +static void R_CheckSectorLightLists(sector_t *sector, sector_t *fakeflat, INT32 *floorlightlevel, INT32 *ceilinglightlevel, extracolormap_t **floorcolormap, extracolormap_t **ceilingcolormap) +{ + // Check and prep all 3D floors. Set the sector floor/ceiling light levels and colormaps. + if (fakeflat->ffloors) + { + fixed_t floorcenterz = P_GetSectorFloorZAt (fakeflat, fakeflat->soundorg.x, fakeflat->soundorg.y); + fixed_t ceilingcenterz = P_GetSectorCeilingZAt(fakeflat, fakeflat->soundorg.x, fakeflat->soundorg.y); + + boolean anyMoved = fakeflat->moved; + + if (anyMoved == false) + { + for (ffloor_t *rover = fakeflat->ffloors; rover; rover = rover->next) + { + sector_t *controlSec = §ors[rover->secnum]; + + if (controlSec->moved == true) + { + anyMoved = true; + break; + } + } + } + + if (anyMoved == true) + { + fakeflat->numlights = sector->numlights = 0; + R_Prep3DFloors(fakeflat); + sector->lightlist = fakeflat->lightlist; + sector->numlights = fakeflat->numlights; + sector->moved = fakeflat->moved = false; + } + + INT32 light = R_GetPlaneLight(fakeflat, floorcenterz, false); + if (fakeflat->floorlightsec == -1 && !fakeflat->floorlightabsolute) + *floorlightlevel = max(0, min(255, *fakeflat->lightlist[light].lightlevel + fakeflat->floorlightlevel)); + *floorcolormap = *fakeflat->lightlist[light].extra_colormap; + + light = R_GetPlaneLight(fakeflat, ceilingcenterz, false); + if (fakeflat->ceilinglightsec == -1 && !fakeflat->ceilinglightabsolute) + *ceilinglightlevel = max(0, min(255, *fakeflat->lightlist[light].lightlevel + fakeflat->ceilinglightlevel)); + *ceilingcolormap = *fakeflat->lightlist[light].extra_colormap; + } +} + static void R_Subsector(size_t num) { INT32 count, floorlightlevel, ceilinglightlevel, light; @@ -877,43 +925,7 @@ static void R_Subsector(size_t num) floorcenterz = P_GetSectorFloorZAt (frontsector, frontsector->soundorg.x, frontsector->soundorg.y); ceilingcenterz = P_GetSectorCeilingZAt(frontsector, frontsector->soundorg.x, frontsector->soundorg.y); - // Check and prep all 3D floors. Set the sector floor/ceiling light levels and colormaps. - if (frontsector->ffloors) - { - boolean anyMoved = frontsector->moved; - - if (anyMoved == false) - { - for (rover = frontsector->ffloors; rover; rover = rover->next) - { - sector_t *controlSec = §ors[rover->secnum]; - - if (controlSec->moved == true) - { - anyMoved = true; - break; - } - } - } - - if (anyMoved == true) - { - frontsector->numlights = sub->sector->numlights = 0; - R_Prep3DFloors(frontsector); - sub->sector->lightlist = frontsector->lightlist; - sub->sector->numlights = frontsector->numlights; - sub->sector->moved = frontsector->moved = false; - } - - light = R_GetPlaneLight(frontsector, floorcenterz, false); - if (frontsector->floorlightsec == -1 && !frontsector->floorlightabsolute) - floorlightlevel = max(0, min(255, *frontsector->lightlist[light].lightlevel + frontsector->floorlightlevel)); - floorcolormap = *frontsector->lightlist[light].extra_colormap; - light = R_GetPlaneLight(frontsector, ceilingcenterz, false); - if (frontsector->ceilinglightsec == -1 && !frontsector->ceilinglightabsolute) - ceilinglightlevel = max(0, min(255, *frontsector->lightlist[light].lightlevel + frontsector->ceilinglightlevel)); - ceilingcolormap = *frontsector->lightlist[light].extra_colormap; - } + R_CheckSectorLightLists(sub->sector, frontsector, &floorlightlevel, &ceilinglightlevel, &floorcolormap, &ceilingcolormap); sub->sector->extra_colormap = frontsector->extra_colormap; @@ -1301,3 +1313,57 @@ void R_RenderBSPNode(INT32 bspnum) R_Subsector(bspnum == -1 ? 0 : bspnum & ~NF_SUBSECTOR); } + +void R_RenderPortalHorizonLine(sector_t *sector) +{ + INT32 floorlightlevel, ceilinglightlevel; + static sector_t tempsec; // Deep water hack + extracolormap_t *floorcolormap; + extracolormap_t *ceilingcolormap; + + frontsector = sector; + backsector = NULL; + + // Deep water/fake ceiling effect. + frontsector = R_FakeFlat(frontsector, &tempsec, &floorlightlevel, &ceilinglightlevel, false); + + floorcolormap = ceilingcolormap = frontsector->extra_colormap; + + R_CheckSectorLightLists(sector, frontsector, &floorlightlevel, &ceilinglightlevel, &floorcolormap, &ceilingcolormap); + + sector->extra_colormap = frontsector->extra_colormap; + + if (P_GetSectorFloorZAt(frontsector, viewx, viewy) < viewz + || frontsector->floorpic == skyflatnum + || (frontsector->heightsec != -1 && sectors[frontsector->heightsec].ceilingpic == skyflatnum)) + { + floorplane = R_FindPlane(frontsector, frontsector->floorheight, frontsector->floorpic, floorlightlevel, + frontsector->floorxoffset, frontsector->flooryoffset, frontsector->floorangle, floorcolormap, NULL, NULL, NULL, NULL); + } + else + floorplane = NULL; + + if (P_GetSectorCeilingZAt(frontsector, viewx, viewy) > viewz + || frontsector->ceilingpic == skyflatnum + || (frontsector->heightsec != -1 && sectors[frontsector->heightsec].floorpic == skyflatnum)) + { + ceilingplane = R_FindPlane(frontsector, frontsector->ceilingheight, frontsector->ceilingpic, + ceilinglightlevel, frontsector->ceilingxoffset, frontsector->ceilingyoffset, frontsector->ceilingangle, + ceilingcolormap, NULL, NULL, NULL, NULL); + } + else + ceilingplane = NULL; + + numffloors = 0; + portalline = false; + doorclosed = 0; + bothceilingssky = bothfloorssky = false; + horizonline = true; + + firstseg = NULL; + curline = &segs[0]; + + R_ClipSolidWallSegment(portalclipstart, portalclipend); + + curline = NULL; +} diff --git a/src/r_bsp.h b/src/r_bsp.h index f36f7e64d..3effb916a 100644 --- a/src/r_bsp.h +++ b/src/r_bsp.h @@ -34,6 +34,8 @@ extern drawseg_t *ds_p; extern boolean bothceilingssky; extern boolean bothfloorssky; +extern boolean horizonline; + extern INT32 doorclosed; // BSP? @@ -41,6 +43,7 @@ void R_ClearClipSegs(void); void R_PortalClearClipSegs(INT32 start, INT32 end); void R_ClearDrawSegs(void); void R_RenderBSPNode(INT32 bspnum); +void R_RenderPortalHorizonLine(sector_t *sector); void R_SortPolyObjects(subsector_t *sub); diff --git a/src/r_defs.h b/src/r_defs.h index a8addfe0b..dee15975f 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -210,11 +210,13 @@ typedef enum typedef enum { - SECPORTAL_LINE, - SECPORTAL_OBJECT, - SECPORTAL_SKYBOX, - SECPORTAL_FLOOR, - SECPORTAL_CEILING + SECPORTAL_LINE, // Works similar to a line portal + SECPORTAL_SKYBOX, // Uses the skybox object as the reference view + SECPORTAL_PLANE, // Eternity Engine's plane portal type + SECPORTAL_HORIZON, // Eternity Engine's horizon portal type + SECPORTAL_OBJECT, // Uses an object as the reference view + SECPORTAL_FLOOR, // Uses a sector as the reference view; the view height is aligned with the sector's floor + SECPORTAL_CEILING // Uses a sector as the reference view; the view height is aligned with the sector's ceiling } secportaltype_e; typedef struct sectorportal_s diff --git a/src/r_main.c b/src/r_main.c index 8a0575d66..c73039c2a 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -1547,9 +1547,18 @@ void R_RenderPlayerView(player_t *player) Mask_Pre(&masks[nummasks - 1]); curdrawsegs = ds_p; - // Render the BSP from the new viewpoint, and clip - // any sprites with the new clipsegs and window. - R_RenderBSPNode((INT32)numnodes - 1); + if (portal->is_horizon) + { + // If the portal is a plane or a horizon portal, then we just render a horizon line + R_RenderPortalHorizonLine(portal->horizon_sector); + } + else + { + // Render the BSP from the new viewpoint, and clip + // any sprites with the new clipsegs and window. + R_RenderBSPNode((INT32)numnodes - 1); + } + Mask_Post(&masks[nummasks - 1]); R_ClipSprites(ds_p - (masks[nummasks - 1].drawsegs[1] - masks[nummasks - 1].drawsegs[0]), portal); diff --git a/src/r_portal.c b/src/r_portal.c index c485aa033..e4af45f9f 100644 --- a/src/r_portal.c +++ b/src/r_portal.c @@ -16,6 +16,7 @@ #include "r_main.h" #include "doomstat.h" #include "p_spec.h" // Skybox viewpoints +#include "p_slopes.h" // P_GetSectorFloorZAt and P_GetSectorCeilingZAt #include "p_local.h" #include "z_zone.h" #include "r_things.h" @@ -191,6 +192,8 @@ void Portal_Add2Lines (const INT32 line1, const INT32 line2, const INT32 x1, con Portal_GetViewpointForLine(portal, start, dest); portal->clipline = line2; + portal->is_horizon = false; + portal->horizon_sector = NULL; Portal_ClipRange(portal); @@ -312,6 +315,8 @@ void Portal_AddSkybox (const visplane_t* plane) portal->viewz += viewz * -mh->skybox_scalez; portal->clipline = -1; + portal->is_horizon = false; + portal->horizon_sector = NULL; } /** Creates a sector portal out of a visplane. @@ -337,6 +342,8 @@ void Portal_AddSectorPortal (const visplane_t* plane) Portal_ClipVisplane(plane, portal); portal->clipline = -1; + portal->is_horizon = false; + portal->horizon_sector = NULL; switch (secportal->type) { @@ -354,13 +361,25 @@ void Portal_AddSectorPortal (const visplane_t* plane) case SECPORTAL_FLOOR: x = secportal->sector->soundorg.x; y = secportal->sector->soundorg.y; - z = secportal->sector->floorheight; + z = P_GetSectorFloorZAt(secportal->sector, x, y); angle = 0; break; case SECPORTAL_CEILING: x = secportal->sector->soundorg.x; y = secportal->sector->soundorg.y; - z = secportal->sector->ceilingheight; + z = P_GetSectorCeilingZAt(secportal->sector, x, y); + angle = 0; + break; + case SECPORTAL_PLANE: + case SECPORTAL_HORIZON: + portal->is_horizon = true; + portal->horizon_sector = plane->sector; + x = plane->sector->soundorg.x; + y = plane->sector->soundorg.y; + if (secportal->type == SECPORTAL_PLANE) + z = -viewz; + else + z = 0; angle = 0; break; default: diff --git a/src/r_portal.h b/src/r_portal.h index 1e4bbfb12..1a8291f3c 100644 --- a/src/r_portal.h +++ b/src/r_portal.h @@ -30,6 +30,10 @@ typedef struct portal_s fixed_t viewz; angle_t viewangle; + // For horizon portals + boolean is_horizon; + sector_t *horizon_sector; + UINT8 pass; /**< Keeps track of the portal's recursion depth. */ INT32 clipline; /**< Optional clipline for line-based portals. */ diff --git a/src/r_segs.c b/src/r_segs.c index 2a86cc41a..c3c3cc334 100644 --- a/src/r_segs.c +++ b/src/r_segs.c @@ -1787,8 +1787,6 @@ void R_StoreWallRange(INT32 start, INT32 stop) else { // two sided line - bothceilingssky = bothfloorssky = false; - SLOPEPARAMS(backsector->c_slope, worldhigh, worldhighslope, backsector->ceilingheight) SLOPEPARAMS(backsector->f_slope, worldlow, worldlowslope, backsector->floorheight) worldhigh -= viewz; @@ -2334,7 +2332,7 @@ void R_StoreWallRange(INT32 start, INT32 stop) worldtopslope >>= 4; worldbottomslope >>= 4; - if (linedef->special == HORIZONSPECIAL) { // HORIZON LINES + if (horizonline) { // HORIZON LINES topstep = bottomstep = 0; topfrac = bottomfrac = (centeryfrac>>4); topfrac++; // Prevent 1px HOM @@ -2435,7 +2433,7 @@ void R_StoreWallRange(INT32 start, INT32 stop) { ffloor[i].f_pos >>= 4; ffloor[i].f_pos_slope >>= 4; - if (linedef->special == HORIZONSPECIAL) // Horizon lines extend FOFs in contact with them too. + if (horizonline) // Horizon lines extend FOFs in contact with them too. { ffloor[i].f_step = 0; ffloor[i].f_frac = (centeryfrac>>4); From 563233a55ffcd9466f5da6908ff2b5d7845edf4c Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Thu, 24 Aug 2023 01:09:46 -0300 Subject: [PATCH 075/227] Add editor definition for linedef type 6 --- extras/conf/udb/Includes/SRB222_linedefs.cfg | 28 ++++++++++++++++++++ extras/conf/udb/Includes/SRB222_misc.cfg | 11 ++++++++ src/p_spec.h | 18 ++++++------- 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/extras/conf/udb/Includes/SRB222_linedefs.cfg b/extras/conf/udb/Includes/SRB222_linedefs.cfg index 680778623..9f3e41d39 100644 --- a/extras/conf/udb/Includes/SRB222_linedefs.cfg +++ b/extras/conf/udb/Includes/SRB222_linedefs.cfg @@ -10,6 +10,34 @@ udmf prefix = "(0)"; } + 6 + { + title = "Sector Portal"; + prefix = "(6)"; + arg0 + { + title = "Target sector tag"; + type = 13; + } + arg1 + { + title = "Type"; + type = 11; + enum = "sectorportal"; + } + arg2 + { + title = "Affected planes"; + type = 11; + enum = "floorceiling"; + } + arg3 + { + title = "Miscellaneous"; + type = 0; + } + } + 7 { title = "Sector Flat Alignment"; diff --git a/extras/conf/udb/Includes/SRB222_misc.cfg b/extras/conf/udb/Includes/SRB222_misc.cfg index e274fece6..18bb0aec7 100644 --- a/extras/conf/udb/Includes/SRB222_misc.cfg +++ b/extras/conf/udb/Includes/SRB222_misc.cfg @@ -453,6 +453,17 @@ enums 3 = "Reverse subtract"; 4 = "Modulate"; } + + sectorportal + { + 0 = "Normal"; + 1 = "Copied"; + 2 = "Skybox"; + 3 = "Plane"; + 4 = "Horizon"; + 7 = "Sector"; + 8 = "Object"; + } } //Default things filters diff --git a/src/p_spec.h b/src/p_spec.h index 6ceec1cae..87f9951a6 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -478,17 +478,17 @@ typedef enum typedef enum { - TMSECPORTAL_NORMAL, - TMSECPORTAL_COPIED, - TMSECPORTAL_SKYBOX, - TMSECPORTAL_PLANE, - TMSECPORTAL_HORIZON, + TMSECPORTAL_NORMAL = 0, + TMSECPORTAL_COPIED = 1, + TMSECPORTAL_SKYBOX = 2, + TMSECPORTAL_PLANE = 3, + TMSECPORTAL_HORIZON = 4, // The two portal types below are unimplemented - TMSECPORTAL_COPY_PORTAL_TO_LINE, - TMSECPORTAL_INTERACTIVE, + TMSECPORTAL_COPY_PORTAL_TO_LINE = 5, + TMSECPORTAL_INTERACTIVE = 6, // The two portal types below are new to SRB2 - TMSECPORTAL_SECTOR, - TMSECPORTAL_OBJECT + TMSECPORTAL_SECTOR = 7, + TMSECPORTAL_OBJECT = 8 } textmapsecportaltype_t; // GETSECSPECIAL (specialval, section) From 6303e37fbe88fcfec9243c1ad572c17c4a41ab77 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Thu, 24 Aug 2023 01:19:58 -0300 Subject: [PATCH 076/227] Use P_SetTarget here --- src/p_spec.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/p_spec.c b/src/p_spec.c index 3334b184b..e76968b8a 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -6625,13 +6625,13 @@ void P_SpawnSpecials(boolean fromnetsave) { sectorportal_t *floorportal = P_SectorGetFloorPortalOrCreate(target_sector); floorportal->type = SECPORTAL_OBJECT; - floorportal->mobj = mobj; + P_SetTarget(&floorportal->mobj, mobj); } if (ceiling) { sectorportal_t *ceilportal = P_SectorGetCeilingPortalOrCreate(target_sector); ceilportal->type = SECPORTAL_OBJECT; - ceilportal->mobj = mobj; + P_SetTarget(&ceilportal->mobj, mobj); } } } From ff5d53e54d309aef952543ab1c1d2b0e593b3d74 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Thu, 24 Aug 2023 16:04:31 -0300 Subject: [PATCH 077/227] Serialize sector portals --- src/hardware/hw_main.c | 52 +--------------- src/p_saveg.c | 131 ++++++++++++++++++++++++++++++++++------- src/p_spec.c | 4 ++ src/r_bsp.c | 115 ++++++++++++++++++------------------ src/r_bsp.h | 1 + src/r_defs.h | 3 +- src/r_portal.c | 2 +- 7 files changed, 178 insertions(+), 130 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 30f9c4073..5b8f1178d 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -2927,64 +2927,16 @@ static void HWR_Subsector(size_t num) } //SoM: 4/7/2000: Test to make Boom water work in Hardware mode. - gl_frontsector = R_FakeFlat(gl_frontsector, &tempsec, &floorlightlevel, - &ceilinglightlevel, false); - //FIXME: Use floorlightlevel and ceilinglightlevel insted of lightlevel. + gl_frontsector = R_FakeFlat(gl_frontsector, &tempsec, &floorlightlevel, &ceilinglightlevel, false); floorcolormap = ceilingcolormap = gl_frontsector->extra_colormap; - // ------------------------------------------------------------------------ - // sector lighting, DISABLED because it's done in HWR_StoreWallRange - // ------------------------------------------------------------------------ - /// \todo store a RGBA instead of just intensity, allow coloured sector lighting - //light = (FUBYTE)(sub->sector->lightlevel & 0xFF) / 255.0f; - //gl_cursectorlight.red = light; - //gl_cursectorlight.green = light; - //gl_cursectorlight.blue = light; - //gl_cursectorlight.alpha = light; - -// ----- end special tricks ----- cullFloorHeight = P_GetSectorFloorZAt (gl_frontsector, viewx, viewy); cullCeilingHeight = P_GetSectorCeilingZAt(gl_frontsector, viewx, viewy); locFloorHeight = P_GetSectorFloorZAt (gl_frontsector, gl_frontsector->soundorg.x, gl_frontsector->soundorg.y); locCeilingHeight = P_GetSectorCeilingZAt(gl_frontsector, gl_frontsector->soundorg.x, gl_frontsector->soundorg.y); - if (gl_frontsector->ffloors) - { - boolean anyMoved = gl_frontsector->moved; - - if (anyMoved == false) - { - for (rover = gl_frontsector->ffloors; rover; rover = rover->next) - { - sector_t *controlSec = §ors[rover->secnum]; - if (controlSec->moved == true) - { - anyMoved = true; - break; - } - } - } - - if (anyMoved == true) - { - gl_frontsector->numlights = sub->sector->numlights = 0; - R_Prep3DFloors(gl_frontsector); - sub->sector->lightlist = gl_frontsector->lightlist; - sub->sector->numlights = gl_frontsector->numlights; - sub->sector->moved = gl_frontsector->moved = false; - } - - light = R_GetPlaneLight(gl_frontsector, locFloorHeight, false); - if (gl_frontsector->floorlightsec == -1 && !gl_frontsector->floorlightabsolute) - floorlightlevel = max(0, min(255, *gl_frontsector->lightlist[light].lightlevel + gl_frontsector->floorlightlevel)); - floorcolormap = *gl_frontsector->lightlist[light].extra_colormap; - - light = R_GetPlaneLight(gl_frontsector, locCeilingHeight, false); - if (gl_frontsector->ceilinglightsec == -1 && !gl_frontsector->ceilinglightabsolute) - ceilinglightlevel = max(0, min(255, *gl_frontsector->lightlist[light].lightlevel + gl_frontsector->ceilinglightlevel)); - ceilingcolormap = *gl_frontsector->lightlist[light].extra_colormap; - } + R_CheckSectorLightLists(sub->sector, gl_frontsector, &floorlightlevel, &ceilinglightlevel, &floorcolormap, &ceilingcolormap); sub->sector->extra_colormap = gl_frontsector->extra_colormap; diff --git a/src/p_saveg.c b/src/p_saveg.c index faecd1377..b3ac59e81 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -41,20 +41,19 @@ UINT8 *save_p; // Block UINT32s to attempt to ensure that the correct data is // being sent and received -#define ARCHIVEBLOCK_MISC 0x7FEEDEED -#define ARCHIVEBLOCK_PLAYERS 0x7F448008 -#define ARCHIVEBLOCK_WORLD 0x7F8C08C0 -#define ARCHIVEBLOCK_POBJS 0x7F928546 -#define ARCHIVEBLOCK_THINKERS 0x7F37037C -#define ARCHIVEBLOCK_SPECIALS 0x7F228378 -#define ARCHIVEBLOCK_EMBLEMS 0x7F4A5445 +#define ARCHIVEBLOCK_MISC 0x7FEEDEED +#define ARCHIVEBLOCK_PLAYERS 0x7F448008 +#define ARCHIVEBLOCK_WORLD 0x7F8C08C0 +#define ARCHIVEBLOCK_POBJS 0x7F928546 +#define ARCHIVEBLOCK_THINKERS 0x7F37037C +#define ARCHIVEBLOCK_SPECIALS 0x7F228378 +#define ARCHIVEBLOCK_EMBLEMS 0x7F4A5445 +#define ARCHIVEBLOCK_SECPORTALS 0x7FBE34C9 // Note: This cannot be bigger // than an UINT16 typedef enum { -// RFLAGPOINT = 0x01, -// BFLAGPOINT = 0x02, CAPSULE = 0x04, AWAYVIEW = 0x08, FIRSTAXIS = 0x10, @@ -853,20 +852,22 @@ static void P_NetUnArchiveWaypoints(void) #define SD_DIFF3 0x80 // diff3 flags -#define SD_TAGLIST 0x01 -#define SD_COLORMAP 0x02 +#define SD_TAGLIST 0x01 +#define SD_COLORMAP 0x02 #define SD_CRUMBLESTATE 0x04 -#define SD_FLOORLIGHT 0x08 -#define SD_CEILLIGHT 0x10 -#define SD_FLAG 0x20 -#define SD_SPECIALFLAG 0x40 -#define SD_DIFF4 0x80 +#define SD_FLOORLIGHT 0x08 +#define SD_CEILLIGHT 0x10 +#define SD_FLAG 0x20 +#define SD_SPECIALFLAG 0x40 +#define SD_DIFF4 0x80 //diff4 flags -#define SD_DAMAGETYPE 0x01 -#define SD_TRIGGERTAG 0x02 -#define SD_TRIGGERER 0x04 -#define SD_GRAVITY 0x08 +#define SD_DAMAGETYPE 0x01 +#define SD_TRIGGERTAG 0x02 +#define SD_TRIGGERER 0x04 +#define SD_GRAVITY 0x08 +#define SD_FLOORPORTAL 0x10 +#define SD_CEILPORTAL 0x20 #define LD_FLAG 0x01 #define LD_SPECIAL 0x02 @@ -1064,6 +1065,10 @@ static void ArchiveSectors(void) diff4 |= SD_TRIGGERER; if (ss->gravity != spawnss->gravity) diff4 |= SD_GRAVITY; + if (ss->portal_floor != spawnss->portal_floor) + diff4 |= SD_FLOORPORTAL; + if (ss->portal_ceiling != spawnss->portal_ceiling) + diff4 |= SD_CEILPORTAL; if (ss->ffloors && CheckFFloorDiff(ss)) diff |= SD_FFLOORS; @@ -1145,6 +1150,10 @@ static void ArchiveSectors(void) WRITEUINT8(save_p, ss->triggerer); if (diff4 & SD_GRAVITY) WRITEFIXED(save_p, ss->gravity); + if (diff4 & SD_FLOORPORTAL) + WRITEUINT32(save_p, ss->portal_floor); + if (diff4 & SD_CEILPORTAL) + WRITEUINT32(save_p, ss->portal_ceiling); if (diff & SD_FFLOORS) ArchiveFFloors(ss); } @@ -1265,6 +1274,10 @@ static void UnArchiveSectors(void) sectors[i].triggerer = READUINT8(save_p); if (diff4 & SD_GRAVITY) sectors[i].gravity = READFIXED(save_p); + if (diff4 & SD_FLOORPORTAL) + sectors[i].portal_floor = READUINT32(save_p); + if (diff4 & SD_CEILPORTAL) + sectors[i].portal_ceiling = READUINT32(save_p); if (diff & SD_FFLOORS) UnArchiveFFloors(§ors[i]); @@ -4735,6 +4748,82 @@ static inline void P_NetUnArchiveEmblems(void) } } +static void P_NetArchiveSectorPortals(void) +{ + WRITEUINT32(save_p, ARCHIVEBLOCK_SECPORTALS); + + WRITEUINT32(save_p, secportalcount); + + for (size_t i = 0; i < secportalcount; i++) + { + UINT8 type = secportals[i].type; + + WRITEUINT8(save_p, type); + + switch (type) + { + case SECPORTAL_LINE: + WRITEUINT32(save_p, SaveLine(secportals[i].line.start)); + WRITEUINT32(save_p, SaveLine(secportals[i].line.dest)); + break; + case SECPORTAL_PLANE: + case SECPORTAL_HORIZON: + case SECPORTAL_FLOOR: + case SECPORTAL_CEILING: + WRITEUINT32(save_p, SaveSector(secportals[i].sector)); + break; + case SECPORTAL_OBJECT: + if (secportals[i].mobj && !P_MobjWasRemoved(secportals[i].mobj)) + SaveMobjnum(secportals[i].mobj); + else + WRITEUINT32(save_p, 0); + break; + default: + break; + } + } +} + +static void P_NetUnArchiveSectorPortals(void) +{ + if (READUINT32(save_p) != ARCHIVEBLOCK_SECPORTALS) + I_Error("Bad $$$.sav at archive block Secportals"); + + Z_Free(secportals); + P_InitSectorPortals(); + + UINT32 count = READUINT32(save_p); + + for (UINT32 i = 0; i < count; i++) + { + UINT32 id = P_NewSectorPortal(); + + sectorportal_t *secportal = &secportals[id]; + + secportal->type = READUINT8(save_p); + + switch (secportal->type) + { + case SECPORTAL_LINE: + secportal->line.start = LoadLine(READUINT32(save_p)); + secportal->line.dest = LoadLine(READUINT32(save_p)); + break; + case SECPORTAL_PLANE: + case SECPORTAL_HORIZON: + case SECPORTAL_FLOOR: + case SECPORTAL_CEILING: + secportal->sector = LoadSector(READUINT32(save_p)); + break; + case SECPORTAL_OBJECT: + id = READUINT32(save_p); + secportal->mobj = (id == 0) ? NULL : P_FindNewPosition(id); + break; + default: + break; + } + } +} + static inline void P_ArchiveLuabanksAndConsistency(void) { UINT8 i, banksinuse = NUM_LUABANKS; @@ -4821,6 +4910,7 @@ void P_SaveNetGame(boolean resending) P_NetArchiveSpecials(); P_NetArchiveColormaps(); P_NetArchiveWaypoints(); + P_NetArchiveSectorPortals(); } LUA_Archive(); @@ -4861,6 +4951,7 @@ boolean P_LoadNetGame(boolean reloading) P_NetUnArchiveSpecials(); P_NetUnArchiveColormaps(); P_NetUnArchiveWaypoints(); + P_NetUnArchiveSectorPortals(); P_RelinkPointers(); P_FinishMobjs(); } diff --git a/src/p_spec.c b/src/p_spec.c index e76968b8a..d6b7b18b6 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -6209,6 +6209,8 @@ UINT32 P_NewSectorPortal(void) secportals = Z_Realloc(secportals, secportalcapacity * sizeof(sectorportal_t), PU_LEVEL, NULL); } + secportals[i].type = SECPORTAL_NONE; + return (UINT32)i; } @@ -6536,11 +6538,13 @@ void P_SpawnSpecials(boolean fromnetsave) { sectorportal_t *floorportal = P_SectorGetFloorPortalOrCreate(lines[i].frontsector); floorportal->type = type; + floorportal->sector = lines[i].frontsector; } if (ceiling) { sectorportal_t *ceilportal = P_SectorGetCeilingPortalOrCreate(lines[i].frontsector); ceilportal->type = type; + ceilportal->sector = lines[i].frontsector; } break; } diff --git a/src/r_bsp.c b/src/r_bsp.c index 0486cd699..91b4fdbc0 100644 --- a/src/r_bsp.c +++ b/src/r_bsp.c @@ -847,51 +847,6 @@ static void R_AddPolyObjects(subsector_t *sub) drawseg_t *firstseg; -static void R_CheckSectorLightLists(sector_t *sector, sector_t *fakeflat, INT32 *floorlightlevel, INT32 *ceilinglightlevel, extracolormap_t **floorcolormap, extracolormap_t **ceilingcolormap) -{ - // Check and prep all 3D floors. Set the sector floor/ceiling light levels and colormaps. - if (fakeflat->ffloors) - { - fixed_t floorcenterz = P_GetSectorFloorZAt (fakeflat, fakeflat->soundorg.x, fakeflat->soundorg.y); - fixed_t ceilingcenterz = P_GetSectorCeilingZAt(fakeflat, fakeflat->soundorg.x, fakeflat->soundorg.y); - - boolean anyMoved = fakeflat->moved; - - if (anyMoved == false) - { - for (ffloor_t *rover = fakeflat->ffloors; rover; rover = rover->next) - { - sector_t *controlSec = §ors[rover->secnum]; - - if (controlSec->moved == true) - { - anyMoved = true; - break; - } - } - } - - if (anyMoved == true) - { - fakeflat->numlights = sector->numlights = 0; - R_Prep3DFloors(fakeflat); - sector->lightlist = fakeflat->lightlist; - sector->numlights = fakeflat->numlights; - sector->moved = fakeflat->moved = false; - } - - INT32 light = R_GetPlaneLight(fakeflat, floorcenterz, false); - if (fakeflat->floorlightsec == -1 && !fakeflat->floorlightabsolute) - *floorlightlevel = max(0, min(255, *fakeflat->lightlist[light].lightlevel + fakeflat->floorlightlevel)); - *floorcolormap = *fakeflat->lightlist[light].extra_colormap; - - light = R_GetPlaneLight(fakeflat, ceilingcenterz, false); - if (fakeflat->ceilinglightsec == -1 && !fakeflat->ceilinglightabsolute) - *ceilinglightlevel = max(0, min(255, *fakeflat->lightlist[light].lightlevel + fakeflat->ceilinglightlevel)); - *ceilingcolormap = *fakeflat->lightlist[light].extra_colormap; - } -} - static void R_Subsector(size_t num) { INT32 count, floorlightlevel, ceilinglightlevel, light; @@ -1098,18 +1053,18 @@ static void R_Subsector(size_t num) } } - // killough 9/18/98: Fix underwater slowdown, by passing real sector - // instead of fake one. Improve sprite lighting by basing sprite - // lightlevels on floor & ceiling lightlevels in the surrounding area. - // - // 10/98 killough: - // - // NOTE: TeamTNT fixed this bug incorrectly, messing up sprite lighting!!! - // That is part of the 242 effect!!! If you simply pass sub->sector to - // the old code you will not get correct lighting for underwater sprites!!! - // Either you must pass the fake sector and handle validcount here, on the - // real sector, or you must account for the lighting in some other way, - // like passing it as an argument. + // killough 9/18/98: Fix underwater slowdown, by passing real sector + // instead of fake one. Improve sprite lighting by basing sprite + // lightlevels on floor & ceiling lightlevels in the surrounding area. + // + // 10/98 killough: + // + // NOTE: TeamTNT fixed this bug incorrectly, messing up sprite lighting!!! + // That is part of the 242 effect!!! If you simply pass sub->sector to + // the old code you will not get correct lighting for underwater sprites!!! + // Either you must pass the fake sector and handle validcount here, on the + // real sector, or you must account for the lighting in some other way, + // like passing it as an argument. R_AddSprites(sub->sector, (floorlightlevel+ceilinglightlevel)/2); firstseg = NULL; @@ -1120,7 +1075,6 @@ static void R_Subsector(size_t num) while (count--) { -// CONS_Debug(DBG_GAMELOGIC, "Adding normal line %d...(%d)\n", line->linedef-lines, leveltime); if (!line->glseg && !line->polyseg) // ignore segs that belong to polyobjects R_AddLine(line); line++; @@ -1128,6 +1082,51 @@ static void R_Subsector(size_t num) } } +void R_CheckSectorLightLists(sector_t *sector, sector_t *fakeflat, INT32 *floorlightlevel, INT32 *ceilinglightlevel, extracolormap_t **floorcolormap, extracolormap_t **ceilingcolormap) +{ + // Check and prep all 3D floors. Set the sector floor/ceiling light levels and colormaps. + if (fakeflat->ffloors) + { + fixed_t floorcenterz = P_GetSectorFloorZAt (fakeflat, fakeflat->soundorg.x, fakeflat->soundorg.y); + fixed_t ceilingcenterz = P_GetSectorCeilingZAt(fakeflat, fakeflat->soundorg.x, fakeflat->soundorg.y); + + boolean anyMoved = fakeflat->moved; + + if (anyMoved == false) + { + for (ffloor_t *rover = fakeflat->ffloors; rover; rover = rover->next) + { + sector_t *controlSec = §ors[rover->secnum]; + + if (controlSec->moved == true) + { + anyMoved = true; + break; + } + } + } + + if (anyMoved == true) + { + fakeflat->numlights = sector->numlights = 0; + R_Prep3DFloors(fakeflat); + sector->lightlist = fakeflat->lightlist; + sector->numlights = fakeflat->numlights; + sector->moved = fakeflat->moved = false; + } + + INT32 light = R_GetPlaneLight(fakeflat, floorcenterz, false); + if (fakeflat->floorlightsec == -1 && !fakeflat->floorlightabsolute) + *floorlightlevel = max(0, min(255, *fakeflat->lightlist[light].lightlevel + fakeflat->floorlightlevel)); + *floorcolormap = *fakeflat->lightlist[light].extra_colormap; + + light = R_GetPlaneLight(fakeflat, ceilingcenterz, false); + if (fakeflat->ceilinglightsec == -1 && !fakeflat->ceilinglightabsolute) + *ceilinglightlevel = max(0, min(255, *fakeflat->lightlist[light].lightlevel + fakeflat->ceilinglightlevel)); + *ceilingcolormap = *fakeflat->lightlist[light].extra_colormap; + } +} + // // R_Prep3DFloors // diff --git a/src/r_bsp.h b/src/r_bsp.h index 3effb916a..44ddd0b1b 100644 --- a/src/r_bsp.h +++ b/src/r_bsp.h @@ -57,4 +57,5 @@ boolean R_IsEmptyLine(seg_t *line, sector_t *front, sector_t *back); INT32 R_GetPlaneLight(sector_t *sector, fixed_t planeheight, boolean underside); void R_Prep3DFloors(sector_t *sector); +void R_CheckSectorLightLists(sector_t *sector, sector_t *fakeflat, INT32 *floorlightlevel, INT32 *ceilinglightlevel, extracolormap_t **floorcolormap, extracolormap_t **ceilingcolormap); #endif diff --git a/src/r_defs.h b/src/r_defs.h index dee15975f..b6ea8d6e5 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -216,7 +216,8 @@ typedef enum SECPORTAL_HORIZON, // Eternity Engine's horizon portal type SECPORTAL_OBJECT, // Uses an object as the reference view SECPORTAL_FLOOR, // Uses a sector as the reference view; the view height is aligned with the sector's floor - SECPORTAL_CEILING // Uses a sector as the reference view; the view height is aligned with the sector's ceiling + SECPORTAL_CEILING, // Uses a sector as the reference view; the view height is aligned with the sector's ceiling + SECPORTAL_NONE = 0xFF } secportaltype_e; typedef struct sectorportal_s diff --git a/src/r_portal.c b/src/r_portal.c index e4af45f9f..327821b9d 100644 --- a/src/r_portal.c +++ b/src/r_portal.c @@ -373,7 +373,7 @@ void Portal_AddSectorPortal (const visplane_t* plane) case SECPORTAL_PLANE: case SECPORTAL_HORIZON: portal->is_horizon = true; - portal->horizon_sector = plane->sector; + portal->horizon_sector = secportal->sector; x = plane->sector->soundorg.x; y = plane->sector->soundorg.y; if (secportal->type == SECPORTAL_PLANE) From 9335ee3031599f6d4b4835fa6d29ad8821ccbbb3 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Thu, 24 Aug 2023 17:36:38 -0300 Subject: [PATCH 078/227] Implement "copy portal to line" sector portal type --- extras/conf/udb/Includes/SRB222_misc.cfg | 1 + src/hardware/hw_main.c | 4 +- src/p_map.c | 2 +- src/p_saveg.c | 9 +- src/p_setup.c | 1 + src/p_spec.c | 98 ++++++++++++------ src/p_spec.h | 5 +- src/r_bsp.c | 11 +- src/r_defs.h | 6 +- src/r_portal.c | 122 ++++++++++++++++------- src/r_portal.h | 1 + 11 files changed, 186 insertions(+), 74 deletions(-) diff --git a/extras/conf/udb/Includes/SRB222_misc.cfg b/extras/conf/udb/Includes/SRB222_misc.cfg index 18bb0aec7..2fb863921 100644 --- a/extras/conf/udb/Includes/SRB222_misc.cfg +++ b/extras/conf/udb/Includes/SRB222_misc.cfg @@ -461,6 +461,7 @@ enums 2 = "Skybox"; 3 = "Plane"; 4 = "Horizon"; + 5 = "Transfer to line"; 7 = "Sector"; 8 = "Object"; } diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 5b8f1178d..1d5543202 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -577,7 +577,7 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool for (i = 0; i < subsector->numlines; i++, line++) { - if (!line->glseg && line->linedef->special == HORIZONSPECIAL && R_PointOnSegSide(dup_viewx, dup_viewy, line) == 0) + if (!line->glseg && line->linedef->special == SPECIAL_HORIZON_LINE && R_PointOnSegSide(dup_viewx, dup_viewy, line) == 0) { P_ClosestPointOnLine(viewx, viewy, line->linedef, &v); dist = FIXED_TO_FLOAT(R_PointToDist(v.x, v.y)); @@ -1466,7 +1466,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom else { // Single sided line... Deal only with the middletexture (if one exists) - if (gl_midtexture && gl_linedef->special != HORIZONSPECIAL) // (Ignore horizon line for OGL) + if (gl_midtexture && gl_linedef->special != SPECIAL_HORIZON_LINE) // (Ignore horizon line for OGL) { fixed_t texturevpeg; diff --git a/src/p_map.c b/src/p_map.c index 132a3cf85..b7fcbb2dd 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -3473,7 +3473,7 @@ static void PTR_GlideClimbTraverse(line_t *li) } // see about climbing on the wall - if (!(checkline->flags & ML_NOCLIMB) && checkline->special != HORIZONSPECIAL) + if (!(checkline->flags & ML_NOCLIMB) && checkline->special != SPECIAL_HORIZON_LINE) { boolean canclimb; angle_t climbangle, climbline; diff --git a/src/p_saveg.c b/src/p_saveg.c index b3ac59e81..80b111946 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -886,6 +886,7 @@ static void P_NetUnArchiveWaypoints(void) #define LD_ARGS 0x10 #define LD_STRINGARGS 0x20 #define LD_EXECUTORDELAY 0x40 +#define LD_TRANSFPORTAL 0x80 static boolean P_AreArgsEqual(const line_t *li, const line_t *spawnli) { @@ -1312,6 +1313,9 @@ static void ArchiveLines(void) if (li->executordelay != spawnli->executordelay) diff2 |= LD_EXECUTORDELAY; + if (li->secportal != spawnli->secportal) + diff2 |= LD_TRANSFPORTAL; + if (li->sidenum[0] != 0xffff) { si = &sides[li->sidenum[0]]; @@ -1402,6 +1406,8 @@ static void ArchiveLines(void) } if (diff2 & LD_EXECUTORDELAY) WRITEINT32(save_p, li->executordelay); + if (diff2 & LD_TRANSFPORTAL) + WRITEUINT32(save_p, li->secportal); } } WRITEUINT16(save_p, 0xffff); @@ -1486,7 +1492,8 @@ static void UnArchiveLines(void) } if (diff2 & LD_EXECUTORDELAY) li->executordelay = READINT32(save_p); - + if (diff2 & LD_TRANSFPORTAL) + li->secportal = READUINT32(save_p); } } diff --git a/src/p_setup.c b/src/p_setup.c index 565a44eae..88cd74ed0 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -1108,6 +1108,7 @@ static void P_InitializeLinedef(line_t *ld) ld->polyobj = NULL; ld->callcount = 0; + ld->secportal = UINT32_MAX; // cph 2006/09/30 - fix sidedef errors right away. // cph 2002/07/20 - these errors are fatal if not fixed, so apply them diff --git a/src/p_spec.c b/src/p_spec.c index d6b7b18b6..e1dd6bcec 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -6214,7 +6214,7 @@ UINT32 P_NewSectorPortal(void) return (UINT32)i; } -static boolean P_IsSectorPortalValid(sectorportal_t *secportal) +boolean P_IsSectorPortalValid(sectorportal_t *secportal) { if (secportal == NULL) return false; @@ -6325,28 +6325,59 @@ static void P_DoPortalCopyFromLine(sector_t *dest_sector, int plane_type, int ta } } -static sectorportal_t *P_SectorGetFloorPortalOrCreate(sector_t *sector) +static sectorportal_t *P_SectorGetFloorPortalOrCreate(sector_t *sector, UINT32 *result) { - sectorportal_t *secportal = P_SectorGetFloorPortal(sector); - if (secportal == NULL) + *result = sector->portal_floor; + + if (*result >= secportalcount) { - sector->portal_floor = P_NewSectorPortal(); - return &secportals[sector->portal_floor]; + *result = P_NewSectorPortal(); + sector->portal_floor = *result; } - return secportal; + return &secportals[sector->portal_floor]; } -static sectorportal_t *P_SectorGetCeilingPortalOrCreate(sector_t *sector) +static sectorportal_t *P_SectorGetCeilingPortalOrCreate(sector_t *sector, UINT32 *result) { - sectorportal_t *secportal = P_SectorGetCeilingPortal(sector); - if (secportal == NULL) + *result = sector->portal_floor; + + if (*result >= secportalcount) { - sector->portal_ceiling = P_NewSectorPortal(); - return &secportals[sector->portal_ceiling]; + *result = P_NewSectorPortal(); + sector->portal_ceiling = *result; } - return secportal; + return &secportals[sector->portal_ceiling]; +} + +static void P_CopySectorPortalToLines(UINT32 portal_num, int sector_tag) +{ + for (size_t i = 0; i < numlines; i++) + { + if (lines[i].special != SPECIAL_SECTOR_SETPORTAL) + continue; + + if (lines[i].args[1] != TMSECPORTAL_COPY_PORTAL_TO_LINE) + continue; + + if (lines[i].args[3] != sector_tag) + continue; + + if (lines[i].args[0] != 0) + { + INT32 linenum = -1; + TAG_ITER_LINES(lines[i].args[0], linenum) + { + lines[linenum].secportal = portal_num; + } + } + else + { + // Just transfer it to this line + lines[i].secportal = portal_num; + } + } } /** After the map has loaded, scans for specials that spawn 3Dfloors and @@ -6514,7 +6545,7 @@ void P_SpawnSpecials(boolean fromnetsave) P_AddCameraScanner(§ors[sec], §ors[s], R_PointToAngle2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y)); break; - case 6: // Sector portal + case SPECIAL_SECTOR_SETPORTAL: // Sector portal { int target_sector_tag = lines[i].args[0]; int portal_type = lines[i].args[1]; @@ -6530,28 +6561,32 @@ void P_SpawnSpecials(boolean fromnetsave) ceiling = plane_type == TMP_CEILING; } + UINT32 portal_num = UINT32_MAX; + // Eternity's floor and horizon portal types if (portal_type == TMSECPORTAL_PLANE || portal_type == TMSECPORTAL_HORIZON) { secportaltype_e type = portal_type == TMSECPORTAL_HORIZON ? SECPORTAL_HORIZON : SECPORTAL_PLANE; if (floor) { - sectorportal_t *floorportal = P_SectorGetFloorPortalOrCreate(lines[i].frontsector); + sectorportal_t *floorportal = P_SectorGetFloorPortalOrCreate(lines[i].frontsector, &portal_num); floorportal->type = type; floorportal->sector = lines[i].frontsector; + P_CopySectorPortalToLines(portal_num, target_sector_tag); } if (ceiling) { - sectorportal_t *ceilportal = P_SectorGetCeilingPortalOrCreate(lines[i].frontsector); + sectorportal_t *ceilportal = P_SectorGetCeilingPortalOrCreate(lines[i].frontsector, &portal_num); ceilportal->type = type; ceilportal->sector = lines[i].frontsector; + P_CopySectorPortalToLines(portal_num, target_sector_tag); } break; } INT32 s1 = -1; - TAG_ITER_SECTORS(target_sector_tag, s1) // Target sector tag + TAG_ITER_SECTORS(target_sector_tag, s1) { sector_t *target_sector = §ors[s1]; @@ -6561,7 +6596,7 @@ void P_SpawnSpecials(boolean fromnetsave) INT32 linenum = -1; TAG_ITER_LINES(misc, linenum) { - if (lines[linenum].special == 6 + if (lines[linenum].special == SPECIAL_SECTOR_SETPORTAL && lines[linenum].args[0] == target_sector_tag && lines[linenum].args[1] == portal_type && lines[linenum].args[2] == plane_type @@ -6569,17 +6604,19 @@ void P_SpawnSpecials(boolean fromnetsave) { if (floor) { - sectorportal_t *floorportal = P_SectorGetFloorPortalOrCreate(target_sector); + sectorportal_t *floorportal = P_SectorGetFloorPortalOrCreate(target_sector, &portal_num); floorportal->type = SECPORTAL_LINE; floorportal->line.start = &lines[i]; floorportal->line.dest = &lines[linenum]; + P_CopySectorPortalToLines(portal_num, target_sector_tag); } if (ceiling) { - sectorportal_t *ceilportal = P_SectorGetCeilingPortalOrCreate(target_sector); + sectorportal_t *ceilportal = P_SectorGetCeilingPortalOrCreate(target_sector, &portal_num); ceilportal->type = SECPORTAL_LINE; ceilportal->line.start = &lines[i]; ceilportal->line.dest = &lines[linenum]; + P_CopySectorPortalToLines(portal_num, target_sector_tag); } } } @@ -6589,13 +6626,15 @@ void P_SpawnSpecials(boolean fromnetsave) { if (floor) { - sectorportal_t *floorportal = P_SectorGetFloorPortalOrCreate(target_sector); + sectorportal_t *floorportal = P_SectorGetFloorPortalOrCreate(target_sector, &portal_num); floorportal->type = SECPORTAL_SKYBOX; + P_CopySectorPortalToLines(portal_num, target_sector_tag); } if (ceiling) { - sectorportal_t *ceilportal = P_SectorGetCeilingPortalOrCreate(target_sector); + sectorportal_t *ceilportal = P_SectorGetCeilingPortalOrCreate(target_sector, &portal_num); ceilportal->type = SECPORTAL_SKYBOX; + P_CopySectorPortalToLines(portal_num, target_sector_tag); } } // Plane portal @@ -6607,15 +6646,17 @@ void P_SpawnSpecials(boolean fromnetsave) sector_t *view_sector = §ors[s2]; if (floor) { - sectorportal_t *floorportal = P_SectorGetFloorPortalOrCreate(target_sector); + sectorportal_t *floorportal = P_SectorGetFloorPortalOrCreate(target_sector, &portal_num); floorportal->type = SECPORTAL_CEILING; floorportal->sector = view_sector; + P_CopySectorPortalToLines(portal_num, target_sector_tag); } if (ceiling) { - sectorportal_t *ceilportal = P_SectorGetCeilingPortalOrCreate(target_sector); + sectorportal_t *ceilportal = P_SectorGetCeilingPortalOrCreate(target_sector, &portal_num); ceilportal->type = SECPORTAL_FLOOR; ceilportal->sector = view_sector; + P_CopySectorPortalToLines(portal_num, target_sector_tag); } } } @@ -6627,15 +6668,17 @@ void P_SpawnSpecials(boolean fromnetsave) break; if (floor) { - sectorportal_t *floorportal = P_SectorGetFloorPortalOrCreate(target_sector); + sectorportal_t *floorportal = P_SectorGetFloorPortalOrCreate(target_sector, &portal_num); floorportal->type = SECPORTAL_OBJECT; P_SetTarget(&floorportal->mobj, mobj); + P_CopySectorPortalToLines(portal_num, target_sector_tag); } if (ceiling) { - sectorportal_t *ceilportal = P_SectorGetCeilingPortalOrCreate(target_sector); + sectorportal_t *ceilportal = P_SectorGetCeilingPortalOrCreate(target_sector, &portal_num); ceilportal->type = SECPORTAL_OBJECT; P_SetTarget(&ceilportal->mobj, mobj); + P_CopySectorPortalToLines(portal_num, target_sector_tag); } } } @@ -7448,7 +7491,7 @@ void P_SpawnSpecials(boolean fromnetsave) // Copy portals for (i = 0; i < numlines; i++) { - if (lines[i].special != 6) + if (lines[i].special != SPECIAL_SECTOR_SETPORTAL) continue; int portal_type = lines[i].args[1]; @@ -7470,7 +7513,6 @@ void P_SpawnSpecials(boolean fromnetsave) TAG_ITER_SECTORS(target_sector_tag, s1) P_DoPortalCopyFromLine(§ors[s1], plane_type, tag_to_copy); } - break; } if (!fromnetsave) diff --git a/src/p_spec.h b/src/p_spec.h index 87f9951a6..3bbaba58b 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -483,9 +483,8 @@ typedef enum TMSECPORTAL_SKYBOX = 2, TMSECPORTAL_PLANE = 3, TMSECPORTAL_HORIZON = 4, - // The two portal types below are unimplemented TMSECPORTAL_COPY_PORTAL_TO_LINE = 5, - TMSECPORTAL_INTERACTIVE = 6, + TMSECPORTAL_INTERACTIVE = 6, // unimplemented // The two portal types below are new to SRB2 TMSECPORTAL_SECTOR = 7, TMSECPORTAL_OBJECT = 8 @@ -543,6 +542,8 @@ boolean P_IsFlagAtBase(mobjtype_t flag); void P_InitSectorPortals(void); UINT32 P_NewSectorPortal(void); +boolean P_IsSectorPortalValid(sectorportal_t *secportal); + sectorportal_t *P_SectorGetFloorPortal(sector_t *sector); sectorportal_t *P_SectorGetCeilingPortal(sector_t *sector); diff --git a/src/r_bsp.c b/src/r_bsp.c index 91b4fdbc0..4bb0e290c 100644 --- a/src/r_bsp.c +++ b/src/r_bsp.c @@ -458,7 +458,7 @@ static void R_AddLine(seg_t *line) return; backsector = line->backsector; - horizonline = line->linedef->special == HORIZONSPECIAL; + horizonline = line->linedef->special == SPECIAL_HORIZON_LINE; bothceilingssky = bothfloorssky = false; // Portal line @@ -484,6 +484,15 @@ static void R_AddLine(seg_t *line) } } } + // Transferred portal + else if (line->linedef->secportal != UINT32_MAX && line->side == 0) + { + if (portalrender < cv_maxportals.value) + { + Portal_AddTransferred(line->linedef-lines, line->linedef->secportal, x1, x2); + goto clipsolid; + } + } // Single sided line? if (!backsector) diff --git a/src/r_defs.h b/src/r_defs.h index b6ea8d6e5..865776146 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -536,7 +536,9 @@ typedef enum ST_NEGATIVE } slopetype_t; -#define HORIZONSPECIAL 41 +#define SPECIAL_HORIZON_LINE 41 + +#define SPECIAL_SECTOR_SETPORTAL 6 #define NUMLINEARGS 10 #define NUMLINESTRINGARGS 2 @@ -577,6 +579,8 @@ typedef struct line_s polyobj_t *polyobj; // Belongs to a polyobject? INT16 callcount; // no. of calls left before triggering, for the "X calls" linedef specials, defaults to 0 + + UINT32 secportal; // transferred sector portal } line_t; typedef struct diff --git a/src/r_portal.c b/src/r_portal.c index 327821b9d..f66424ab6 100644 --- a/src/r_portal.c +++ b/src/r_portal.c @@ -263,30 +263,14 @@ static boolean TrimVisplaneBounds (const visplane_t* plane, INT16* start, INT16* return false; } -/** Creates a skybox portal out of a visplane. - * - * Applies the necessary offsets and rotation to give - * a depth illusion to the skybox. - */ -void Portal_AddSkybox (const visplane_t* plane) +static void Portal_GetViewpointForSkybox(portal_t *portal) { - INT16 start, end; - mapheader_t *mh; - portal_t* portal; - - if (TrimVisplaneBounds(plane, &start, &end)) - return; - - portal = Portal_Add(start, end); - - Portal_ClipVisplane(plane, portal); - portal->viewx = skyboxmo[0]->x; portal->viewy = skyboxmo[0]->y; portal->viewz = skyboxmo[0]->z; portal->viewangle = viewangle + skyboxmo[0]->angle; - mh = mapheaderinfo[gamemap-1]; + mapheader_t *mh = mapheaderinfo[gamemap-1]; // If a relative viewpoint exists, offset the viewpoint. if (skyboxmo[1]) @@ -313,31 +297,22 @@ void Portal_AddSkybox (const visplane_t* plane) portal->viewz += viewz / mh->skybox_scalez; else if (mh->skybox_scalez < 0) portal->viewz += viewz * -mh->skybox_scalez; - - portal->clipline = -1; - portal->is_horizon = false; - portal->horizon_sector = NULL; } -/** Creates a sector portal out of a visplane. +/** Creates a skybox portal out of a visplane. + * + * Applies the necessary offsets and rotation to give + * a depth illusion to the skybox. */ -void Portal_AddSectorPortal (const visplane_t* plane) +void Portal_AddSkybox (const visplane_t* plane) { INT16 start, end; - fixed_t x, y, z, angle; - sectorportal_t *secportal = plane->portalsector; - - // Shortcut - if (secportal->type == SECPORTAL_SKYBOX) - { - Portal_AddSkybox(plane); - return; - } + portal_t* portal; if (TrimVisplaneBounds(plane, &start, &end)) return; - portal_t* portal = Portal_Add(start, end); + portal = Portal_Add(start, end); Portal_ClipVisplane(plane, portal); @@ -345,6 +320,13 @@ void Portal_AddSectorPortal (const visplane_t* plane) portal->is_horizon = false; portal->horizon_sector = NULL; + Portal_GetViewpointForSkybox(portal); +} + +static void Portal_GetViewpointForSecPortal(portal_t *portal, sectorportal_t *secportal, fixed_t origin_x, fixed_t origin_y) +{ + fixed_t x, y, z, angle; + switch (secportal->type) { case SECPORTAL_LINE: @@ -374,8 +356,8 @@ void Portal_AddSectorPortal (const visplane_t* plane) case SECPORTAL_HORIZON: portal->is_horizon = true; portal->horizon_sector = secportal->sector; - x = plane->sector->soundorg.x; - y = plane->sector->soundorg.y; + x = secportal->sector->soundorg.x; + y = secportal->sector->soundorg.y; if (secportal->type == SECPORTAL_PLANE) z = -viewz; else @@ -386,8 +368,8 @@ void Portal_AddSectorPortal (const visplane_t* plane) return; } - fixed_t refx = plane->sector->soundorg.x - viewx; - fixed_t refy = plane->sector->soundorg.y - viewy; + fixed_t refx = origin_x - viewx; + fixed_t refy = origin_y - viewy; // Rotate the X/Y to match the target angle if (angle != 0) @@ -404,6 +386,70 @@ void Portal_AddSectorPortal (const visplane_t* plane) portal->viewangle = angle + viewangle; } +/** Creates a sector portal out of a visplane. + */ +void Portal_AddSectorPortal (const visplane_t* plane) +{ + INT16 start, end; + sectorportal_t *secportal = plane->portalsector; + + // Shortcut + if (secportal->type == SECPORTAL_SKYBOX) + { + Portal_AddSkybox(plane); + return; + } + + if (TrimVisplaneBounds(plane, &start, &end)) + return; + + portal_t* portal = Portal_Add(start, end); + + Portal_ClipVisplane(plane, portal); + + portal->clipline = -1; + portal->is_horizon = false; + portal->horizon_sector = NULL; + + Portal_GetViewpointForSecPortal(portal, secportal, plane->sector->soundorg.x, plane->sector->soundorg.y); +} + +/** Creates a transferred sector portal. + */ +void Portal_AddTransferred (const INT32 linenum, UINT32 secportalnum, const INT32 x1, const INT32 x2) +{ + if (secportalnum >= secportalcount) + return; + + sectorportal_t *secportal = &secportals[secportalnum]; + if (!P_IsSectorPortalValid(secportal)) + return; + + portal_t* portal = Portal_Add(x1, x2); + + portal->is_horizon = false; + portal->horizon_sector = NULL; + + if (secportal->type == SECPORTAL_SKYBOX) + Portal_GetViewpointForSkybox(portal); + else + { + line_t *line = &lines[linenum]; + fixed_t refx = (line->v1->x + line->v2->x) / 2; + fixed_t refy = (line->v1->y + line->v2->y) / 2; + Portal_GetViewpointForSecPortal(portal, secportal, refx, refy); + } + + if (secportal->type == SECPORTAL_LINE) + portal->clipline = secportal->line.dest - lines; + else + portal->clipline = -1; + + Portal_ClipRange(portal); + + portalline = true; +} + /** Creates portals for the currently existing sky visplanes. * The visplanes are also removed and cleared from the list. */ diff --git a/src/r_portal.h b/src/r_portal.h index 1a8291f3c..7e0f63fe1 100644 --- a/src/r_portal.h +++ b/src/r_portal.h @@ -58,6 +58,7 @@ void Portal_Remove (portal_t* portal); void Portal_Add2Lines (const INT32 line1, const INT32 line2, const INT32 x1, const INT32 x2); void Portal_AddSkybox (const visplane_t* plane); void Portal_AddSectorPortal (const visplane_t* plane); +void Portal_AddTransferred (const INT32 linenum, UINT32 secportalnum, const INT32 x1, const INT32 x2); void Portal_ClipRange (portal_t* portal); void Portal_ClipApply (const portal_t* portal); From 87d40fc3cc7459cce775e1afd73c355abf047312 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 25 Aug 2023 00:18:20 -0300 Subject: [PATCH 079/227] Use original portal's origin for the viewpoint displacement --- src/p_saveg.c | 4 ++++ src/p_spec.c | 35 ++++++++++++++++++++--------------- src/r_bsp.c | 2 +- src/r_defs.h | 3 +++ src/r_portal.c | 17 ++++++----------- src/r_portal.h | 2 +- 6 files changed, 35 insertions(+), 28 deletions(-) diff --git a/src/p_saveg.c b/src/p_saveg.c index 80b111946..bf6856506 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -4766,6 +4766,8 @@ static void P_NetArchiveSectorPortals(void) UINT8 type = secportals[i].type; WRITEUINT8(save_p, type); + WRITEFIXED(save_p, secportals[i].origin.x); + WRITEFIXED(save_p, secportals[i].origin.y); switch (type) { @@ -4808,6 +4810,8 @@ static void P_NetUnArchiveSectorPortals(void) sectorportal_t *secportal = &secportals[id]; secportal->type = READUINT8(save_p); + secportal->origin.x = READFIXED(save_p); + secportal->origin.y = READFIXED(save_p); switch (secportal->type) { diff --git a/src/p_spec.c b/src/p_spec.c index e1dd6bcec..ed745f1fb 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -6325,30 +6325,35 @@ static void P_DoPortalCopyFromLine(sector_t *dest_sector, int plane_type, int ta } } -static sectorportal_t *P_SectorGetFloorPortalOrCreate(sector_t *sector, UINT32 *result) +static sectorportal_t *P_SectorGetPortalOrCreate(sector_t *sector, UINT32 *num, UINT32 *result) { - *result = sector->portal_floor; + sectorportal_t *secportal = NULL; - if (*result >= secportalcount) + if (*num >= secportalcount) { - *result = P_NewSectorPortal(); - sector->portal_floor = *result; + *num = P_NewSectorPortal(); + secportal = &secportals[*num]; + secportal->origin.x = sector->soundorg.x; + secportal->origin.y = sector->soundorg.y; + *result = *num; + } + else + { + *result = *num; + secportal = &secportals[*num]; } - return &secportals[sector->portal_floor]; + return secportal; +} + +static sectorportal_t *P_SectorGetFloorPortalOrCreate(sector_t *sector, UINT32 *result) +{ + return P_SectorGetPortalOrCreate(sector, §or->portal_floor, result); } static sectorportal_t *P_SectorGetCeilingPortalOrCreate(sector_t *sector, UINT32 *result) { - *result = sector->portal_floor; - - if (*result >= secportalcount) - { - *result = P_NewSectorPortal(); - sector->portal_ceiling = *result; - } - - return &secportals[sector->portal_ceiling]; + return P_SectorGetPortalOrCreate(sector, §or->portal_ceiling, result); } static void P_CopySectorPortalToLines(UINT32 portal_num, int sector_tag) diff --git a/src/r_bsp.c b/src/r_bsp.c index 4bb0e290c..b845e0d14 100644 --- a/src/r_bsp.c +++ b/src/r_bsp.c @@ -489,7 +489,7 @@ static void R_AddLine(seg_t *line) { if (portalrender < cv_maxportals.value) { - Portal_AddTransferred(line->linedef-lines, line->linedef->secportal, x1, x2); + Portal_AddTransferred(line->linedef->secportal, x1, x2); goto clipsolid; } } diff --git a/src/r_defs.h b/src/r_defs.h index 865776146..d9bc72d1d 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -231,6 +231,9 @@ typedef struct sectorportal_s struct sector_s *sector; struct mobj_s *mobj; }; + struct { + fixed_t x, y; + } origin; } sectorportal_t; typedef struct ffloor_s diff --git a/src/r_portal.c b/src/r_portal.c index f66424ab6..bb40b2c80 100644 --- a/src/r_portal.c +++ b/src/r_portal.c @@ -323,7 +323,7 @@ void Portal_AddSkybox (const visplane_t* plane) Portal_GetViewpointForSkybox(portal); } -static void Portal_GetViewpointForSecPortal(portal_t *portal, sectorportal_t *secportal, fixed_t origin_x, fixed_t origin_y) +static void Portal_GetViewpointForSecPortal(portal_t *portal, sectorportal_t *secportal) { fixed_t x, y, z, angle; @@ -368,8 +368,8 @@ static void Portal_GetViewpointForSecPortal(portal_t *portal, sectorportal_t *se return; } - fixed_t refx = origin_x - viewx; - fixed_t refy = origin_y - viewy; + fixed_t refx = secportal->origin.x - viewx; + fixed_t refy = secportal->origin.y - viewy; // Rotate the X/Y to match the target angle if (angle != 0) @@ -411,12 +411,12 @@ void Portal_AddSectorPortal (const visplane_t* plane) portal->is_horizon = false; portal->horizon_sector = NULL; - Portal_GetViewpointForSecPortal(portal, secportal, plane->sector->soundorg.x, plane->sector->soundorg.y); + Portal_GetViewpointForSecPortal(portal, secportal); } /** Creates a transferred sector portal. */ -void Portal_AddTransferred (const INT32 linenum, UINT32 secportalnum, const INT32 x1, const INT32 x2) +void Portal_AddTransferred (UINT32 secportalnum, const INT32 x1, const INT32 x2) { if (secportalnum >= secportalcount) return; @@ -433,12 +433,7 @@ void Portal_AddTransferred (const INT32 linenum, UINT32 secportalnum, const INT3 if (secportal->type == SECPORTAL_SKYBOX) Portal_GetViewpointForSkybox(portal); else - { - line_t *line = &lines[linenum]; - fixed_t refx = (line->v1->x + line->v2->x) / 2; - fixed_t refy = (line->v1->y + line->v2->y) / 2; - Portal_GetViewpointForSecPortal(portal, secportal, refx, refy); - } + Portal_GetViewpointForSecPortal(portal, secportal); if (secportal->type == SECPORTAL_LINE) portal->clipline = secportal->line.dest - lines; diff --git a/src/r_portal.h b/src/r_portal.h index 7e0f63fe1..69ecb8d7f 100644 --- a/src/r_portal.h +++ b/src/r_portal.h @@ -58,7 +58,7 @@ void Portal_Remove (portal_t* portal); void Portal_Add2Lines (const INT32 line1, const INT32 line2, const INT32 x1, const INT32 x2); void Portal_AddSkybox (const visplane_t* plane); void Portal_AddSectorPortal (const visplane_t* plane); -void Portal_AddTransferred (const INT32 linenum, UINT32 secportalnum, const INT32 x1, const INT32 x2); +void Portal_AddTransferred (UINT32 secportalnum, const INT32 x1, const INT32 x2); void Portal_ClipRange (portal_t* portal); void Portal_ClipApply (const portal_t* portal); From cf1f3103636b5221924c625145c2ddeb02833c27 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 25 Aug 2023 00:47:40 -0300 Subject: [PATCH 080/227] Add sector portals from visplanes while rendering a portal --- src/r_main.c | 7 +++++-- src/r_portal.c | 17 ++++++++++++----- src/r_portal.h | 4 +++- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/r_main.c b/src/r_main.c index c73039c2a..c999b0f5e 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -1513,8 +1513,8 @@ void R_RenderPlayerView(player_t *player) R_ClipSprites(drawsegs, NULL); PS_STOP_TIMING(ps_sw_spritecliptime); - // Add skybox portals caused by sky visplanes. - Portal_AddSkyboxPortals(); + // Add portals caused by visplanes. + Portal_AddPlanePortals(cv_skybox.value); // Portal rendering. Hijacks the BSP traversal. PS_START_TIMING(ps_sw_portaltime); @@ -1559,6 +1559,9 @@ void R_RenderPlayerView(player_t *player) R_RenderBSPNode((INT32)numnodes - 1); } + // Don't add skybox portals while IN a skybox portal, because that'll cause infinite recursion + Portal_AddPlanePortals(!portal->is_skybox); + Mask_Post(&masks[nummasks - 1]); R_ClipSprites(ds_p - (masks[nummasks - 1].drawsegs[1] - masks[nummasks - 1].drawsegs[0]), portal); diff --git a/src/r_portal.c b/src/r_portal.c index bb40b2c80..ffa5d1a0d 100644 --- a/src/r_portal.c +++ b/src/r_portal.c @@ -192,6 +192,7 @@ void Portal_Add2Lines (const INT32 line1, const INT32 line2, const INT32 x1, con Portal_GetViewpointForLine(portal, start, dest); portal->clipline = line2; + portal->is_skybox = false; portal->is_horizon = false; portal->horizon_sector = NULL; @@ -317,6 +318,7 @@ void Portal_AddSkybox (const visplane_t* plane) Portal_ClipVisplane(plane, portal); portal->clipline = -1; + portal->is_skybox = true; portal->is_horizon = false; portal->horizon_sector = NULL; @@ -396,7 +398,8 @@ void Portal_AddSectorPortal (const visplane_t* plane) // Shortcut if (secportal->type == SECPORTAL_SKYBOX) { - Portal_AddSkybox(plane); + if (skyboxmo[0]) + Portal_AddSkybox(plane); return; } @@ -409,6 +412,7 @@ void Portal_AddSectorPortal (const visplane_t* plane) portal->clipline = -1; portal->is_horizon = false; + portal->is_skybox = false; portal->horizon_sector = NULL; Portal_GetViewpointForSecPortal(portal, secportal); @@ -426,7 +430,7 @@ void Portal_AddTransferred (UINT32 secportalnum, const INT32 x1, const INT32 x2) return; portal_t* portal = Portal_Add(x1, x2); - + portal->is_skybox = false; portal->is_horizon = false; portal->horizon_sector = NULL; @@ -445,10 +449,10 @@ void Portal_AddTransferred (UINT32 secportalnum, const INT32 x1, const INT32 x2) portalline = true; } -/** Creates portals for the currently existing sky visplanes. +/** Creates portals for the currently existing portal visplanes. * The visplanes are also removed and cleared from the list. */ -void Portal_AddSkyboxPortals (void) +void Portal_AddPlanePortals (boolean add_skyboxes) { visplane_t *pl; @@ -456,6 +460,9 @@ void Portal_AddSkyboxPortals (void) { for (pl = visplanes[i]; pl; pl = pl->next) { + if (pl->minx >= pl->maxx) + continue; + boolean added_portal = false; // Render sector portal if recursiveness limit hasn't been reached @@ -466,7 +473,7 @@ void Portal_AddSkyboxPortals (void) } // Render skybox portal - if (!added_portal && pl->picnum == skyflatnum && cv_skybox.value && skyboxmo[0]) + if (!added_portal && pl->picnum == skyflatnum && add_skyboxes && skyboxmo[0]) { Portal_AddSkybox(pl); added_portal = true; diff --git a/src/r_portal.h b/src/r_portal.h index 69ecb8d7f..b3c4b0edc 100644 --- a/src/r_portal.h +++ b/src/r_portal.h @@ -34,6 +34,8 @@ typedef struct portal_s boolean is_horizon; sector_t *horizon_sector; + boolean is_skybox; + UINT8 pass; /**< Keeps track of the portal's recursion depth. */ INT32 clipline; /**< Optional clipline for line-based portals. */ @@ -63,5 +65,5 @@ void Portal_AddTransferred (UINT32 secportalnum, const INT32 x1, const INT32 x2) void Portal_ClipRange (portal_t* portal); void Portal_ClipApply (const portal_t* portal); -void Portal_AddSkyboxPortals (void); +void Portal_AddPlanePortals (boolean add_skyboxes); #endif From d7bc644dfeb3dd42465d7d8badca8ed033136701 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 25 Aug 2023 00:57:10 -0300 Subject: [PATCH 081/227] Check for cv_skybox.value if in a portal too --- src/r_main.c | 2 +- src/r_portal.c | 28 +++++++++++++--------------- src/r_portal.h | 2 -- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/r_main.c b/src/r_main.c index c999b0f5e..3d0eafe37 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -1560,7 +1560,7 @@ void R_RenderPlayerView(player_t *player) } // Don't add skybox portals while IN a skybox portal, because that'll cause infinite recursion - Portal_AddPlanePortals(!portal->is_skybox); + Portal_AddPlanePortals(cv_skybox.value && !portal->is_skybox); Mask_Post(&masks[nummasks - 1]); diff --git a/src/r_portal.c b/src/r_portal.c index ffa5d1a0d..3e7954ca6 100644 --- a/src/r_portal.c +++ b/src/r_portal.c @@ -305,13 +305,13 @@ static void Portal_GetViewpointForSkybox(portal_t *portal) * Applies the necessary offsets and rotation to give * a depth illusion to the skybox. */ -void Portal_AddSkybox (const visplane_t* plane) +static boolean Portal_AddSkybox (const visplane_t* plane) { INT16 start, end; portal_t* portal; if (TrimVisplaneBounds(plane, &start, &end)) - return; + return false; portal = Portal_Add(start, end); @@ -323,6 +323,8 @@ void Portal_AddSkybox (const visplane_t* plane) portal->horizon_sector = NULL; Portal_GetViewpointForSkybox(portal); + + return true; } static void Portal_GetViewpointForSecPortal(portal_t *portal, sectorportal_t *secportal) @@ -390,7 +392,7 @@ static void Portal_GetViewpointForSecPortal(portal_t *portal, sectorportal_t *se /** Creates a sector portal out of a visplane. */ -void Portal_AddSectorPortal (const visplane_t* plane) +static boolean Portal_AddSectorPortal (const visplane_t* plane) { INT16 start, end; sectorportal_t *secportal = plane->portalsector; @@ -398,13 +400,13 @@ void Portal_AddSectorPortal (const visplane_t* plane) // Shortcut if (secportal->type == SECPORTAL_SKYBOX) { - if (skyboxmo[0]) - Portal_AddSkybox(plane); - return; + if (cv_skybox.value && skyboxmo[0]) + return Portal_AddSkybox(plane); + return false; } if (TrimVisplaneBounds(plane, &start, &end)) - return; + return false; portal_t* portal = Portal_Add(start, end); @@ -416,6 +418,8 @@ void Portal_AddSectorPortal (const visplane_t* plane) portal->horizon_sector = NULL; Portal_GetViewpointForSecPortal(portal, secportal); + + return true; } /** Creates a transferred sector portal. @@ -467,17 +471,11 @@ void Portal_AddPlanePortals (boolean add_skyboxes) // Render sector portal if recursiveness limit hasn't been reached if (pl->portalsector && portalrender < cv_maxportals.value) - { - Portal_AddSectorPortal(pl); - added_portal = true; - } + added_portal = Portal_AddSectorPortal(pl); // Render skybox portal if (!added_portal && pl->picnum == skyflatnum && add_skyboxes && skyboxmo[0]) - { - Portal_AddSkybox(pl); - added_portal = true; - } + added_portal = Portal_AddSkybox(pl); // don't render this visplane anymore if (added_portal) diff --git a/src/r_portal.h b/src/r_portal.h index b3c4b0edc..2485e45a7 100644 --- a/src/r_portal.h +++ b/src/r_portal.h @@ -58,8 +58,6 @@ extern INT32 portalclipstart, portalclipend; void Portal_InitList (void); void Portal_Remove (portal_t* portal); void Portal_Add2Lines (const INT32 line1, const INT32 line2, const INT32 x1, const INT32 x2); -void Portal_AddSkybox (const visplane_t* plane); -void Portal_AddSectorPortal (const visplane_t* plane); void Portal_AddTransferred (UINT32 secportalnum, const INT32 x1, const INT32 x2); void Portal_ClipRange (portal_t* portal); From 3505c90d5c68bea63bac4c3cd6bb3836c98a49b8 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 25 Aug 2023 04:28:46 -0300 Subject: [PATCH 082/227] Clarify comment --- src/r_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/r_main.c b/src/r_main.c index 3d0eafe37..a84d1cc40 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -1559,7 +1559,7 @@ void R_RenderPlayerView(player_t *player) R_RenderBSPNode((INT32)numnodes - 1); } - // Don't add skybox portals while IN a skybox portal, because that'll cause infinite recursion + // Don't add skybox portals while already rendering a skybox view, because that'll cause an infinite loop Portal_AddPlanePortals(cv_skybox.value && !portal->is_skybox); Mask_Post(&masks[nummasks - 1]); From 5996c8a5ac1c81ed5f5aab3437052ed8ac5d1394 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 25 Aug 2023 05:02:23 -0300 Subject: [PATCH 083/227] Fix P_GetMobjByTag so that it iterates through mapthings --- src/p_spec.c | 15 +++++---------- src/r_portal.c | 3 ++- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/p_spec.c b/src/p_spec.c index ed745f1fb..0711a155f 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -6295,17 +6295,12 @@ boolean P_CompareSectorPortals(sectorportal_t *a, sectorportal_t *b) static mobj_t *P_GetMobjByTag(INT32 tag) { - for (thinker_t *th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) + INT32 mtnum = -1; + + TAG_ITER_THINGS(tag, mtnum) { - if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) - continue; - - mobj_t *mo = (mobj_t *)th; - - if (mo->spawnpoint == NULL) - continue; - - if (Tag_Find(&mo->spawnpoint->tags, tag)) + mobj_t *mo = mapthings[mtnum].mobj; + if (mo) return mo; } diff --git a/src/r_portal.c b/src/r_portal.c index 3e7954ca6..4d042cae3 100644 --- a/src/r_portal.c +++ b/src/r_portal.c @@ -329,7 +329,8 @@ static boolean Portal_AddSkybox (const visplane_t* plane) static void Portal_GetViewpointForSecPortal(portal_t *portal, sectorportal_t *secportal) { - fixed_t x, y, z, angle; + fixed_t x, y, z; + angle_t angle; switch (secportal->type) { From 2c86f266d307246602d0471616b891a063b4b0bc Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 25 Aug 2023 05:30:12 -0300 Subject: [PATCH 084/227] Update editor definition --- extras/conf/udb/Includes/SRB222_linedefs.cfg | 21 +++++++++++++++----- extras/conf/udb/Includes/SRB222_misc.cfg | 12 ----------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/extras/conf/udb/Includes/SRB222_linedefs.cfg b/extras/conf/udb/Includes/SRB222_linedefs.cfg index 9f3e41d39..dcc5cf335 100644 --- a/extras/conf/udb/Includes/SRB222_linedefs.cfg +++ b/extras/conf/udb/Includes/SRB222_linedefs.cfg @@ -12,7 +12,7 @@ udmf 6 { - title = "Sector Portal"; + title = "Sector Set Portal"; prefix = "(6)"; arg0 { @@ -21,9 +21,20 @@ udmf } arg1 { - title = "Type"; + title = "Portal type"; type = 11; - enum = "sectorportal"; + enum + { + 0 = "Link to portal with same tag"; + 1 = "Copy portal from second tag"; + 2 = "Skybox portal"; + 3 = "Plane portal"; + 4 = "Horizon portal"; + 5 = "Copy portal to line"; + 6 = "Interactive portal (unimplemented)"; + 7 = "Link to sector with same tag"; + 8 = "Link to object with same tag"; + } } arg2 { @@ -33,8 +44,8 @@ udmf } arg3 { - title = "Miscellaneous"; - type = 0; + title = "Misc"; + tooltip = "For type 0 portal: specifies whether the line belongs to the sector viewed\nthrough the portal (1) or the sector in which the portal is seen (0).\nFor type 1 portal: specifies the sector tag of the portal to copy.\nFor type 7 portal: specifies the sector tag to make a portal to.\nFor type 8 portal: specifies the object tag to make a portal to."; } } diff --git a/extras/conf/udb/Includes/SRB222_misc.cfg b/extras/conf/udb/Includes/SRB222_misc.cfg index 2fb863921..e274fece6 100644 --- a/extras/conf/udb/Includes/SRB222_misc.cfg +++ b/extras/conf/udb/Includes/SRB222_misc.cfg @@ -453,18 +453,6 @@ enums 3 = "Reverse subtract"; 4 = "Modulate"; } - - sectorportal - { - 0 = "Normal"; - 1 = "Copied"; - 2 = "Skybox"; - 3 = "Plane"; - 4 = "Horizon"; - 5 = "Transfer to line"; - 7 = "Sector"; - 8 = "Object"; - } } //Default things filters From d6e13fe27a1359096b54f6f108ecf19a58e0eb7d Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 25 Aug 2023 05:31:08 -0300 Subject: [PATCH 085/227] "same tag" -> "second tag" --- extras/conf/udb/Includes/SRB222_linedefs.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extras/conf/udb/Includes/SRB222_linedefs.cfg b/extras/conf/udb/Includes/SRB222_linedefs.cfg index dcc5cf335..152cafe21 100644 --- a/extras/conf/udb/Includes/SRB222_linedefs.cfg +++ b/extras/conf/udb/Includes/SRB222_linedefs.cfg @@ -32,8 +32,8 @@ udmf 4 = "Horizon portal"; 5 = "Copy portal to line"; 6 = "Interactive portal (unimplemented)"; - 7 = "Link to sector with same tag"; - 8 = "Link to object with same tag"; + 7 = "Link to sector with second tag"; + 8 = "Link to object with second tag"; } } arg2 From e0ea677ca42c43715332c9a12b8f55e3e89f2900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Wed, 30 Aug 2023 19:32:13 +0200 Subject: [PATCH 086/227] Make chat spam protection more configurable --- src/d_netcmd.c | 5 ++++- src/g_game.c | 4 +++- src/g_game.h | 2 +- src/hu_stuff.c | 36 +++++++++++++++++++++++++----------- 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 33281e992..4d9779a97 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -619,6 +619,10 @@ void D_RegisterServerCommands(void) CV_RegisterVar(&cv_addons_folder); CV_RegisterVar(&cv_dummyconsvar); + + CV_RegisterVar(&cv_chatspamprotection); + CV_RegisterVar(&cv_chatspamspeed); + CV_RegisterVar(&cv_chatspamburst); } // ========================================================================= @@ -762,7 +766,6 @@ void D_RegisterClientCommands(void) CV_RegisterVar(&cv_chatheight); CV_RegisterVar(&cv_chatwidth); CV_RegisterVar(&cv_chattime); - CV_RegisterVar(&cv_chatspamprotection); CV_RegisterVar(&cv_chatbacktint); CV_RegisterVar(&cv_consolechat); CV_RegisterVar(&cv_chatnotifications); diff --git a/src/g_game.c b/src/g_game.c index 9780bcd4d..4dc84b328 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -304,7 +304,9 @@ consvar_t cv_chatheight= CVAR_INIT ("chatheight", "8", CV_SAVE, chatheight_cons_ consvar_t cv_chatnotifications= CVAR_INIT ("chatnotifications", "On", CV_SAVE, CV_OnOff, NULL); // chat spam protection (why would you want to disable that???) -consvar_t cv_chatspamprotection= CVAR_INIT ("chatspamprotection", "On", CV_SAVE, CV_OnOff, NULL); +consvar_t cv_chatspamprotection= CVAR_INIT ("chatspamprotection", "On", CV_SAVE|CV_NETVAR, CV_OnOff, NULL); +consvar_t cv_chatspamspeed= CVAR_INIT ("chatspamspeed", "35", CV_SAVE|CV_NETVAR, CV_Unsigned, NULL); +consvar_t cv_chatspamburst= CVAR_INIT ("chatspamburst", "3", CV_SAVE|CV_NETVAR, CV_Unsigned, NULL); // minichat text background consvar_t cv_chatbacktint = CVAR_INIT ("chatbacktint", "On", CV_SAVE, CV_OnOff, NULL); diff --git a/src/g_game.h b/src/g_game.h index a8c285f79..cdaf7d2f5 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -51,7 +51,7 @@ extern consvar_t cv_pauseifunfocused; // used in game menu extern consvar_t cv_tutorialprompt; -extern consvar_t cv_chatwidth, cv_chatnotifications, cv_chatheight, cv_chattime, cv_consolechat, cv_chatbacktint, cv_chatspamprotection, cv_compactscoreboard; +extern consvar_t cv_chatwidth, cv_chatnotifications, cv_chatheight, cv_chattime, cv_consolechat, cv_chatbacktint, cv_chatspamprotection, cv_chatspamspeed, cv_chatspamburst, cv_compactscoreboard; extern consvar_t cv_crosshair, cv_crosshair2; extern consvar_t cv_invertmouse, cv_alwaysfreelook, cv_chasefreelook, cv_mousemove; extern consvar_t cv_invertmouse2, cv_alwaysfreelook2, cv_chasefreelook2, cv_mousemove2; diff --git a/src/hu_stuff.c b/src/hu_stuff.c index 091e2b2fb..11c2a9e3d 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -630,7 +630,9 @@ static void Command_CSay_f(void) DoSayCommand(0, 1, HU_CSAY); } -static tic_t stop_spamming[MAXPLAYERS]; + +static tic_t spam_tokens[MAXPLAYERS]; +static tic_t spam_tics[MAXPLAYERS]; /** Receives a message, processing an ::XD_SAY command. * \sa DoSayCommand @@ -682,14 +684,14 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) // before we do anything, let's verify the guy isn't spamming, get this easier on us. //if (stop_spamming[playernum] != 0 && cv_chatspamprotection.value && !(flags & HU_CSAY)) - if (stop_spamming[playernum] != 0 && consoleplayer != playernum && cv_chatspamprotection.value && !(flags & HU_CSAY)) + if (spam_tokens[playernum] <= 0 && cv_chatspamprotection.value && !(flags & HU_CSAY)) { CONS_Debug(DBG_NETPLAY,"Received SAY cmd too quickly from Player %d (%s), assuming as spam and blocking message.\n", playernum+1, player_names[playernum]); - stop_spamming[playernum] = 4; + spam_tics[playernum] = 0; spam_eatmsg = 1; } else - stop_spamming[playernum] = 4; // you can hold off for 4 tics, can you? + spam_tokens[playernum] -= 1; // run the lua hook even if we were supposed to eat the msg, netgame consistency goes first. @@ -871,6 +873,25 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) // void HU_Ticker(void) { + // do this server-side, too + if (netgame) + { + size_t i = 0; + + // handle spam while we're at it: + for(; (i= (tic_t)cv_chatspamspeed.value) + { + spam_tokens[i]++; + spam_tics[i] = 0; + } + } + } + } + if (dedicated) return; @@ -894,13 +915,6 @@ void HU_Ticker(void) { size_t i = 0; - // handle spam while we're at it: - for(; (i 0) - stop_spamming[i]--; - } - // handle chat timers for (i=0; (i Date: Mon, 29 May 2023 16:37:48 +0200 Subject: [PATCH 087/227] Copy interpolation to shields and overlays Also handle overlays in splitscreen properly --- src/hardware/hw_main.c | 4 +++ src/hardware/hw_md2.c | 2 ++ src/p_mobj.c | 70 ++++++++++++++++++++++++------------------ src/r_things.c | 48 +++++++++++++++++++++++++++++ src/r_things.h | 2 ++ 5 files changed, 96 insertions(+), 30 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index bc66955fc..9b5c58abb 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -5120,6 +5120,8 @@ static void HWR_ProjectSprite(mobj_t *thing) spriteyscale = FIXED_TO_FLOAT(interp.spriteyscale); // transform the origin point + if (thing->type == MT_OVERLAY) // Handle overlays + R_ThingOffsetOverlay(thing, &interp.x, &interp.y); tr_x = FIXED_TO_FLOAT(interp.x) - gl_viewx; tr_y = FIXED_TO_FLOAT(interp.y) - gl_viewy; @@ -5439,6 +5441,8 @@ static void HWR_ProjectSprite(mobj_t *thing) // calculate tz for tracer, same way it is calculated for this sprite // transform the origin point + if (thing->tracer->type == MT_OVERLAY) // Handle overlays + R_ThingOffsetOverlay(thing->tracer, &tracer_interp.x, &tracer_interp.y); tr_x = FIXED_TO_FLOAT(tracer_interp.x) - gl_viewx; tr_y = FIXED_TO_FLOAT(tracer_interp.y) - gl_viewy; diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c index 87881be8d..5e6eb5d11 100644 --- a/src/hardware/hw_md2.c +++ b/src/hardware/hw_md2.c @@ -1580,6 +1580,8 @@ boolean HWR_DrawModel(gl_vissprite_t *spr) #undef INTERPOLERATION_LIMIT #endif + if (spr->mobj->type == MT_OVERLAY) // Handle overlays + R_ThingOffsetOverlay(spr->mobj, &interp.x, &interp.y); //Hurdler: it seems there is still a small problem with mobj angle p.x = FIXED_TO_FLOAT(interp.x); p.y = FIXED_TO_FLOAT(interp.y)+md2->offset; diff --git a/src/p_mobj.c b/src/p_mobj.c index 686f08478..d2d0d8d72 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6764,15 +6764,33 @@ static boolean P_ShieldLook(mobj_t *thing, shieldtype_t shield) P_SetScale(thing, FixedMul(thing->target->scale, thing->target->player->shieldscale)); thing->destscale = thing->scale; + thing->old_scale = FixedMul(thing->target->old_scale, thing->target->player->shieldscale); + +#define NewMH(mobj) mobj->height // Ugly mobj-height and player-height defines, for the sake of prettier code +#define NewPH(player) P_GetPlayerHeight(player) +#define OldMH(mobj) FixedMul(mobj->height, FixedDiv(mobj->old_scale, mobj->scale)) +#define OldPH(player) FixedMul(player->height, player->mo->old_scale) P_UnsetThingPosition(thing); thing->x = thing->target->x; thing->y = thing->target->y; + thing->old_x = thing->target->old_x; + thing->old_y = thing->target->old_y; if (thing->eflags & MFE_VERTICALFLIP) - thing->z = thing->target->z + (thing->target->height - thing->height + FixedDiv(P_GetPlayerHeight(thing->target->player) - thing->target->height, 3*FRACUNIT)) - FixedMul(2*FRACUNIT, thing->target->scale); + { + thing->z = thing->target->z + NewMH(thing->target) - NewMH(thing) + ((NewPH(thing->target->player) - NewMH(thing->target)) / 3) - (thing->target->scale * 2); + thing->old_z = thing->target->old_z + OldMH(thing->target) - OldMH(thing) + ((OldPH(thing->target->player) - OldMH(thing->target)) / 3) - (thing->target->old_scale * 2); + } else - thing->z = thing->target->z - (FixedDiv(P_GetPlayerHeight(thing->target->player) - thing->target->height, 3*FRACUNIT)) + FixedMul(2*FRACUNIT, thing->target->scale); + { + thing->z = thing->target->z - ((NewPH(thing->target->player) - NewMH(thing->target)) / 3) + (thing->target->scale * 2); + thing->old_z = thing->target->old_z - ((OldPH(thing->target->player) - OldMH(thing->target)) / 3) + (thing->target->old_scale * 2); + } P_SetThingPosition(thing); P_CheckPosition(thing, thing->x, thing->y); +#undef NewMH +#undef NewPH +#undef OldMH +#undef OldPH if (P_MobjWasRemoved(thing)) return false; @@ -6838,10 +6856,11 @@ void P_RunOverlays(void) { // run overlays mobj_t *mo, *next = NULL; - fixed_t destx,desty,zoffs; for (mo = overlaycap; mo; mo = next) { + fixed_t zoffs; + I_Assert(!P_MobjWasRemoved(mo)); // grab next in chain, then unset the chain target @@ -6857,31 +6876,11 @@ void P_RunOverlays(void) continue; } - if (!splitscreen /*&& rendermode != render_soft*/) - { - angle_t viewingangle; - - if (players[displayplayer].awayviewtics && players[displayplayer].awayviewmobj != NULL && !P_MobjWasRemoved(players[displayplayer].awayviewmobj)) - viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, players[displayplayer].awayviewmobj->x, players[displayplayer].awayviewmobj->y); - else if (!camera.chase && players[displayplayer].mo) - viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, players[displayplayer].mo->x, players[displayplayer].mo->y); - else - viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, camera.x, camera.y); - - if (!(mo->state->frame & FF_ANIMATE) && mo->state->var1) - viewingangle += ANGLE_180; - destx = mo->target->x + P_ReturnThrustX(mo->target, viewingangle, FixedMul(FRACUNIT/4, mo->scale)); - desty = mo->target->y + P_ReturnThrustY(mo->target, viewingangle, FixedMul(FRACUNIT/4, mo->scale)); - } - else - { - destx = mo->target->x; - desty = mo->target->y; - } - mo->eflags = (mo->eflags & ~MFE_VERTICALFLIP) | (mo->target->eflags & MFE_VERTICALFLIP); mo->scale = mo->destscale = mo->target->scale; + mo->old_scale = mo->target->old_scale; mo->angle = (mo->target->player ? mo->target->player->drawangle : mo->target->angle) + mo->movedir; + mo->old_angle = (mo->target->player ? mo->target->player->old_drawangle : mo->target->old_angle) + mo->movedir; if (!(mo->state->frame & FF_ANIMATE)) zoffs = FixedMul(((signed)mo->state->var2)*FRACUNIT, mo->scale); @@ -6891,15 +6890,26 @@ void P_RunOverlays(void) zoffs = 0; P_UnsetThingPosition(mo); - mo->x = destx; - mo->y = desty; + mo->x = mo->target->x; + mo->y = mo->target->y; + mo->old_x = mo->target->old_x; + mo->old_y = mo->target->old_y; mo->radius = mo->target->radius; mo->height = mo->target->height; if (mo->eflags & MFE_VERTICALFLIP) - mo->z = (mo->target->z + mo->target->height - mo->height) - zoffs; + { + mo->z = mo->target->z + mo->target->height - mo->height - zoffs; + if (mo->scale == mo->old_scale) + mo->old_z = mo->target->old_z + mo->target->height - mo->height - zoffs; + else // Interpolate height scale changes - mo and mo->target have the same scales here, so don't interpolate them individually + mo->old_z = mo->target->old_z + FixedMul(mo->target->height - mo->height, FixedDiv(mo->old_scale, mo->scale)) - zoffs; + } else - mo->z = mo->target->z + zoffs; - if (mo->state->var1) + { + mo->z = mo->target->z + zoffs; + mo->old_z = mo->target->old_z + zoffs; + } + if (!(mo->state->frame & FF_ANIMATE) && mo->state->var1) P_SetUnderlayPosition(mo); else P_SetThingPosition(mo); diff --git a/src/r_things.c b/src/r_things.c index 6e334990b..c1c2de875 100644 --- a/src/r_things.c +++ b/src/r_things.c @@ -1649,6 +1649,8 @@ static void R_ProjectSprite(mobj_t *thing) this_scale = interp.scale; // transform the origin point + if (thing->type == MT_OVERLAY) // Handle overlays + R_ThingOffsetOverlay(thing, &interp.x, &interp.y); tr_x = interp.x - viewx; tr_y = interp.y - viewy; @@ -1987,6 +1989,8 @@ static void R_ProjectSprite(mobj_t *thing) R_InterpolateMobjState(thing, FRACUNIT, &tracer_interp); } + if (thing->type == MT_OVERLAY) // Handle overlays + R_ThingOffsetOverlay(thing, &tracer_interp.x, &tracer_interp.y); tr_x = (tracer_interp.x + sort_x) - viewx; tr_y = (tracer_interp.y + sort_y) - viewy; tz = FixedMul(tr_x, viewcos) + FixedMul(tr_y, viewsin); @@ -3521,6 +3525,50 @@ boolean R_ThingIsFullDark(mobj_t *thing) return ((thing->frame & FF_BRIGHTMASK) == FF_FULLDARK || (thing->renderflags & RF_BRIGHTMASK) == RF_FULLDARK); } +// Offsets MT_OVERLAY towards the camera at render-time - Works in splitscreen! +// The &x and &y arguments should be pre-interpolated, and will be modified +void R_ThingOffsetOverlay(mobj_t *thing, fixed_t *x, fixed_t *y) +{ + mobj_t *mobj = thing; + INT16 offset = 0; // Offset towards or away from the camera, and how much + fixed_t offsetscale = thing->scale; // Occasionally needs to be interpolated + angle_t viewingangle; + UINT8 looplimit = 255; // Prevent infinite loops - A chain of 255 connected overlays is enough for any sane use case + +#ifdef PARANOIA + if (P_MobjWasRemoved(mobj) || !x || !y) + I_Error("R_ThingOffsetOverlay: thing, x, or y is invalid"); +#endif + + do // Get the overlay's offset + { + // Does the overlay use FF_ANIMATE? If not, if var1 is non-zero, it's an underlay instead of an overlay + if (!(mobj->state->frame & FF_ANIMATE) && mobj->state->var1) + offset += 1; // Underlay below the target, away from the camera + else + offset -= 1; // Overlay on top of the target, towards the camera + + looplimit -= 1; + mobj = mobj->target; + } while (!P_MobjWasRemoved(mobj) && mobj->type == MT_OVERLAY && looplimit > 0); // Handle overlays following other overlays + + // Does the offset scale need to be interpolated? + if (thing->scale != thing->old_scale && R_UsingFrameInterpolation() && !paused) + { + interpmobjstate_t interp = {0}; + R_InterpolateMobjState(thing, rendertimefrac, &interp); + offsetscale = interp.scale; + } + + + // Get the angle from the camera to the X and Y coordinates + viewingangle = R_PointToAngle(*x, *y); + + // Finally, offset the X and Y coordinates towards or away from the camera + *x += P_ReturnThrustX(thing, viewingangle, FixedMul(offset * (FRACUNIT/4), offsetscale)); + *y += P_ReturnThrustY(thing, viewingangle, FixedMul(offset * (FRACUNIT/4), offsetscale)); +} + // // R_DrawMasked // diff --git a/src/r_things.h b/src/r_things.h index e11005363..9988d81bc 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -88,6 +88,8 @@ boolean R_ThingIsFullBright (mobj_t *thing); boolean R_ThingIsSemiBright (mobj_t *thing); boolean R_ThingIsFullDark (mobj_t *thing); +void R_ThingOffsetOverlay (mobj_t *thing, fixed_t *outx, fixed_t *outy); + // -------------- // MASKED DRAWING // -------------- From a977fd607559ccb73a28b59bae5991215c2f8744 Mon Sep 17 00:00:00 2001 From: SMS Alfredo <65426124+SMS-Alfredo@users.noreply.github.com> Date: Sun, 17 Sep 2023 18:45:57 -0500 Subject: [PATCH 088/227] Expose P_LineIsBlocking --- src/lua_baselib.c | 40 ++++++++++++++++++++++++++++++++++++++++ src/p_local.h | 1 + src/p_map.c | 29 +++++++++++++++++------------ 3 files changed, 58 insertions(+), 12 deletions(-) diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 4af5066ae..741f44bb6 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -1840,6 +1840,45 @@ static int lib_pMoveOrigin(lua_State *L) return 2; } +static int lib_pLineIsBlocking(lua_State *L) +{ + mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ)); + line_t *line = *((line_t **)luaL_checkudata(L, 2, META_LINE)); + NOHUD + INLEVEL + if (!mo) + return LUA_ErrInvalid(L, "mobj_t"); + if (!line) + return LUA_ErrInvalid(L, "line_t"); + + // P_LineOpening in P_LineIsBlocking sets these variables. + // We want to keep their old values after so that whatever + // map collision code uses them doesn't get messed up. + fixed_t oldopentop = opentop; + fixed_t oldopenbottom = openbottom; + fixed_t oldopenrange = openrange; + fixed_t oldlowfloor = lowfloor; + fixed_t oldhighceiling = highceiling; + pslope_t *oldopentopslope = opentopslope; + pslope_t *oldopenbottomslope = openbottomslope; + ffloor_t *oldopenfloorrover = openfloorrover; + ffloor_t *oldopenceilingrover = openceilingrover; + + lua_pushboolean(L, P_LineIsBlocking(mo, line)); + + opentop = oldopentop; + openbottom = oldopenbottom; + openrange = oldopenrange; + lowfloor = oldlowfloor; + highceiling = oldhighceiling; + opentopslope = oldopentopslope; + openbottomslope = oldopenbottomslope; + openfloorrover = oldopenfloorrover; + openceilingrover = oldopenceilingrover; + + return 1; +} + static int lib_pSlideMove(lua_State *L) { mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ)); @@ -4147,6 +4186,7 @@ static luaL_Reg lib[] = { {"P_TeleportMove",lib_pTeleportMove}, {"P_SetOrigin",lib_pSetOrigin}, {"P_MoveOrigin",lib_pMoveOrigin}, + {"P_LineIsBlocking",lib_pLineIsBlocking}, {"P_SlideMove",lib_pSlideMove}, {"P_BounceMove",lib_pBounceMove}, {"P_CheckSight", lib_pCheckSight}, diff --git a/src/p_local.h b/src/p_local.h index 4b330184b..036636626 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -418,6 +418,7 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff); boolean P_Move(mobj_t *actor, fixed_t speed); boolean P_SetOrigin(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z); boolean P_MoveOrigin(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z); +boolean P_LineIsBlocking(mobj_t *mo, line_t *li); void P_SlideMove(mobj_t *mo); void P_BounceMove(mobj_t *mo); boolean P_CheckSight(mobj_t *t1, mobj_t *t2); diff --git a/src/p_map.c b/src/p_map.c index 80135db74..a69fa06e2 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -3400,36 +3400,41 @@ static boolean P_IsClimbingValid(player_t *player, angle_t angle) return false; } -static boolean PTR_LineIsBlocking(line_t *li) +// +// P_LineIsBlocking +// +// Determines if line would block mo's movement +// +boolean P_LineIsBlocking(mobj_t *mo, line_t *li) { // one-sided linedefs are always solid to sliding movement. if (!li->backsector) - return !P_PointOnLineSide(slidemo->x, slidemo->y, li); + return !P_PointOnLineSide(mo->x, mo->y, li); - if (!(slidemo->flags & MF_MISSILE)) + if (!(mo->flags & MF_MISSILE)) { if (li->flags & ML_IMPASSIBLE) return true; - if ((slidemo->flags & (MF_ENEMY|MF_BOSS)) && li->flags & ML_BLOCKMONSTERS) + if ((mo->flags & (MF_ENEMY|MF_BOSS)) && li->flags & ML_BLOCKMONSTERS) return true; } // set openrange, opentop, openbottom - P_LineOpening(li, slidemo); + P_LineOpening(li, mo); - if (openrange < slidemo->height) + if (openrange < mo->height) return true; // doesn't fit - if (opentop - slidemo->z < slidemo->height) + if (opentop - mo->z < mo->height) return true; // mobj is too high - if (openbottom - slidemo->z > FixedMul(MAXSTEPMOVE, slidemo->scale)) + if (openbottom - mo->z > FixedMul(MAXSTEPMOVE, mo->scale)) return true; // too big a step up - if (slidemo->player - && openrange < P_GetPlayerHeight(slidemo->player) - && !P_PlayerCanEnterSpinGaps(slidemo->player)) + if (mo->player + && openrange < P_GetPlayerHeight(mo->player) + && !P_PlayerCanEnterSpinGaps(mo->player)) return true; // nonspin character should not take this path return false; @@ -3533,7 +3538,7 @@ static boolean PTR_SlideTraverse(intercept_t *in) li = in->d.line; - if (!PTR_LineIsBlocking(li)) + if (!P_LineIsBlocking(slidemo, li)) return true; // the line blocks movement, From ecf950e7498ee179155c7cdb3e08331b41c4874e Mon Sep 17 00:00:00 2001 From: SMS Alfredo <65426124+SMS-Alfredo@users.noreply.github.com> Date: Tue, 19 Sep 2023 11:54:51 -0500 Subject: [PATCH 089/227] Add "allowflip" Parameter to P_DoJump --- src/lua_baselib.c | 3 ++- src/p_local.h | 2 +- src/p_user.c | 20 ++++++++++---------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 8cb6d185d..8485376b5 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -1659,11 +1659,12 @@ static int lib_pDoJump(lua_State *L) { player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER)); boolean soundandstate = (boolean)lua_opttrueboolean(L, 2); + boolean allowflip = (boolean)lua_opttrueboolean(L, 3); NOHUD INLEVEL if (!player) return LUA_ErrInvalid(L, "player_t"); - P_DoJump(player, soundandstate); + P_DoJump(player, soundandstate, allowflip); return 0; } diff --git a/src/p_local.h b/src/p_local.h index 4b330184b..a1804d302 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -203,7 +203,7 @@ void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius); void P_Earthquake(mobj_t *inflictor, mobj_t *source, fixed_t radius); boolean P_HomingAttack(mobj_t *source, mobj_t *enemy); /// \todo doesn't belong in p_user boolean P_SuperReady(player_t *player); -void P_DoJump(player_t *player, boolean soundandstate); +void P_DoJump(player_t *player, boolean soundandstate, boolean allowflip); #define P_AnalogMove(player) (P_ControlStyle(player) == CS_LMAOGALOG) boolean P_TransferToNextMare(player_t *player); UINT8 P_FindLowestMare(void); diff --git a/src/p_user.c b/src/p_user.c index 24e3f8111..850d0b448 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -4415,7 +4415,7 @@ boolean P_SuperReady(player_t *player) // // Jump routine for the player // -void P_DoJump(player_t *player, boolean soundandstate) +void P_DoJump(player_t *player, boolean soundandstate, boolean allowflip) { fixed_t factor; const fixed_t dist6 = FixedMul(FixedDiv(player->speed, player->mo->scale), player->actionspd)/20; @@ -4588,7 +4588,7 @@ void P_DoJump(player_t *player, boolean soundandstate) if (player->charflags & SF_NOJUMPDAMAGE) player->pflags &= ~PF_SPINNING; - if (P_InJumpFlipSector(player->mo)) // Flip gravity on jump? + if (allowflip && P_InJumpFlipSector(player->mo)) // Flip gravity on jump? { player->mo->flags2 ^= MF2_OBJECTFLIP; S_StartSound(player->mo, sfx_s3k73); // Play gravity flip sound @@ -4806,7 +4806,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd) #if 0 if ((player->charability == CA_TWINSPIN) && (player->speed > FixedMul(player->runspeed, player->mo->scale))) { - P_DoJump(player, false); + P_DoJump(player, false, false); player->pflags &= ~PF_STARTJUMP; player->mo->momz = FixedMul(player->mo->momz, 3*FRACUNIT/2); // NOT 1.5 times the jump height, but 2.25 times. P_SetPlayerMobjState(player->mo, S_PLAY_TWINSPIN); @@ -4886,7 +4886,7 @@ void P_DoJumpShield(player_t *player) return; player->pflags &= ~PF_JUMPED; - P_DoJump(player, false); + P_DoJump(player, false, true); player->secondjump = 0; player->pflags |= PF_THOKKED|PF_SHIELDABILITY; player->pflags &= ~(PF_STARTJUMP|PF_SPINNING|PF_BOUNCING); @@ -4929,7 +4929,7 @@ void P_DoBubbleBounce(player_t *player) player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_SHIELDABILITY); S_StartSound(player->mo, sfx_s3k44); P_MobjCheckWater(player->mo); - P_DoJump(player, false); + P_DoJump(player, false, false); if (player->charflags & SF_NOJUMPSPIN) P_SetPlayerMobjState(player->mo, S_PLAY_FALL); else @@ -4959,7 +4959,7 @@ void P_DoAbilityBounce(player_t *player, boolean changemomz) else if (player->mo->eflags & MFE_UNDERWATER) prevmomz /= 2; - P_DoJump(player, false); + P_DoJump(player, false, false); player->pflags &= ~(PF_STARTJUMP|PF_JUMPED); minmomz = FixedMul(player->mo->momz, 3*FRACUNIT/2); @@ -5332,7 +5332,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd) // Jump S3&K style while in quicksand. else if (P_InQuicksand(player->mo)) { - P_DoJump(player, true); + P_DoJump(player, true, false); player->secondjump = 0; player->pflags &= ~PF_THOKKED; } @@ -5345,7 +5345,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd) // can't jump while in air, can't jump while jumping else if (onground || player->climbing || player->powers[pw_carry]) { - P_DoJump(player, true); + P_DoJump(player, true, true); player->secondjump = 0; player->pflags &= ~PF_THOKKED; } @@ -5381,7 +5381,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd) if ((player->charability == CA_JUMPTHOK) && !(player->pflags & PF_THOKKED)) { player->pflags &= ~PF_JUMPED; - P_DoJump(player, false); + P_DoJump(player, false, true); } P_InstaThrust(player->mo, player->mo->angle, FixedMul(actionspd, player->mo->scale)); @@ -5490,7 +5490,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd) { player->pflags |= PF_THOKKED; player->pflags &= ~(PF_JUMPED|PF_SPINNING); - P_DoJump(player, true); + P_DoJump(player, true, true); player->secondjump++; } break; From 71b0f002c93415f210c4b6658e9641407b19d2d0 Mon Sep 17 00:00:00 2001 From: SMS Alfredo <65426124+SMS-Alfredo@users.noreply.github.com> Date: Tue, 19 Sep 2023 17:17:00 -0500 Subject: [PATCH 090/227] Only Consider ExitLevel a Cheat When Used to Cheat --- src/g_game.c | 243 ++++++++++++++++++++++------------------- src/g_game.h | 1 + src/netcode/d_netcmd.c | 55 ++++++---- src/netcode/d_netcmd.h | 1 - src/p_inter.c | 18 +-- src/p_setup.c | 2 +- src/p_user.c | 4 +- 7 files changed, 176 insertions(+), 148 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index fae311694..41d7b2ceb 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2143,7 +2143,7 @@ boolean G_Responder(event_t *ev) if (! netgame) F_StartGameEvaluation(); else if (server || IsPlayerAdmin(consoleplayer)) - D_SendExitLevel(false); + SendNetXCmd(XD_EXITLEVEL, NULL, 0); return true; } } @@ -3991,14 +3991,137 @@ static void G_HandleSaveLevel(void) } } +// +// G_GetNextMap +// +INT16 G_GetNextMap(boolean ignoretokens, boolean silent) +{ + INT32 i; + INT16 newmapnum; + boolean spec = G_IsSpecialStage(gamemap); + + // go to next level + // newmapnum is 0-based, unlike gamemap + if (nextmapoverride != 0) + newmapnum = (INT16)(nextmapoverride-1); + else if (marathonmode && mapheaderinfo[gamemap-1]->marathonnext) + newmapnum = (INT16)(mapheaderinfo[gamemap-1]->marathonnext-1); + else + { + newmapnum = (INT16)(mapheaderinfo[gamemap-1]->nextlevel-1); + if (marathonmode && newmapnum == spmarathon_start-1) + newmapnum = 1100-1; // No infinite loop for you + } + + INT16 gametype_to_use; + + if (nextgametype >= 0 && nextgametype < gametypecount) + gametype_to_use = nextgametype; + else + gametype_to_use = gametype; + + // If newmapnum is actually going to get used, make sure it points to + // a map of the proper gametype -- skip levels that don't support + // the current gametype. (Helps avoid playing boss levels in Race, + // for instance). + if (!spec || nextmapoverride) + { + if (newmapnum >= 0 && newmapnum < NUMMAPS) + { + INT16 cm = newmapnum; + UINT32 tolflag = G_TOLFlag(gametype_to_use); + UINT8 visitedmap[(NUMMAPS+7)/8]; + + memset(visitedmap, 0, sizeof (visitedmap)); + + while (!mapheaderinfo[cm] || !(mapheaderinfo[cm]->typeoflevel & tolflag)) + { + visitedmap[cm/8] |= (1<<(cm&7)); + if (!mapheaderinfo[cm]) + cm = -1; // guarantee error execution + else if (marathonmode && mapheaderinfo[cm]->marathonnext) + cm = (INT16)(mapheaderinfo[cm]->marathonnext-1); + else + cm = (INT16)(mapheaderinfo[cm]->nextlevel-1); + + if (cm >= NUMMAPS || cm < 0) // out of range (either 1100ish or error) + { + cm = newmapnum; //Start the loop again so that the error checking below is executed. + + //Make sure the map actually exists before you try to go to it! + if ((W_CheckNumForName(G_BuildMapName(cm + 1)) == LUMPERROR)) + { + if (!silent) + CONS_Alert(CONS_ERROR, M_GetText("Next map given (MAP %d) doesn't exist! Reverting to MAP01.\n"), cm+1); + cm = 0; + break; + } + } + + if (visitedmap[cm/8] & (1<<(cm&7))) // smells familiar + { + // We got stuck in a loop, came back to the map we started on + // without finding one supporting the current gametype. + // Thus, print a warning, and just use this map anyways. + if (!silent) + CONS_Alert(CONS_WARNING, M_GetText("Can't find a compatible map after map %d; using map %d anyway\n"), prevmap+1, cm+1); + break; + } + } + newmapnum = cm; + } + + // wrap around in race + if (newmapnum >= 1100-1 && newmapnum <= 1102-1 && !(gametyperules & GTR_CAMPAIGN)) + newmapnum = (INT16)(spstage_start-1); + + if (newmapnum < 0 || (newmapnum >= NUMMAPS && newmapnum < 1100-1) || newmapnum > 1103-1) + I_Error("Followed map %d to invalid map %d\n", prevmap + 1, newmapnum + 1); + + if (!spec) + lastmap = newmapnum; // Remember last map for when you come out of the special stage. + } + + if (!ignoretokens && (gottoken = ((gametyperules & GTR_SPECIALSTAGES) && token))) + { + token--; + +// if (!nextmapoverride) // Having a token should pull the player into the special stage before going to the overridden map (Issue #933) + for (i = 0; i < 7; i++) + if (!(emeralds & (1<marathonnext) - nextmap = (INT16)(mapheaderinfo[gamemap-1]->marathonnext-1); - else - { - nextmap = (INT16)(mapheaderinfo[gamemap-1]->nextlevel-1); - if (marathonmode && nextmap == spmarathon_start-1) - nextmap = 1100-1; // No infinite loop for you - } - - INT16 gametype_to_use; - - if (nextgametype >= 0 && nextgametype < gametypecount) - gametype_to_use = nextgametype; - else - gametype_to_use = gametype; - - // If nextmap is actually going to get used, make sure it points to - // a map of the proper gametype -- skip levels that don't support - // the current gametype. (Helps avoid playing boss levels in Race, - // for instance). - if (!spec || nextmapoverride) - { - if (nextmap >= 0 && nextmap < NUMMAPS) - { - INT16 cm = nextmap; - UINT32 tolflag = G_TOLFlag(gametype_to_use); - UINT8 visitedmap[(NUMMAPS+7)/8]; - - memset(visitedmap, 0, sizeof (visitedmap)); - - while (!mapheaderinfo[cm] || !(mapheaderinfo[cm]->typeoflevel & tolflag)) - { - visitedmap[cm/8] |= (1<<(cm&7)); - if (!mapheaderinfo[cm]) - cm = -1; // guarantee error execution - else if (marathonmode && mapheaderinfo[cm]->marathonnext) - cm = (INT16)(mapheaderinfo[cm]->marathonnext-1); - else - cm = (INT16)(mapheaderinfo[cm]->nextlevel-1); - - if (cm >= NUMMAPS || cm < 0) // out of range (either 1100ish or error) - { - cm = nextmap; //Start the loop again so that the error checking below is executed. - - //Make sure the map actually exists before you try to go to it! - if ((W_CheckNumForName(G_BuildMapName(cm + 1)) == LUMPERROR)) - { - CONS_Alert(CONS_ERROR, M_GetText("Next map given (MAP %d) doesn't exist! Reverting to MAP01.\n"), cm+1); - cm = 0; - break; - } - } - - if (visitedmap[cm/8] & (1<<(cm&7))) // smells familiar - { - // We got stuck in a loop, came back to the map we started on - // without finding one supporting the current gametype. - // Thus, print a warning, and just use this map anyways. - CONS_Alert(CONS_WARNING, M_GetText("Can't find a compatible map after map %d; using map %d anyway\n"), prevmap+1, cm+1); - break; - } - } - nextmap = cm; - } - - // wrap around in race - if (nextmap >= 1100-1 && nextmap <= 1102-1 && !(gametyperules & GTR_CAMPAIGN)) - nextmap = (INT16)(spstage_start-1); - - if (nextmap < 0 || (nextmap >= NUMMAPS && nextmap < 1100-1) || nextmap > 1103-1) - I_Error("Followed map %d to invalid map %d\n", prevmap + 1, nextmap + 1); - - if (!spec) - lastmap = nextmap; // Remember last map for when you come out of the special stage. - } - - if ((gottoken = ((gametyperules & GTR_SPECIALSTAGES) && token))) - { - token--; - -// if (!nextmapoverride) // Having a token should pull the player into the special stage before going to the overridden map (Issue #933) - for (i = 0; i < 7; i++) - if (!(emeralds & (1<= numneeded) { if (server) - D_SendExitLevel(false); + SendNetXCmd(XD_EXITLEVEL, NULL, 0); } else player->exiting = 3; @@ -11773,7 +11773,7 @@ void P_PlayerThink(player_t *player) else { if (server) - D_SendExitLevel(false); + SendNetXCmd(XD_EXITLEVEL, NULL, 0); } } From ab8aa245d65d5e297c60eb3b34639a2fce706093 Mon Sep 17 00:00:00 2001 From: SMS Alfredo <65426124+SMS-Alfredo@users.noreply.github.com> Date: Tue, 19 Sep 2023 17:30:46 -0500 Subject: [PATCH 091/227] Add the Other PlayersForExit Conditions --- src/netcode/d_netcmd.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/netcode/d_netcmd.c b/src/netcode/d_netcmd.c index e4f742ae7..e0cfc6e73 100644 --- a/src/netcode/d_netcmd.c +++ b/src/netcode/d_netcmd.c @@ -4490,6 +4490,13 @@ static void Command_ExitLevel_f(void) INT32 i; for (i = 0; i < MAXPLAYERS; i++) { + if (!playeringame[i] || players[i].spectator || players[i].bot) + continue; + if (players[i].quittime > 30 * TICRATE) + continue; + if (players[i].lives <= 0) + continue; + if ((players[i].pflags & PF_FINISHED) || players[i].exiting) { SendNetXCmd(XD_EXITLEVEL, NULL, 0); From 8fd9a82a19d49cae116ccbd0eb338c231eaa4cd3 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Thu, 21 Sep 2023 02:06:06 -0300 Subject: [PATCH 092/227] Raise sidedef limits Also, fixes reading of GL3 nodes to use 32-bit indices for line IDs. --- src/lua_maplib.c | 2 +- src/p_map.c | 2 +- src/p_maputl.c | 4 +- src/p_saveg.c | 29 ++++++------ src/p_setup.c | 121 ++++++++++++++++++++++++++--------------------- src/p_spec.c | 14 +++--- src/r_defs.h | 4 +- 7 files changed, 97 insertions(+), 79 deletions(-) diff --git a/src/lua_maplib.c b/src/lua_maplib.c index e34397993..7302c727a 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -1072,7 +1072,7 @@ static int line_get(lua_State *L) LUA_PushUserdata(L, &sides[line->sidenum[0]], META_SIDE); return 1; case line_backside: // backside - if (line->sidenum[1] == 0xffff) + if (line->sidenum[1] == NO_SIDEDEF) return 0; LUA_PushUserdata(L, &sides[line->sidenum[1]], META_SIDE); return 1; diff --git a/src/p_map.c b/src/p_map.c index 80135db74..e328dc9e6 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -3900,7 +3900,7 @@ retry: P_PathTraverse(leadx, traily, leadx + mo->momx, traily + mo->momy, PT_ADDLINES, PTR_SlideTraverse); - if (bestslideline && mo->player && bestslideline->sidenum[1] != 0xffff) + if (bestslideline && mo->player && bestslideline->sidenum[1] != NO_SIDEDEF) { sector_t *sec = P_PointOnLineSide(mo->x, mo->y, bestslideline) ? bestslideline->frontsector : bestslideline->backsector; P_CheckLavaWall(mo, sec); diff --git a/src/p_maputl.c b/src/p_maputl.c index e36d5fd72..9c30d3ead 100644 --- a/src/p_maputl.c +++ b/src/p_maputl.c @@ -290,7 +290,7 @@ void P_CameraLineOpening(line_t *linedef) sector_t *back; fixed_t frontfloor, frontceiling, backfloor, backceiling; - if (linedef->sidenum[1] == 0xffff) + if (linedef->sidenum[1] == NO_SIDEDEF) { // single sided line openrange = 0; @@ -426,7 +426,7 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj) { sector_t *front, *back; - if (linedef->sidenum[1] == 0xffff) + if (linedef->sidenum[1] == NO_SIDEDEF) { // single sided line openrange = 0; diff --git a/src/p_saveg.c b/src/p_saveg.c index faecd1377..3bcfea7cd 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -1079,7 +1079,7 @@ static void ArchiveSectors(void) if (diff) { - WRITEUINT16(save_p, i); + WRITEUINT32(save_p, i); WRITEUINT8(save_p, diff); if (diff & SD_DIFF2) WRITEUINT8(save_p, diff2); @@ -1150,18 +1150,19 @@ static void ArchiveSectors(void) } } - WRITEUINT16(save_p, 0xffff); + WRITEUINT32(save_p, 0xffffffff); } static void UnArchiveSectors(void) { - UINT16 i, j; + UINT32 i; + UINT16 j; UINT8 diff, diff2, diff3, diff4; for (;;) { - i = READUINT16(save_p); + i = READUINT32(save_p); - if (i == 0xffff) + if (i == 0xffffffff) break; if (i > numsectors) @@ -1299,7 +1300,7 @@ static void ArchiveLines(void) if (li->executordelay != spawnli->executordelay) diff2 |= LD_EXECUTORDELAY; - if (li->sidenum[0] != 0xffff) + if (li->sidenum[0] != NO_SIDEDEF) { si = &sides[li->sidenum[0]]; spawnsi = &spawnsides[li->sidenum[0]]; @@ -1313,7 +1314,7 @@ static void ArchiveLines(void) if (si->midtexture != spawnsi->midtexture) diff |= LD_S1MIDTEX; } - if (li->sidenum[1] != 0xffff) + if (li->sidenum[1] != NO_SIDEDEF) { si = &sides[li->sidenum[1]]; spawnsi = &spawnsides[li->sidenum[1]]; @@ -1332,7 +1333,7 @@ static void ArchiveLines(void) if (diff) { - WRITEINT16(save_p, i); + WRITEUINT32(save_p, i); WRITEUINT8(save_p, diff); if (diff & LD_DIFF2) WRITEUINT8(save_p, diff2); @@ -1391,21 +1392,21 @@ static void ArchiveLines(void) WRITEINT32(save_p, li->executordelay); } } - WRITEUINT16(save_p, 0xffff); + WRITEUINT32(save_p, 0xffffffff); } static void UnArchiveLines(void) { - UINT16 i; + UINT32 i; line_t *li; side_t *si; UINT8 diff, diff2; // no diff3 for (;;) { - i = READUINT16(save_p); + i = READUINT32(save_p); - if (i == 0xffff) + if (i == 0xffffffff) break; if (i > numlines) I_Error("Invalid line number %u from server", i); @@ -1892,7 +1893,7 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type) if (diff & MD_RTIME) WRITEINT32(save_p, mobj->reactiontime); if (diff & MD_STATE) - WRITEUINT16(save_p, mobj->state-states); + WRITEUINT32(save_p, mobj->state-states); if (diff & MD_TICS) WRITEINT32(save_p, mobj->tics); if (diff & MD_SPRITE) { @@ -2916,7 +2917,7 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker) mobj->reactiontime = mobj->info->reactiontime; if (diff & MD_STATE) - mobj->state = &states[READUINT16(save_p)]; + mobj->state = &states[READUINT32(save_p)]; else mobj->state = &states[mobj->info->spawnstate]; if (diff & MD_TICS) diff --git a/src/p_setup.c b/src/p_setup.c index 0390761b6..733a47792 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -1111,40 +1111,40 @@ static void P_InitializeLinedef(line_t *ld) // cph 2006/09/30 - fix sidedef errors right away. // cph 2002/07/20 - these errors are fatal if not fixed, so apply them for (j = 0; j < 2; j++) - if (ld->sidenum[j] != 0xffff && ld->sidenum[j] >= (UINT16)numsides) + if (ld->sidenum[j] != NO_SIDEDEF && ld->sidenum[j] >= (UINT32)numsides) { - ld->sidenum[j] = 0xffff; + ld->sidenum[j] = NO_SIDEDEF; CONS_Debug(DBG_SETUP, "P_InitializeLinedef: Linedef %s has out-of-range sidedef number\n", sizeu1((size_t)(ld - lines))); } // killough 11/98: fix common wad errors (missing sidedefs): - if (ld->sidenum[0] == 0xffff) + if (ld->sidenum[0] == NO_SIDEDEF) { ld->sidenum[0] = 0; // Substitute dummy sidedef for missing right side // cph - print a warning about the bug CONS_Debug(DBG_SETUP, "P_InitializeLinedef: Linedef %s missing first sidedef\n", sizeu1((size_t)(ld - lines))); } - if ((ld->sidenum[1] == 0xffff) && (ld->flags & ML_TWOSIDED)) + if ((ld->sidenum[1] == NO_SIDEDEF) && (ld->flags & ML_TWOSIDED)) { ld->flags &= ~ML_TWOSIDED; // Clear 2s flag for missing left side // cph - print a warning about the bug CONS_Debug(DBG_SETUP, "P_InitializeLinedef: Linedef %s has two-sided flag set, but no second sidedef\n", sizeu1((size_t)(ld - lines))); } - if (ld->sidenum[0] != 0xffff) + if (ld->sidenum[0] != NO_SIDEDEF) { sides[ld->sidenum[0]].special = ld->special; sides[ld->sidenum[0]].line = ld; } - if (ld->sidenum[1] != 0xffff) + if (ld->sidenum[1] != NO_SIDEDEF) { sides[ld->sidenum[1]].special = ld->special; sides[ld->sidenum[1]].line = ld; } } -static void P_SetLinedefV1(size_t i, UINT16 vertex_num) +static void P_SetLinedefV1(size_t i, UINT32 vertex_num) { if (vertex_num >= numvertexes) { @@ -1154,7 +1154,7 @@ static void P_SetLinedefV1(size_t i, UINT16 vertex_num) lines[i].v1 = &vertexes[vertex_num]; } -static void P_SetLinedefV2(size_t i, UINT16 vertex_num) +static void P_SetLinedefV2(size_t i, UINT32 vertex_num) { if (vertex_num >= numvertexes) { @@ -1179,17 +1179,22 @@ static void P_LoadLinedefs(UINT8 *data) memset(ld->stringargs, 0x00, NUMLINESTRINGARGS*sizeof(*ld->stringargs)); ld->alpha = FRACUNIT; ld->executordelay = 0; - P_SetLinedefV1(i, SHORT(mld->v1)); - P_SetLinedefV2(i, SHORT(mld->v2)); + P_SetLinedefV1(i, (UINT16)SHORT(mld->v1)); + P_SetLinedefV2(i, (UINT16)SHORT(mld->v2)); - ld->sidenum[0] = SHORT(mld->sidenum[0]); - ld->sidenum[1] = SHORT(mld->sidenum[1]); + ld->sidenum[0] = (UINT16)SHORT(mld->sidenum[0]); + ld->sidenum[1] = (UINT16)SHORT(mld->sidenum[1]); + + if (ld->sidenum[0] == 0xffff) + ld->sidenum[0] = NO_SIDEDEF; + if (ld->sidenum[1] == 0xffff) + ld->sidenum[1] = NO_SIDEDEF; P_InitializeLinedef(ld); } } -static void P_SetSidedefSector(size_t i, UINT16 sector_num) +static void P_SetSidedefSector(size_t i, UINT32 sector_num) { // cph 2006/09/30 - catch out-of-range sector numbers; use sector 0 instead if (sector_num >= numsectors) @@ -1350,7 +1355,7 @@ static void P_LoadSidedefs(UINT8 *data) sd->offsetx_top = sd->offsetx_mid = sd->offsetx_bot = 0; sd->offsety_top = sd->offsety_mid = sd->offsety_bot = 0; - P_SetSidedefSector(i, SHORT(msd->sector)); + P_SetSidedefSector(i, (UINT16)SHORT(msd->sector)); // Special info stored in texture fields! switch (sd->special) @@ -2489,7 +2494,7 @@ static void P_WriteTextmap(void) fprintf(f, "v1 = %s;\n", sizeu1(wlines[i].v1 - vertexes)); fprintf(f, "v2 = %s;\n", sizeu1(wlines[i].v2 - vertexes)); fprintf(f, "sidefront = %d;\n", wlines[i].sidenum[0]); - if (wlines[i].sidenum[1] != 0xffff) + if (wlines[i].sidenum[1] != NO_SIDEDEF) fprintf(f, "sideback = %d;\n", wlines[i].sidenum[1]); firsttag = Tag_FGet(&wlines[i].tags); if (firsttag != 0) @@ -2954,8 +2959,8 @@ static void P_LoadTextmap(void) memset(ld->stringargs, 0x00, NUMLINESTRINGARGS*sizeof(*ld->stringargs)); ld->alpha = FRACUNIT; ld->executordelay = 0; - ld->sidenum[0] = 0xffff; - ld->sidenum[1] = 0xffff; + ld->sidenum[0] = NO_SIDEDEF; + ld->sidenum[1] = NO_SIDEDEF; TextmapParse(linesPos[i], i, ParseTextmapLinedefParameter); @@ -2963,7 +2968,7 @@ static void P_LoadTextmap(void) I_Error("P_LoadTextmap: linedef %s has no v1 value set!\n", sizeu1(i)); if (!ld->v2) I_Error("P_LoadTextmap: linedef %s has no v2 value set!\n", sizeu1(i)); - if (ld->sidenum[0] == 0xffff) + if (ld->sidenum[0] == NO_SIDEDEF) I_Error("P_LoadTextmap: linedef %s has no sidefront value set!\n", sizeu1(i)); P_InitializeLinedef(ld); @@ -3017,7 +3022,7 @@ static void P_ProcessLinedefsAfterSidedefs(void) for (; i--; ld++) { ld->frontsector = sides[ld->sidenum[0]].sector; //e6y: Can't be -1 here - ld->backsector = ld->sidenum[1] != 0xffff ? sides[ld->sidenum[1]].sector : 0; + ld->backsector = ld->sidenum[1] != NO_SIDEDEF ? sides[ld->sidenum[1]].sector : 0; if (udmf) continue; @@ -3256,9 +3261,9 @@ static void P_InitializeSeg(seg_t *seg) { if (seg->linedef) { - UINT16 side = seg->linedef->sidenum[seg->side]; + UINT32 side = seg->linedef->sidenum[seg->side]; - if (side == 0xffff) + if (side == NO_SIDEDEF) I_Error("P_InitializeSeg: Seg %s refers to side %d of linedef %s, which doesn't exist!\n", sizeu1((size_t)(seg - segs)), seg->side, sizeu1((size_t)(seg->linedef - lines))); seg->sidedef = &sides[side]; @@ -3443,7 +3448,7 @@ static boolean P_LoadExtraVertices(UINT8 **data) static boolean P_LoadExtendedSubsectorsAndSegs(UINT8 **data, nodetype_t nodetype) { size_t i, k; - INT16 m; + size_t m; seg_t *seg; // Subsectors @@ -3466,23 +3471,33 @@ static boolean P_LoadExtendedSubsectorsAndSegs(UINT8 **data, nodetype_t nodetype { case NT_XGLN: case NT_XGL3: - for (m = 0; m < subsectors[i].numlines; m++, k++) + for (m = 0; m < (size_t)subsectors[i].numlines; m++, k++) { UINT32 vertexnum = READUINT32((*data)); - UINT16 linenum; - if (vertexnum >= numvertexes) - I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid vertex %d!\n", sizeu1(k), m, vertexnum); + I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %s has invalid vertex %d!\n", sizeu1(k), sizeu1(m), vertexnum); segs[k - 1 + ((m == 0) ? subsectors[i].numlines : 0)].v2 = segs[k].v1 = &vertexes[vertexnum]; READUINT32((*data)); // partner, can be ignored by software renderer - linenum = (nodetype == NT_XGL3) ? READUINT32((*data)) : READUINT16((*data)); - if (linenum != 0xFFFF && linenum >= numlines) - I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %s has invalid linedef %d!\n", sizeu1(k), sizeu2(i), linenum); - segs[k].glseg = (linenum == 0xFFFF); - segs[k].linedef = (linenum == 0xFFFF) ? NULL : &lines[linenum]; + if (nodetype == NT_XGL3) + { + UINT32 linenum = READUINT32((*data)); + if (linenum != 0xFFFFFFFF && linenum >= numlines) + I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %s has invalid linedef %d!\n", sizeu1(k), sizeu2(i), linenum); + segs[k].glseg = linenum == 0xFFFFFFFF; + segs[k].linedef = linenum == 0xFFFFFFFF ? NULL : &lines[linenum]; + } + else + { + UINT16 linenum = READUINT16((*data)); + if (linenum != 0xFFFF && linenum >= numlines) + I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %s has invalid linedef %d!\n", sizeu1(k), sizeu2(i), linenum); + segs[k].glseg = linenum == 0xFFFF; + segs[k].linedef = linenum == 0xFFFF ? NULL : &lines[linenum]; + } + segs[k].side = READUINT8((*data)); } while (segs[subsectors[i].firstline].glseg) @@ -3494,18 +3509,18 @@ static boolean P_LoadExtendedSubsectorsAndSegs(UINT8 **data, nodetype_t nodetype break; case NT_XNOD: - for (m = 0; m < subsectors[i].numlines; m++, k++) + for (m = 0; m < (size_t)subsectors[i].numlines; m++, k++) { UINT32 v1num = READUINT32((*data)); UINT32 v2num = READUINT32((*data)); UINT16 linenum = READUINT16((*data)); if (v1num >= numvertexes) - I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid v1 %d!\n", sizeu1(k), m, v1num); + I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %s has invalid v1 %d!\n", sizeu1(k), sizeu1(m), v1num); if (v2num >= numvertexes) - I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid v2 %d!\n", sizeu1(k), m, v2num); + I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %s has invalid v2 %d!\n", sizeu1(k), sizeu1(m), v2num); if (linenum >= numlines) - I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid linedef %d!\n", sizeu1(k), m, linenum); + I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %s has invalid linedef %d!\n", sizeu1(k), sizeu1(m), linenum); segs[k].v1 = &vertexes[v1num]; segs[k].v2 = &vertexes[v2num]; @@ -3990,7 +4005,7 @@ static void P_LinkMapData(void) if (!seg->sidedef) CorruptMapError(va("P_LinkMapData: seg->sidedef is NULL " "(subsector %s, firstline is %d)", sizeu1(i), ss->firstline)); - if (seg->sidedef - sides < 0 || seg->sidedef - sides > (UINT16)numsides) + if (seg->sidedef - sides < 0 || seg->sidedef - sides > (UINT32)numsides) CorruptMapError(va("P_LinkMapData: seg->sidedef refers to sidedef %s of %s " "(subsector %s, firstline is %d)", sizeu1(sidei), sizeu2(numsides), sizeu3(i), ss->firstline)); @@ -4915,7 +4930,7 @@ static void P_ConvertBinaryLinedefTypes(void) break; case 259: //Custom FOF - if (lines[i].sidenum[1] == 0xffff) + if (lines[i].sidenum[1] == NO_SIDEDEF) I_Error("Custom FOF (tag %d) found without a linedef back side!", tag); lines[i].args[0] = tag; @@ -5269,8 +5284,8 @@ static void P_ConvertBinaryLinedefTypes(void) lines[i].args[1] = sides[lines[i].sidenum[0]].midtexture; lines[i].args[2] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS; lines[i].args[3] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS; - lines[i].args[4] = (lines[i].sidenum[1] != 0xffff) ? sides[lines[i].sidenum[1]].textureoffset >> FRACBITS : 0; - lines[i].args[5] = (lines[i].sidenum[1] != 0xffff) ? sides[lines[i].sidenum[1]].rowoffset >> FRACBITS : -1; + lines[i].args[4] = (lines[i].sidenum[1] != NO_SIDEDEF) ? sides[lines[i].sidenum[1]].textureoffset >> FRACBITS : 0; + lines[i].args[5] = (lines[i].sidenum[1] != NO_SIDEDEF) ? sides[lines[i].sidenum[1]].rowoffset >> FRACBITS : -1; lines[i].args[6] = sides[lines[i].sidenum[0]].bottomtexture; break; case 414: //Play sound effect @@ -5374,7 +5389,7 @@ static void P_ConvertBinaryLinedefTypes(void) lines[i].args[1] = max(sides[lines[i].sidenum[0]].textureoffset >> FRACBITS, 0); // failsafe: if user specifies Back Y Offset and NOT Front Y Offset, use the Back Offset // to be consistent with other light and fade specials - lines[i].args[2] = ((lines[i].sidenum[1] != 0xFFFF && !(sides[lines[i].sidenum[0]].rowoffset >> FRACBITS)) ? + lines[i].args[2] = ((lines[i].sidenum[1] != NO_SIDEDEF && !(sides[lines[i].sidenum[0]].rowoffset >> FRACBITS)) ? max(min(sides[lines[i].sidenum[1]].rowoffset >> FRACBITS, 255), 0) : max(min(sides[lines[i].sidenum[0]].rowoffset >> FRACBITS, 255), 0)); } @@ -5479,7 +5494,7 @@ static void P_ConvertBinaryLinedefTypes(void) break; case 442: //Change object type state lines[i].args[0] = tag; - lines[i].args[1] = (lines[i].sidenum[1] == 0xffff) ? 1 : 0; + lines[i].args[1] = (lines[i].sidenum[1] == NO_SIDEDEF) ? 1 : 0; break; case 443: //Call Lua function if (lines[i].stringargs[0] == NULL) @@ -5547,7 +5562,7 @@ static void P_ConvertBinaryLinedefTypes(void) case 452: //Set FOF translucency lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS; lines[i].args[1] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS; - lines[i].args[2] = lines[i].sidenum[1] != 0xffff ? (sides[lines[i].sidenum[1]].textureoffset >> FRACBITS) : (P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS); + lines[i].args[2] = lines[i].sidenum[1] != NO_SIDEDEF ? (sides[lines[i].sidenum[1]].textureoffset >> FRACBITS) : (P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS); if (lines[i].flags & ML_MIDPEG) lines[i].args[3] |= TMST_RELATIVE; if (lines[i].flags & ML_NOCLIMB) @@ -5556,8 +5571,8 @@ static void P_ConvertBinaryLinedefTypes(void) case 453: //Fade FOF lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS; lines[i].args[1] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS; - lines[i].args[2] = lines[i].sidenum[1] != 0xffff ? (sides[lines[i].sidenum[1]].textureoffset >> FRACBITS) : (lines[i].dx >> FRACBITS); - lines[i].args[3] = lines[i].sidenum[1] != 0xffff ? (sides[lines[i].sidenum[1]].rowoffset >> FRACBITS) : (abs(lines[i].dy) >> FRACBITS); + lines[i].args[2] = lines[i].sidenum[1] != NO_SIDEDEF ? (sides[lines[i].sidenum[1]].textureoffset >> FRACBITS) : (lines[i].dx >> FRACBITS); + lines[i].args[3] = lines[i].sidenum[1] != NO_SIDEDEF ? (sides[lines[i].sidenum[1]].rowoffset >> FRACBITS) : (abs(lines[i].dy) >> FRACBITS); if (lines[i].flags & ML_MIDPEG) lines[i].args[4] |= TMFT_RELATIVE; if (lines[i].flags & ML_WRAPMIDTEX) @@ -5584,7 +5599,7 @@ static void P_ConvertBinaryLinedefTypes(void) break; case 455: //Fade colormap { - INT32 speed = (INT32)((((lines[i].flags & ML_DONTPEGBOTTOM) || !sides[lines[i].sidenum[0]].rowoffset) && lines[i].sidenum[1] != 0xFFFF) ? + INT32 speed = (INT32)((((lines[i].flags & ML_DONTPEGBOTTOM) || !sides[lines[i].sidenum[0]].rowoffset) && lines[i].sidenum[1] != NO_SIDEDEF) ? abs(sides[lines[i].sidenum[1]].rowoffset >> FRACBITS) : abs(sides[lines[i].sidenum[0]].rowoffset >> FRACBITS)); @@ -5614,7 +5629,7 @@ static void P_ConvertBinaryLinedefTypes(void) lines[i].args[0] = tag; lines[i].args[1] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS; lines[i].args[2] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS; - lines[i].args[3] = (lines[i].sidenum[1] != 0xffff) ? sides[lines[i].sidenum[1]].textureoffset >> FRACBITS : 0; + lines[i].args[3] = (lines[i].sidenum[1] != NO_SIDEDEF) ? sides[lines[i].sidenum[1]].textureoffset >> FRACBITS : 0; lines[i].args[4] = !!(lines[i].flags & ML_NOSKEW); break; case 459: //Control text prompt @@ -5634,7 +5649,7 @@ static void P_ConvertBinaryLinedefTypes(void) lines[i].args[2] |= TMP_ALLPLAYERS; if (lines[i].flags & ML_MIDSOLID) lines[i].args[2] |= TMP_FREEZETHINKERS;*/ - lines[i].args[3] = (lines[i].sidenum[1] != 0xFFFF) ? sides[lines[i].sidenum[1]].textureoffset >> FRACBITS : tag; + lines[i].args[3] = (lines[i].sidenum[1] != NO_SIDEDEF) ? sides[lines[i].sidenum[1]].textureoffset >> FRACBITS : tag; break; case 460: //Award rings lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS; @@ -5647,7 +5662,7 @@ static void P_ConvertBinaryLinedefTypes(void) lines[i].args[3] = (lines[i].flags & ML_SKEWTD) ? AngleFixed(R_PointToAngle2(lines[i].v1->x, lines[i].v1->y, lines[i].v2->x, lines[i].v2->y)) >> FRACBITS : 0; if (lines[i].flags & ML_NOCLIMB) { - if (lines[i].sidenum[1] != 0xffff) // Make sure the linedef has a back side + if (lines[i].sidenum[1] != NO_SIDEDEF) // Make sure the linedef has a back side { lines[i].args[4] = 1; lines[i].args[5] = sides[lines[i].sidenum[1]].textureoffset >> FRACBITS; @@ -5681,7 +5696,7 @@ static void P_ConvertBinaryLinedefTypes(void) lines[i].args[0] = tag; lines[i].args[1] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS; lines[i].args[2] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS; - if (lines[i].sidenum[1] != 0xffff) + if (lines[i].sidenum[1] != NO_SIDEDEF) lines[i].args[3] = sides[lines[i].sidenum[1]].textureoffset >> FRACBITS; break; case 482: //Polyobject - move @@ -5753,7 +5768,7 @@ static void P_ConvertBinaryLinedefTypes(void) if (!(lines[i].flags & ML_DONTPEGBOTTOM)) lines[i].args[1] /= 100; // allow Back Y Offset to be consistent with other fade specials - lines[i].args[2] = (lines[i].sidenum[1] != 0xffff && !sides[lines[i].sidenum[0]].rowoffset) ? + lines[i].args[2] = (lines[i].sidenum[1] != NO_SIDEDEF && !sides[lines[i].sidenum[0]].rowoffset) ? abs(sides[lines[i].sidenum[1]].rowoffset >> FRACBITS) : abs(sides[lines[i].sidenum[0]].rowoffset >> FRACBITS); if (lines[i].flags & ML_MIDPEG) @@ -5780,7 +5795,7 @@ static void P_ConvertBinaryLinedefTypes(void) lines[i].args[0] = tag; if (lines[i].flags & ML_MIDPEG) { - if (lines[i].sidenum[1] == 0xffff) + if (lines[i].sidenum[1] == NO_SIDEDEF) { CONS_Debug(DBG_GAMELOGIC, "Line special %d (line #%s) missing back side!\n", lines[i].special, sizeu1(i)); lines[i].special = 0; @@ -5810,7 +5825,7 @@ static void P_ConvertBinaryLinedefTypes(void) lines[i].args[0] = lines[i].special >= 507; if (lines[i].special % 2 == 0) { - if (lines[i].sidenum[1] == 0xffff) + if (lines[i].sidenum[1] == NO_SIDEDEF) { CONS_Debug(DBG_GAMELOGIC, "Line special %d (line #%s) missing back side!\n", lines[i].special, sizeu1(i)); lines[i].special = 0; @@ -5966,7 +5981,7 @@ static void P_ConvertBinaryLinedefTypes(void) { UINT8 side = lines[i].special >= 714; - if (side == 1 && lines[i].sidenum[1] == 0xffff) + if (side == 1 && lines[i].sidenum[1] == NO_SIDEDEF) CONS_Debug(DBG_GAMELOGIC, "P_ConvertBinaryMap: Line special %d (line #%s) missing 2nd side!\n", lines[i].special, sizeu1(i)); else { diff --git a/src/p_spec.c b/src/p_spec.c index 28ecc60f4..73bb0da88 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -557,7 +557,7 @@ static inline sector_t *getSector(INT32 currentSector, INT32 line, INT32 side) */ static inline boolean twoSided(INT32 sector, INT32 line) { - return (sectors[sector].lines[line])->sidenum[1] != 0xffff; + return (sectors[sector].lines[line])->sidenum[1] != NO_SIDEDEF; } #endif @@ -2897,7 +2897,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) { size_t linenum; side_t *setfront = &sides[line->sidenum[0]]; - side_t *setback = (line->args[3] && line->sidenum[1] != 0xffff) ? &sides[line->sidenum[1]] : setfront; + side_t *setback = (line->args[3] && line->sidenum[1] != NO_SIDEDEF) ? &sides[line->sidenum[1]] : setfront; side_t *this; boolean always = !(line->args[2]); // If args[2] is set: Only change mid texture if mid texture already exists on tagged lines, etc. @@ -2919,7 +2919,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) } // Back side - if (line->args[1] != TMSD_FRONT && lines[linenum].sidenum[1] != 0xffff) + if (line->args[1] != TMSD_FRONT && lines[linenum].sidenum[1] != NO_SIDEDEF) { this = &sides[lines[linenum].sidenum[1]]; if (always || this->toptexture) this->toptexture = setback->toptexture; @@ -3153,7 +3153,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) if (line->args[2] & TMCF_RELATIVE) { - extracolormap_t *target = (!udmf && (line->flags & ML_TFERLINE) && line->sidenum[1] != 0xFFFF) ? + extracolormap_t *target = (!udmf && (line->flags & ML_TFERLINE) && line->sidenum[1] != NO_SIDEDEF) ? sides[line->sidenum[1]].colormap_data : sectors[secnum].extra_colormap; // use back colormap instead of target sector extracolormap_t *exc = R_AddColormaps( @@ -3482,7 +3482,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) } if (!udmf && (line->flags & ML_TFERLINE)) // use back colormap instead of target sector - sectors[secnum].extra_colormap = (line->sidenum[1] != 0xFFFF) ? + sectors[secnum].extra_colormap = (line->sidenum[1] != NO_SIDEDEF) ? sides[line->sidenum[1]].colormap_data : NULL; exc = sectors[secnum].extra_colormap; @@ -7598,7 +7598,7 @@ static void P_SpawnScrollers(void) { if (l->args[1] != TMSD_BACK) Add_Scroller(sc_side, l->args[2] << (FRACBITS - SCROLL_SHIFT), l->args[3] << (FRACBITS - SCROLL_SHIFT), control, lines[s].sidenum[0], accel, 0); - if (l->args[1] != TMSD_FRONT && lines[s].sidenum[1] != 0xffff) + if (l->args[1] != TMSD_FRONT && lines[s].sidenum[1] != NO_SIDEDEF) Add_Scroller(sc_side, l->args[2] << (FRACBITS - SCROLL_SHIFT), l->args[3] << (FRACBITS - SCROLL_SHIFT), control, lines[s].sidenum[1], accel, 0); } break; @@ -7609,7 +7609,7 @@ static void P_SpawnScrollers(void) Add_Scroller(sc_side, -l->args[1] << FRACBITS, l->args[2] << FRACBITS, -1, l->sidenum[0], accel, 0); if (l->args[0] != TMSD_FRONT) { - if (l->sidenum[1] != 0xffff) + if (l->sidenum[1] != NO_SIDEDEF) Add_Scroller(sc_side, -l->args[1] << FRACBITS, l->args[2] << FRACBITS, -1, l->sidenum[1], accel, 0); else CONS_Debug(DBG_GAMELOGIC, "Line special 500 (line #%s) missing back side!\n", sizeu1(i)); diff --git a/src/r_defs.h b/src/r_defs.h index dfd2d6d70..6463e6f5d 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -512,6 +512,8 @@ typedef enum #define NUMLINEARGS 10 #define NUMLINESTRINGARGS 2 +#define NO_SIDEDEF 0xFFFFFFFF + typedef struct line_s { // Vertices, from v1 to v2. @@ -529,7 +531,7 @@ typedef struct line_s char *stringargs[NUMLINESTRINGARGS]; // Visual appearance: sidedefs. - UINT16 sidenum[2]; // sidenum[1] will be 0xffff if one-sided + UINT32 sidenum[2]; // sidenum[1] will be NO_SIDEDEF if one-sided fixed_t alpha; // translucency UINT8 blendmode; // blendmode INT32 executordelay; From 01491a4d3168d5d633a7ac7b62bca6ca94d1a949 Mon Sep 17 00:00:00 2001 From: SMS Alfredo <65426124+SMS-Alfredo@users.noreply.github.com> Date: Thu, 5 Oct 2023 11:36:19 -0500 Subject: [PATCH 093/227] Undo Unintentional Changes --- src/p_mobj.c | 48 ++++++++++++++---------------------------------- 1 file changed, 14 insertions(+), 34 deletions(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index 430fa9256..6ccb643f5 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -9684,6 +9684,11 @@ static boolean P_MobjRegularThink(mobj_t *mobj) A_AttractChase(mobj); break; case MT_EMBLEM: + if (P_EmblemWasCollected(mobj->health - 1) || !P_CanPickupEmblem(&players[consoleplayer], mobj->health - 1)) + mobj->frame |= (tr_trans50 << FF_TRANSSHIFT); + else + mobj->frame &= ~FF_TRANSMASK; + if (mobj->flags2 & MF2_NIGHTSPULL) P_NightsItemChase(mobj); break; @@ -11993,8 +11998,8 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i) break; case MT_EMBLEM: - if (netgame || multiplayer) - return false; // Single player (You're next on my shit list) + if (!G_CoopGametype()) + return false; // Gametype's not right break; default: break; @@ -12138,7 +12143,6 @@ static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj) INT32 j; emblem_t* emblem = M_GetLevelEmblems(gamemap); skincolornum_t emcolor; - boolean validEmblem = true; while (emblem) { @@ -12163,42 +12167,19 @@ static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj) emcolor = M_GetEmblemColor(&emblemlocations[j]); // workaround for compiler complaint about bad function casting mobj->color = (UINT16)emcolor; - validEmblem = !emblemlocations[j].collected; + mobj->frame &= ~FF_TRANSMASK; - if (emblemlocations[j].type == ET_SKIN) + if (emblemlocations[j].type == ET_GLOBAL) { - INT32 skinnum = M_EmblemSkinNum(&emblemlocations[j]); - - if (players[0].skin != skinnum) + mobj->reactiontime = emblemlocations[j].var; + if (emblemlocations[j].var & GE_NIGHTSITEM) { - validEmblem = false; + mobj->flags |= MF_NIGHTSITEM; + mobj->flags &= ~MF_SPECIAL; + mobj->flags2 |= MF2_DONTDRAW; } } - if (validEmblem == false) - { - P_UnsetThingPosition(mobj); - mobj->flags |= MF_NOCLIP; - mobj->flags &= ~MF_SPECIAL; - mobj->flags |= MF_NOBLOCKMAP; - mobj->frame |= (tr_trans50 << FF_TRANSSHIFT); - P_SetThingPosition(mobj); - } - else - { - mobj->frame &= ~FF_TRANSMASK; - - if (emblemlocations[j].type == ET_GLOBAL) - { - mobj->reactiontime = emblemlocations[j].var; - if (emblemlocations[j].var & GE_NIGHTSITEM) - { - mobj->flags |= MF_NIGHTSITEM; - mobj->flags &= ~MF_SPECIAL; - mobj->flags2 |= MF2_DONTDRAW; - } - } - } return true; } @@ -13697,7 +13678,6 @@ void P_SpawnItemPattern(mapthing_t *mthing, boolean bonustime) UINT8 numitemtypes; if (!udmf) return; - CONS_Printf("Itemstring: %s\n", mthing->stringargs[0]); P_ParseItemTypes(mthing->stringargs[0], itemtypes, &numitemtypes); P_SpawnItemCircle(mthing, itemtypes, numitemtypes, mthing->args[0], mthing->args[1] << FRACBITS, bonustime); return; From 7632113d570951b22d2bcbb5adfa40d0ad6120d8 Mon Sep 17 00:00:00 2001 From: SMS Alfredo <65426124+SMS-Alfredo@users.noreply.github.com> Date: Thu, 5 Oct 2023 11:50:17 -0500 Subject: [PATCH 094/227] Oops Whitespace --- src/p_mobj.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index 6ccb643f5..1be7ced7a 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -9688,7 +9688,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) mobj->frame |= (tr_trans50 << FF_TRANSSHIFT); else mobj->frame &= ~FF_TRANSMASK; - + if (mobj->flags2 & MF2_NIGHTSPULL) P_NightsItemChase(mobj); break; From 290fc9f81f680a5357382bc501a91e86649664a0 Mon Sep 17 00:00:00 2001 From: SMS Alfredo <65426124+SMS-Alfredo@users.noreply.github.com> Date: Thu, 5 Oct 2023 12:04:46 -0500 Subject: [PATCH 095/227] Restore Ringslinger Collectable Behavior --- src/p_mobj.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/p_mobj.c b/src/p_mobj.c index 1be7ced7a..1818875c5 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -2386,6 +2386,23 @@ boolean P_ZMovement(mobj_t *mo) // Remove from death pits. DON'T FUCKING DESPAWN IT DAMMIT mo->fuse = 1; return false; + case MT_BOUNCERING: + case MT_INFINITYRING: + case MT_AUTOMATICRING: + case MT_RAILRING: + case MT_EXPLOSIONRING: + case MT_SCATTERRING: + case MT_GRENADERING: + case MT_BOUNCEPICKUP: + case MT_RAILPICKUP: + case MT_AUTOPICKUP: + case MT_EXPLODEPICKUP: + case MT_SCATTERPICKUP: + case MT_GRENADEPICKUP: + //Don't remove respawning ringslinger collectables on death pits + if (!(mo->flags2 & MF2_DONTRESPAWN)) + break; + /* FALLTHRU */ default: if (mo->flags & MF_ENEMY || mo->flags & MF_BOSS || mo->type == MT_MINECART) { From d2996a308c4d54019a6dafb5255a38cdf6b12267 Mon Sep 17 00:00:00 2001 From: spherallic Date: Wed, 14 Jun 2023 13:15:05 +0200 Subject: [PATCH 096/227] Add shortcuts for current/next map to map command --- src/g_game.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index fae311694..f415b45db 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -5373,16 +5373,27 @@ void G_FreeMapSearch(mapsearchfreq_t *freq, INT32 freqc) INT32 G_FindMapByNameOrCode(const char *mapname, char **realmapnamep) { boolean usemapcode = false; - INT32 newmapnum; - - size_t mapnamelen; - + size_t mapnamelen = strlen(mapname); char *p; - mapnamelen = strlen(mapname); - - if (mapnamelen == 2)/* maybe two digit code */ + if (mapnamelen == 1) + { + if (strcmp(mapname, "*") == 0) // current map + return gamemap; + else if (strcmp(mapname, "+") == 0 && mapheaderinfo[gamemap-1]) // next map + { + newmapnum = mapheaderinfo[gamemap-1]->nextlevel; + if (newmapnum < 1 || newmapnum > NUMMAPS) + { + CONS_Alert(CONS_ERROR, M_GetText("NextLevel (%d) is not a valid map.\n"), newmapnum); + return 0; + } + else + return newmapnum; + } + } + else if (mapnamelen == 2)/* maybe two digit code */ { if (( newmapnum = M_MapNumber(mapname[0], mapname[1]) )) usemapcode = true; From 4fc5c9af34806eaa726202d6e43e6fce704b28fe Mon Sep 17 00:00:00 2001 From: spherallic Date: Tue, 10 Oct 2023 16:36:52 +0200 Subject: [PATCH 097/227] Simplify single-character checks --- src/g_game.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index f415b45db..5eec1cd50 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -5379,9 +5379,9 @@ INT32 G_FindMapByNameOrCode(const char *mapname, char **realmapnamep) if (mapnamelen == 1) { - if (strcmp(mapname, "*") == 0) // current map + if (mapname[0] == '*') // current map return gamemap; - else if (strcmp(mapname, "+") == 0 && mapheaderinfo[gamemap-1]) // next map + else if (mapname[0] == '+' && mapheaderinfo[gamemap-1]) // next map { newmapnum = mapheaderinfo[gamemap-1]->nextlevel; if (newmapnum < 1 || newmapnum > NUMMAPS) From 9f116c7c9ef29b77e42df8ccae94087c81fc3285 Mon Sep 17 00:00:00 2001 From: LJ Sonic Date: Wed, 18 Oct 2023 16:50:06 +0200 Subject: [PATCH 098/227] Fix mouse events not being fired when the mouse is ungrabbed --- src/sdl/i_video.c | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c index 590d7d142..a70e5a860 100644 --- a/src/sdl/i_video.c +++ b/src/sdl/i_video.c @@ -382,10 +382,8 @@ static INT32 Impl_SDL_Scancode_To_Keycode(SDL_Scancode code) return 0; } -static boolean IgnoreMouse(void) +static boolean ShouldIgnoreMouse(void) { - if (cv_alwaysgrabmouse.value) - return false; if (menuactive) return !M_MouseNeeded(); if (paused || con_destlines || chat_on) @@ -393,11 +391,20 @@ static boolean IgnoreMouse(void) if (gamestate != GS_LEVEL && gamestate != GS_INTERMISSION && gamestate != GS_CONTINUING && gamestate != GS_CUTSCENE) return true; - if (!mousegrabbedbylua) - return true; return false; } +static boolean ShouldGrabMouse(void) +{ + if (cv_alwaysgrabmouse.value) + return true; + if (ShouldIgnoreMouse()) + return false; + if (!mousegrabbedbylua) + return false; + return true; +} + static void SDLdoGrabMouse(void) { SDL_ShowCursor(SDL_DISABLE); @@ -424,7 +431,7 @@ void I_UpdateMouseGrab(void) { if (SDL_WasInit(SDL_INIT_VIDEO) == SDL_INIT_VIDEO && window != NULL && SDL_GetMouseFocus() == window && SDL_GetKeyboardFocus() == window - && USE_MOUSEINPUT && !IgnoreMouse()) + && USE_MOUSEINPUT && ShouldGrabMouse()) SDLdoGrabMouse(); } @@ -640,7 +647,7 @@ static void Impl_HandleWindowEvent(SDL_WindowEvent evt) } //else firsttimeonmouse = SDL_FALSE; - if (USE_MOUSEINPUT && !IgnoreMouse()) + if (USE_MOUSEINPUT && ShouldGrabMouse()) SDLdoGrabMouse(); } else if (!mousefocus && !kbfocus) @@ -692,7 +699,7 @@ static void Impl_HandleMouseMotionEvent(SDL_MouseMotionEvent evt) if (USE_MOUSEINPUT) { - if ((SDL_GetMouseFocus() != window && SDL_GetKeyboardFocus() != window) || (IgnoreMouse() && !firstmove)) + if ((SDL_GetMouseFocus() != window && SDL_GetKeyboardFocus() != window) || (!ShouldGrabMouse() && !firstmove)) { SDLdoUngrabMouse(); firstmove = false; @@ -745,7 +752,7 @@ static void Impl_HandleMouseButtonEvent(SDL_MouseButtonEvent evt, Uint32 type) // this apparently makes a mouse button down event but not a mouse button up event, // resulting in whatever key was pressed down getting "stuck" if we don't ignore it. // -- Monster Iestyn (28/05/18) - if (SDL_GetMouseFocus() != window || IgnoreMouse()) + if (SDL_GetMouseFocus() != window || ShouldIgnoreMouse()) return; /// \todo inputEvent.button.which @@ -1127,7 +1134,7 @@ void I_StartupMouse(void) } else firsttimeonmouse = SDL_FALSE; - if (cv_usemouse.value && !IgnoreMouse()) + if (cv_usemouse.value && ShouldGrabMouse()) SDLdoGrabMouse(); else SDLdoUngrabMouse(); From 9d650b0ddca510f61d2d9f948bffb948175f5ba0 Mon Sep 17 00:00:00 2001 From: Alam Ed Arias Date: Wed, 25 Oct 2023 07:26:34 -0400 Subject: [PATCH 099/227] Makefile: add lua_colorlib.c --- src/Sourcefile | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Sourcefile b/src/Sourcefile index 6ed1f3b4c..7beb98c9e 100644 --- a/src/Sourcefile +++ b/src/Sourcefile @@ -94,3 +94,4 @@ lua_blockmaplib.c lua_hudlib.c lua_hudlib_drawlist.c lua_inputlib.c +lua_colorlib.c From 229d0364d4a8b37beec8e9f4851da6be426533b5 Mon Sep 17 00:00:00 2001 From: Alam Ed Arias Date: Wed, 25 Oct 2023 07:40:55 -0400 Subject: [PATCH 100/227] Fixed lookup sides for 32-bit systems --- src/lua_maplib.c | 5 ++++- src/p_setup.c | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lua_maplib.c b/src/lua_maplib.c index 7302c727a..2a02b1a24 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -1243,6 +1243,9 @@ static int side_get(lua_State *L) return 1; case side_text: { + boolean isfrontside; + size_t sidei = side-sides; + if (udmf) { LUA_Deprecated(L, "(sidedef_t).text", "(sidedef_t).line.stringargs"); @@ -1250,7 +1253,7 @@ static int side_get(lua_State *L) return 1; } - boolean isfrontside = side->line->sidenum[0] == side-sides; + isfrontside = side->line->sidenum[0] == sidei; lua_pushstring(L, side->line->stringargs[isfrontside ? 0 : 1]); return 1; diff --git a/src/p_setup.c b/src/p_setup.c index 733a47792..effd2c368 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -4005,7 +4005,7 @@ static void P_LinkMapData(void) if (!seg->sidedef) CorruptMapError(va("P_LinkMapData: seg->sidedef is NULL " "(subsector %s, firstline is %d)", sizeu1(i), ss->firstline)); - if (seg->sidedef - sides < 0 || seg->sidedef - sides > (UINT32)numsides) + if (seg->sidedef - sides < 0 || sidei > numsides) CorruptMapError(va("P_LinkMapData: seg->sidedef refers to sidedef %s of %s " "(subsector %s, firstline is %d)", sizeu1(sidei), sizeu2(numsides), sizeu3(i), ss->firstline)); From fd41bf188dc895a1039a1f8f12ebadadb728c3e7 Mon Sep 17 00:00:00 2001 From: Alam Ed Arias Date: Wed, 25 Oct 2023 12:16:10 -0400 Subject: [PATCH 101/227] sidenum[0] is an uint32_t, not uint16_t --- src/p_spec.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/p_spec.c b/src/p_spec.c index 73bb0da88..2dcd45f4d 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1796,7 +1796,7 @@ void P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller) if (trigid < 0 || trigid > 31) // limited by 32 bit variable { - CONS_Debug(DBG_GAMELOGIC, "Unlockable trigger (sidedef %hu): bad trigger ID %d\n", triggerline->sidenum[0], trigid); + CONS_Debug(DBG_GAMELOGIC, "Unlockable trigger (sidedef %u): bad trigger ID %d\n", triggerline->sidenum[0], trigid); return; } else if (!(unlocktriggers & (1 << trigid))) @@ -1809,7 +1809,7 @@ void P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller) if (unlockid <= 0 || unlockid > MAXUNLOCKABLES) // limited by unlockable count { - CONS_Debug(DBG_GAMELOGIC, "Unlockable check (sidedef %hu): bad unlockable ID %d\n", triggerline->sidenum[0], unlockid); + CONS_Debug(DBG_GAMELOGIC, "Unlockable check (sidedef %u): bad unlockable ID %d\n", triggerline->sidenum[0], unlockid); return; } else if (!(serverGamedata->unlocked[unlockid-1])) @@ -2940,7 +2940,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) INT32 trigid = line->args[0]; if (trigid < 0 || trigid > 31) // limited by 32 bit variable - CONS_Debug(DBG_GAMELOGIC, "Unlockable trigger (sidedef %hu): bad trigger ID %d\n", line->sidenum[0], trigid); + CONS_Debug(DBG_GAMELOGIC, "Unlockable trigger (sidedef %u): bad trigger ID %d\n", line->sidenum[0], trigid); else { unlocktriggers |= 1 << trigid; From b404c965a6e3d73dc8903cc2f3c99b00578e385d Mon Sep 17 00:00:00 2001 From: Alam Ed Arias Date: Thu, 26 Oct 2023 09:47:50 +0000 Subject: [PATCH 102/227] Update lua_hooklib.c You need to "void" the functions --- src/lua_hooklib.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c index 00237217d..2e17a0dea 100644 --- a/src/lua_hooklib.c +++ b/src/lua_hooklib.c @@ -718,17 +718,17 @@ static void hook_think_frame(int type) } } -void LUA_HookPreThinkFrame() +void LUA_HookPreThinkFrame(void) { hook_think_frame(HOOK(PreThinkFrame)); } -void LUA_HookThinkFrame() +void LUA_HookThinkFrame(void) { hook_think_frame(HOOK(ThinkFrame)); } -void LUA_HookPostThinkFrame() +void LUA_HookPostThinkFrame(void) { hook_think_frame(HOOK(PostThinkFrame)); } From 13055a1ae4e14f381778e901614984f2dd95fbd4 Mon Sep 17 00:00:00 2001 From: Alam Ed Arias Date: Thu, 26 Oct 2023 13:15:36 +0000 Subject: [PATCH 103/227] Update filesrch.c Check if S_ISLNK is defined, if not, skip symlink code --- src/filesrch.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/filesrch.c b/src/filesrch.c index 9977d69c3..3b6abdf88 100644 --- a/src/filesrch.c +++ b/src/filesrch.c @@ -23,6 +23,11 @@ #include #endif #include + +#ifndef S_ISLNK +#define IGNORE_SYMLINKS +#endif + #ifndef IGNORE_SYMLINKS #include #include @@ -467,8 +472,10 @@ filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *want else strcpy(filename,dent->d_name); #ifndef IGNORE_SYMLINKS - if (lstat(filename, &statbuf) != -1) { - if (S_ISLNK(statbuf.st_mode)) { + if (lstat(filename, &statbuf) != -1) + { + if (S_ISLNK(statbuf.st_mode)) + { char *tempbuf = realpath(filename, NULL); if (!tempbuf) I_Error("Error parsing link %s: %s", filename, strerror(errno)); From 6613886198ddcc5915098c47196094de0774ef58 Mon Sep 17 00:00:00 2001 From: Alam Ed Arias Date: Thu, 26 Oct 2023 18:36:43 +0000 Subject: [PATCH 104/227] Update v_video.c Killed `unused variable` warning --- src/v_video.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/v_video.c b/src/v_video.c index e599b2799..477355f9a 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -1179,7 +1179,6 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c) const UINT8 *deststop; UINT32 alphalevel = ((c & V_ALPHAMASK) >> V_ALPHASHIFT); UINT32 blendmode = ((c & V_BLENDMASK) >> V_BLENDSHIFT); - INT32 u; UINT8 perplayershuffle = 0; From b98d9dfe522fa17ecae47632543fc1b2ce16d121 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Thu, 26 Oct 2023 23:43:07 +0200 Subject: [PATCH 105/227] Use nanosleep for I_SleepDuration on *nix --- src/i_time.c | 35 ----------------------------------- src/sdl/i_system.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 35 deletions(-) diff --git a/src/i_time.c b/src/i_time.c index fae26abed..aa18f6e36 100644 --- a/src/i_time.c +++ b/src/i_time.c @@ -88,38 +88,3 @@ void I_UpdateTime(fixed_t timescale) g_time.timefrac = FLOAT_TO_FIXED(fractional); } } - -void I_SleepDuration(precise_t duration) -{ - UINT64 precision = I_GetPrecisePrecision(); - INT32 sleepvalue = cv_sleep.value; - UINT64 delaygranularity; - precise_t cur; - precise_t dest; - - { - double gran = round(((double)(precision / 1000) * sleepvalue * MIN_SLEEP_DURATION_MS)); - delaygranularity = (UINT64)gran; - } - - cur = I_GetPreciseTime(); - dest = cur + duration; - - // the reason this is not dest > cur is because the precise counter may wrap - // two's complement arithmetic is our friend here, though! - // e.g. cur 0xFFFFFFFFFFFFFFFE = -2, dest 0x0000000000000001 = 1 - // 0x0000000000000001 - 0xFFFFFFFFFFFFFFFE = 3 - while ((INT64)(dest - cur) > 0) - { - // If our cv_sleep value exceeds the remaining sleep duration, use the - // hard sleep function. - if (sleepvalue > 0 && (dest - cur) > delaygranularity) - { - I_Sleep(sleepvalue); - } - - // Otherwise, this is a spinloop. - - cur = I_GetPreciseTime(); - } -} diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index b05f40ee3..acc57f8d2 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -2265,6 +2265,52 @@ void I_Sleep(UINT32 ms) SDL_Delay(ms); } +void I_SleepDuration(precise_t duration) +{ +#if defined(__linux__) || defined(__FreeBSD__) + UINT64 precision = I_GetPrecisePrecision(); + struct timespec ts = { + .tv_sec = duration / precision, + .tv_nsec = duration * 1000000000 / precision % 1000000000, + }; + int status; + do status = clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, &ts); + while (status == EINTR); +#else + UINT64 precision = I_GetPrecisePrecision(); + INT32 sleepvalue = cv_sleep.value; + UINT64 delaygranularity; + precise_t cur; + precise_t dest; + + { + double gran = round(((double)(precision / 1000) * sleepvalue * MIN_SLEEP_DURATION_MS)); + delaygranularity = (UINT64)gran; + } + + cur = I_GetPreciseTime(); + dest = cur + duration; + + // the reason this is not dest > cur is because the precise counter may wrap + // two's complement arithmetic is our friend here, though! + // e.g. cur 0xFFFFFFFFFFFFFFFE = -2, dest 0x0000000000000001 = 1 + // 0x0000000000000001 - 0xFFFFFFFFFFFFFFFE = 3 + while ((INT64)(dest - cur) > 0) + { + // If our cv_sleep value exceeds the remaining sleep duration, use the + // hard sleep function. + if (sleepvalue > 0 && (dest - cur) > delaygranularity) + { + I_Sleep(sleepvalue); + } + + // Otherwise, this is a spinloop. + + cur = I_GetPreciseTime(); + } +#endif +} + #ifdef NEWSIGNALHANDLER ATTRNORETURN static FUNCNORETURN void newsignalhandler_Warn(const char *pr) { From e264caffe3599f37c119aeac91c3e54f16bce9d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Fri, 2 Jun 2023 23:44:50 +0200 Subject: [PATCH 106/227] Fix dangling pointer in mapthing after removing mobj --- src/p_mobj.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/p_mobj.c b/src/p_mobj.c index a81845918..5c352cd04 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -11204,6 +11204,10 @@ void P_RemoveMobj(mobj_t *mobj) P_SetTarget(&mobj->hnext, P_SetTarget(&mobj->hprev, NULL)); + // clear the reference from the mapthing + if (mobj->spawnpoint) + mobj->spawnpoint->mobj = NULL; + R_RemoveMobjInterpolator(mobj); // free block From e92787e75f7a773f78222d37d411bf2fde8c35a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Sat, 28 Oct 2023 15:44:45 +0200 Subject: [PATCH 107/227] Fix Windows build --- src/i_time.c | 6 ------ src/sdl/i_system.c | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/i_time.c b/src/i_time.c index aa18f6e36..39854b242 100644 --- a/src/i_time.c +++ b/src/i_time.c @@ -30,12 +30,6 @@ static precise_t enterprecise, oldenterprecise; static fixed_t entertic, oldentertics; static double tictimer; -// A little more than the minimum sleep duration on Windows. -// May be incorrect for other platforms, but we don't currently have a way to -// query the scheduler granularity. SDL will do what's needed to make this as -// low as possible though. -#define MIN_SLEEP_DURATION_MS 2.1 - tic_t I_GetTime(void) { return g_time.time; diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index acc57f8d2..3da4e6152 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -41,6 +41,12 @@ typedef LPVOID (WINAPI *p_MapViewOfFile) (HANDLE, DWORD, DWORD, DWORD, SIZE_T); #include #undef SystemFunction036 +// A little more than the minimum sleep duration on Windows. +// May be incorrect for other platforms, but we don't currently have a way to +// query the scheduler granularity. SDL will do what's needed to make this as +// low as possible though. +#define MIN_SLEEP_DURATION_MS 2.1 + #endif #include #include From d6b92de6910fccb845820fb206e3af3d7979270b Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Tue, 31 Oct 2023 16:52:12 -0300 Subject: [PATCH 108/227] Revert these --- src/p_saveg.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/p_saveg.c b/src/p_saveg.c index 3bcfea7cd..b496dce50 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -1893,7 +1893,7 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type) if (diff & MD_RTIME) WRITEINT32(save_p, mobj->reactiontime); if (diff & MD_STATE) - WRITEUINT32(save_p, mobj->state-states); + WRITEUINT16(save_p, mobj->state-states); if (diff & MD_TICS) WRITEINT32(save_p, mobj->tics); if (diff & MD_SPRITE) { @@ -2917,7 +2917,7 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker) mobj->reactiontime = mobj->info->reactiontime; if (diff & MD_STATE) - mobj->state = &states[READUINT32(save_p)]; + mobj->state = &states[READUINT16(save_p)]; else mobj->state = &states[mobj->info->spawnstate]; if (diff & MD_TICS) From 9013ceeacc9660fbe4f3dd2249a061dc77211929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Wed, 1 Nov 2023 17:31:17 +0100 Subject: [PATCH 109/227] Fix buffer overflow when when fetching typenames on freeslots --- src/p_tick.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/p_tick.c b/src/p_tick.c index 444b68d2f..b0155d780 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -236,6 +236,8 @@ static const char *MobjTypeName(const mobj_t *mobj) { if (mobj->thinker.debug_mobjtype != MT_NULL) { + if (mobj->thinker.debug_mobjtype >= MT_FIRSTFREESLOT) + return "MT_FREESLOT"; return MOBJTYPE_LIST[mobj->thinker.debug_mobjtype]; } } From 7e7ff84f0806906f2019aedb8ab6d48d18744075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Wed, 1 Nov 2023 17:18:26 +0100 Subject: [PATCH 110/227] Fix buffer overflow when tag bits are set --- src/taglist.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/taglist.c b/src/taglist.c index e4e385b9e..7bd004627 100644 --- a/src/taglist.c +++ b/src/taglist.c @@ -180,10 +180,10 @@ void Taggroup_Add (taggroup_t *garray[], const mtag_t tag, size_t id) if (Taggroup_Find(group, id) != (size_t)-1) return; - if (! in_bit_array(tags_available, tag)) + if (! in_bit_array(tags_available, (UINT16)tag)) { num_tags++; - set_bit_array(tags_available, tag); + set_bit_array(tags_available, (UINT16)tag); } // Create group if empty. @@ -220,10 +220,10 @@ static void Taggroup_Add_Init(taggroup_t *garray[], const mtag_t tag, size_t id) group = garray[(UINT16)tag]; - if (! in_bit_array(tags_available, tag)) + if (! in_bit_array(tags_available, (UINT16)tag)) { num_tags++; - set_bit_array(tags_available, tag); + set_bit_array(tags_available, (UINT16)tag); } // Create group if empty. @@ -271,7 +271,7 @@ void Taggroup_Remove (taggroup_t *garray[], const mtag_t tag, size_t id) if (group->count == 1 && total_elements_with_tag(tag) == 1) { num_tags--; - unset_bit_array(tags_available, tag); + unset_bit_array(tags_available, (UINT16)tag); } // Strip away taggroup if no elements left. From ede41c474b11767ac584957b944a95a5e8201744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Wed, 1 Nov 2023 18:30:37 +0100 Subject: [PATCH 111/227] Pass -fwrapv to GCC compilation flags --- src/Makefile.d/versions.mk | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Makefile.d/versions.mk b/src/Makefile.d/versions.mk index d2877b374..73d352138 100644 --- a/src/Makefile.d/versions.mk +++ b/src/Makefile.d/versions.mk @@ -160,6 +160,10 @@ opts+=-O0 endif endif +ifdef GCC45 +opts+=-fwrapv +endif + ifdef VALGRIND ifdef GCC46 WFLAGS+=-Wno-error=unused-but-set-variable From 6d650728f6594d751de6291b40b567f50204bc90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Thu, 2 Nov 2023 21:58:36 +0100 Subject: [PATCH 112/227] Update CMake and XCode build systems --- src/CMakeLists.txt | 4 ++++ src/Makefile.d/detect.mk | 3 ++- src/Makefile.d/versions.mk | 2 +- src/sdl/macosx/Srb2mac.pbproj/project.pbxproj | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 22c1def27..5fbc7002a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -260,6 +260,10 @@ target_compile_options(SRB2SDL2 PRIVATE -Wdisabled-optimization > + $<$,3.4.0>: + -fwrapv + > + $<$,4.0.0>: -Wold-style-definition -Wmissing-field-initializers diff --git a/src/Makefile.d/detect.mk b/src/Makefile.d/detect.mk index 9e2736946..3d9fc2766 100644 --- a/src/Makefile.d/detect.mk +++ b/src/Makefile.d/detect.mk @@ -65,7 +65,8 @@ gcc_versions:=\ 75 74 73 72 71 70\ 64 63 62 61 60\ 55 54 53 52 51 50\ - 49 48 47 46 45 44 43 42 41 40 + 49 48 47 46 45 44 43 42 41 40\ + 34 latest_gcc_version:=13.2 diff --git a/src/Makefile.d/versions.mk b/src/Makefile.d/versions.mk index 73d352138..40ccd9f26 100644 --- a/src/Makefile.d/versions.mk +++ b/src/Makefile.d/versions.mk @@ -160,7 +160,7 @@ opts+=-O0 endif endif -ifdef GCC45 +ifdef GCC34 opts+=-fwrapv endif diff --git a/src/sdl/macosx/Srb2mac.pbproj/project.pbxproj b/src/sdl/macosx/Srb2mac.pbproj/project.pbxproj index 909bb2ced..40f580be1 100644 --- a/src/sdl/macosx/Srb2mac.pbproj/project.pbxproj +++ b/src/sdl/macosx/Srb2mac.pbproj/project.pbxproj @@ -2133,7 +2133,7 @@ INSTALL_PATH = "$(HOME)/Applications"; JAVA_COMPILER_DEBUGGING_SYMBOLS = NO; OPTIMIZATION_CFLAGS = "-O2"; - OTHER_CFLAGS = "-DMAC_ALERT -DUNIXCOMMON -DSDLMAIN -DHAVE_MIXER -DHAVE_PNG -D_BIG_ENDIAN -DSTDC_HEADERS -DSDL -Wall -Winline -fno-strict-aliasing"; + OTHER_CFLAGS = "-DMAC_ALERT -DUNIXCOMMON -DSDLMAIN -DHAVE_MIXER -DHAVE_PNG -D_BIG_ENDIAN -DSTDC_HEADERS -DSDL -Wall -Winline -fno-strict-aliasing -fwrapv"; OTHER_REZFLAGS = ""; PREBINDING = NO; PRODUCT_NAME = Srb2; From 7e8c0d87a2d616e5e916b230623a44c1248164ce Mon Sep 17 00:00:00 2001 From: Zwip-Zwap Zapony Date: Thu, 2 Nov 2023 21:21:05 +0000 Subject: [PATCH 113/227] Return freeslot name instead of MT_FREESLOT (thanks Zwip-Zwap_Zapony) --- src/p_tick.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/p_tick.c b/src/p_tick.c index b0155d780..1c658bc1e 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -226,23 +226,22 @@ void P_AddThinker(const thinklistnum_t n, thinker_t *thinker) #ifdef PARANOIA static const char *MobjTypeName(const mobj_t *mobj) { + mobjtype_t type; actionf_p1 p1 = mobj->thinker.function.acp1; if (p1 == (actionf_p1)P_MobjThinker) - { - return MOBJTYPE_LIST[mobj->type]; - } - else if (p1 == (actionf_p1)P_RemoveThinkerDelayed) - { - if (mobj->thinker.debug_mobjtype != MT_NULL) - { - if (mobj->thinker.debug_mobjtype >= MT_FIRSTFREESLOT) - return "MT_FREESLOT"; - return MOBJTYPE_LIST[mobj->thinker.debug_mobjtype]; - } - } + type = mobj->type; + else if (p1 == (actionf_p1)P_RemoveThinkerDelayed && mobj->thinker.debug_mobjtype != MT_NULL) + type = mobj->thinker.debug_mobjtype; + else + return ""; - return ""; + if (type < 0 || type >= NUMMOBJTYPES || (type >= MT_FIRSTFREESLOT && !FREE_MOBJS[type - MT_FIRSTFREESLOT])) + return ""; + else if (type >= MT_FIRSTFREESLOT) + return FREE_MOBJS[type - MT_FIRSTFREESLOT]; // This doesn't include "MT_"... + else + return MOBJTYPE_LIST[type]; } static const char *MobjThinkerName(const mobj_t *mobj) From 80475adabf6fb7625b0b955dc8c5eeb18006757d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Sat, 4 Nov 2023 11:48:20 +0100 Subject: [PATCH 114/227] Always apply -fwrapv on builds --- src/CMakeLists.txt | 6 ++---- src/Makefile | 2 +- src/Makefile.d/detect.mk | 3 +-- src/Makefile.d/versions.mk | 4 ---- 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5fbc7002a..405fa9572 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -253,6 +253,7 @@ target_compile_options(SRB2SDL2 PRIVATE -Winline -Wformat-y2k -Wformat-security + -fwrapv $<$,2.9.5>: -Wno-div-by-zero @@ -260,10 +261,6 @@ target_compile_options(SRB2SDL2 PRIVATE -Wdisabled-optimization > - $<$,3.4.0>: - -fwrapv - > - $<$,4.0.0>: -Wold-style-definition -Wmissing-field-initializers @@ -326,6 +323,7 @@ target_compile_options(SRB2SDL2 PRIVATE -Wno-error=non-literal-null-conversion -Wno-error=constant-conversion -Wno-error=unused-but-set-variable + -fwrapv > # C, MSVC diff --git a/src/Makefile b/src/Makefile index a0a18be76..b730cd0bb 100644 --- a/src/Makefile +++ b/src/Makefile @@ -164,7 +164,7 @@ sources:= makedir:=../make # -DCOMPVERSION: flag to use comptime.h -opts:=-DCOMPVERSION -g +opts:=-DCOMPVERSION -g -fwrapv libs:= # This is a list of variables names, of which if defined, diff --git a/src/Makefile.d/detect.mk b/src/Makefile.d/detect.mk index 3d9fc2766..9e2736946 100644 --- a/src/Makefile.d/detect.mk +++ b/src/Makefile.d/detect.mk @@ -65,8 +65,7 @@ gcc_versions:=\ 75 74 73 72 71 70\ 64 63 62 61 60\ 55 54 53 52 51 50\ - 49 48 47 46 45 44 43 42 41 40\ - 34 + 49 48 47 46 45 44 43 42 41 40 latest_gcc_version:=13.2 diff --git a/src/Makefile.d/versions.mk b/src/Makefile.d/versions.mk index 40ccd9f26..d2877b374 100644 --- a/src/Makefile.d/versions.mk +++ b/src/Makefile.d/versions.mk @@ -160,10 +160,6 @@ opts+=-O0 endif endif -ifdef GCC34 -opts+=-fwrapv -endif - ifdef VALGRIND ifdef GCC46 WFLAGS+=-Wno-error=unused-but-set-variable From 5deee87bd293430a201e2ced5748d07791c18a30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Sat, 4 Nov 2023 19:12:23 +0100 Subject: [PATCH 115/227] Fix keyhandler menus not blocking in-game movement --- src/m_menu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/m_menu.c b/src/m_menu.c index 56d82eed2..68974d61a 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -3376,7 +3376,7 @@ boolean M_Responder(event_t *ev) // ignore ev_keydown events if the key maps to a character, since // the ev_text event will follow immediately after in that case. if (ev->type == ev_keydown && ch >= 32 && ch <= 127) - return false; + return true; routine(ch); return true; From 4cf1d7fe10ad2dee57c59679a74e1148c38f3779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Sun, 5 Nov 2023 13:52:23 +0100 Subject: [PATCH 116/227] Cache and reuse removed mobjs when spawning mobjs --- src/d_think.h | 1 + src/p_local.h | 1 + src/p_mobj.c | 17 +++++++++++++++-- src/p_setup.c | 1 + src/p_tick.c | 12 +++++++++++- 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/d_think.h b/src/d_think.h index efc1589bf..589124587 100644 --- a/src/d_think.h +++ b/src/d_think.h @@ -51,6 +51,7 @@ typedef struct thinker_s // killough 11/98: count of how many other objects reference // this one using pointers. Used for garbage collection. INT32 references; + boolean cachable; #ifdef PARANOIA INT32 debug_mobjtype; diff --git a/src/p_local.h b/src/p_local.h index c26c09860..70eb3435e 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -71,6 +71,7 @@ typedef enum NUM_THINKERLISTS } thinklistnum_t; /**< Thinker lists. */ extern thinker_t thlist[]; +extern mobj_t *mobjcache; void P_InitThinkers(void); void P_AddThinker(const thinklistnum_t n, thinker_t *thinker); diff --git a/src/p_mobj.c b/src/p_mobj.c index 7a5aaf424..f424fba06 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -45,6 +45,8 @@ actioncache_t actioncachehead; static mobj_t *overlaycap = NULL; +mobj_t *mobjcache = NULL; + void P_InitCachedActions(void) { actioncachehead.prev = actioncachehead.next = &actioncachehead; @@ -10659,7 +10661,16 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) type = MT_RAY; } - mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL); + if (mobjcache != NULL) + { + mobj = mobjcache; + mobjcache = mobjcache->hnext; + memset(mobj, 0, sizeof(*mobj)); + } + else + { + mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL); + } // this is officially a mobj, declared as soon as possible. mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker; @@ -11214,7 +11225,9 @@ void P_RemoveMobj(mobj_t *mobj) INT32 prevreferences; if (!mobj->thinker.references) { - Z_Free(mobj); // No refrrences? Can be removed immediately! :D + // no references, dump it directly in the mobj cache + mobj->hnext = mobjcache; + mobjcache = mobj; return; } diff --git a/src/p_setup.c b/src/p_setup.c index 7f6fcd36c..ab6b68bd1 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7806,6 +7806,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) Patch_FreeTag(PU_PATCH_LOWPRIORITY); Patch_FreeTag(PU_PATCH_ROTATED); Z_FreeTags(PU_LEVEL, PU_PURGELEVEL - 1); + mobjcache = NULL; R_InitializeLevelInterpolators(); diff --git a/src/p_tick.c b/src/p_tick.c index 444b68d2f..c19f901e3 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -217,6 +217,7 @@ void P_AddThinker(const thinklistnum_t n, thinker_t *thinker) thlist[n].prev = thinker; thinker->references = 0; // killough 11/98: init reference counter to 0 + thinker->cachable = n == THINK_MOBJ; #ifdef PARANOIA thinker->debug_mobjtype = MT_NULL; @@ -319,7 +320,16 @@ void P_RemoveThinkerDelayed(thinker_t *thinker) (next->prev = currentthinker = thinker->prev)->next = next; R_DestroyLevelInterpolators(thinker); - Z_Free(thinker); + if (thinker->cachable) + { + // put cachable thinkers in the mobj cache, so we can avoid allocations + ((mobj_t *)thinker)->hnext = mobjcache; + mobjcache = (mobj_t *)thinker; + } + else + { + Z_Free(thinker); + } } // From b10b9c35ed3af0a72c0547e413a8abca49cc9776 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Mon, 6 Nov 2023 19:09:43 +0100 Subject: [PATCH 117/227] Add support for multiple admin passwords --- src/netcode/d_clisrv.c | 27 ++++++++++++++++----------- src/netcode/d_clisrv.h | 4 ++-- src/netcode/d_netcmd.c | 35 +++++++++++++++++++++++++++++++---- src/netcode/d_netcmd.h | 1 + 4 files changed, 50 insertions(+), 17 deletions(-) diff --git a/src/netcode/d_clisrv.c b/src/netcode/d_clisrv.c index 7804b068f..71b87d28c 100644 --- a/src/netcode/d_clisrv.c +++ b/src/netcode/d_clisrv.c @@ -90,8 +90,8 @@ INT16 consistancy[BACKUPTICS]; // true when a player is connecting or disconnecting so that the gameplay has stopped in its tracks boolean hu_stopped = false; -UINT8 adminpassmd5[16]; -boolean adminpasswordset = false; +UINT8 (*adminpassmd5)[16]; +UINT32 adminpasscount = 0; tic_t neededtic; SINT8 servernode = 0; // the number of the server node @@ -862,26 +862,31 @@ static void PT_Login(SINT8 node, INT32 netconsole) #ifndef NOMD5 UINT8 finalmd5[16];/* Well, it's the cool thing to do? */ + UINT32 i; if (doomcom->datalength < 16)/* ignore partial sends */ return; - if (!adminpasswordset) + if (adminpasscount == 0) { CONS_Printf(M_GetText("Password from %s failed (no password set).\n"), player_names[netconsole]); return; } - // Do the final pass to compare with the sent md5 - D_MD5PasswordPass(adminpassmd5, 16, va("PNUM%02d", netconsole), &finalmd5); - - if (!memcmp(netbuffer->u.md5sum, finalmd5, 16)) + for (i = 0; i < adminpasscount; i++) { - CONS_Printf(M_GetText("%s passed authentication.\n"), player_names[netconsole]); - COM_BufInsertText(va("promote %d\n", netconsole)); // do this immediately + // Do the final pass to compare with the sent md5 + D_MD5PasswordPass(adminpassmd5[i], 16, va("PNUM%02d", netconsole), &finalmd5); + + if (!memcmp(netbuffer->u.md5sum, finalmd5, 16)) + { + CONS_Printf(M_GetText("%s passed authentication.\n"), player_names[netconsole]); + COM_BufInsertText(va("promote %d\n", netconsole)); // do this immediately + return; + } } - else - CONS_Printf(M_GetText("Password from %s failed.\n"), player_names[netconsole]); + + CONS_Printf(M_GetText("Password from %s failed.\n"), player_names[netconsole]); #else (void)netconsole; #endif diff --git a/src/netcode/d_clisrv.h b/src/netcode/d_clisrv.h index db9a780cd..53ebfb15b 100644 --- a/src/netcode/d_clisrv.h +++ b/src/netcode/d_clisrv.h @@ -127,8 +127,8 @@ tic_t GetLag(INT32 node); void D_MD5PasswordPass(const UINT8 *buffer, size_t len, const char *salt, void *dest); -extern UINT8 adminpassmd5[16]; -extern boolean adminpasswordset; +extern UINT8 (*adminpassmd5)[16]; +extern UINT32 adminpasscount; extern boolean hu_stopped; diff --git a/src/netcode/d_netcmd.c b/src/netcode/d_netcmd.c index 9b87e8cfe..bfacc26fa 100644 --- a/src/netcode/d_netcmd.c +++ b/src/netcode/d_netcmd.c @@ -150,6 +150,7 @@ static void Command_Clearscores_f(void); // Remote Administration static void Command_Changepassword_f(void); +static void Command_Clearpassword_f(void); static void Command_Login_f(void); static void Got_Verification(UINT8 **cp, INT32 playernum); static void Got_Removal(UINT8 **cp, INT32 playernum); @@ -468,6 +469,7 @@ void D_RegisterServerCommands(void) // Remote Administration COM_AddCommand("password", Command_Changepassword_f, COM_LUA); + COM_AddCommand("clearpassword", Command_Clearpassword_f, COM_LUA); COM_AddCommand("login", Command_Login_f, COM_LUA); // useful in dedicated to kick off remote admin COM_AddCommand("promote", Command_Verify_f, COM_LUA); RegisterNetXCmd(XD_VERIFIED, Got_Verification); @@ -2841,8 +2843,15 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum) void D_SetPassword(const char *pw) { - D_MD5PasswordPass((const UINT8 *)pw, strlen(pw), BASESALT, &adminpassmd5); - adminpasswordset = true; + adminpassmd5 = Z_Realloc(adminpassmd5, sizeof(*adminpassmd5) * ++adminpasscount, PU_STATIC, NULL); + D_MD5PasswordPass((const UINT8 *)pw, strlen(pw), BASESALT, &adminpassmd5[adminpasscount-1]); +} + +void D_ClearPassword(void) +{ + Z_Free(adminpassmd5); + adminpassmd5 = NULL; + adminpasscount = 0; } // Remote Administration @@ -2860,12 +2869,30 @@ static void Command_Changepassword_f(void) if (COM_Argc() != 2) { - CONS_Printf(M_GetText("password : change remote admin password\n")); + CONS_Printf(M_GetText("password : add remote admin password\n")); return; } D_SetPassword(COM_Argv(1)); - CONS_Printf(M_GetText("Password set.\n")); + CONS_Printf(M_GetText("Password added.\n")); +#endif +} + +// Remote Administration +static void Command_Clearpassword_f(void) +{ +#ifdef NOMD5 + // If we have no MD5 support then completely disable XD_LOGIN responses for security. + CONS_Alert(CONS_NOTICE, "Remote administration commands are not supported in this build.\n"); +#else + if (client) // cannot change remotely + { + CONS_Printf(M_GetText("Only the server can use this.\n")); + return; + } + + D_ClearPassword(); + CONS_Printf(M_GetText("Passwords cleared.\n")); #endif } diff --git a/src/netcode/d_netcmd.h b/src/netcode/d_netcmd.h index 4849079d0..0f2a1f92b 100644 --- a/src/netcode/d_netcmd.h +++ b/src/netcode/d_netcmd.h @@ -209,6 +209,7 @@ void ClearAdminPlayers(void); void RemoveAdminPlayer(INT32 playernum); void ItemFinder_OnChange(void); void D_SetPassword(const char *pw); +void D_ClearPassword(void); // used for the player setup menu UINT8 CanChangeSkin(INT32 playernum); From c6cd14c1565dff1d10d92c41032a7c074c3bf403 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Sat, 11 Nov 2023 14:12:51 +0100 Subject: [PATCH 118/227] Allow specifying interface for IPv6 addresses --- src/m_menu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/m_menu.c b/src/m_menu.c index 629f53d24..c0e8df02c 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -11947,7 +11947,7 @@ static void M_HandleConnectIP(INT32 choice) // Rudimentary number and period enforcing - also allows letters so hostnames can be used instead // and square brackets for RFC 2732 IPv6 addresses if ((choice >= '-' && choice <= ':') || - (choice == '[' || choice == ']') || + (choice == '[' || choice == ']' || choice == '%') || (choice >= 'A' && choice <= 'Z') || (choice >= 'a' && choice <= 'z')) { From 2450a0df70c80629a399ebd9f79e3e3bd838a9b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Sat, 11 Nov 2023 19:09:39 +0100 Subject: [PATCH 119/227] Do not send disconnected players' statuses to the MS --- src/netcode/d_clisrv.c | 11 +++++++++++ src/netcode/d_clisrv.h | 1 + src/netcode/server_connection.c | 4 ++-- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/netcode/d_clisrv.c b/src/netcode/d_clisrv.c index 7804b068f..ac0b42d49 100644 --- a/src/netcode/d_clisrv.c +++ b/src/netcode/d_clisrv.c @@ -1618,6 +1618,17 @@ INT32 D_NumPlayers(void) return num; } +/** Returns the number of nodes on the server. + */ +INT32 D_NumNodes(void) +{ + INT32 num = 0; + for (INT32 ix = 0; ix < MAXNETNODES; ix++) + if (netnodes[ix].ingame) + num++; + return num; +} + /** Similar to the above, but counts only bots. * Purpose is to remove bots from both the player count and the * max player count on the server view diff --git a/src/netcode/d_clisrv.h b/src/netcode/d_clisrv.h index db9a780cd..092878421 100644 --- a/src/netcode/d_clisrv.h +++ b/src/netcode/d_clisrv.h @@ -121,6 +121,7 @@ extern char motd[254], server_context[8]; extern UINT8 playernode[MAXPLAYERS]; INT32 D_NumPlayers(void); +INT32 D_NumNodes(void); INT32 D_NumBots(void); tic_t GetLag(INT32 node); diff --git a/src/netcode/server_connection.c b/src/netcode/server_connection.c index 2164f411a..258d4cce4 100644 --- a/src/netcode/server_connection.c +++ b/src/netcode/server_connection.c @@ -109,7 +109,7 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime) netbuffer->u.serverinfo.leveltime = (tic_t)LONG(leveltime); // Exclude bots from both counts - netbuffer->u.serverinfo.numberofplayer = (UINT8)(D_NumPlayers() - D_NumBots()); + netbuffer->u.serverinfo.numberofplayer = (UINT8)D_NumNodes(); netbuffer->u.serverinfo.maxplayer = (UINT8)(cv_maxplayers.value - D_NumBots()); netbuffer->u.serverinfo.refusereason = GetRefuseReason(node); @@ -164,7 +164,7 @@ static void SV_SendPlayerInfo(INT32 node) for (UINT8 i = 0; i < MAXPLAYERS; i++) { - if (!playeringame[i]) + if (!netnodes[playernode[i]].ingame) { netbuffer->u.playerinfo[i].num = 255; // This slot is empty. continue; From 6a6b65ddc45cc0c7d2964e7cd76c7214a5d23131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Thu, 16 Nov 2023 21:03:06 +0100 Subject: [PATCH 120/227] Optimize Z position functions on sloped sectors --- src/p_mobj.c | 8 ++++---- src/r_main.c | 28 ++++++++++++++++++++++++++++ src/r_main.h | 1 + 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index 7a5aaf424..2f9702d1c 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -1109,7 +1109,7 @@ fixed_t P_MobjFloorZ(mobj_t *mobj, sector_t *sector, sector_t *boundsec, fixed_t testy += y; // If the highest point is in the sector, then we have it easy! Just get the Z at that point - if (R_PointInSubsector(testx, testy)->sector == (boundsec ? boundsec : sector)) + if (R_IsPointInSector(boundsec ? boundsec : sector, testx, testy)) return P_GetSlopeZAt(slope, testx, testy); // If boundsec is set, we're looking for specials. In that case, iterate over every line in this sector to find the TRUE highest/lowest point @@ -1186,7 +1186,7 @@ fixed_t P_MobjCeilingZ(mobj_t *mobj, sector_t *sector, sector_t *boundsec, fixed testy += y; // If the highest point is in the sector, then we have it easy! Just get the Z at that point - if (R_PointInSubsector(testx, testy)->sector == (boundsec ? boundsec : sector)) + if (R_IsPointInSector(boundsec ? boundsec : sector, testx, testy)) return P_GetSlopeZAt(slope, testx, testy); // If boundsec is set, we're looking for specials. In that case, iterate over every line in this sector to find the TRUE highest/lowest point @@ -1264,7 +1264,7 @@ fixed_t P_CameraFloorZ(camera_t *mobj, sector_t *sector, sector_t *boundsec, fix testy += y; // If the highest point is in the sector, then we have it easy! Just get the Z at that point - if (R_PointInSubsector(testx, testy)->sector == (boundsec ? boundsec : sector)) + if (R_IsPointInSector(boundsec ? boundsec : sector, testx, testy)) return P_GetSlopeZAt(slope, testx, testy); // If boundsec is set, we're looking for specials. In that case, iterate over every line in this sector to find the TRUE highest/lowest point @@ -1341,7 +1341,7 @@ fixed_t P_CameraCeilingZ(camera_t *mobj, sector_t *sector, sector_t *boundsec, f testy += y; // If the highest point is in the sector, then we have it easy! Just get the Z at that point - if (R_PointInSubsector(testx, testy)->sector == (boundsec ? boundsec : sector)) + if (R_IsPointInSector(boundsec ? boundsec : sector, testx, testy)) return P_GetSlopeZAt(slope, testx, testy); // If boundsec is set, we're looking for specials. In that case, iterate over every line in this sector to find the TRUE highest/lowest point diff --git a/src/r_main.c b/src/r_main.c index 6c7bedbf1..aab6ed620 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -1027,6 +1027,34 @@ void R_Init(void) framecount = 0; } +// +// R_IsPointInSector +// +boolean R_IsPointInSector(sector_t *sector, fixed_t x, fixed_t y) +{ + size_t i; + line_t *closest = NULL; + fixed_t closestdist = 0x7fffffff; + + for (i = 0; i < sector->linecount; i++) + { + vertex_t v; + fixed_t dist; + + // find the line closest to the point we're looking for. + P_ClosestPointOnLine(x, y, sector->lines[i], &v); + dist = R_PointToDist2(0, 0, v.x - x, v.y - y); + if (dist < closestdist) + { + closest = sector->lines[i]; + closestdist = dist; + } + } + + // if the side of the closest line is in this sector, we're inside of it. + return P_PointOnLineSide(x, y, closest) == 0 ? closest->frontsector == sector : closest->backsector == sector; +} + // // R_PointInSubsector // diff --git a/src/r_main.h b/src/r_main.h index a6fb42ba2..02c640b51 100644 --- a/src/r_main.h +++ b/src/r_main.h @@ -79,6 +79,7 @@ fixed_t R_PointToDist(fixed_t x, fixed_t y); fixed_t R_PointToDist2(fixed_t px2, fixed_t py2, fixed_t px1, fixed_t py1); fixed_t R_ScaleFromGlobalAngle(angle_t visangle); +boolean R_IsPointInSector(sector_t *sector, fixed_t x, fixed_t y); subsector_t *R_PointInSubsector(fixed_t x, fixed_t y); subsector_t *R_PointInSubsectorOrNull(fixed_t x, fixed_t y); From db9b26c8764ca157510821350ee0fbb9d52807d2 Mon Sep 17 00:00:00 2001 From: Zwip-Zwap Zapony Date: Thu, 16 Nov 2023 20:36:08 +0000 Subject: [PATCH 121/227] Use INT32_MAX --- src/r_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/r_main.c b/src/r_main.c index aab6ed620..06454646b 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -1034,7 +1034,7 @@ boolean R_IsPointInSector(sector_t *sector, fixed_t x, fixed_t y) { size_t i; line_t *closest = NULL; - fixed_t closestdist = 0x7fffffff; + fixed_t closestdist = INT32_MAX; for (i = 0; i < sector->linecount; i++) { From 8cc7a88c5f09de7391d50a0f10401ac7ff7861d6 Mon Sep 17 00:00:00 2001 From: LJ Sonic Date: Thu, 16 Nov 2023 22:00:23 +0100 Subject: [PATCH 122/227] Make module music volume more accurate --- src/sdl/mixer_sound.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sdl/mixer_sound.c b/src/sdl/mixer_sound.c index 0a39c7f28..bd4a23c0d 100644 --- a/src/sdl/mixer_sound.c +++ b/src/sdl/mixer_sound.c @@ -759,8 +759,8 @@ static void mix_gme(void *udata, Uint8 *stream, int len) music_volume = 18; // apply volume to stream - for (i = 0, p = (short *)stream; i < len/2; i++, p++) - *p = ((INT32)*p) * (music_volume*internal_volume/100)*2 / 40; + for (i = 0, p = (short *)stream; i < len / 2; i++, p++) + *p = ((INT32)*p) * music_volume * internal_volume / 100 / 20; } #endif @@ -783,8 +783,8 @@ static void mix_openmpt(void *udata, Uint8 *stream, int len) music_volume = 18; // apply volume to stream - for (i = 0, p = (short *)stream; i < len/2; i++, p++) - *p = ((INT32)*p) * (music_volume*internal_volume/100)*2 / 40; + for (i = 0, p = (short *)stream; i < len / 2; i++, p++) + *p = ((INT32)*p) * music_volume * internal_volume / 100 / 20; } #endif From 13aed6ad25fd723c8ea2bf08c0bdc5486806fc77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Sat, 18 Nov 2023 14:44:59 +0100 Subject: [PATCH 123/227] Add apropos console command --- src/command.c | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/command.c b/src/command.c index e0deff8e1..d20c9c4ff 100644 --- a/src/command.c +++ b/src/command.c @@ -51,9 +51,11 @@ static void COM_CEchoDuration_f(void); static void COM_Exec_f(void); static void COM_Wait_f(void); static void COM_Help_f(void); +static void COM_Apropos_f(void); static void COM_Toggle_f(void); static void COM_Add_f(void); + static void CV_EnforceExecVersion(void); static boolean CV_FilterVarByVersion(consvar_t *v, const char *valstr); static boolean CV_Command(void); @@ -344,6 +346,7 @@ void COM_Init(void) COM_AddCommand("exec", COM_Exec_f, 0); COM_AddCommand("wait", COM_Wait_f, 0); COM_AddCommand("help", COM_Help_f, COM_LUA); + COM_AddCommand("apropos", COM_Apropos_f, COM_LUA); COM_AddCommand("toggle", COM_Toggle_f, COM_LUA); COM_AddCommand("add", COM_Add_f, COM_LUA); RegisterNetXCmd(XD_NETVAR, Got_NetVar); @@ -879,7 +882,7 @@ static void COM_Help_f(void) boolean floatmode = false; const char *cvalue = NULL; CONS_Printf("\x82""Variable %s:\n", cvar->name); - CONS_Printf(M_GetText(" flags :")); + CONS_Printf(M_GetText(" flags: ")); if (cvar->flags & CV_SAVE) CONS_Printf("AUTOSAVE "); if (cvar->flags & CV_FLOAT) @@ -1030,6 +1033,35 @@ static void COM_Help_f(void) } } +static void COM_Apropos_f(void) +{ + xcommand_t *cmd; + consvar_t *cvar; + + if (COM_Argc() != 2) + { + CONS_Printf(M_GetText("apropos : Search for cvars and cmds containing text\n")); + return; + } + + CONS_Printf("\x82""Matching variables:\n"); + for (cvar = consvar_vars; cvar; cvar = cvar->next) + { + if (cvar->flags & CV_NOSHOWHELP) + continue; + if (strstr(cvar->name, COM_Argv(1)) != NULL) + CONS_Printf("%s ", cvar->name); + } + + CONS_Printf("\x82""\nMatching commands:\n"); + for (cmd = com_commands; cmd; cmd = cmd->next) + { + if (strstr(cmd->name, COM_Argv(1)) != NULL) + CONS_Printf("%s ", cmd->name); + } + CONS_Printf("\n"); +} + /** Toggles a console variable. Useful for on/off values. * * This works on on/off, yes/no values only From fa050cdebf89b8ea0aeb888a2b9289e5ae0ffd98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Sat, 18 Nov 2023 20:44:26 +0100 Subject: [PATCH 124/227] Redesign search mechanism --- src/command.c | 90 ++++++++++++++++++++++++++++++--------------------- src/console.c | 27 ++-------------- 2 files changed, 57 insertions(+), 60 deletions(-) diff --git a/src/command.c b/src/command.c index d20c9c4ff..d41a55153 100644 --- a/src/command.c +++ b/src/command.c @@ -51,7 +51,7 @@ static void COM_CEchoDuration_f(void); static void COM_Exec_f(void); static void COM_Wait_f(void); static void COM_Help_f(void); -static void COM_Apropos_f(void); +static void COM_Find_f(void); static void COM_Toggle_f(void); static void COM_Add_f(void); @@ -346,7 +346,7 @@ void COM_Init(void) COM_AddCommand("exec", COM_Exec_f, 0); COM_AddCommand("wait", COM_Wait_f, 0); COM_AddCommand("help", COM_Help_f, COM_LUA); - COM_AddCommand("apropos", COM_Apropos_f, COM_LUA); + COM_AddCommand("find", COM_Find_f, COM_LUA); COM_AddCommand("toggle", COM_Toggle_f, COM_LUA); COM_AddCommand("add", COM_Add_f, COM_LUA); RegisterNetXCmd(XD_NETVAR, Got_NetVar); @@ -979,31 +979,8 @@ static void COM_Help_f(void) return; } - CONS_Printf("No exact match, searching...\n"); - - // variables - CONS_Printf("\x82""Variables:\n"); - for (cvar = consvar_vars; cvar; cvar = cvar->next) - { - if ((cvar->flags & CV_NOSHOWHELP) || (!strstr(cvar->name, help))) - continue; - CONS_Printf("%s ", cvar->name); - i++; - } - - // commands - CONS_Printf("\x82""\nCommands:\n"); - for (cmd = com_commands; cmd; cmd = cmd->next) - { - if (!strstr(cmd->name, help)) - continue; - CONS_Printf("%s ",cmd->name); - i++; - } - - CONS_Printf("\x82""\nCheck wiki.srb2.org for more or type help \n"); - - CONS_Debug(DBG_GAMELOGIC, "\x87Total : %d\n", i); + CONS_Printf("No variable or command named %s", help); + CONS_Printf("\x82""\nCheck wiki.srb2.org for more or try typing help without arguments\n"); } return; } @@ -1033,33 +1010,74 @@ static void COM_Help_f(void) } } -static void COM_Apropos_f(void) +static void COM_Find_f(void) { + static char prefix[80]; xcommand_t *cmd; consvar_t *cvar; + cmdalias_t *alias; + const char *match; + const char *help; + size_t helplen; + boolean matchesany; if (COM_Argc() != 2) { - CONS_Printf(M_GetText("apropos : Search for cvars and cmds containing text\n")); + CONS_Printf(M_GetText("find : Search for variables, commands and aliases containing \n")); return; } - CONS_Printf("\x82""Matching variables:\n"); + help = COM_Argv(1); + helplen = strlen(help); + CONS_Printf("\x82""Variables:\n"); + matchesany = false; for (cvar = consvar_vars; cvar; cvar = cvar->next) { if (cvar->flags & CV_NOSHOWHELP) continue; - if (strstr(cvar->name, COM_Argv(1)) != NULL) - CONS_Printf("%s ", cvar->name); + match = strstr(cvar->name, help); + if (match != NULL) + { + memcpy(prefix, cvar->name, match - cvar->name); + prefix[match - cvar->name] = '\0'; + CONS_Printf(" %s\x83%s\x80%s\n", prefix, help, &match[helplen]); + matchesany = true; + } } + if (!matchesany) + CONS_Printf(" (none)\n"); - CONS_Printf("\x82""\nMatching commands:\n"); + CONS_Printf("\x82""Commands:\n"); + matchesany = false; for (cmd = com_commands; cmd; cmd = cmd->next) { - if (strstr(cmd->name, COM_Argv(1)) != NULL) - CONS_Printf("%s ", cmd->name); + match = strstr(cmd->name, help); + if (match != NULL) + { + memcpy(prefix, cmd->name, match - cmd->name); + prefix[match - cmd->name] = '\0'; + CONS_Printf(" %s\x83%s\x80%s\n", prefix, help, &match[helplen]); + matchesany = true; + } } - CONS_Printf("\n"); + if (!matchesany) + CONS_Printf(" (none)\n"); + + CONS_Printf("\x82""Aliases:\n"); + matchesany = false; + for (alias = com_alias; alias; alias = alias->next) + { + match = strstr(alias->name, help); + if (match != NULL) + { + memcpy(prefix, alias->name, match - alias->name); + prefix[match - alias->name] = '\0'; + CONS_Printf(" %s\x83%s\x80%s\n", prefix, help, &match[helplen]); + matchesany = true; + } + } + if (!matchesany) + CONS_Printf(" (none)\n"); } /** Toggles a console variable. Useful for on/off values. diff --git a/src/console.c b/src/console.c index dbd7c938a..01b90ebc3 100644 --- a/src/console.c +++ b/src/console.c @@ -921,7 +921,8 @@ boolean CON_Responder(event_t *ev) static UINT8 consdown = false; // console is treated differently due to rare usage // sequential completions a la 4dos - static char completion[80]; + static char completioncmd[80 + sizeof("find ")] = "find "; + static char *completion = &completioncmd[sizeof("find ")-1]; static INT32 skips; @@ -1057,36 +1058,14 @@ boolean CON_Responder(event_t *ev) // show all cvars/commands that match what we have inputted if (key == KEY_TAB) { - size_t i, len; - if (!completion[0]) { if (!input_len || input_len >= 40 || strchr(inputlines[inputline], ' ')) return true; strcpy(completion, inputlines[inputline]); } - len = strlen(completion); - - //first check commands - CONS_Printf("\nCommands:\n"); - for (i = 0, cmd = COM_CompleteCommand(completion, i); cmd; cmd = COM_CompleteCommand(completion, ++i)) - CONS_Printf(" \x83" "%s" "\x80" "%s\n", completion, cmd+len); - if (i == 0) CONS_Printf(" (none)\n"); - - //now we move on to CVARs - CONS_Printf("Variables:\n"); - for (i = 0, cmd = CV_CompleteVar(completion, i); cmd; cmd = CV_CompleteVar(completion, ++i)) - CONS_Printf(" \x83" "%s" "\x80" "%s\n", completion, cmd+len); - if (i == 0) CONS_Printf(" (none)\n"); - - //and finally aliases - CONS_Printf("Aliases:\n"); - for (i = 0, cmd = COM_CompleteAlias(completion, i); cmd; cmd = COM_CompleteAlias(completion, ++i)) - CONS_Printf(" \x83" "%s" "\x80" "%s\n", completion, cmd+len); - if (i == 0) CONS_Printf(" (none)\n"); - + COM_BufInsertText(completioncmd); completion[0] = 0; - return true; } // --- From e6dfeace843570ad7a13565eb729d143ce95a1e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Sun, 19 Nov 2023 13:17:18 +0100 Subject: [PATCH 125/227] Fix anisotropic and FOV settings not saving --- src/hardware/hw_main.c | 17 ++--------------- src/hardware/hw_main.h | 1 - src/r_main.c | 2 +- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 2d7c99861..a473a9292 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -169,7 +169,6 @@ ps_metric_t ps_hw_batchdrawtime = {0}; boolean gl_init = false; boolean gl_maploaded = false; -boolean gl_sessioncommandsadded = false; boolean gl_shadersavailable = true; // ========================================================================== @@ -6595,7 +6594,7 @@ consvar_t cv_glfakecontrast = CVAR_INIT ("gr_fakecontrast", "Smooth", CV_SAVE, g consvar_t cv_glslopecontrast = CVAR_INIT ("gr_slopecontrast", "Off", CV_SAVE, CV_OnOff, NULL); consvar_t cv_glfiltermode = CVAR_INIT ("gr_filtermode", "Nearest", CV_SAVE|CV_CALL, glfiltermode_cons_t, CV_glfiltermode_OnChange); -consvar_t cv_glanisotropicmode = CVAR_INIT ("gr_anisotropicmode", "1", CV_CALL, glanisotropicmode_cons_t, CV_glanisotropic_OnChange); +consvar_t cv_glanisotropicmode = CVAR_INIT ("gr_anisotropicmode", "1", CV_SAVE|CV_CALL, glanisotropicmode_cons_t, CV_glanisotropic_OnChange); consvar_t cv_glsolvetjoin = CVAR_INIT ("gr_solvetjoin", "On", 0, CV_OnOff, NULL); @@ -6637,6 +6636,7 @@ void HWR_AddCommands(void) CV_RegisterVar(&cv_glallowshaders); CV_RegisterVar(&cv_glfiltermode); + CV_RegisterVar(&cv_glanisotropicmode); CV_RegisterVar(&cv_glsolvetjoin); CV_RegisterVar(&cv_glbatching); @@ -6646,14 +6646,6 @@ void HWR_AddCommands(void) #endif } -void HWR_AddSessionCommands(void) -{ - if (gl_sessioncommandsadded) - return; - CV_RegisterVar(&cv_glanisotropicmode); - gl_sessioncommandsadded = true; -} - // -------------------------------------------------------------------------- // Setup the hardware renderer // -------------------------------------------------------------------------- @@ -6664,7 +6656,6 @@ void HWR_Startup(void) CONS_Printf("HWR_Startup()...\n"); HWR_InitPolyPool(); - HWR_AddSessionCommands(); HWR_InitMapTextures(); HWR_InitModels(); #ifdef ALAM_LIGHTING @@ -6687,10 +6678,6 @@ void HWR_Startup(void) // -------------------------------------------------------------------------- void HWR_Switch(void) { - // Add session commands - if (!gl_sessioncommandsadded) - HWR_AddSessionCommands(); - // Set special states from CVARs HWD.pfnSetSpecialState(HWD_SET_TEXTUREFILTERMODE, cv_glfiltermode.value); HWD.pfnSetSpecialState(HWD_SET_TEXTUREANISOTROPICMODE, cv_glanisotropicmode.value); diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h index 9450ca2c5..cce4e8f0a 100644 --- a/src/hardware/hw_main.h +++ b/src/hardware/hw_main.h @@ -54,7 +54,6 @@ UINT8 *HWR_GetScreenshot(void); boolean HWR_Screenshot(const char *pathname); void HWR_AddCommands(void); -void HWR_AddSessionCommands(void); void transform(float *cx, float *cy, float *cz); INT32 HWR_GetTextureUsed(void); void HWR_DoPostProcessor(player_t *player); diff --git a/src/r_main.c b/src/r_main.c index 6c7bedbf1..0cfccab8c 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -158,7 +158,7 @@ consvar_t cv_drawdist = CVAR_INIT ("drawdist", "Infinite", CV_SAVE, drawdist_con consvar_t cv_drawdist_nights = CVAR_INIT ("drawdist_nights", "2048", CV_SAVE, drawdist_cons_t, NULL); consvar_t cv_drawdist_precip = CVAR_INIT ("drawdist_precip", "1024", CV_SAVE, drawdist_precip_cons_t, NULL); //consvar_t cv_precipdensity = CVAR_INIT ("precipdensity", "Moderate", CV_SAVE, precipdensity_cons_t, NULL); -consvar_t cv_fov = CVAR_INIT ("fov", "90", CV_FLOAT|CV_CALL, fov_cons_t, Fov_OnChange); +consvar_t cv_fov = CVAR_INIT ("fov", "90", CV_SAVE|CV_FLOAT|CV_CALL, fov_cons_t, Fov_OnChange); // Okay, whoever said homremoval causes a performance hit should be shot. consvar_t cv_homremoval = CVAR_INIT ("homremoval", "No", CV_SAVE, homremoval_cons_t, NULL); From fddcb5cfdce9f70f9afbf57365c13e30b1ecd1be Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Tue, 21 Nov 2023 21:15:09 -0300 Subject: [PATCH 126/227] Fix P_GetSectorColormapAt --- src/lua_baselib.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 97bc9319c..3074187a3 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -2025,14 +2025,18 @@ static int lib_pCeilingzAtPos(lua_State *L) static int lib_pGetSectorColormapAt(lua_State *L) { + boolean has_sector = false; sector_t *sector = NULL; - if (!lua_isnone(L, 1) && lua_isuserdata(L, 1)) + if (!lua_isnoneornil(L, 1)) + { + has_sector = true; sector = *((sector_t **)luaL_checkudata(L, 1, META_SECTOR)); + } fixed_t x = luaL_checkfixed(L, 2); fixed_t y = luaL_checkfixed(L, 3); fixed_t z = luaL_checkfixed(L, 4); INLEVEL - if (!sector) + if (has_sector && !sector) return LUA_ErrInvalid(L, "sector_t"); extracolormap_t *exc; if (sector) From 563ce141ce0406a915cee9260033a209d53181ab Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Tue, 21 Nov 2023 21:15:09 -0300 Subject: [PATCH 127/227] Fix P_GetSectorColormapAt --- src/lua_baselib.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 97bc9319c..3074187a3 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -2025,14 +2025,18 @@ static int lib_pCeilingzAtPos(lua_State *L) static int lib_pGetSectorColormapAt(lua_State *L) { + boolean has_sector = false; sector_t *sector = NULL; - if (!lua_isnone(L, 1) && lua_isuserdata(L, 1)) + if (!lua_isnoneornil(L, 1)) + { + has_sector = true; sector = *((sector_t **)luaL_checkudata(L, 1, META_SECTOR)); + } fixed_t x = luaL_checkfixed(L, 2); fixed_t y = luaL_checkfixed(L, 3); fixed_t z = luaL_checkfixed(L, 4); INLEVEL - if (!sector) + if (has_sector && !sector) return LUA_ErrInvalid(L, "sector_t"); extracolormap_t *exc; if (sector) From 46bd7344c5b17b72e1cbe647f3e0649be8c54abe Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Thu, 23 Nov 2023 13:39:24 -0300 Subject: [PATCH 128/227] Rename 'sector.extracolormap' to just 'sector.colormap' --- src/lua_maplib.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lua_maplib.c b/src/lua_maplib.c index 2cc4ec2a2..1d85b786f 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -57,7 +57,7 @@ enum sector_e { sector_ffloors, sector_fslope, sector_cslope, - sector_extracolormap, + sector_colormap, sector_flags, sector_specialflags, sector_damagetype, @@ -96,7 +96,7 @@ static const char *const sector_opt[] = { "ffloors", "f_slope", "c_slope", - "extracolormap", + "colormap", "flags", "specialflags", "damagetype", @@ -753,7 +753,7 @@ static int sector_get(lua_State *L) case sector_cslope: // c_slope LUA_PushUserdata(L, sector->c_slope, META_SLOPE); return 1; - case sector_extracolormap: // extra_colormap + case sector_colormap: // extra_colormap LUA_PushUserdata(L, sector->extra_colormap, META_EXTRACOLORMAP); return 1; case sector_flags: // flags From 85f6e37ed08dbdb4f69a038fa6ca4bc6703489dc Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Thu, 23 Nov 2023 13:56:42 -0300 Subject: [PATCH 129/227] Update comment --- src/lua_script.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lua_script.c b/src/lua_script.c index 9dea62b83..188983c12 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -1345,7 +1345,7 @@ static UINT8 ArchiveValue(int TABLESINDEX, int myindex) { skin_t *skin = *((skin_t **)lua_touserdata(gL, myindex)); WRITEUINT8(save_p, ARCH_SKIN); - WRITEUINT8(save_p, skin->skinnum); // UINT8 because MAXSKINS is only 32 + WRITEUINT8(save_p, skin->skinnum); // UINT8 because MAXSKINS must be <= 256 break; } default: From 1009c31ad1eb9fccc30c757e3c236e3e82a08d45 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Thu, 23 Nov 2023 14:36:02 -0300 Subject: [PATCH 130/227] Change some instances of sizeu1 to sizeu2 --- src/p_setup.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/p_setup.c b/src/p_setup.c index effd2c368..211ce54e2 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -3264,7 +3264,7 @@ static void P_InitializeSeg(seg_t *seg) UINT32 side = seg->linedef->sidenum[seg->side]; if (side == NO_SIDEDEF) - I_Error("P_InitializeSeg: Seg %s refers to side %d of linedef %s, which doesn't exist!\n", sizeu1((size_t)(seg - segs)), seg->side, sizeu1((size_t)(seg->linedef - lines))); + I_Error("P_InitializeSeg: Seg %s refers to side %d of linedef %s, which doesn't exist!\n", sizeu1((size_t)(seg - segs)), seg->side, sizeu2((size_t)(seg->linedef - lines))); seg->sidedef = &sides[side]; @@ -3475,7 +3475,7 @@ static boolean P_LoadExtendedSubsectorsAndSegs(UINT8 **data, nodetype_t nodetype { UINT32 vertexnum = READUINT32((*data)); if (vertexnum >= numvertexes) - I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %s has invalid vertex %d!\n", sizeu1(k), sizeu1(m), vertexnum); + I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %s has invalid vertex %d!\n", sizeu1(k), sizeu2(m), vertexnum); segs[k - 1 + ((m == 0) ? subsectors[i].numlines : 0)].v2 = segs[k].v1 = &vertexes[vertexnum]; @@ -3516,11 +3516,11 @@ static boolean P_LoadExtendedSubsectorsAndSegs(UINT8 **data, nodetype_t nodetype UINT16 linenum = READUINT16((*data)); if (v1num >= numvertexes) - I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %s has invalid v1 %d!\n", sizeu1(k), sizeu1(m), v1num); + I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %s has invalid v1 %d!\n", sizeu1(k), sizeu2(m), v1num); if (v2num >= numvertexes) - I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %s has invalid v2 %d!\n", sizeu1(k), sizeu1(m), v2num); + I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %s has invalid v2 %d!\n", sizeu1(k), sizeu2(m), v2num); if (linenum >= numlines) - I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %s has invalid linedef %d!\n", sizeu1(k), sizeu1(m), linenum); + I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %s has invalid linedef %d!\n", sizeu1(k), sizeu2(m), linenum); segs[k].v1 = &vertexes[v1num]; segs[k].v2 = &vertexes[v2num]; @@ -4012,7 +4012,7 @@ static void P_LinkMapData(void) if (!seg->sidedef->sector) CorruptMapError(va("P_LinkMapData: seg->sidedef->sector is NULL " "(subsector %s, firstline is %d, sidedef is %s)", sizeu1(i), ss->firstline, - sizeu1(sidei))); + sizeu2(sidei))); ss->sector = seg->sidedef->sector; } From 59aa82a83cf5c546a64de2fe6f3eace3c03f38ea Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Thu, 23 Nov 2023 18:21:29 -0300 Subject: [PATCH 131/227] Correct texture scaling, part 1 --- src/r_segs.c | 242 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 165 insertions(+), 77 deletions(-) diff --git a/src/r_segs.c b/src/r_segs.c index 4c5c0dee9..18b5ba550 100644 --- a/src/r_segs.c +++ b/src/r_segs.c @@ -42,25 +42,24 @@ angle_t rw_normalangle; // angle to line origin angle_t rw_angle1; fixed_t rw_distance; -// Horizontal scaling hack shenanigans. -fixed_t rw_distance_scalex_top; -fixed_t rw_distance_scalex_mid; -fixed_t rw_distance_scalex_bot; // // regular wall // static INT32 rw_x, rw_stopx; static angle_t rw_centerangle; -static fixed_t rw_offset; +static fixed_t rw_offset, rw_offsetx; static fixed_t rw_offset_top, rw_offset_mid, rw_offset_bot; -static fixed_t rw_offset2; // for splats static fixed_t rw_scale, rw_scalestep; static fixed_t rw_midtexturemid, rw_toptexturemid, rw_bottomtexturemid; static INT32 worldtop, worldbottom, worldhigh, worldlow; static INT32 worldtopslope, worldbottomslope, worldhighslope, worldlowslope; // worldtop/bottom at end of slope static fixed_t rw_toptextureslide, rw_midtextureslide, rw_bottomtextureslide; // Defines how to adjust Y offsets along the wall for slopes static fixed_t rw_midtextureback, rw_midtexturebackslide; // Values for masked midtexture height calculation +static fixed_t rw_midtexturescalex, rw_midtexturescaley; +static fixed_t rw_toptexturescalex, rw_toptexturescaley; +static fixed_t rw_bottomtexturescalex, rw_bottomtexturescaley; +static fixed_t rw_invmidtexturescalex, rw_invtoptexturescalex, rw_invbottomtexturescalex; // Lactozilla: 3D floor clipping static boolean rw_floormarked = false; @@ -73,7 +72,6 @@ static fixed_t *rw_bsilheight = NULL; static fixed_t pixhigh, pixlow, pixhighstep, pixlowstep; static fixed_t topfrac, topstep; static fixed_t bottomfrac, bottomstep; -static fixed_t topxscale, topyscale, midxscale, midyscale, botxscale, botyscale; static lighttable_t **walllights; static fixed_t *maskedtexturecol; @@ -1055,10 +1053,11 @@ static void R_RenderSegLoop (void) INT32 mid; fixed_t texturecolumn = 0; - fixed_t texturecolumn_top = 0; - fixed_t texturecolumn_mid = 0; - fixed_t texturecolumn_bot = 0; + fixed_t toptexturecolumn = 0; + fixed_t bottomtexturecolumn = 0; fixed_t oldtexturecolumn = -1; + fixed_t oldtexturecolumn_top = -1; + fixed_t oldtexturecolumn_bottom = -1; INT32 top; INT32 bottom; INT32 i; @@ -1225,20 +1224,10 @@ static void R_RenderSegLoop (void) //SoM: Calculate offsets for Thick fake floors. // calculate texture offset angle = (rw_centerangle + xtoviewangle[rw_x])>>ANGLETOFINESHIFT; - texturecolumn = rw_offset-FixedMul(FINETANGENT(angle),rw_distance); - texturecolumn_top = (rw_offset_top - FixedMul(FINETANGENT(angle),rw_distance_scalex_top))>>FRACBITS; - texturecolumn_mid = (rw_offset_mid - FixedMul(FINETANGENT(angle),rw_distance_scalex_mid))>>FRACBITS; - texturecolumn_bot = (rw_offset_bot - FixedMul(FINETANGENT(angle),rw_distance_scalex_bot))>>FRACBITS; - if (oldtexturecolumn != -1) { - rw_bottomtexturemid += FixedMul(rw_bottomtextureslide, oldtexturecolumn-texturecolumn); - rw_midtexturemid += FixedMul(rw_midtextureslide, oldtexturecolumn-texturecolumn); - rw_toptexturemid += FixedMul(rw_toptextureslide, oldtexturecolumn-texturecolumn); - rw_midtextureback += FixedMul(rw_midtexturebackslide, oldtexturecolumn-texturecolumn); - } - oldtexturecolumn = texturecolumn; + fixed_t distance = FixedMul(FINETANGENT(angle), rw_distance); - INT32 itexturecolumn = texturecolumn >> FRACBITS; + texturecolumn = FixedDiv(rw_offset - distance, rw_invmidtexturescalex); // texturecolumn and lighting are independent of wall tiers if (segtextured) @@ -1251,7 +1240,6 @@ static void R_RenderSegLoop (void) dc_colormap = walllights[pindex]; dc_x = rw_x; - dc_iscale = 0xffffffffu / (unsigned)rw_scale; if (frontsector->extra_colormap) dc_colormap = frontsector->extra_colormap->colormap + (dc_colormap - colormaps); @@ -1301,11 +1289,13 @@ static void R_RenderSegLoop (void) // single sided line if (yl <= yh && yh >= 0 && yl < viewheight) { + fixed_t offset = texturecolumn + rw_offsetx; + dc_yl = yl; dc_yh = yh; - dc_texturemid = FixedMul(rw_midtexturemid, midyscale); - dc_iscale = FixedMul(0xffffffffu / (unsigned)rw_scale, midyscale); - dc_source = R_GetColumn(midtexture, itexturecolumn + texturecolumn_mid); + dc_texturemid = rw_midtexturemid; + dc_iscale = FixedMul(0xffffffffu / (unsigned)rw_scale, rw_midtexturescaley); + dc_source = R_GetColumn(midtexture, offset >> FRACBITS); dc_texheight = textureheight[midtexture]>>FRACBITS; //profile stuff --------------------------------------------------------- @@ -1354,6 +1344,8 @@ static void R_RenderSegLoop (void) if (mid >= floorclip[rw_x]) mid = floorclip[rw_x]-1; + toptexturecolumn = FixedDiv(rw_offset - distance, rw_invtoptexturescalex); + if (mid >= yl) // back ceiling lower than front ceiling ? { if (yl >= viewheight) // entirely off bottom of screen @@ -1363,11 +1355,16 @@ static void R_RenderSegLoop (void) } else if (mid >= 0) // safe to draw top texture { + fixed_t offset = rw_offset_top; + if (rw_toptexturescalex < 0) + offset = -offset; + offset = toptexturecolumn + offset; + dc_yl = yl; dc_yh = mid; - dc_texturemid = FixedMul(rw_toptexturemid, topyscale); - dc_iscale = FixedMul(0xffffffffu / (unsigned)rw_scale, topyscale); - dc_source = R_GetColumn(toptexture, itexturecolumn + texturecolumn_top); + dc_texturemid = rw_toptexturemid; + dc_iscale = FixedMul(0xffffffffu / (unsigned)rw_scale, rw_toptexturescaley); + dc_source = R_GetColumn(toptexture, toptexturecolumn >> FRACBITS); dc_texheight = textureheight[toptexture]>>FRACBITS; colfunc(); ceilingclip[rw_x] = (INT16)mid; @@ -1377,6 +1374,10 @@ static void R_RenderSegLoop (void) } else if (!rw_ceilingmarked) ceilingclip[rw_x] = topclip; + + if (oldtexturecolumn_top != -1) + rw_toptexturemid += FixedMul(rw_toptextureslide, oldtexturecolumn_top-toptexturecolumn); + oldtexturecolumn_top = toptexturecolumn; } else if (markceiling && (!rw_ceilingmarked)) // no top wall ceilingclip[rw_x] = topclip; @@ -1391,6 +1392,8 @@ static void R_RenderSegLoop (void) if (mid <= ceilingclip[rw_x]) mid = ceilingclip[rw_x]+1; + bottomtexturecolumn = FixedDiv(rw_offset - distance, rw_invbottomtexturescalex); + if (mid <= yh) // back floor higher than front floor ? { if (yh < 0) // entirely off top of screen @@ -1400,11 +1403,16 @@ static void R_RenderSegLoop (void) } else if (mid < viewheight) // safe to draw bottom texture { + fixed_t offset = rw_offset_bot; + if (rw_bottomtexturescalex < 0) + offset = -offset; + offset = bottomtexturecolumn + offset; + dc_yl = mid; dc_yh = yh; - dc_texturemid = FixedMul(rw_bottomtexturemid, botyscale); - dc_iscale = FixedMul(0xffffffffu / (unsigned)rw_scale, botyscale); - dc_source = R_GetColumn(bottomtexture, itexturecolumn + texturecolumn_bot); + dc_texturemid = rw_bottomtexturemid; + dc_iscale = FixedMul(0xffffffffu / (unsigned)rw_scale, rw_bottomtexturescaley); + dc_source = R_GetColumn(bottomtexture, offset >> FRACBITS); dc_texheight = textureheight[bottomtexture]>>FRACBITS; colfunc(); floorclip[rw_x] = (INT16)mid; @@ -1414,6 +1422,10 @@ static void R_RenderSegLoop (void) } else if (!rw_floormarked) floorclip[rw_x] = bottomclip; + + if (oldtexturecolumn_bottom != -1) + rw_bottomtexturemid += FixedMul(rw_bottomtextureslide, oldtexturecolumn_bottom-bottomtexturecolumn); + oldtexturecolumn_bottom = bottomtexturecolumn; } else if (markfloor && (!rw_floormarked)) // no bottom wall floorclip[rw_x] = bottomclip; @@ -1423,7 +1435,7 @@ static void R_RenderSegLoop (void) { // save texturecol // for backdrawing of masked mid texture - maskedtexturecol[rw_x] = (INT16)texturecolumn_mid; + maskedtexturecol[rw_x] = texturecolumn >> FRACBITS; if (maskedtextureheight != NULL) { maskedtextureheight[rw_x] = (curline->linedef->flags & ML_MIDPEG) ? @@ -1442,6 +1454,17 @@ static void R_RenderSegLoop (void) } } + if (midtexture || maskedtextureheight) + { + if (oldtexturecolumn != -1) + { + rw_midtexturemid += FixedMul(rw_midtextureslide, oldtexturecolumn-texturecolumn); + rw_midtextureback += FixedMul(rw_midtexturebackslide, oldtexturecolumn-texturecolumn); + } + + oldtexturecolumn = texturecolumn; + } + for (i = 0; i < numffloors; i++) { if (curline->polyseg && (ffloor[i].polyobj != curline->polyseg)) @@ -1765,32 +1788,67 @@ void R_StoreWallRange(INT32 start, INT32 stop) ceilingbackslide = FixedMul(backsector->c_slope->zdelta, FINECOSINE((lineangle-backsector->c_slope->xydirection)>>ANGLETOFINESHIFT)); } + rw_midtexturescalex = sidedef->scalex_mid; + rw_midtexturescaley = sidedef->scaley_mid; + rw_invmidtexturescalex = FixedDiv(FRACUNIT, rw_midtexturescalex); + if (!backsector) { - fixed_t texheight; - // single sided line midtexture = R_GetTextureNum(sidedef->midtexture); - texheight = textureheight[midtexture]; + // a single sided line is terminal, so it must mark ends markfloor = markceiling = true; - if (linedef->flags & ML_NOSKEW) { - if (linedef->flags & ML_DONTPEGBOTTOM) - rw_midtexturemid = frontsector->floorheight + texheight - viewz; - else - rw_midtexturemid = frontsector->ceilingheight - viewz; - } - else if (linedef->flags & ML_DONTPEGBOTTOM) + + fixed_t rowoffset = sidedef->rowoffset + sidedef->offsety_mid; + fixed_t texheight = textureheight[midtexture]; + + if (rw_midtexturescaley > 0) { - rw_midtexturemid = worldbottom + texheight; - rw_midtextureslide = floorfrontslide; + if (linedef->flags & ML_NOSKEW) + { + if (linedef->flags & ML_DONTPEGBOTTOM) + rw_midtexturemid = FixedMul(frontsector->floorheight - viewz, rw_midtexturescaley) + texheight; + else + rw_midtexturemid = FixedMul(frontsector->ceilingheight - viewz, rw_midtexturescaley); + } + else if (linedef->flags & ML_DONTPEGBOTTOM) + { + rw_midtexturemid = FixedMul(worldbottom, rw_midtexturescaley) + texheight; + rw_midtextureslide = FixedMul(floorfrontslide, rw_midtexturescaley); + } + else + { + // top of texture at top + rw_midtexturemid = FixedMul(worldtop, rw_midtexturescaley); + rw_midtextureslide = FixedMul(ceilingfrontslide, rw_midtexturescaley); + } } else { - // top of texture at top - rw_midtexturemid = worldtop; - rw_midtextureslide = ceilingfrontslide; + // Upside down + rowoffset = -rowoffset; + + if (linedef->flags & ML_NOSKEW) + { + if (linedef->flags & ML_DONTPEGBOTTOM) + rw_midtexturemid = FixedMul(frontsector->floorheight - viewz, rw_midtexturescaley); + else + rw_midtexturemid = FixedMul(frontsector->ceilingheight - viewz, rw_midtexturescaley) + texheight; + } + else if (linedef->flags & ML_DONTPEGBOTTOM) + { + rw_midtexturemid = FixedMul(worldbottom, rw_midtexturescaley); + rw_midtextureslide = FixedMul(floorfrontslide, rw_midtexturescaley); + } + else + { + // top of texture at top + rw_midtexturemid = FixedMul(worldtop, rw_midtexturescaley) + texheight; + rw_midtextureslide = FixedMul(ceilingfrontslide, rw_midtexturescaley); + } } - rw_midtexturemid += sidedef->rowoffset + sidedef->offsety_mid; + + rw_midtexturemid += rowoffset; ds_p->silhouette = SIL_BOTH; ds_p->sprtopclip = screenheightarray; @@ -1976,16 +2034,28 @@ void R_StoreWallRange(INT32 start, INT32 stop) } } + fixed_t toprowoffset = sidedef->rowoffset + sidedef->offsety_top; + fixed_t botrowoffset = sidedef->rowoffset + sidedef->offsety_bot; + // check TOP TEXTURE if (!bothceilingssky // never draw the top texture if on && (worldhigh < worldtop || worldhighslope < worldtopslope)) { - fixed_t texheight; - // top texture toptexture = R_GetTextureNum(sidedef->toptexture); - texheight = textureheight[toptexture]; - if (!(linedef->flags & ML_SKEWTD)) { // Ignore slopes for lower/upper textures unless flag is checked + rw_toptexturescalex = sidedef->scalex_top; + rw_toptexturescaley = sidedef->scaley_top; + + rw_invtoptexturescalex = FixedDiv(FRACUNIT, rw_toptexturescalex); + + if (rw_toptexturescaley < 0) + toprowoffset = -toprowoffset; + + fixed_t texheight = textureheight[toptexture]; + + // Ignore slopes for lower/upper textures unless flag is checked + if (!(linedef->flags & ML_SKEWTD)) + { if (linedef->flags & ML_DONTPEGTOP) rw_toptexturemid = frontsector->ceilingheight - viewz; else @@ -2002,7 +2072,11 @@ void R_StoreWallRange(INT32 start, INT32 stop) rw_toptexturemid = worldhigh + texheight; rw_toptextureslide = ceilingbackslide; } + + rw_toptexturemid = FixedMul(rw_toptexturemid, rw_toptexturescaley); + rw_toptextureslide = FixedMul(rw_toptextureslide, rw_toptexturescaley); } + // check BOTTOM TEXTURE if (!bothfloorssky // never draw the bottom texture if on && (worldlow > worldbottom || worldlowslope > worldbottomslope)) // Only if VISIBLE!!! @@ -2010,7 +2084,17 @@ void R_StoreWallRange(INT32 start, INT32 stop) // bottom texture bottomtexture = R_GetTextureNum(sidedef->bottomtexture); - if (!(linedef->flags & ML_SKEWTD)) { // Ignore slopes for lower/upper textures unless flag is checked + rw_bottomtexturescalex = sidedef->scalex_bot; + rw_bottomtexturescaley = sidedef->scaley_bot; + + rw_invbottomtexturescalex = FixedDiv(FRACUNIT, rw_bottomtexturescalex); + + if (rw_bottomtexturescaley < 0) + botrowoffset = -botrowoffset; + + // Ignore slopes for lower/upper textures unless flag is checked + if (!(linedef->flags & ML_SKEWTD)) + { if (linedef->flags & ML_DONTPEGBOTTOM) rw_bottomtexturemid = frontsector->floorheight - viewz; else @@ -2023,14 +2107,19 @@ void R_StoreWallRange(INT32 start, INT32 stop) rw_bottomtexturemid = worldbottom; rw_bottomtextureslide = floorfrontslide; } - else { // top of texture at top + else + { + // top of texture at top rw_bottomtexturemid = worldlow; rw_bottomtextureslide = floorbackslide; } + + rw_bottomtexturemid = FixedMul(rw_bottomtexturemid, rw_bottomtexturescaley); + rw_bottomtextureslide = FixedMul(rw_bottomtextureslide, rw_bottomtexturescaley); } - rw_toptexturemid += sidedef->rowoffset + sidedef->offsety_top; - rw_bottomtexturemid += sidedef->rowoffset + sidedef->offsety_bot; + rw_toptexturemid += toprowoffset; + rw_bottomtexturemid += botrowoffset; R_AllocTextureColumnTables(rw_stopx - start); @@ -2241,7 +2330,8 @@ void R_StoreWallRange(INT32 start, INT32 stop) maskedtextureheight = ds_p->maskedtextureheight; // note to red, this == &(ds_p->maskedtextureheight[0]) if (curline->polyseg) - { // use REAL front and back floors please, so midtexture rendering isn't mucked up + { + // use REAL front and back floors please, so midtexture rendering isn't mucked up rw_midtextureslide = rw_midtexturebackslide = 0; if (linedef->flags & ML_MIDPEG) rw_midtexturemid = rw_midtextureback = max(curline->frontsector->floorheight, curline->backsector->floorheight) - viewz; @@ -2252,7 +2342,8 @@ void R_StoreWallRange(INT32 start, INT32 stop) { // Set midtexture starting height if (linedef->flags & ML_NOSKEW) - { // Ignore slopes when texturing + { + // Ignore slopes when texturing rw_midtextureslide = rw_midtexturebackslide = 0; if (linedef->flags & ML_MIDPEG) rw_midtexturemid = rw_midtextureback = max(frontsector->floorheight, backsector->floorheight) - viewz; @@ -2275,6 +2366,10 @@ void R_StoreWallRange(INT32 start, INT32 stop) rw_midtexturebackslide = ceilingbackslide; } } + + rw_midtexturemid = FixedMul(rw_midtexturemid, rw_midtexturescaley); + rw_midtextureback = FixedMul(rw_midtextureback, rw_midtexturescaley); + rw_midtexturemid += sidedef->rowoffset + sidedef->offsety_mid; rw_midtextureback += sidedef->rowoffset + sidedef->offsety_mid; @@ -2311,26 +2406,19 @@ void R_StoreWallRange(INT32 start, INT32 stop) if (rw_normalangle-rw_angle1 < ANGLE_180) rw_offset = -rw_offset; - /// don't use texture offset for splats - rw_offset2 = rw_offset + curline->offset; - - // Per-texture scaling, offsetting. - topxscale = sidedef->scalex_top; - midxscale = sidedef->scalex_mid; - botxscale = sidedef->scalex_bot; - topyscale = sidedef->scaley_top; - midyscale = sidedef->scaley_mid; - botyscale = sidedef->scaley_bot; - rw_offset_top = FixedMul(rw_offset + curline->offset + sidedef->textureoffset + sidedef->offsetx_top, topxscale); - rw_offset_mid = FixedMul(rw_offset + curline->offset + sidedef->textureoffset + sidedef->offsetx_mid, midxscale); - rw_offset_bot = FixedMul(rw_offset + curline->offset + sidedef->textureoffset + sidedef->offsetx_bot, botxscale); - rw_distance_scalex_top = FixedMul(rw_distance, topxscale); - rw_distance_scalex_mid = FixedMul(rw_distance, midxscale); - rw_distance_scalex_bot = FixedMul(rw_distance, botxscale); - - rw_offset += sidedef->textureoffset + curline->offset; + rw_offset += curline->offset; rw_centerangle = ANGLE_90 + viewangle - rw_normalangle; + rw_offsetx = sidedef->textureoffset; + + rw_offset_top = rw_offsetx + sidedef->offsetx_top; + rw_offset_mid = rw_offsetx + sidedef->offsetx_mid; + rw_offset_bot = rw_offsetx + sidedef->offsetx_bot; + + rw_offsetx = rw_offset_mid; + if (rw_midtexturescalex < 0) + rw_offsetx = -rw_offsetx; + // calculate light table // use different light tables // for horizontal / vertical / diagonal From 10537deacfa811923479245211c0e91ebd9a3126 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Thu, 23 Nov 2023 19:16:03 -0300 Subject: [PATCH 132/227] Correct texture scaling, part 2 --- src/r_defs.h | 1 + src/r_segs.c | 106 +++++++++++++++++++++++++++++---------------------- 2 files changed, 61 insertions(+), 46 deletions(-) diff --git a/src/r_defs.h b/src/r_defs.h index 83d9825ca..06a16de9e 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -761,6 +761,7 @@ typedef struct drawseg_s INT16 *sprtopclip; INT16 *sprbottomclip; fixed_t *maskedtexturecol; + fixed_t *invscale; struct visplane_s *ffloorplanes[MAXFFLOORS]; INT32 numffloorplanes; diff --git a/src/r_segs.c b/src/r_segs.c index 18b5ba550..f3c380b91 100644 --- a/src/r_segs.c +++ b/src/r_segs.c @@ -74,8 +74,9 @@ static fixed_t topfrac, topstep; static fixed_t bottomfrac, bottomstep; static lighttable_t **walllights; -static fixed_t *maskedtexturecol; +static fixed_t *maskedtexturecol = NULL; static fixed_t *maskedtextureheight = NULL; +static fixed_t *invscale = NULL; //SoM: 3/23/2000: Use boom opening limit removal static size_t numopenings; @@ -165,7 +166,8 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2) frontsector = curline->frontsector; backsector = curline->backsector; - texnum = R_GetTextureNum(curline->sidedef->midtexture); + sidedef = curline->sidedef; + texnum = R_GetTextureNum(sidedef->midtexture); windowbottom = windowtop = sprbotscreen = INT32_MAX; ldef = curline->linedef; @@ -203,9 +205,13 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2) colfunc = colfuncs[COLDRAWFUNC_FUZZY]; } + fixed_t wall_scaley = sidedef->scaley_mid; + fixed_t scalestep = FixedDiv(ds->scalestep, wall_scaley); + fixed_t scale1 = FixedDiv(ds->scale1, wall_scaley); + range = max(ds->x2-ds->x1, 1); - rw_scalestep = ds->scalestep; - spryscale = ds->scale1 + (x1 - ds->x1)*rw_scalestep; + rw_scalestep = scalestep; + spryscale = scale1 + (x1 - ds->x1)*rw_scalestep; // Texture must be cached before setting colfunc_2s, // otherwise texture[texnum]->holes may be false when it shouldn't be @@ -316,8 +322,8 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2) else back = backsector; - if (ds->curline->sidedef->repeatcnt) - repeats = 1 + ds->curline->sidedef->repeatcnt; + if (sidedef->repeatcnt) + repeats = 1 + sidedef->repeatcnt; else if (ldef->flags & ML_WRAPMIDTEX) { fixed_t high, low; @@ -343,15 +349,14 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2) { if (times > 0) { - rw_scalestep = ds->scalestep; - spryscale = ds->scale1 + (x1 - ds->x1)*rw_scalestep; - if (dc_numlights) - { // reset all lights to their starting heights - for (i = 0; i < dc_numlights; i++) - { - rlight = &dc_lightlist[i]; - rlight->height = rlight->startheight; - } + rw_scalestep = scalestep; + spryscale = scale1 + (x1 - ds->x1)*rw_scalestep; + + // reset all lights to their starting heights + for (i = 0; i < dc_numlights; i++) + { + rlight = &dc_lightlist[i]; + rlight->height = rlight->startheight; } } @@ -393,8 +398,10 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2) sprbotscreen = INT32_MAX; sprtopscreen = windowtop = (centeryfrac - FixedMul(dc_texturemid, spryscale)); - realbot = windowbottom = FixedMul(textureheight[texnum], spryscale) + sprtopscreen; - dc_iscale = 0xffffffffu / (unsigned)spryscale; + realbot = FixedMul(textureheight[texnum], spryscale) + sprtopscreen; + dc_iscale = FixedMul(ds->invscale[dc_x], wall_scaley); + + windowbottom = realbot; // draw the texture col = (column_t *)((UINT8 *)R_GetColumn(texnum, (maskedtexturecol[dc_x] >> FRACBITS)) - 3); @@ -469,7 +476,7 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2) dc_colormap = frontsector->extra_colormap->colormap + (dc_colormap - colormaps); sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale); - dc_iscale = 0xffffffffu / (unsigned)spryscale; + dc_iscale = FixedMul(ds->invscale[dc_x], wall_scaley); // draw the texture col = (column_t *)((UINT8 *)R_GetColumn(texnum, (maskedtexturecol[dc_x] >> FRACBITS)) - 3); @@ -1431,27 +1438,15 @@ static void R_RenderSegLoop (void) floorclip[rw_x] = bottomclip; } - if (maskedtexture || numthicksides) - { - // save texturecol - // for backdrawing of masked mid texture - maskedtexturecol[rw_x] = texturecolumn >> FRACBITS; + if (maskedtexturecol) + maskedtexturecol[rw_x] = texturecolumn; - if (maskedtextureheight != NULL) { - maskedtextureheight[rw_x] = (curline->linedef->flags & ML_MIDPEG) ? - max(rw_midtexturemid, rw_midtextureback) : - min(rw_midtexturemid, rw_midtextureback); - } - } - - if (dc_numlights) + if (maskedtextureheight) { - for (i = 0; i < dc_numlights; i++) - { - dc_lightlist[i].height += dc_lightlist[i].heightstep; - if (dc_lightlist[i].flags & FOF_CUTSOLIDS) - dc_lightlist[i].botheight += dc_lightlist[i].botheightstep; - } + if (curline->linedef->flags & ML_MIDPEG) + maskedtextureheight[rw_x] = max(rw_midtexturemid, rw_midtextureback); + else + maskedtextureheight[rw_x] = min(rw_midtexturemid, rw_midtextureback); } if (midtexture || maskedtextureheight) @@ -1465,6 +1460,19 @@ static void R_RenderSegLoop (void) oldtexturecolumn = texturecolumn; } + if (invscale) + invscale[rw_x] = 0xffffffffu / (unsigned)rw_scale; + + if (dc_numlights) + { + for (i = 0; i < dc_numlights; i++) + { + dc_lightlist[i].height += dc_lightlist[i].heightstep; + if (dc_lightlist[i].flags & FOF_CUTSOLIDS) + dc_lightlist[i].botheight += dc_lightlist[i].botheightstep; + } + } + for (i = 0; i < numffloors; i++) { if (curline->polyseg && (ffloor[i].polyobj != curline->polyseg)) @@ -1569,6 +1577,8 @@ static void R_AllocTextureColumnTables(size_t range) ds->maskedtexturecol = (ds->maskedtexturecol - oldtable) + texturecolumntable; if (ds->thicksidecol + ds->x1 >= oldtable && ds->thicksidecol + ds->x1 <= oldlast) ds->thicksidecol = (ds->thicksidecol - oldtable) + texturecolumntable; + if (ds->invscale + ds->x1 >= oldtable && ds->invscale + ds->x1 <= oldlast) + ds->invscale = (ds->invscale - oldtable) + texturecolumntable; } } @@ -1592,7 +1602,10 @@ void R_StoreWallRange(INT32 start, INT32 stop) fixed_t ceilingfrontslide, floorfrontslide, ceilingbackslide, floorbackslide; static size_t maxdrawsegs = 0; + maskedtexturecol = NULL; maskedtextureheight = NULL; + invscale = NULL; + //initialize segleft and segright memset(&segleft, 0x00, sizeof(segleft)); memset(&segright, 0x00, sizeof(segright)); @@ -1749,6 +1762,7 @@ void R_StoreWallRange(INT32 start, INT32 stop) ds_p->maskedtexturecol = NULL; ds_p->numthicksides = numthicksides = 0; ds_p->thicksidecol = NULL; + ds_p->invscale = NULL; ds_p->tsilheight = 0; numbackffloors = 0; @@ -2134,10 +2148,9 @@ void R_StoreWallRange(INT32 start, INT32 stop) // Used for height comparisons and etc across FOFs and slopes fixed_t high1, highslope1, low1, lowslope1, high2, highslope2, low2, lowslope2; - //markceiling = markfloor = true; maskedtexture = true; - ds_p->thicksidecol = maskedtexturecol = curtexturecolumntable - rw_x; + ds_p->thicksidecol = curtexturecolumntable - rw_x; curtexturecolumntable += rw_stopx - rw_x; lowcut = max(worldbottom, worldlow) + viewz; @@ -2319,13 +2332,8 @@ void R_StoreWallRange(INT32 start, INT32 stop) if (sidedef->midtexture > 0 && sidedef->midtexture < numtextures) { // masked midtexture - if (!ds_p->thicksidecol) - { - ds_p->maskedtexturecol = maskedtexturecol = curtexturecolumntable - rw_x; - curtexturecolumntable += rw_stopx - rw_x; - } - else - ds_p->maskedtexturecol = ds_p->thicksidecol; + ds_p->maskedtexturecol = maskedtexturecol = curtexturecolumntable - rw_x; + curtexturecolumntable += rw_stopx - rw_x; maskedtextureheight = ds_p->maskedtextureheight; // note to red, this == &(ds_p->maskedtextureheight[0]) @@ -2438,6 +2446,12 @@ void R_StoreWallRange(INT32 start, INT32 stop) walllights = scalelight[lightnum]; } + if (maskedtexture) + { + ds_p->invscale = invscale = curtexturecolumntable - rw_x; + curtexturecolumntable += rw_stopx - rw_x; + } + // if a floor / ceiling plane is on the wrong side // of the view plane, it is definitely invisible // and doesn't need to be marked. From 2f20f21eb6dd6ba80983bc9b53edb15f099dcceb Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Thu, 23 Nov 2023 20:03:23 -0300 Subject: [PATCH 133/227] Correct texture scaling, part 3 --- src/r_defs.h | 2 + src/r_segs.c | 105 ++++++++++++++++++++++++++------------------------- 2 files changed, 55 insertions(+), 52 deletions(-) diff --git a/src/r_defs.h b/src/r_defs.h index 06a16de9e..82d752260 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -757,6 +757,8 @@ typedef struct drawseg_s fixed_t bsilheight; // do not clip sprites above this fixed_t tsilheight; // do not clip sprites below this + fixed_t offsetx; + // Pointers to lists for sprite clipping, all three adjusted so [x1] is first value. INT16 *sprtopclip; INT16 *sprbottomclip; diff --git a/src/r_segs.c b/src/r_segs.c index f3c380b91..ec3877670 100644 --- a/src/r_segs.c +++ b/src/r_segs.c @@ -76,6 +76,7 @@ static fixed_t bottomfrac, bottomstep; static lighttable_t **walllights; static fixed_t *maskedtexturecol = NULL; static fixed_t *maskedtextureheight = NULL; +static fixed_t *thicksidecol = NULL; static fixed_t *invscale = NULL; //SoM: 3/23/2000: Use boom opening limit removal @@ -535,7 +536,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) INT32 i, p; fixed_t bottombounds = viewheight << FRACBITS; fixed_t topbounds = (con_clipviewtop - 1) << FRACBITS; - fixed_t offsetvalue = 0; + fixed_t offsetvalue; lightlist_t *light; r_lightlist_t *rlight; INT32 range; @@ -544,11 +545,13 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) // NOTE: INT64 instead of fixed_t because overflow concerns INT64 top_frac, top_step, bottom_frac, bottom_step; // skew FOF walls with slopes? - boolean slopeskew = false; fixed_t ffloortextureslide = 0; INT32 oldx = -1; fixed_t left_top, left_bottom; // needed here for slope skewing pslope_t *skewslope = NULL; + boolean do_texture_skew; + UINT32 lineflags; + fixed_t wall_scalex, wall_scaley; void (*colfunc_2s) (column_t *); @@ -560,7 +563,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) curline = ds->curline; backsector = pfloor->target; frontsector = curline->frontsector == pfloor->target ? curline->backsector : curline->frontsector; - texnum = R_GetTextureNum(sides[pfloor->master->sidenum[0]].midtexture); + sidedef = &sides[pfloor->master->sidenum[0]]; colfunc = colfuncs[BASEDRAWFUNC]; @@ -568,8 +571,13 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) { size_t linenum = curline->linedef-backsector->lines[0]; newline = pfloor->master->frontsector->lines[0] + linenum; - texnum = R_GetTextureNum(sides[newline->sidenum[0]].midtexture); + sidedef = &sides[newline->sidenum[0]]; + lineflags = newline->flags; } + else + lineflags = pfloor->master->flags; + + texnum = R_GetTextureNum(sidedef->midtexture); if (pfloor->fofflags & FOF_TRANSLUCENT) { @@ -721,7 +729,13 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) walllights = scalelight[lightnum]; } - maskedtexturecol = ds->thicksidecol; + wall_scalex = FixedDiv(FRACUNIT, sidedef->scalex_mid); + wall_scaley = sidedef->scaley_mid; + + thicksidecol = ds->thicksidecol; + + for (INT32 x = x1; x <= x2; x++) + thicksidecol[x] = FixedDiv(thicksidecol[x], wall_scalex) + ds->offsetx; mfloorclip = ds->sprbottomclip; mceilingclip = ds->sprtopclip; @@ -731,51 +745,29 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) left_top = P_GetFFloorTopZAt (pfloor, ds->leftpos.x, ds->leftpos.y) - viewz; left_bottom = P_GetFFloorBottomZAt(pfloor, ds->leftpos.x, ds->leftpos.y) - viewz; + do_texture_skew = lineflags & ML_SKEWTD; skewslope = *pfloor->t_slope; // skew using top slope by default - if (newline) - { - if (newline->flags & ML_SKEWTD) - slopeskew = true; - } - else if (pfloor->master->flags & ML_SKEWTD) - slopeskew = true; - if (slopeskew) - dc_texturemid = left_top; + if (do_texture_skew) + dc_texturemid = FixedMul(left_top, wall_scaley); else - dc_texturemid = *pfloor->topheight - viewz; + dc_texturemid = FixedMul(*pfloor->topheight - viewz, wall_scaley); - if (newline) + offsetvalue = sidedef->rowoffset + sidedef->offsety_mid; + + if (lineflags & ML_DONTPEGBOTTOM) { - offsetvalue = sides[newline->sidenum[0]].rowoffset + sides[newline->sidenum[0]].offsety_mid; - if (newline->flags & ML_DONTPEGBOTTOM) - { - skewslope = *pfloor->b_slope; // skew using bottom slope - if (slopeskew) - dc_texturemid = left_bottom; - else - offsetvalue -= *pfloor->topheight - *pfloor->bottomheight; - } - } - else - { - offsetvalue = sides[pfloor->master->sidenum[0]].rowoffset + sides[pfloor->master->sidenum[0]].offsety_mid; - if (curline->linedef->flags & ML_DONTPEGBOTTOM) - { - skewslope = *pfloor->b_slope; // skew using bottom slope - if (slopeskew) - dc_texturemid = left_bottom; - else - offsetvalue -= *pfloor->topheight - *pfloor->bottomheight; - } + skewslope = *pfloor->b_slope; // skew using bottom slope + if (do_texture_skew) + dc_texturemid = FixedMul(left_bottom, wall_scaley); + else + offsetvalue -= FixedMul(*pfloor->topheight - *pfloor->bottomheight, wall_scaley); } - if (slopeskew) + if (do_texture_skew && skewslope) { angle_t lineangle = R_PointToAngle2(curline->v1->x, curline->v1->y, curline->v2->x, curline->v2->y); - - if (skewslope) - ffloortextureslide = FixedMul(skewslope->zdelta, FINECOSINE((lineangle-skewslope->xydirection)>>ANGLETOFINESHIFT)); + ffloortextureslide = FixedMul(skewslope->zdelta, FINECOSINE((lineangle-skewslope->xydirection)>>ANGLETOFINESHIFT)); } dc_texturemid += offsetvalue; @@ -829,7 +821,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) if (ffloortextureslide) { if (oldx != -1) - dc_texturemid += FixedMul(ffloortextureslide, maskedtexturecol[oldx]-maskedtexturecol[dc_x]); + dc_texturemid += FixedMul(ffloortextureslide, thicksidecol[oldx]-thicksidecol[dc_x]); oldx = dc_x; } @@ -862,10 +854,10 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) continue; } - dc_iscale = 0xffffffffu / (unsigned)spryscale; + dc_iscale = FixedMul(0xffffffffu / (unsigned)spryscale, wall_scaley); // Get data for the column - col = (column_t *)((UINT8 *)R_GetColumn(texnum, (maskedtexturecol[dc_x] >> FRACBITS)) - 3); + col = (column_t *)((UINT8 *)R_GetColumn(texnum, (thicksidecol[dc_x] >> FRACBITS)) - 3); // SoM: New code does not rely on R_DrawColumnShadowed_8 which // will (hopefully) put less strain on the stack. @@ -1439,7 +1431,10 @@ static void R_RenderSegLoop (void) } if (maskedtexturecol) - maskedtexturecol[rw_x] = texturecolumn; + maskedtexturecol[rw_x] = texturecolumn + rw_offsetx; + + if (thicksidecol) + thicksidecol[rw_x] = rw_offset - distance; if (maskedtextureheight) { @@ -1551,10 +1546,9 @@ static void R_AllocClippingTables(size_t range) static void R_AllocTextureColumnTables(size_t range) { size_t pos = curtexturecolumntable - texturecolumntable; + size_t need = range * 3; - // For both tables, we reserve exactly an amount of memory that's equivalent to - // how many columns the seg will take on the entire screen (think about it) - if (pos + range < texturecolumntablesize) + if (pos + need < texturecolumntablesize) return; fixed_t *oldtable = texturecolumntable; @@ -1563,7 +1557,7 @@ static void R_AllocTextureColumnTables(size_t range) if (texturecolumntablesize == 0) texturecolumntablesize = 16384; - texturecolumntablesize += range; + texturecolumntablesize += need; texturecolumntable = Z_Realloc(texturecolumntable, texturecolumntablesize * sizeof (*texturecolumntable), PU_STATIC, NULL); curtexturecolumntable = texturecolumntable + pos; @@ -1604,6 +1598,7 @@ void R_StoreWallRange(INT32 start, INT32 stop) maskedtexturecol = NULL; maskedtextureheight = NULL; + thicksidecol = NULL; invscale = NULL; //initialize segleft and segright @@ -2135,9 +2130,9 @@ void R_StoreWallRange(INT32 start, INT32 stop) rw_toptexturemid += toprowoffset; rw_bottomtexturemid += botrowoffset; + // allocate space for masked texture tables R_AllocTextureColumnTables(rw_stopx - start); - // allocate space for masked texture tables if (frontsector && backsector && !Tag_Compare(&frontsector->tags, &backsector->tags) && (backsector->ffloors || frontsector->ffloors)) { ffloor_t *rover; @@ -2150,7 +2145,7 @@ void R_StoreWallRange(INT32 start, INT32 stop) maskedtexture = true; - ds_p->thicksidecol = curtexturecolumntable - rw_x; + ds_p->thicksidecol = thicksidecol = curtexturecolumntable - rw_x; curtexturecolumntable += rw_stopx - rw_x; lowcut = max(worldbottom, worldlow) + viewz; @@ -2329,14 +2324,17 @@ void R_StoreWallRange(INT32 start, INT32 stop) ds_p->numthicksides = numthicksides = i; } + + // masked midtexture if (sidedef->midtexture > 0 && sidedef->midtexture < numtextures) { - // masked midtexture ds_p->maskedtexturecol = maskedtexturecol = curtexturecolumntable - rw_x; curtexturecolumntable += rw_stopx - rw_x; maskedtextureheight = ds_p->maskedtextureheight; // note to red, this == &(ds_p->maskedtextureheight[0]) + maskedtexture = true; + if (curline->polyseg) { // use REAL front and back floors please, so midtexture rendering isn't mucked up @@ -2427,6 +2425,9 @@ void R_StoreWallRange(INT32 start, INT32 stop) if (rw_midtexturescalex < 0) rw_offsetx = -rw_offsetx; + if (numthicksides) + ds_p->offsetx = rw_offsetx; + // calculate light table // use different light tables // for horizontal / vertical / diagonal From f3c98158b66760861b7f6badad019b203e92883a Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Thu, 23 Nov 2023 20:08:46 -0300 Subject: [PATCH 134/227] Fix top texture offsets --- src/r_segs.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/r_segs.c b/src/r_segs.c index ec3877670..c7d77fa33 100644 --- a/src/r_segs.c +++ b/src/r_segs.c @@ -49,7 +49,7 @@ fixed_t rw_distance; static INT32 rw_x, rw_stopx; static angle_t rw_centerangle; static fixed_t rw_offset, rw_offsetx; -static fixed_t rw_offset_top, rw_offset_mid, rw_offset_bot; +static fixed_t rw_offset_top, rw_offset_mid, rw_offset_bottom; static fixed_t rw_scale, rw_scalestep; static fixed_t rw_midtexturemid, rw_toptexturemid, rw_bottomtexturemid; static INT32 worldtop, worldbottom, worldhigh, worldlow; @@ -1046,6 +1046,7 @@ UINT32 nombre = 100000; static void R_RenderSegLoop (void) { angle_t angle; + fixed_t textureoffset; size_t pindex; INT32 yl; INT32 yh; @@ -1223,10 +1224,8 @@ static void R_RenderSegLoop (void) //SoM: Calculate offsets for Thick fake floors. // calculate texture offset angle = (rw_centerangle + xtoviewangle[rw_x])>>ANGLETOFINESHIFT; - - fixed_t distance = FixedMul(FINETANGENT(angle), rw_distance); - - texturecolumn = FixedDiv(rw_offset - distance, rw_invmidtexturescalex); + textureoffset = rw_offset - FixedMul(FINETANGENT(angle), rw_distance); + texturecolumn = FixedDiv(textureoffset, rw_invmidtexturescalex); // texturecolumn and lighting are independent of wall tiers if (segtextured) @@ -1343,7 +1342,7 @@ static void R_RenderSegLoop (void) if (mid >= floorclip[rw_x]) mid = floorclip[rw_x]-1; - toptexturecolumn = FixedDiv(rw_offset - distance, rw_invtoptexturescalex); + toptexturecolumn = FixedDiv(textureoffset, rw_invtoptexturescalex); if (mid >= yl) // back ceiling lower than front ceiling ? { @@ -1363,7 +1362,7 @@ static void R_RenderSegLoop (void) dc_yh = mid; dc_texturemid = rw_toptexturemid; dc_iscale = FixedMul(0xffffffffu / (unsigned)rw_scale, rw_toptexturescaley); - dc_source = R_GetColumn(toptexture, toptexturecolumn >> FRACBITS); + dc_source = R_GetColumn(toptexture, offset >> FRACBITS); dc_texheight = textureheight[toptexture]>>FRACBITS; colfunc(); ceilingclip[rw_x] = (INT16)mid; @@ -1391,7 +1390,7 @@ static void R_RenderSegLoop (void) if (mid <= ceilingclip[rw_x]) mid = ceilingclip[rw_x]+1; - bottomtexturecolumn = FixedDiv(rw_offset - distance, rw_invbottomtexturescalex); + bottomtexturecolumn = FixedDiv(textureoffset, rw_invbottomtexturescalex); if (mid <= yh) // back floor higher than front floor ? { @@ -1402,7 +1401,7 @@ static void R_RenderSegLoop (void) } else if (mid < viewheight) // safe to draw bottom texture { - fixed_t offset = rw_offset_bot; + fixed_t offset = rw_offset_bottom; if (rw_bottomtexturescalex < 0) offset = -offset; offset = bottomtexturecolumn + offset; @@ -1434,7 +1433,7 @@ static void R_RenderSegLoop (void) maskedtexturecol[rw_x] = texturecolumn + rw_offsetx; if (thicksidecol) - thicksidecol[rw_x] = rw_offset - distance; + thicksidecol[rw_x] = textureoffset; if (maskedtextureheight) { @@ -2388,6 +2387,8 @@ void R_StoreWallRange(INT32 start, INT32 stop) if (segtextured) { + fixed_t sideoffset = sidedef->textureoffset; + offsetangle = rw_normalangle-rw_angle1; if (offsetangle > ANGLE_180) @@ -2415,11 +2416,9 @@ void R_StoreWallRange(INT32 start, INT32 stop) rw_offset += curline->offset; rw_centerangle = ANGLE_90 + viewangle - rw_normalangle; - rw_offsetx = sidedef->textureoffset; - - rw_offset_top = rw_offsetx + sidedef->offsetx_top; - rw_offset_mid = rw_offsetx + sidedef->offsetx_mid; - rw_offset_bot = rw_offsetx + sidedef->offsetx_bot; + rw_offset_top = sideoffset + sidedef->offsetx_top; + rw_offset_mid = sideoffset + sidedef->offsetx_mid; + rw_offset_bottom = sideoffset + sidedef->offsetx_bot; rw_offsetx = rw_offset_mid; if (rw_midtexturescalex < 0) From ca4f7cc5082d2d9bd22a0dd8a32b891ccda1d7d3 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Thu, 23 Nov 2023 21:43:18 -0300 Subject: [PATCH 135/227] Correct OpenGL wall texture scaling --- src/hardware/hw_main.c | 173 ++++++++++++++++++++++------------------- src/p_setup.c | 4 +- 2 files changed, 93 insertions(+), 84 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 0f36a51f7..2bb24497a 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -1149,6 +1149,10 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom // check TOP TEXTURE if ((worldhighslope < worldtopslope || worldhigh < worldtop) && gl_toptexture) { + grTex = HWR_GetTexture(gl_toptexture); + xscale = FixedToFloat(gl_sidedef->scalex_top); + yscale = FixedToFloat(gl_sidedef->scaley_top); + fixed_t texheight = FixedDiv(textureheight[gl_toptexture], gl_sidedef->scaley_top); // PEGGING @@ -1159,41 +1163,39 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom else texturevpeg = gl_backsector->ceilingheight + texheight - gl_frontsector->ceilingheight; + texturevpeg *= yscale; + texturevpeg += gl_sidedef->rowoffset + gl_sidedef->offsety_top; // This is so that it doesn't overflow and screw up the wall, it doesn't need to go higher than the texture's height anyway texturevpeg %= texheight; - grTex = HWR_GetTexture(gl_toptexture); - xscale = FIXED_TO_FLOAT(gl_sidedef->scalex_top) * grTex->scaleX; - yscale = FIXED_TO_FLOAT(gl_sidedef->scaley_top) * grTex->scaleY; - - wallVerts[3].t = wallVerts[2].t = texturevpeg * yscale; - wallVerts[0].t = wallVerts[1].t = (texturevpeg + gl_frontsector->ceilingheight - gl_backsector->ceilingheight) * yscale; - wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_top) * xscale; - wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_top) * xscale; + wallVerts[3].t = wallVerts[2].t = texturevpeg * grTex->scaleY; + wallVerts[0].t = wallVerts[1].t = (texturevpeg + (gl_frontsector->ceilingheight - gl_backsector->ceilingheight) * yscale) * grTex->scaleY; + wallVerts[0].s = wallVerts[3].s = ((cliplow * xscale) + gl_sidedef->offsetx_top) * grTex->scaleX; + wallVerts[2].s = wallVerts[1].s = ((cliphigh * xscale) + gl_sidedef->offsetx_top) * grTex->scaleX; // Adjust t value for sloped walls if (!(gl_linedef->flags & ML_SKEWTD)) { // Unskewed - wallVerts[3].t -= (worldtop - gl_frontsector->ceilingheight) * yscale; - wallVerts[2].t -= (worldtopslope - gl_frontsector->ceilingheight) * yscale; - wallVerts[0].t -= (worldhigh - gl_backsector->ceilingheight) * yscale; - wallVerts[1].t -= (worldhighslope - gl_backsector->ceilingheight) * yscale; + wallVerts[3].t -= (worldtop - gl_frontsector->ceilingheight) * yscale * grTex->scaleY; + wallVerts[2].t -= (worldtopslope - gl_frontsector->ceilingheight) * yscale * grTex->scaleY; + wallVerts[0].t -= (worldhigh - gl_backsector->ceilingheight) * yscale * grTex->scaleY; + wallVerts[1].t -= (worldhighslope - gl_backsector->ceilingheight) * yscale * grTex->scaleY; } else if (gl_linedef->flags & ML_DONTPEGTOP) { // Skewed by top - wallVerts[0].t = (texturevpeg + worldtop - worldhigh) * yscale; - wallVerts[1].t = (texturevpeg + worldtopslope - worldhighslope) * yscale; + wallVerts[0].t = (texturevpeg + (worldtop - worldhigh) * yscale) * grTex->scaleY; + wallVerts[1].t = (texturevpeg + (worldtopslope - worldhighslope) * yscale) * grTex->scaleY; } else { // Skewed by bottom - wallVerts[0].t = wallVerts[1].t = (texturevpeg + worldtop - worldhigh) * yscale; - wallVerts[3].t = wallVerts[0].t - (worldtop - worldhigh) * yscale; - wallVerts[2].t = wallVerts[1].t - (worldtopslope - worldhighslope) * yscale; + wallVerts[0].t = wallVerts[1].t = (texturevpeg + (worldtop - worldhigh) * yscale) * grTex->scaleY; + wallVerts[3].t = wallVerts[0].t - (worldtop - worldhigh) * yscale * grTex->scaleY; + wallVerts[2].t = wallVerts[1].t - (worldtopslope - worldhighslope) * yscale * grTex->scaleY; } // set top/bottom coords @@ -1213,6 +1215,10 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom // check BOTTOM TEXTURE if ((worldlowslope > worldbottomslope || worldlow > worldbottom) && gl_bottomtexture) { + grTex = HWR_GetTexture(gl_bottomtexture); + xscale = FixedToFloat(gl_sidedef->scalex_bot); + yscale = FixedToFloat(gl_sidedef->scaley_bot); + // PEGGING if (!(gl_linedef->flags & ML_DONTPEGBOTTOM)) texturevpeg = 0; @@ -1221,40 +1227,38 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom else texturevpeg = gl_frontsector->floorheight - gl_backsector->floorheight; + texturevpeg *= yscale; + texturevpeg += gl_sidedef->rowoffset + gl_sidedef->offsety_bot; // This is so that it doesn't overflow and screw up the wall, it doesn't need to go higher than the texture's height anyway texturevpeg %= FixedDiv(textureheight[gl_bottomtexture], gl_sidedef->scaley_bot); - grTex = HWR_GetTexture(gl_bottomtexture); - xscale = FIXED_TO_FLOAT(gl_sidedef->scalex_bot) * grTex->scaleX; - yscale = FIXED_TO_FLOAT(gl_sidedef->scaley_bot) * grTex->scaleY; - - wallVerts[3].t = wallVerts[2].t = texturevpeg * yscale; - wallVerts[0].t = wallVerts[1].t = (texturevpeg + gl_backsector->floorheight - gl_frontsector->floorheight) * yscale; - wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_bot) * xscale; - wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_bot) * xscale; + wallVerts[3].t = wallVerts[2].t = texturevpeg * grTex->scaleY; + wallVerts[0].t = wallVerts[1].t = (texturevpeg + (gl_backsector->floorheight - gl_frontsector->floorheight) * yscale) * grTex->scaleY; + wallVerts[0].s = wallVerts[3].s = ((cliplow * xscale) + gl_sidedef->offsetx_bot) * grTex->scaleX; + wallVerts[2].s = wallVerts[1].s = ((cliphigh * xscale) + gl_sidedef->offsetx_bot) * grTex->scaleX; // Adjust t value for sloped walls if (!(gl_linedef->flags & ML_SKEWTD)) { // Unskewed - wallVerts[0].t -= (worldbottom - gl_frontsector->floorheight) * yscale; - wallVerts[1].t -= (worldbottomslope - gl_frontsector->floorheight) * yscale; - wallVerts[3].t -= (worldlow - gl_backsector->floorheight) * yscale; - wallVerts[2].t -= (worldlowslope - gl_backsector->floorheight) * yscale; + wallVerts[0].t -= (worldbottom - gl_frontsector->floorheight) * yscale * grTex->scaleY; + wallVerts[1].t -= (worldbottomslope - gl_frontsector->floorheight) * yscale * grTex->scaleY; + wallVerts[3].t -= (worldlow - gl_backsector->floorheight) * yscale * grTex->scaleY; + wallVerts[2].t -= (worldlowslope - gl_backsector->floorheight) * yscale * grTex->scaleY; } else if (gl_linedef->flags & ML_DONTPEGBOTTOM) { // Skewed by bottom - wallVerts[0].t = wallVerts[1].t = (texturevpeg + worldlow - worldbottom) * yscale; - wallVerts[2].t = wallVerts[1].t - (worldlowslope - worldbottomslope) * yscale; + wallVerts[0].t = wallVerts[1].t = (texturevpeg + (worldlow - worldbottom) * yscale) * grTex->scaleY; + wallVerts[2].t = wallVerts[1].t - (worldlowslope - worldbottomslope) * yscale * grTex->scaleY; } else { // Skewed by top - wallVerts[0].t = (texturevpeg + worldlow - worldbottom) * yscale; - wallVerts[1].t = (texturevpeg + worldlowslope - worldbottomslope) * yscale; + wallVerts[0].t = (texturevpeg + (worldlow - worldbottom) * yscale) * grTex->scaleY; + wallVerts[1].t = (texturevpeg + (worldlowslope - worldbottomslope) * yscale) * grTex->scaleY; } // set top/bottom coords @@ -1311,6 +1315,10 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom else repeats = 1; + grTex = HWR_GetTexture(gl_midtexture); + xscale = FixedToFloat(gl_sidedef->scalex_mid); + yscale = FixedToFloat(gl_sidedef->scaley_mid); + // SoM: a little note: popentop and popenbottom // record the limits the texture can be displayed in. // polytop and polybottom, are the ideal (i.e. unclipped) @@ -1338,19 +1346,21 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom // Find the wall's coordinates fixed_t midtexheight = texheight * repeats; + fixed_t rowoffset = FixedDiv(gl_sidedef->rowoffset + gl_sidedef->offsety_mid, gl_sidedef->scaley_mid); + // Texture is not skewed if (gl_linedef->flags & ML_NOSKEW) { // Peg it to the floor if (gl_linedef->flags & ML_MIDPEG) { - polybottom = max(front->floorheight, back->floorheight) + gl_sidedef->rowoffset + gl_sidedef->offsety_mid; + polybottom = max(front->floorheight, back->floorheight) + rowoffset; polytop = polybottom + midtexheight; } // Peg it to the ceiling else { - polytop = min(front->ceilingheight, back->ceilingheight) + gl_sidedef->rowoffset + gl_sidedef->offsety_mid; + polytop = min(front->ceilingheight, back->ceilingheight) + rowoffset; polybottom = polytop - midtexheight; } @@ -1361,17 +1371,17 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom // Skew the texture, but peg it to the floor else if (gl_linedef->flags & ML_MIDPEG) { - polybottom = popenbottom + gl_sidedef->rowoffset + gl_sidedef->offsety_mid; + polybottom = popenbottom + rowoffset; polytop = polybottom + midtexheight; - polybottomslope = popenbottomslope + gl_sidedef->rowoffset + gl_sidedef->offsety_mid; + polybottomslope = popenbottomslope + rowoffset; polytopslope = polybottomslope + midtexheight; } // Skew it according to the ceiling's slope else { - polytop = popentop + gl_sidedef->rowoffset; + polytop = popentop + rowoffset; polybottom = polytop - midtexheight; - polytopslope = popentopslope + gl_sidedef->rowoffset; + polytopslope = popentopslope + rowoffset; polybottomslope = polytopslope - midtexheight; } @@ -1413,19 +1423,15 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom texturevpegslope = polytopslope - hS; } - grTex = HWR_GetTexture(gl_midtexture); - xscale = FIXED_TO_FLOAT(gl_sidedef->scalex_mid) * grTex->scaleX; - yscale = FIXED_TO_FLOAT(gl_sidedef->scaley_mid) * grTex->scaleY; - // Left side - wallVerts[3].t = texturevpeg * yscale; - wallVerts[0].t = (h - l + texturevpeg) * yscale; - wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_mid) * xscale; + wallVerts[3].t = texturevpeg * yscale * grTex->scaleY; + wallVerts[0].t = (h - l + texturevpeg) * yscale * grTex->scaleY; + wallVerts[0].s = wallVerts[3].s = ((cliplow * xscale) + gl_sidedef->offsetx_mid) * grTex->scaleX; // Right side - wallVerts[2].t = texturevpegslope * yscale; - wallVerts[1].t = (hS - lS + texturevpegslope) * yscale; - wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_mid) * xscale; + wallVerts[2].t = texturevpegslope * yscale * grTex->scaleY; + wallVerts[1].t = (hS - lS + texturevpegslope) * yscale * grTex->scaleY; + wallVerts[2].s = wallVerts[1].s = ((cliphigh * xscale) + gl_sidedef->offsetx_mid) * grTex->scaleX; // set top/bottom coords // Take the texture peg into account, rather than changing the offsets past @@ -1483,38 +1489,40 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom // Single sided line... Deal only with the middletexture (if one exists) if (gl_midtexture && gl_linedef->special != HORIZONSPECIAL) // (Ignore horizon line for OGL) { + grTex = HWR_GetTexture(gl_midtexture); + xscale = FixedToFloat(gl_sidedef->scalex_mid); + yscale = FixedToFloat(gl_sidedef->scaley_mid); + fixed_t texturevpeg; // PEGGING if ((gl_linedef->flags & (ML_DONTPEGBOTTOM|ML_NOSKEW)) == (ML_DONTPEGBOTTOM|ML_NOSKEW)) - texturevpeg = gl_frontsector->floorheight + textureheight[gl_sidedef->midtexture] - gl_frontsector->ceilingheight + gl_sidedef->rowoffset + gl_sidedef->offsety_mid; + texturevpeg = (gl_frontsector->floorheight + textureheight[gl_sidedef->midtexture] - gl_frontsector->ceilingheight) * yscale; else if (gl_linedef->flags & ML_DONTPEGBOTTOM) - texturevpeg = worldbottom + textureheight[gl_sidedef->midtexture] - worldtop + gl_sidedef->rowoffset + gl_sidedef->offsety_mid; + texturevpeg = (worldbottom + textureheight[gl_sidedef->midtexture] - worldtop) * yscale; else // top of texture at top - texturevpeg = gl_sidedef->rowoffset + gl_sidedef->offsety_mid; + texturevpeg = 0; - grTex = HWR_GetTexture(gl_midtexture); - xscale = FIXED_TO_FLOAT(gl_sidedef->scalex_mid) * grTex->scaleX; - yscale = FIXED_TO_FLOAT(gl_sidedef->scaley_mid) * grTex->scaleY; + texturevpeg += gl_sidedef->rowoffset + gl_sidedef->offsety_mid; - wallVerts[3].t = wallVerts[2].t = texturevpeg * yscale; - wallVerts[0].t = wallVerts[1].t = (texturevpeg + gl_frontsector->ceilingheight - gl_frontsector->floorheight) * yscale; - wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_mid) * xscale; - wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_mid) * xscale; + 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 * xscale) + gl_sidedef->offsetx_mid) * grTex->scaleX; + wallVerts[2].s = wallVerts[1].s = ((cliphigh * xscale) + gl_sidedef->offsetx_mid) * grTex->scaleX; // Texture correction for slopes if (gl_linedef->flags & ML_NOSKEW) { - wallVerts[3].t += (gl_frontsector->ceilingheight - worldtop) * yscale; - wallVerts[2].t += (gl_frontsector->ceilingheight - worldtopslope) * yscale; - wallVerts[0].t += (gl_frontsector->floorheight - worldbottom) * yscale; - wallVerts[1].t += (gl_frontsector->floorheight - worldbottomslope) * yscale; + wallVerts[3].t += (gl_frontsector->ceilingheight - worldtop) * yscale * grTex->scaleY; + wallVerts[2].t += (gl_frontsector->ceilingheight - worldtopslope) * yscale * grTex->scaleY; + wallVerts[0].t += (gl_frontsector->floorheight - worldbottom) * yscale * grTex->scaleY; + wallVerts[1].t += (gl_frontsector->floorheight - worldbottomslope) * yscale * yscale; } else if (gl_linedef->flags & ML_DONTPEGBOTTOM) { - wallVerts[3].t = wallVerts[0].t + (worldbottom-worldtop) * yscale; - wallVerts[2].t = wallVerts[1].t + (worldbottomslope-worldtopslope) * yscale; + wallVerts[3].t = wallVerts[0].t + ((worldbottom - worldtop) * yscale) * grTex->scaleY; + wallVerts[2].t = wallVerts[1].t + ((worldbottomslope - worldtopslope) * yscale) * grTex->scaleY; } else { - wallVerts[0].t = wallVerts[3].t - (worldbottom-worldtop) * yscale; - wallVerts[1].t = wallVerts[2].t - (worldbottomslope-worldtopslope) * yscale; + wallVerts[0].t = wallVerts[3].t - ((worldbottom - worldtop) * yscale) * grTex->scaleY; + wallVerts[1].t = wallVerts[2].t - ((worldbottomslope - worldtopslope) * yscale) * grTex->scaleY; } //Set textures properly on single sided walls that are sloped @@ -1661,36 +1669,37 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom } grTex = HWR_GetTexture(texnum); - xscale = FIXED_TO_FLOAT(sides[rover->master->sidenum[0]].scalex_mid) * grTex->scaleX; - yscale = FIXED_TO_FLOAT(sides[rover->master->sidenum[0]].scaley_mid) * grTex->scaleY; + xscale = FixedToFloat(sides[rover->master->sidenum[0]].scalex_mid); + yscale = FixedToFloat(sides[rover->master->sidenum[0]].scaley_mid); if (!slopeskew) // no skewing { if (attachtobottom) - texturevpeg -= *rover->topheight - *rover->bottomheight; - wallVerts[3].t = (*rover->topheight - h + texturevpeg) * yscale; - wallVerts[2].t = (*rover->topheight - hS + texturevpeg) * yscale; - wallVerts[0].t = (*rover->topheight - l + texturevpeg) * yscale; - wallVerts[1].t = (*rover->topheight - lS + texturevpeg) * yscale; + texturevpeg -= (*rover->topheight - *rover->bottomheight) * yscale; + + wallVerts[3].t = (((*rover->topheight - h) * yscale) + texturevpeg) * grTex->scaleY; + wallVerts[2].t = (((*rover->topheight - hS) * yscale) + texturevpeg) * grTex->scaleY; + wallVerts[0].t = (((*rover->topheight - l) * yscale) + texturevpeg) * grTex->scaleY; + wallVerts[1].t = (((*rover->topheight - lS) * yscale) + texturevpeg) * grTex->scaleY; } else { if (!attachtobottom) // skew by top { - wallVerts[3].t = wallVerts[2].t = texturevpeg * yscale; - wallVerts[0].t = (h - l + texturevpeg) * yscale; - wallVerts[1].t = (hS - lS + texturevpeg) * yscale; + wallVerts[3].t = wallVerts[2].t = texturevpeg * grTex->scaleY; + wallVerts[0].t = (((h - l) * yscale) + texturevpeg) * grTex->scaleY; + wallVerts[1].t = (((hS - lS) * yscale) + texturevpeg) * grTex->scaleY; } else // skew by bottom { - wallVerts[0].t = wallVerts[1].t = texturevpeg * yscale; - wallVerts[3].t = wallVerts[0].t - (h - l) * yscale; - wallVerts[2].t = wallVerts[1].t - (hS - lS) * yscale; + wallVerts[0].t = wallVerts[1].t = texturevpeg * grTex->scaleY; + wallVerts[3].t = wallVerts[0].t - ((h - l) * yscale) * grTex->scaleY; + wallVerts[2].t = wallVerts[1].t - ((hS - lS) * yscale) * grTex->scaleY; } } - wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_mid) * xscale; - wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_mid) * xscale; + wallVerts[0].s = wallVerts[3].s = ((cliplow * xscale) + gl_sidedef->offsetx_mid) * grTex->scaleX; + wallVerts[2].s = wallVerts[1].s = ((cliphigh * xscale) + gl_sidedef->offsetx_mid) * grTex->scaleX; } if (rover->fofflags & FOF_FOG) { diff --git a/src/p_setup.c b/src/p_setup.c index 1df91f70b..94dfb75ee 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -3330,7 +3330,7 @@ static void P_LoadSegs(UINT8 *data) seg->length = P_SegLength(seg); #ifdef HWRENDER - seg->flength = (rendermode == render_opengl) ? P_SegLengthFloat(seg) : 0; + seg->flength = P_SegLengthFloat(seg); #endif seg->glseg = false; @@ -3562,7 +3562,7 @@ static boolean P_LoadExtendedSubsectorsAndSegs(UINT8 **data, nodetype_t nodetype } seg->length = P_SegLength(seg); #ifdef HWRENDER - seg->flength = (rendermode == render_opengl) ? P_SegLengthFloat(seg) : 0; + seg->flength = P_SegLengthFloat(seg); #endif } From 41cacd783b0245f0737e6bcdb70177d9b5009f71 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Thu, 23 Nov 2023 21:56:18 -0300 Subject: [PATCH 136/227] Serialize new side fields --- src/hardware/hw_main.c | 12 +- src/lua_maplib.c | 16 +-- src/p_saveg.c | 293 ++++++++++++++++++++++++++--------------- src/p_setup.c | 40 +++--- src/r_defs.h | 8 +- src/r_segs.c | 8 +- 6 files changed, 232 insertions(+), 145 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 2bb24497a..8686a59e8 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -1216,8 +1216,8 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom if ((worldlowslope > worldbottomslope || worldlow > worldbottom) && gl_bottomtexture) { grTex = HWR_GetTexture(gl_bottomtexture); - xscale = FixedToFloat(gl_sidedef->scalex_bot); - yscale = FixedToFloat(gl_sidedef->scaley_bot); + xscale = FixedToFloat(gl_sidedef->scalex_bottom); + yscale = FixedToFloat(gl_sidedef->scaley_bottom); // PEGGING if (!(gl_linedef->flags & ML_DONTPEGBOTTOM)) @@ -1229,15 +1229,15 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom texturevpeg *= yscale; - texturevpeg += gl_sidedef->rowoffset + gl_sidedef->offsety_bot; + texturevpeg += gl_sidedef->rowoffset + gl_sidedef->offsety_bottom; // This is so that it doesn't overflow and screw up the wall, it doesn't need to go higher than the texture's height anyway - texturevpeg %= FixedDiv(textureheight[gl_bottomtexture], gl_sidedef->scaley_bot); + texturevpeg %= FixedDiv(textureheight[gl_bottomtexture], gl_sidedef->scaley_bottom); wallVerts[3].t = wallVerts[2].t = texturevpeg * grTex->scaleY; wallVerts[0].t = wallVerts[1].t = (texturevpeg + (gl_backsector->floorheight - gl_frontsector->floorheight) * yscale) * grTex->scaleY; - wallVerts[0].s = wallVerts[3].s = ((cliplow * xscale) + gl_sidedef->offsetx_bot) * grTex->scaleX; - wallVerts[2].s = wallVerts[1].s = ((cliphigh * xscale) + gl_sidedef->offsetx_bot) * grTex->scaleX; + wallVerts[0].s = wallVerts[3].s = ((cliplow * xscale) + gl_sidedef->offsetx_bottom) * grTex->scaleX; + wallVerts[2].s = wallVerts[1].s = ((cliphigh * xscale) + gl_sidedef->offsetx_bottom) * grTex->scaleX; // Adjust t value for sloped walls if (!(gl_linedef->flags & ML_SKEWTD)) diff --git a/src/lua_maplib.c b/src/lua_maplib.c index c15892ab4..d81d28ba1 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -1218,10 +1218,10 @@ static int side_get(lua_State *L) lua_pushfixed(L, side->offsety_mid); return 1; case side_offsetx_bot: - lua_pushfixed(L, side->offsetx_bot); + lua_pushfixed(L, side->offsetx_bottom); return 1; case side_offsety_bot: - lua_pushfixed(L, side->offsety_bot); + lua_pushfixed(L, side->offsety_bottom); return 1; case side_scalex_top: lua_pushfixed(L, side->scalex_top); @@ -1236,10 +1236,10 @@ static int side_get(lua_State *L) lua_pushfixed(L, side->scaley_mid); return 1; case side_scalex_bot: - lua_pushfixed(L, side->scalex_bot); + lua_pushfixed(L, side->scalex_bottom); return 1; case side_scaley_bot: - lua_pushfixed(L, side->scaley_bot); + lua_pushfixed(L, side->scaley_bottom); return 1; case side_toptexture: lua_pushinteger(L, side->toptexture); @@ -1324,10 +1324,10 @@ static int side_set(lua_State *L) side->offsety_mid = luaL_checkfixed(L, 3); break; case side_offsetx_bot: - side->offsetx_bot = luaL_checkfixed(L, 3); + side->offsetx_bottom = luaL_checkfixed(L, 3); break; case side_offsety_bot: - side->offsety_bot = luaL_checkfixed(L, 3); + side->offsety_bottom = luaL_checkfixed(L, 3); break; case side_scalex_top: side->scalex_top = luaL_checkfixed(L, 3); @@ -1342,10 +1342,10 @@ static int side_set(lua_State *L) side->scaley_mid = luaL_checkfixed(L, 3); break; case side_scalex_bot: - side->scalex_bot = luaL_checkfixed(L, 3); + side->scalex_bottom = luaL_checkfixed(L, 3); break; case side_scaley_bot: - side->scaley_bot = luaL_checkfixed(L, 3); + side->scaley_bottom = luaL_checkfixed(L, 3); break; case side_toptexture: side->toptexture = luaL_checkinteger(L, 3); diff --git a/src/p_saveg.c b/src/p_saveg.c index 4aa2318fd..4ab7aeccc 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -868,23 +868,42 @@ static void P_NetUnArchiveWaypoints(void) #define SD_TRIGGERER 0x04 #define SD_GRAVITY 0x08 -#define LD_FLAG 0x01 -#define LD_SPECIAL 0x02 -#define LD_CLLCOUNT 0x04 -#define LD_S1TEXOFF 0x08 -#define LD_S1TOPTEX 0x10 -#define LD_S1BOTTEX 0x20 -#define LD_S1MIDTEX 0x40 -#define LD_DIFF2 0x80 +#define LD_FLAG 0x01 +#define LD_SPECIAL 0x02 +#define LD_CLLCOUNT 0x04 +#define LD_ARGS 0x08 +#define LD_STRINGARGS 0x10 +#define LD_EXECUTORDELAY 0x20 +#define LD_SIDE1 0x40 +#define LD_SIDE2 0x80 -// diff2 flags -#define LD_S2TEXOFF 0x01 -#define LD_S2TOPTEX 0x02 -#define LD_S2BOTTEX 0x04 -#define LD_S2MIDTEX 0x08 -#define LD_ARGS 0x10 -#define LD_STRINGARGS 0x20 -#define LD_EXECUTORDELAY 0x40 +// sidedef flags +enum +{ + LD_SDTEXOFFX = 1, + LD_SDTEXOFFY = 1<<1, + LD_SDTOPTEX = 1<<2, + LD_SDBOTTEX = 1<<3, + LD_SDMIDTEX = 1<<4, + LD_SDTOPOFFX = 1<<5, + LD_SDTOPOFFY = 1<<6, + LD_SDMIDOFFX = 1<<7, + LD_SDMIDOFFY = 1<<8, + LD_SDBOTOFFX = 1<<9, + LD_SDBOTOFFY = 1<<10, + LD_SDTOPSCALEX = 1<<11, + LD_SDTOPSCALEY = 1<<12, + LD_SDMIDSCALEX = 1<<13, + LD_SDMIDSCALEY = 1<<14, + LD_SDBOTSCALEX = 1<<15, + LD_SDBOTSCALEY = 1<<16, + LD_SDLIGHT = 1<<17, + LD_SDTOPLIGHT = 1<<18, + LD_SDMIDLIGHT = 1<<19, + LD_SDBOTLIGHT = 1<<20, + LD_SDREPEATCNT = 1<<21, + LD_SDFLAGS = 1<<22 +}; static boolean P_AreArgsEqual(const line_t *li, const line_t *spawnli) { @@ -1271,18 +1290,103 @@ static void UnArchiveSectors(void) } } +static UINT32 GetSideDiff(const side_t *si, const side_t *spawnsi) +{ + UINT32 diff = 0; + if (si->textureoffset != spawnsi->textureoffset) + diff |= LD_SDTEXOFFX; + if (si->rowoffset != spawnsi->rowoffset) + diff |= LD_SDTEXOFFY; + //SoM: 4/1/2000: Some textures are colormaps. Don't worry about invalid textures. + if (si->toptexture != spawnsi->toptexture) + diff |= LD_SDTOPTEX; + if (si->bottomtexture != spawnsi->bottomtexture) + diff |= LD_SDBOTTEX; + if (si->midtexture != spawnsi->midtexture) + diff |= LD_SDMIDTEX; + if (si->offsetx_top != spawnsi->offsetx_top) + diff |= LD_SDTOPOFFX; + if (si->offsetx_mid != spawnsi->offsetx_mid) + diff |= LD_SDMIDOFFX; + if (si->offsetx_bottom != spawnsi->offsetx_bottom) + diff |= LD_SDBOTOFFX; + if (si->offsety_top != spawnsi->offsety_top) + diff |= LD_SDTOPOFFY; + if (si->offsety_mid != spawnsi->offsety_mid) + diff |= LD_SDMIDOFFY; + if (si->offsety_bottom != spawnsi->offsety_bottom) + diff |= LD_SDBOTOFFY; + if (si->scalex_top != spawnsi->scalex_top) + diff |= LD_SDTOPSCALEX; + if (si->scalex_mid != spawnsi->scalex_mid) + diff |= LD_SDMIDSCALEX; + if (si->scalex_bottom != spawnsi->scalex_bottom) + diff |= LD_SDBOTSCALEX; + if (si->scaley_top != spawnsi->scaley_top) + diff |= LD_SDTOPSCALEY; + if (si->scaley_mid != spawnsi->scaley_mid) + diff |= LD_SDMIDSCALEY; + if (si->scaley_bottom != spawnsi->scaley_bottom) + diff |= LD_SDBOTSCALEY; + if (si->repeatcnt != spawnsi->repeatcnt) + diff |= LD_SDREPEATCNT; + return diff; +} + +static void ArchiveSide(const side_t *si, UINT32 diff) +{ + WRITEUINT32(save_p, diff); + + if (diff & LD_SDTEXOFFX) + WRITEFIXED(save_p, si->textureoffset); + if (diff & LD_SDTEXOFFY) + WRITEFIXED(save_p, si->rowoffset); + if (diff & LD_SDTOPTEX) + WRITEINT32(save_p, si->toptexture); + if (diff & LD_SDBOTTEX) + WRITEINT32(save_p, si->bottomtexture); + if (diff & LD_SDMIDTEX) + WRITEINT32(save_p, si->midtexture); + if (diff & LD_SDTOPOFFX) + WRITEFIXED(save_p, si->offsetx_top); + if (diff & LD_SDMIDOFFX) + WRITEFIXED(save_p, si->offsetx_mid); + if (diff & LD_SDBOTOFFX) + WRITEFIXED(save_p, si->offsetx_bottom); + if (diff & LD_SDTOPOFFY) + WRITEFIXED(save_p, si->offsety_top); + if (diff & LD_SDMIDOFFY) + WRITEFIXED(save_p, si->offsety_mid); + if (diff & LD_SDBOTOFFY) + WRITEFIXED(save_p, si->offsety_bottom); + if (diff & LD_SDTOPSCALEX) + WRITEFIXED(save_p, si->scalex_top); + if (diff & LD_SDMIDSCALEX) + WRITEFIXED(save_p, si->scalex_mid); + if (diff & LD_SDBOTSCALEX) + WRITEFIXED(save_p, si->scalex_bottom); + if (diff & LD_SDTOPSCALEY) + WRITEFIXED(save_p, si->scaley_top); + if (diff & LD_SDMIDSCALEY) + WRITEFIXED(save_p, si->scaley_mid); + if (diff & LD_SDBOTSCALEY) + WRITEFIXED(save_p, si->scaley_bottom); + if (diff & LD_SDREPEATCNT) + WRITEINT16(save_p, si->repeatcnt); +} + static void ArchiveLines(void) { size_t i; const line_t *li = lines; const line_t *spawnli = spawnlines; - const side_t *si; - const side_t *spawnsi; - UINT8 diff, diff2; // no diff3 + UINT8 diff; + UINT32 diff2; + UINT32 diff3; for (i = 0; i < numlines; i++, spawnli++, li++) { - diff = diff2 = 0; + diff = diff2 = diff3 = 0; if (li->special != spawnli->special) diff |= LD_SPECIAL; @@ -1291,84 +1395,44 @@ static void ArchiveLines(void) diff |= LD_CLLCOUNT; if (!P_AreArgsEqual(li, spawnli)) - diff2 |= LD_ARGS; + diff |= LD_ARGS; if (!P_AreStringArgsEqual(li, spawnli)) - diff2 |= LD_STRINGARGS; + diff |= LD_STRINGARGS; if (li->executordelay != spawnli->executordelay) - diff2 |= LD_EXECUTORDELAY; + diff |= LD_EXECUTORDELAY; if (li->sidenum[0] != 0xffff) { - si = &sides[li->sidenum[0]]; - spawnsi = &spawnsides[li->sidenum[0]]; - if (si->textureoffset != spawnsi->textureoffset) - diff |= LD_S1TEXOFF; - //SoM: 4/1/2000: Some textures are colormaps. Don't worry about invalid textures. - if (si->toptexture != spawnsi->toptexture) - diff |= LD_S1TOPTEX; - if (si->bottomtexture != spawnsi->bottomtexture) - diff |= LD_S1BOTTEX; - if (si->midtexture != spawnsi->midtexture) - diff |= LD_S1MIDTEX; + diff2 = GetSideDiff(&sides[li->sidenum[0]], &spawnsides[li->sidenum[0]]); + if (diff2) + diff |= LD_SIDE1; } if (li->sidenum[1] != 0xffff) { - si = &sides[li->sidenum[1]]; - spawnsi = &spawnsides[li->sidenum[1]]; - if (si->textureoffset != spawnsi->textureoffset) - diff2 |= LD_S2TEXOFF; - if (si->toptexture != spawnsi->toptexture) - diff2 |= LD_S2TOPTEX; - if (si->bottomtexture != spawnsi->bottomtexture) - diff2 |= LD_S2BOTTEX; - if (si->midtexture != spawnsi->midtexture) - diff2 |= LD_S2MIDTEX; + diff3 = GetSideDiff(&sides[li->sidenum[1]], &spawnsides[li->sidenum[1]]); + if (diff3) + diff |= LD_SIDE2; } - if (diff2) - diff |= LD_DIFF2; - if (diff) { WRITEINT16(save_p, i); WRITEUINT8(save_p, diff); - if (diff & LD_DIFF2) - WRITEUINT8(save_p, diff2); if (diff & LD_FLAG) WRITEINT16(save_p, li->flags); if (diff & LD_SPECIAL) WRITEINT16(save_p, li->special); if (diff & LD_CLLCOUNT) WRITEINT16(save_p, li->callcount); - - si = &sides[li->sidenum[0]]; - if (diff & LD_S1TEXOFF) - WRITEFIXED(save_p, si->textureoffset); - if (diff & LD_S1TOPTEX) - WRITEINT32(save_p, si->toptexture); - if (diff & LD_S1BOTTEX) - WRITEINT32(save_p, si->bottomtexture); - if (diff & LD_S1MIDTEX) - WRITEINT32(save_p, si->midtexture); - - si = &sides[li->sidenum[1]]; - if (diff2 & LD_S2TEXOFF) - WRITEFIXED(save_p, si->textureoffset); - if (diff2 & LD_S2TOPTEX) - WRITEINT32(save_p, si->toptexture); - if (diff2 & LD_S2BOTTEX) - WRITEINT32(save_p, si->bottomtexture); - if (diff2 & LD_S2MIDTEX) - WRITEINT32(save_p, si->midtexture); - if (diff2 & LD_ARGS) + if (diff & LD_ARGS) { UINT8 j; for (j = 0; j < NUMLINEARGS; j++) WRITEINT32(save_p, li->args[j]); } - if (diff2 & LD_STRINGARGS) + if (diff & LD_STRINGARGS) { UINT8 j; for (j = 0; j < NUMLINESTRINGARGS; j++) @@ -1387,19 +1451,64 @@ static void ArchiveLines(void) WRITECHAR(save_p, li->stringargs[j][k]); } } - if (diff2 & LD_EXECUTORDELAY) + if (diff & LD_EXECUTORDELAY) WRITEINT32(save_p, li->executordelay); + if (diff & LD_SIDE1) + ArchiveSide(&sides[li->sidenum[0]], diff2); + if (diff & LD_SIDE2) + ArchiveSide(&sides[li->sidenum[1]], diff3); } } WRITEUINT16(save_p, 0xffff); } +static void UnArchiveSide(side_t *si) +{ + UINT32 diff = READUINT32(save_p); + + if (diff & LD_SDTEXOFFX) + si->textureoffset = READFIXED(save_p); + if (diff & LD_SDTEXOFFY) + si->rowoffset = READFIXED(save_p); + if (diff & LD_SDTOPTEX) + si->toptexture = READINT32(save_p); + if (diff & LD_SDBOTTEX) + si->bottomtexture = READINT32(save_p); + if (diff & LD_SDMIDTEX) + si->midtexture = READINT32(save_p); + if (diff & LD_SDTOPOFFX) + si->offsetx_top = READFIXED(save_p); + if (diff & LD_SDMIDOFFX) + si->offsetx_mid = READFIXED(save_p); + if (diff & LD_SDBOTOFFX) + si->offsetx_bottom = READFIXED(save_p); + if (diff & LD_SDTOPOFFY) + si->offsety_top = READFIXED(save_p); + if (diff & LD_SDMIDOFFY) + si->offsety_mid = READFIXED(save_p); + if (diff & LD_SDBOTOFFY) + si->offsety_bottom = READFIXED(save_p); + if (diff & LD_SDTOPSCALEX) + si->scalex_top = READFIXED(save_p); + if (diff & LD_SDMIDSCALEX) + si->scalex_mid = READFIXED(save_p); + if (diff & LD_SDBOTSCALEX) + si->scalex_bottom = READFIXED(save_p); + if (diff & LD_SDTOPSCALEY) + si->scaley_top = READFIXED(save_p); + if (diff & LD_SDMIDSCALEY) + si->scaley_mid = READFIXED(save_p); + if (diff & LD_SDBOTSCALEY) + si->scaley_bottom = READFIXED(save_p); + if (diff & LD_SDREPEATCNT) + si->repeatcnt = READINT16(save_p); +} + static void UnArchiveLines(void) { UINT16 i; line_t *li; - side_t *si; - UINT8 diff, diff2; // no diff3 + UINT8 diff; for (;;) { @@ -1413,44 +1522,19 @@ static void UnArchiveLines(void) diff = READUINT8(save_p); li = &lines[i]; - if (diff & LD_DIFF2) - diff2 = READUINT8(save_p); - else - diff2 = 0; - if (diff & LD_FLAG) li->flags = READINT16(save_p); if (diff & LD_SPECIAL) li->special = READINT16(save_p); if (diff & LD_CLLCOUNT) li->callcount = READINT16(save_p); - - si = &sides[li->sidenum[0]]; - if (diff & LD_S1TEXOFF) - si->textureoffset = READFIXED(save_p); - if (diff & LD_S1TOPTEX) - si->toptexture = READINT32(save_p); - if (diff & LD_S1BOTTEX) - si->bottomtexture = READINT32(save_p); - if (diff & LD_S1MIDTEX) - si->midtexture = READINT32(save_p); - - si = &sides[li->sidenum[1]]; - if (diff2 & LD_S2TEXOFF) - si->textureoffset = READFIXED(save_p); - if (diff2 & LD_S2TOPTEX) - si->toptexture = READINT32(save_p); - if (diff2 & LD_S2BOTTEX) - si->bottomtexture = READINT32(save_p); - if (diff2 & LD_S2MIDTEX) - si->midtexture = READINT32(save_p); - if (diff2 & LD_ARGS) + if (diff & LD_ARGS) { UINT8 j; for (j = 0; j < NUMLINEARGS; j++) li->args[j] = READINT32(save_p); } - if (diff2 & LD_STRINGARGS) + if (diff & LD_STRINGARGS) { UINT8 j; for (j = 0; j < NUMLINESTRINGARGS; j++) @@ -1471,9 +1555,12 @@ static void UnArchiveLines(void) li->stringargs[j][len] = '\0'; } } - if (diff2 & LD_EXECUTORDELAY) + if (diff & LD_EXECUTORDELAY) li->executordelay = READINT32(save_p); - + if (diff & LD_SIDE1) + UnArchiveSide(&sides[li->sidenum[0]]); + if (diff & LD_SIDE2) + UnArchiveSide(&sides[li->sidenum[1]]); } } diff --git a/src/p_setup.c b/src/p_setup.c index 94dfb75ee..79607aea4 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -1347,11 +1347,11 @@ static void P_LoadSidedefs(UINT8 *data) } sd->rowoffset = SHORT(msd->rowoffset)<offsetx_top = sd->offsetx_mid = sd->offsetx_bot = 0; - sd->offsety_top = sd->offsety_mid = sd->offsety_bot = 0; + sd->offsetx_top = sd->offsetx_mid = sd->offsetx_bottom = 0; + sd->offsety_top = sd->offsety_mid = sd->offsety_bottom = 0; - sd->scalex_top = sd->scalex_mid = sd->scalex_bot = FRACUNIT; - sd->scaley_top = sd->scaley_mid = sd->scaley_bot = FRACUNIT; + sd->scalex_top = sd->scalex_mid = sd->scalex_bottom = FRACUNIT; + sd->scaley_top = sd->scaley_mid = sd->scaley_bottom = FRACUNIT; P_SetSidedefSector(i, SHORT(msd->sector)); @@ -1886,25 +1886,25 @@ static void ParseTextmapSidedefParameter(UINT32 i, const char *param, const char else if (fastcmp(param, "offsetx_mid")) sides[i].offsetx_mid = atol(val) << FRACBITS; else if (fastcmp(param, "offsetx_bottom")) - sides[i].offsetx_bot = atol(val) << FRACBITS; + sides[i].offsetx_bottom = atol(val) << FRACBITS; else if (fastcmp(param, "offsety_top")) sides[i].offsety_top = atol(val) << FRACBITS; else if (fastcmp(param, "offsety_mid")) sides[i].offsety_mid = atol(val) << FRACBITS; else if (fastcmp(param, "offsety_bottom")) - sides[i].offsety_bot = atol(val) << FRACBITS; + sides[i].offsety_bottom = atol(val) << FRACBITS; else if (fastcmp(param, "scalex_top")) sides[i].scalex_top = FLOAT_TO_FIXED(atof(val)); else if (fastcmp(param, "scalex_mid")) sides[i].scalex_mid = FLOAT_TO_FIXED(atof(val)); else if (fastcmp(param, "scalex_bottom")) - sides[i].scalex_bot = FLOAT_TO_FIXED(atof(val)); + sides[i].scalex_bottom = FLOAT_TO_FIXED(atof(val)); else if (fastcmp(param, "scaley_top")) sides[i].scaley_top = FLOAT_TO_FIXED(atof(val)); else if (fastcmp(param, "scaley_mid")) sides[i].scaley_mid = FLOAT_TO_FIXED(atof(val)); else if (fastcmp(param, "scaley_bottom")) - sides[i].scaley_bot = FLOAT_TO_FIXED(atof(val)); + sides[i].scaley_bottom = FLOAT_TO_FIXED(atof(val)); else if (fastcmp(param, "texturetop")) sides[i].toptexture = R_TextureNumForName(val); else if (fastcmp(param, "texturebottom")) @@ -2609,10 +2609,10 @@ static void P_WriteTextmap(void) fprintf(f, "offsetx_mid = %d;\n", wsides[i].offsetx_mid >> FRACBITS); if (wsides[i].offsety_mid != 0) fprintf(f, "offsety_mid = %d;\n", wsides[i].offsety_mid >> FRACBITS); - if (wsides[i].offsetx_bot != 0) - fprintf(f, "offsetx_bottom = %d;\n", wsides[i].offsetx_bot >> FRACBITS); - if (wsides[i].offsety_bot != 0) - fprintf(f, "offsety_bottom = %d;\n", wsides[i].offsety_bot >> FRACBITS); + if (wsides[i].offsetx_bottom != 0) + fprintf(f, "offsetx_bottom = %d;\n", wsides[i].offsetx_bottom >> FRACBITS); + if (wsides[i].offsety_bottom != 0) + fprintf(f, "offsety_bottom = %d;\n", wsides[i].offsety_bottom >> FRACBITS); if (wsides[i].scalex_top != FRACUNIT) fprintf(f, "scalex_top = %f;\n", FIXED_TO_FLOAT(wsides[i].scalex_top)); if (wsides[i].scaley_top != FRACUNIT) @@ -2621,10 +2621,10 @@ static void P_WriteTextmap(void) fprintf(f, "scalex_mid = %f;\n", FIXED_TO_FLOAT(wsides[i].scalex_mid)); if (wsides[i].scaley_mid != FRACUNIT) fprintf(f, "scaley_mid = %f;\n", FIXED_TO_FLOAT(wsides[i].scaley_mid)); - if (wsides[i].scalex_bot != FRACUNIT) - fprintf(f, "scalex_bottom = %f;\n", FIXED_TO_FLOAT(wsides[i].scalex_bot)); - if (wsides[i].scaley_bot != FRACUNIT) - fprintf(f, "scaley_bottom = %f;\n", FIXED_TO_FLOAT(wsides[i].scaley_bot)); + if (wsides[i].scalex_bottom != FRACUNIT) + fprintf(f, "scalex_bottom = %f;\n", FIXED_TO_FLOAT(wsides[i].scalex_bottom)); + if (wsides[i].scaley_bottom != FRACUNIT) + fprintf(f, "scaley_bottom = %f;\n", FIXED_TO_FLOAT(wsides[i].scaley_bottom)); if (wsides[i].toptexture > 0 && wsides[i].toptexture < numtextures) fprintf(f, "texturetop = \"%.*s\";\n", 8, textures[wsides[i].toptexture]->name); if (wsides[i].bottomtexture > 0 && wsides[i].bottomtexture < numtextures) @@ -3001,10 +3001,10 @@ static void P_LoadTextmap(void) // Defaults. sd->textureoffset = 0; sd->rowoffset = 0; - sd->offsetx_top = sd->offsetx_mid = sd->offsetx_bot = 0; - sd->offsety_top = sd->offsety_mid = sd->offsety_bot = 0; - sd->scalex_top = sd->scalex_mid = sd->scalex_bot = FRACUNIT; - sd->scaley_top = sd->scaley_mid = sd->scaley_bot = FRACUNIT; + sd->offsetx_top = sd->offsetx_mid = sd->offsetx_bottom = 0; + sd->offsety_top = sd->offsety_mid = sd->offsety_bottom = 0; + sd->scalex_top = sd->scalex_mid = sd->scalex_bottom = FRACUNIT; + sd->scaley_top = sd->scaley_mid = sd->scaley_bottom = FRACUNIT; sd->toptexture = R_TextureNumForName("-"); sd->midtexture = R_TextureNumForName("-"); sd->bottomtexture = R_TextureNumForName("-"); diff --git a/src/r_defs.h b/src/r_defs.h index 82d752260..c9ac42c71 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -559,11 +559,11 @@ typedef struct fixed_t rowoffset; // per-texture offsets for UDMF - fixed_t offsetx_top, offsetx_mid, offsetx_bot; - fixed_t offsety_top, offsety_mid, offsety_bot; + fixed_t offsetx_top, offsetx_mid, offsetx_bottom; + fixed_t offsety_top, offsety_mid, offsety_bottom; - fixed_t scalex_top, scalex_mid, scalex_bot; - fixed_t scaley_top, scaley_mid, scaley_bot; + fixed_t scalex_top, scalex_mid, scalex_bottom; + fixed_t scaley_top, scaley_mid, scaley_bottom; // Texture indices. // We do not maintain names here. diff --git a/src/r_segs.c b/src/r_segs.c index c7d77fa33..8a8827c10 100644 --- a/src/r_segs.c +++ b/src/r_segs.c @@ -2043,7 +2043,7 @@ void R_StoreWallRange(INT32 start, INT32 stop) } fixed_t toprowoffset = sidedef->rowoffset + sidedef->offsety_top; - fixed_t botrowoffset = sidedef->rowoffset + sidedef->offsety_bot; + fixed_t botrowoffset = sidedef->rowoffset + sidedef->offsety_bottom; // check TOP TEXTURE if (!bothceilingssky // never draw the top texture if on @@ -2092,8 +2092,8 @@ void R_StoreWallRange(INT32 start, INT32 stop) // bottom texture bottomtexture = R_GetTextureNum(sidedef->bottomtexture); - rw_bottomtexturescalex = sidedef->scalex_bot; - rw_bottomtexturescaley = sidedef->scaley_bot; + rw_bottomtexturescalex = sidedef->scalex_bottom; + rw_bottomtexturescaley = sidedef->scaley_bottom; rw_invbottomtexturescalex = FixedDiv(FRACUNIT, rw_bottomtexturescalex); @@ -2418,7 +2418,7 @@ void R_StoreWallRange(INT32 start, INT32 stop) rw_offset_top = sideoffset + sidedef->offsetx_top; rw_offset_mid = sideoffset + sidedef->offsetx_mid; - rw_offset_bottom = sideoffset + sidedef->offsetx_bot; + rw_offset_bottom = sideoffset + sidedef->offsetx_bottom; rw_offsetx = rw_offset_mid; if (rw_midtexturescalex < 0) From c53fd6de1660e8255318a0fd8caacaf1439755bf Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 24 Nov 2023 01:27:02 -0300 Subject: [PATCH 137/227] Fix minor issues with 3D floor sides in OpenGL --- src/hardware/hw_main.c | 69 +++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 42 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 8686a59e8..8e5421040 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -1607,15 +1607,17 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom if ((high1 < lowcut && highslope1 < lowcutslope) || (low1 > highcut && lowslope1 > highcutslope)) continue; - texnum = R_GetTextureNum(sides[rover->master->sidenum[0]].midtexture); + side_t *side = &sides[rover->master->sidenum[0]]; if (rover->master->flags & ML_TFERLINE) { size_t linenum = gl_curline->linedef-gl_backsector->lines[0]; newline = rover->master->frontsector->lines[0] + linenum; - texnum = R_GetTextureNum(sides[newline->sidenum[0]].midtexture); + side = &sides[newline->sidenum[0]]; } + texnum = R_GetTextureNum(side->midtexture); + h = P_GetFFloorTopZAt (rover, v1x, v1y); hS = P_GetFFloorTopZAt (rover, v2x, v2y); l = P_GetFFloorBottomZAt(rover, v1x, v1y); @@ -1631,14 +1633,13 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom l = lowcut; lS = lowcutslope; } - //Hurdler: HW code starts here - //FIXME: check if peging is correct - // set top/bottom coords + // set top/bottom coords wallVerts[3].y = FIXED_TO_FLOAT(h); wallVerts[2].y = FIXED_TO_FLOAT(hS); wallVerts[0].y = FIXED_TO_FLOAT(l); wallVerts[1].y = FIXED_TO_FLOAT(lS); + if (rover->fofflags & FOF_FOG) { wallVerts[3].t = wallVerts[2].t = 0; @@ -1648,31 +1649,17 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom } else { - fixed_t texturevpeg; - boolean attachtobottom = false; - boolean slopeskew = false; // skew FOF walls with slopes? - // Wow, how was this missing from OpenGL for so long? // ...Oh well, anyway, Lower Unpegged now changes pegging of FOFs like in software // -- Monster Iestyn 26/06/18 - if (newline) - { - texturevpeg = sides[newline->sidenum[0]].rowoffset + sides[newline->sidenum[0]].offsety_mid; - attachtobottom = !!(newline->flags & ML_DONTPEGBOTTOM); - slopeskew = !!(newline->flags & ML_SKEWTD); - } - else - { - texturevpeg = sides[rover->master->sidenum[0]].rowoffset + sides[rover->master->sidenum[0]].offsety_mid; - attachtobottom = !!(gl_linedef->flags & ML_DONTPEGBOTTOM); - slopeskew = !!(rover->master->flags & ML_SKEWTD); - } + fixed_t texturevpeg = side->rowoffset + side->offsety_mid; + boolean attachtobottom = !!(rover->master->flags & ML_DONTPEGBOTTOM); grTex = HWR_GetTexture(texnum); - xscale = FixedToFloat(sides[rover->master->sidenum[0]].scalex_mid); - yscale = FixedToFloat(sides[rover->master->sidenum[0]].scaley_mid); + xscale = FixedToFloat(side->scalex_mid); + yscale = FixedToFloat(side->scaley_mid); - if (!slopeskew) // no skewing + if (!(rover->master->flags & ML_SKEWTD)) // no skewing { if (attachtobottom) texturevpeg -= (*rover->topheight - *rover->bottomheight) * yscale; @@ -1698,9 +1685,10 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom } } - wallVerts[0].s = wallVerts[3].s = ((cliplow * xscale) + gl_sidedef->offsetx_mid) * grTex->scaleX; - wallVerts[2].s = wallVerts[1].s = ((cliphigh * xscale) + gl_sidedef->offsetx_mid) * grTex->scaleX; + wallVerts[0].s = wallVerts[3].s = ((cliplow * xscale) + side->offsetx_mid) * grTex->scaleX; + wallVerts[2].s = wallVerts[1].s = ((cliphigh * xscale) + side->offsetx_mid) * grTex->scaleX; } + if (rover->fofflags & FOF_FOG) { FBITFIELD blendmode; @@ -1767,14 +1755,17 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom if ((high1 < lowcut && highslope1 < lowcutslope) || (low1 > highcut && lowslope1 > highcutslope)) continue; - texnum = R_GetTextureNum(sides[rover->master->sidenum[0]].midtexture); + side_t *side = &sides[rover->master->sidenum[0]]; if (rover->master->flags & ML_TFERLINE) { size_t linenum = gl_curline->linedef-gl_backsector->lines[0]; newline = rover->master->frontsector->lines[0] + linenum; - texnum = R_GetTextureNum(sides[newline->sidenum[0]].midtexture); + side = &sides[newline->sidenum[0]]; } + + texnum = R_GetTextureNum(side->midtexture); + h = P_GetFFloorTopZAt (rover, v1x, v1y); hS = P_GetFFloorTopZAt (rover, v2x, v2y); l = P_GetFFloorBottomZAt(rover, v1x, v1y); @@ -1808,22 +1799,16 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom else { grTex = HWR_GetTexture(texnum); - xscale = FIXED_TO_FLOAT(sides[rover->master->sidenum[0]].scalex_mid) * grTex->scaleX; - yscale = FIXED_TO_FLOAT(sides[rover->master->sidenum[0]].scaley_mid) * grTex->scaleY; + xscale = FixedToFloat(side->scalex_mid); + yscale = FixedToFloat(side->scaley_mid); - if (newline) - { - wallVerts[3].t = wallVerts[2].t = (*rover->topheight - h + sides[newline->sidenum[0]].rowoffset + sides[newline->sidenum[0]].offsety_mid) * yscale; - wallVerts[0].t = wallVerts[1].t = (h - l + (*rover->topheight - h + sides[newline->sidenum[0]].rowoffset) + sides[newline->sidenum[0]].offsety_mid) * yscale; - } - else - { - wallVerts[3].t = wallVerts[2].t = (*rover->topheight - h + sides[rover->master->sidenum[0]].rowoffset + sides[rover->master->sidenum[0]].offsety_mid) * yscale; - wallVerts[0].t = wallVerts[1].t = (h - l + (*rover->topheight - h + sides[rover->master->sidenum[0]].rowoffset + sides[rover->master->sidenum[0]].offsety_mid)) * yscale; - } + fixed_t diff = (*rover->topheight - h) * yscale; - wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_mid) * xscale; - wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_mid) * xscale; + wallVerts[3].t = wallVerts[2].t = (diff + side->rowoffset + side->offsety_mid) * grTex->scaleY; + wallVerts[0].t = wallVerts[1].t = (((h - l) * yscale) + (diff + side->rowoffset + side->offsety_mid)) * grTex->scaleY; + + wallVerts[0].s = wallVerts[3].s = ((cliplow * xscale) + side->offsetx_mid) * grTex->scaleX; + wallVerts[2].s = wallVerts[1].s = ((cliphigh * xscale) + side->offsetx_mid) * grTex->scaleX; } if (rover->fofflags & FOF_FOG) From 2e4fab3f1a268fb05b1519ff9ee0ff58c8e1e15e Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 24 Nov 2023 01:29:43 -0300 Subject: [PATCH 138/227] Remove unneeded code from HWR_RenderPlane and HWR_RenderPolyObjectPlane --- src/hardware/hw_main.c | 72 ++++++++---------------------------------- 1 file changed, 14 insertions(+), 58 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 8e5421040..3a347196d 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -375,9 +375,6 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool float height; // constant y for all points on the convex flat polygon float flatxref, flatyref, anglef = 0.0f; float fflatwidth = 64.0f, fflatheight = 64.0f; - UINT16 flatflag = 63; - - boolean texflat = false; float tempxsow, tempytow; float scrollx = 0.0f, scrolly = 0.0f; @@ -432,8 +429,8 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool if (levelflat->type == LEVELFLAT_FLAT) { size_t len = W_LumpLength(levelflat->u.flat.lumpnum); - flatflag = R_GetFlatSize(len) - 1; - fflatwidth = fflatheight = (float)(flatflag + 1); + unsigned flatflag = R_GetFlatSize(len); + fflatwidth = fflatheight = (float)flatflag; } else { @@ -447,15 +444,14 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool fflatwidth = levelflat->width; fflatheight = levelflat->height; } - texflat = true; } } else // set no texture HWR_SetCurrentTexture(NULL); // reference point for flat texture coord for each vertex around the polygon - flatxref = (float)(((fixed_t)pv->x & (~flatflag)) / fflatwidth); - flatyref = (float)(((fixed_t)pv->y & (~flatflag)) / fflatheight); + flatxref = 0.0; + flatyref = 0.0; // transform if (FOFsector != NULL) @@ -489,29 +485,12 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool } } - if (angle) // Only needs to be done if there's an altered angle - { - tempxsow = flatxref; - tempytow = flatyref; - - anglef = ANG2RAD(InvAngle(angle)); - - flatxref = (tempxsow * cos(anglef)) - (tempytow * sin(anglef)); - flatyref = (tempxsow * sin(anglef)) + (tempytow * cos(anglef)); - } + anglef = ANG2RAD(InvAngle(angle)); #define SETUP3DVERT(vert, vx, vy) {\ /* Hurdler: add scrolling texture on floor/ceiling */\ - if (texflat)\ - {\ - vert->s = (float)((vx) / fflatwidth) + scrollx;\ - vert->t = -(float)((vy) / fflatheight) + scrolly;\ - }\ - else\ - {\ - vert->s = (float)(((vx) / fflatwidth) - flatxref + scrollx);\ - vert->t = (float)(flatyref - ((vy) / fflatheight) + scrolly);\ - }\ + vert->s = (float)(((vx) / fflatwidth) - flatxref + scrollx);\ + vert->t = (float)(flatyref - ((vy) / fflatheight) + scrolly);\ \ /* Need to rotate before translate */\ if (angle) /* Only needs to be done if there's an altered angle */\ @@ -2664,9 +2643,6 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling, float height = FIXED_TO_FLOAT(fixedheight); // constant y for all points on the convex flat polygon float flatxref, flatyref; float fflatwidth = 64.0f, fflatheight = 64.0f; - UINT16 flatflag = 63; - - boolean texflat = false; float scrollx = 0.0f, scrolly = 0.0f; float tempxsow, tempytow, anglef = 0.0f; @@ -2697,8 +2673,8 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling, if (levelflat->type == LEVELFLAT_FLAT) { size_t len = W_LumpLength(levelflat->u.flat.lumpnum); - flatflag = R_GetFlatSize(len) - 1; - fflatwidth = fflatheight = (float)(flatflag + 1); + unsigned flatflag = R_GetFlatSize(len); + fflatwidth = fflatheight = (float)flatflag; } else { @@ -2712,18 +2688,15 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling, fflatwidth = levelflat->width; fflatheight = levelflat->height; } - texflat = true; } } else // set no texture HWR_SetCurrentTexture(NULL); // reference point for flat texture coord for each vertex around the polygon - flatxref = FIXED_TO_FLOAT(polysector->origVerts[0].x); - flatyref = FIXED_TO_FLOAT(polysector->origVerts[0].y); + flatxref = 0.0; + flatyref = 0.0; - flatxref = (float)(((fixed_t)flatxref & (~flatflag)) / fflatwidth); - flatyref = (float)(((fixed_t)flatyref & (~flatflag)) / fflatheight); // transform v3d = planeVerts; @@ -2759,31 +2732,14 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling, } } - if (angle) // Only needs to be done if there's an altered angle - { - tempxsow = flatxref; - tempytow = flatyref; - - anglef = ANG2RAD(InvAngle(angle)); - - flatxref = (tempxsow * cos(anglef)) - (tempytow * sin(anglef)); - flatyref = (tempxsow * sin(anglef)) + (tempytow * cos(anglef)); - } + anglef = ANG2RAD(InvAngle(angle)); for (i = 0; i < (INT32)nrPlaneVerts; i++,v3d++) { // Go from the polysector's original vertex locations // Means the flat is offset based on the original vertex locations - if (texflat) - { - v3d->s = (float)(FIXED_TO_FLOAT(polysector->origVerts[i].x) / fflatwidth) + scrollx; - v3d->t = -(float)(FIXED_TO_FLOAT(polysector->origVerts[i].y) / fflatheight) + scrolly; - } - else - { - v3d->s = (float)((FIXED_TO_FLOAT(polysector->origVerts[i].x) / fflatwidth) - flatxref + scrollx); - v3d->t = (float)(flatyref - (FIXED_TO_FLOAT(polysector->origVerts[i].y) / fflatheight) + scrolly); - } + v3d->s = (float)((FIXED_TO_FLOAT(polysector->origVerts[i].x) / fflatwidth) - flatxref + scrollx); + v3d->t = (float)(flatyref - (FIXED_TO_FLOAT(polysector->origVerts[i].y) / fflatheight) + scrolly); // Need to rotate before translate if (angle) // Only needs to be done if there's an altered angle From 468b44831661acfe4d10c1a657391fe2e86e7465 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 24 Nov 2023 01:52:57 -0300 Subject: [PATCH 139/227] Add sector texture scale fields --- src/lua_maplib.c | 44 ++++++++++++++++++++++++++++++++------------ src/p_saveg.c | 28 ++++++++++++++++++++++++++++ src/p_setup.c | 22 ++++++++++++++++++++++ src/p_spec.c | 4 ++++ src/r_bsp.c | 44 +++++++++++++++++++++++++++++++++++--------- src/r_defs.h | 8 ++++++++ src/r_plane.c | 12 ++++++++---- src/r_plane.h | 4 ++-- src/r_segs.c | 4 ++++ 9 files changed, 143 insertions(+), 27 deletions(-) diff --git a/src/lua_maplib.c b/src/lua_maplib.c index d81d28ba1..aca07b1c3 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -35,10 +35,14 @@ enum sector_e { sector_floorpic, sector_floorxoffset, sector_flooryoffset, + sector_floorxscale, + sector_flooryscale, sector_floorangle, sector_ceilingpic, sector_ceilingxoffset, sector_ceilingyoffset, + sector_ceilingxscale, + sector_ceilingyscale, sector_ceilingangle, sector_lightlevel, sector_floorlightlevel, @@ -73,10 +77,14 @@ static const char *const sector_opt[] = { "floorpic", "floorxoffset", "flooryoffset", + "floorxscale", + "flooryscale", "floorangle", "ceilingpic", "ceilingxoffset", "ceilingyoffset", + "ceilingxscale", + "ceilingyscale", "ceilingangle", "lightlevel", "floorlightlevel", @@ -666,20 +674,20 @@ static int sector_get(lua_State *L) return 1; } case sector_floorxoffset: - { lua_pushfixed(L, sector->floorxoffset); return 1; - } case sector_flooryoffset: - { lua_pushfixed(L, sector->flooryoffset); return 1; - } + case sector_floorxscale: + lua_pushfixed(L, sector->floorxscale); + return 1; + case sector_flooryscale: + lua_pushfixed(L, sector->flooryscale); + return 1; case sector_floorangle: - { lua_pushangle(L, sector->floorangle); return 1; - } case sector_ceilingpic: // ceilingpic { levelflat_t *levelflat = &levelflats[sector->ceilingpic]; @@ -690,20 +698,20 @@ static int sector_get(lua_State *L) return 1; } case sector_ceilingxoffset: - { lua_pushfixed(L, sector->ceilingxoffset); return 1; - } case sector_ceilingyoffset: - { lua_pushfixed(L, sector->ceilingyoffset); return 1; - } + case sector_ceilingxscale: + lua_pushfixed(L, sector->ceilingxscale); + return 1; + case sector_ceilingyscale: + lua_pushfixed(L, sector->ceilingyscale); + return 1; case sector_ceilingangle: - { lua_pushangle(L, sector->ceilingangle); return 1; - } case sector_lightlevel: lua_pushinteger(L, sector->lightlevel); return 1; @@ -852,6 +860,12 @@ static int sector_set(lua_State *L) case sector_flooryoffset: sector->flooryoffset = luaL_checkfixed(L, 3); break; + case sector_floorxscale: + sector->floorxscale = luaL_checkfixed(L, 3); + break; + case sector_flooryscale: + sector->flooryscale = luaL_checkfixed(L, 3); + break; case sector_floorangle: sector->floorangle = luaL_checkangle(L, 3); break; @@ -864,6 +878,12 @@ static int sector_set(lua_State *L) case sector_ceilingyoffset: sector->ceilingyoffset = luaL_checkfixed(L, 3); break; + case sector_ceilingxscale: + sector->ceilingxscale = luaL_checkfixed(L, 3); + break; + case sector_ceilingyscale: + sector->ceilingyscale = luaL_checkfixed(L, 3); + break; case sector_ceilingangle: sector->ceilingangle = luaL_checkangle(L, 3); break; diff --git a/src/p_saveg.c b/src/p_saveg.c index 4ab7aeccc..eaaf10b86 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -867,6 +867,10 @@ static void P_NetUnArchiveWaypoints(void) #define SD_TRIGGERTAG 0x02 #define SD_TRIGGERER 0x04 #define SD_GRAVITY 0x08 +#define SD_FXSCALE 0x10 +#define SD_FYSCALE 0x20 +#define SD_CXSCALE 0x40 +#define SD_CYSCALE 0x80 #define LD_FLAG 0x01 #define LD_SPECIAL 0x02 @@ -1054,6 +1058,14 @@ static void ArchiveSectors(void) diff2 |= SD_CXOFFS; if (ss->ceilingyoffset != spawnss->ceilingyoffset) diff2 |= SD_CYOFFS; + if (ss->floorxscale != spawnss->floorxscale) + diff2 |= SD_FXSCALE; + if (ss->flooryscale != spawnss->flooryscale) + diff2 |= SD_FYSCALE; + if (ss->ceilingxscale != spawnss->ceilingxscale) + diff2 |= SD_CXSCALE; + if (ss->ceilingyscale != spawnss->ceilingyscale) + diff2 |= SD_CYSCALE; if (ss->floorangle != spawnss->floorangle) diff2 |= SD_FLOORANG; if (ss->ceilingangle != spawnss->ceilingangle) @@ -1164,6 +1176,14 @@ static void ArchiveSectors(void) WRITEUINT8(save_p, ss->triggerer); if (diff4 & SD_GRAVITY) WRITEFIXED(save_p, ss->gravity); + if (diff4 & SD_FXSCALE) + WRITEFIXED(save_p, ss->floorxscale); + if (diff4 & SD_FYSCALE) + WRITEFIXED(save_p, ss->flooryscale); + if (diff4 & SD_CXSCALE) + WRITEFIXED(save_p, ss->ceilingxscale); + if (diff4 & SD_CYSCALE) + WRITEFIXED(save_p, ss->ceilingyscale); if (diff & SD_FFLOORS) ArchiveFFloors(ss); } @@ -1284,6 +1304,14 @@ static void UnArchiveSectors(void) sectors[i].triggerer = READUINT8(save_p); if (diff4 & SD_GRAVITY) sectors[i].gravity = READFIXED(save_p); + if (diff4 & SD_FXSCALE) + sectors[i].floorxscale = READFIXED(save_p); + if (diff4 & SD_FYSCALE) + sectors[i].flooryscale = READFIXED(save_p); + if (diff4 & SD_CXSCALE) + sectors[i].ceilingxscale = READFIXED(save_p); + if (diff4 & SD_CYSCALE) + sectors[i].ceilingyscale = READFIXED(save_p); if (diff & SD_FFLOORS) UnArchiveFFloors(§ors[i]); diff --git a/src/p_setup.c b/src/p_setup.c index 79607aea4..cc80973d9 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -1055,6 +1055,9 @@ static void P_LoadSectors(UINT8 *data) ss->floorxoffset = ss->flooryoffset = 0; ss->ceilingxoffset = ss->ceilingyoffset = 0; + ss->floorxscale = ss->flooryscale = FRACUNIT; + ss->ceilingxscale = ss->ceilingyscale = FRACUNIT; + ss->floorangle = ss->ceilingangle = 0; ss->floorlightlevel = ss->ceilinglightlevel = 0; @@ -1691,6 +1694,14 @@ static void ParseTextmapSectorParameter(UINT32 i, const char *param, const char sectors[i].ceilingxoffset = FLOAT_TO_FIXED(atof(val)); else if (fastcmp(param, "ypanningceiling")) sectors[i].ceilingyoffset = FLOAT_TO_FIXED(atof(val)); + else if (fastcmp(param, "xscalefloor")) + sectors[i].floorxscale = FLOAT_TO_FIXED(atof(val)); + else if (fastcmp(param, "yscalefloor")) + sectors[i].flooryscale = FLOAT_TO_FIXED(atof(val)); + else if (fastcmp(param, "xscaleceiling")) + sectors[i].ceilingxscale = FLOAT_TO_FIXED(atof(val)); + else if (fastcmp(param, "yscaleceiling")) + sectors[i].ceilingyscale = FLOAT_TO_FIXED(atof(val)); else if (fastcmp(param, "rotationfloor")) sectors[i].floorangle = FixedAngle(FLOAT_TO_FIXED(atof(val))); else if (fastcmp(param, "rotationceiling")) @@ -2680,6 +2691,14 @@ static void P_WriteTextmap(void) fprintf(f, "xpanningceiling = %f;\n", FIXED_TO_FLOAT(tempsec.ceilingxoffset)); if (tempsec.ceilingyoffset != 0) fprintf(f, "ypanningceiling = %f;\n", FIXED_TO_FLOAT(tempsec.ceilingyoffset)); + if (tempsec.floorxscale != 0) + fprintf(f, "xscalefloor = %f;\n", FIXED_TO_FLOAT(tempsec.floorxscale)); + if (tempsec.flooryscale != 0) + fprintf(f, "yscalefloor = %f;\n", FIXED_TO_FLOAT(tempsec.flooryscale)); + if (tempsec.ceilingxscale != 0) + fprintf(f, "xscaleceiling = %f;\n", FIXED_TO_FLOAT(tempsec.ceilingxscale)); + if (tempsec.ceilingyscale != 0) + fprintf(f, "yscaleceiling = %f;\n", FIXED_TO_FLOAT(tempsec.ceilingyscale)); if (wsectors[i].floorangle != 0) fprintf(f, "rotationfloor = %f;\n", FIXED_TO_FLOAT(AngleFixed(wsectors[i].floorangle))); if (wsectors[i].ceilingangle != 0) @@ -2915,6 +2934,9 @@ static void P_LoadTextmap(void) sc->floorxoffset = sc->flooryoffset = 0; sc->ceilingxoffset = sc->ceilingyoffset = 0; + sc->floorxscale = sc->flooryscale = FRACUNIT; + sc->ceilingxscale = sc->ceilingyscale = FRACUNIT; + sc->floorangle = sc->ceilingangle = 0; sc->floorlightlevel = sc->ceilinglightlevel = 0; diff --git a/src/p_spec.c b/src/p_spec.c index aa4ee37cf..8c09ad71d 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -5597,6 +5597,8 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, I fflr->bottompic = &sec2->floorpic; fflr->bottomxoffs = &sec2->floorxoffset; fflr->bottomyoffs = &sec2->flooryoffset; + fflr->bottomxscale = &sec2->floorxscale; + fflr->bottomyscale = &sec2->flooryscale; fflr->bottomangle = &sec2->floorangle; // Add the ceiling @@ -5605,6 +5607,8 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, I fflr->toplightlevel = &sec2->lightlevel; fflr->topxoffs = &sec2->ceilingxoffset; fflr->topyoffs = &sec2->ceilingyoffset; + fflr->topxscale = &sec2->ceilingxscale; + fflr->topyscale = &sec2->ceilingyscale; fflr->topangle = &sec2->ceilingangle; // Add slopes diff --git a/src/r_bsp.c b/src/r_bsp.c index 42e050adf..d99de5981 100644 --- a/src/r_bsp.c +++ b/src/r_bsp.c @@ -277,6 +277,8 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel, tempsec->floorpic = s->floorpic; tempsec->floorxoffset = s->floorxoffset; tempsec->flooryoffset = s->flooryoffset; + tempsec->floorxscale = s->floorxscale; + tempsec->flooryscale = s->flooryscale; tempsec->floorangle = s->floorangle; if (underwater) @@ -287,6 +289,8 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel, tempsec->ceilingpic = tempsec->floorpic; tempsec->ceilingxoffset = tempsec->floorxoffset; tempsec->ceilingyoffset = tempsec->flooryoffset; + tempsec->ceilingxscale = tempsec->floorxscale; + tempsec->ceilingyscale = tempsec->flooryscale; tempsec->ceilingangle = tempsec->floorangle; } else @@ -294,6 +298,8 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel, tempsec->ceilingpic = s->ceilingpic; tempsec->ceilingxoffset = s->ceilingxoffset; tempsec->ceilingyoffset = s->ceilingyoffset; + tempsec->ceilingxscale = s->ceilingxscale; + tempsec->ceilingyscale = s->ceilingyscale; tempsec->ceilingangle = s->ceilingangle; } } @@ -317,6 +323,8 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel, tempsec->floorpic = tempsec->ceilingpic = s->ceilingpic; tempsec->floorxoffset = tempsec->ceilingxoffset = s->ceilingxoffset; tempsec->flooryoffset = tempsec->ceilingyoffset = s->ceilingyoffset; + tempsec->floorxscale = tempsec->ceilingxscale = s->ceilingxscale; + tempsec->flooryscale = tempsec->ceilingyscale = s->ceilingyscale; tempsec->floorangle = tempsec->ceilingangle = s->ceilingangle; if (s->floorpic == skyflatnum) // SKYFIX? @@ -325,6 +333,8 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel, tempsec->floorpic = tempsec->ceilingpic; tempsec->floorxoffset = tempsec->ceilingxoffset; tempsec->flooryoffset = tempsec->ceilingyoffset; + tempsec->floorxscale = tempsec->ceilingxscale; + tempsec->flooryscale = tempsec->ceilingyscale; tempsec->floorangle = tempsec->ceilingangle; } else @@ -333,6 +343,8 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel, tempsec->floorpic = s->floorpic; tempsec->floorxoffset = s->floorxoffset; tempsec->flooryoffset = s->flooryoffset; + tempsec->floorxscale = s->floorxscale; + tempsec->flooryscale = s->flooryscale; tempsec->floorangle = s->floorangle; } @@ -362,12 +374,16 @@ boolean R_IsEmptyLine(seg_t *line, sector_t *front, sector_t *back) && back->c_slope == front->c_slope && back->lightlevel == front->lightlevel && !line->sidedef->midtexture - // Check offsets too! + // Check offsets and scale too! && back->floorxoffset == front->floorxoffset && back->flooryoffset == front->flooryoffset + && back->floorxscale == front->floorxscale + && back->flooryscale == front->flooryscale && back->floorangle == front->floorangle && back->ceilingxoffset == front->ceilingxoffset && back->ceilingyoffset == front->ceilingyoffset + && back->ceilingxscale == front->ceilingxscale + && back->ceilingyscale == front->ceilingyscale && back->ceilingangle == front->ceilingangle // Consider altered lighting. && back->floorlightlevel == front->floorlightlevel @@ -909,7 +925,9 @@ static void R_Subsector(size_t num) || (frontsector->heightsec != -1 && sectors[frontsector->heightsec].ceilingpic == skyflatnum)) { floorplane = R_FindPlane(frontsector->floorheight, frontsector->floorpic, floorlightlevel, - frontsector->floorxoffset, frontsector->flooryoffset, frontsector->floorangle, floorcolormap, NULL, NULL, frontsector->f_slope); + frontsector->floorxoffset, frontsector->flooryoffset, + frontsector->floorxscale, frontsector->flooryscale, frontsector->floorangle, + floorcolormap, NULL, NULL, frontsector->f_slope); } else floorplane = NULL; @@ -918,8 +936,9 @@ static void R_Subsector(size_t num) || frontsector->ceilingpic == skyflatnum || (frontsector->heightsec != -1 && sectors[frontsector->heightsec].floorpic == skyflatnum)) { - ceilingplane = R_FindPlane(frontsector->ceilingheight, frontsector->ceilingpic, - ceilinglightlevel, frontsector->ceilingxoffset, frontsector->ceilingyoffset, frontsector->ceilingangle, + ceilingplane = R_FindPlane(frontsector->ceilingheight, frontsector->ceilingpic, ceilinglightlevel, + frontsector->ceilingxoffset, frontsector->ceilingyoffset, + frontsector->ceilingxscale, frontsector->ceilingyscale, frontsector->ceilingangle, ceilingcolormap, NULL, NULL, frontsector->c_slope); } else @@ -962,8 +981,9 @@ static void R_Subsector(size_t num) viewz < heightcheck); ffloor[numffloors].plane = R_FindPlane(*rover->bottomheight, *rover->bottompic, - *frontsector->lightlist[light].lightlevel, *rover->bottomxoffs, - *rover->bottomyoffs, *rover->bottomangle, *frontsector->lightlist[light].extra_colormap, rover, NULL, *rover->b_slope); + *frontsector->lightlist[light].lightlevel, *rover->bottomxoffs, *rover->bottomyoffs, + *rover->bottomxscale, *rover->bottomyscale, *rover->bottomangle, + *frontsector->lightlist[light].extra_colormap, rover, NULL, *rover->b_slope); ffloor[numffloors].slope = *rover->b_slope; @@ -991,7 +1011,8 @@ static void R_Subsector(size_t num) light = R_GetPlaneLight(frontsector, planecenterz, viewz < heightcheck); ffloor[numffloors].plane = R_FindPlane(*rover->topheight, *rover->toppic, - *frontsector->lightlist[light].lightlevel, *rover->topxoffs, *rover->topyoffs, *rover->topangle, + *frontsector->lightlist[light].lightlevel, *rover->topxoffs, *rover->topyoffs, + *rover->topxscale, *rover->topyscale, *rover->topangle, *frontsector->lightlist[light].extra_colormap, rover, NULL, *rover->t_slope); ffloor[numffloors].slope = *rover->t_slope; @@ -1033,7 +1054,9 @@ static void R_Subsector(size_t num) { light = R_GetPlaneLight(frontsector, polysec->floorheight, viewz < polysec->floorheight); ffloor[numffloors].plane = R_FindPlane(polysec->floorheight, polysec->floorpic, - (light == -1 ? frontsector->lightlevel : *frontsector->lightlist[light].lightlevel), polysec->floorxoffset, polysec->flooryoffset, + (light == -1 ? frontsector->lightlevel : *frontsector->lightlist[light].lightlevel), + polysec->floorxoffset, polysec->flooryoffset, + polysec->floorxscale, polysec->flooryscale, polysec->floorangle-po->angle, (light == -1 ? frontsector->extra_colormap : *frontsector->lightlist[light].extra_colormap), NULL, po, NULL); // will ffloors be slopable eventually? @@ -1057,7 +1080,10 @@ static void R_Subsector(size_t num) { light = R_GetPlaneLight(frontsector, polysec->floorheight, viewz < polysec->floorheight); ffloor[numffloors].plane = R_FindPlane(polysec->ceilingheight, polysec->ceilingpic, - (light == -1 ? frontsector->lightlevel : *frontsector->lightlist[light].lightlevel), polysec->ceilingxoffset, polysec->ceilingyoffset, polysec->ceilingangle-po->angle, + (light == -1 ? frontsector->lightlevel : *frontsector->lightlist[light].lightlevel), + polysec->ceilingxoffset, polysec->ceilingyoffset, + polysec->ceilingxscale, polysec->ceilingyscale, + polysec->ceilingangle-po->angle, (light == -1 ? frontsector->extra_colormap : *frontsector->lightlist[light].extra_colormap), NULL, po, NULL); // will ffloors be slopable eventually? diff --git a/src/r_defs.h b/src/r_defs.h index c9ac42c71..3839cb937 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -215,12 +215,16 @@ typedef struct ffloor_s INT16 *toplightlevel; fixed_t *topxoffs; fixed_t *topyoffs; + fixed_t *topxscale; + fixed_t *topyscale; angle_t *topangle; fixed_t *bottomheight; INT32 *bottompic; fixed_t *bottomxoffs; fixed_t *bottomyoffs; + fixed_t *bottomxscale; + fixed_t *bottomyscale; angle_t *bottomangle; // Pointers to pointers. Yup. @@ -426,6 +430,10 @@ typedef struct sector_s fixed_t floorxoffset, flooryoffset; fixed_t ceilingxoffset, ceilingyoffset; + // floor and ceiling texture scale + fixed_t floorxscale, flooryscale; + fixed_t ceilingxscale, ceilingyscale; + // flat angle angle_t floorangle; angle_t ceilingangle; diff --git a/src/r_plane.c b/src/r_plane.c index 29ce26b29..6e186b9bf 100644 --- a/src/r_plane.c +++ b/src/r_plane.c @@ -391,7 +391,8 @@ static visplane_t *new_visplane(unsigned hash) // If not, allocates another of them. // visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel, - fixed_t xoff, fixed_t yoff, angle_t plangle, extracolormap_t *planecolormap, + fixed_t xoff, fixed_t yoff, fixed_t xscale, fixed_t yscale, + angle_t plangle, extracolormap_t *planecolormap, ffloor_t *pfloor, polyobj_t *polyobj, pslope_t *slope) { visplane_t *check; @@ -401,6 +402,7 @@ visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel, { xoff += viewx; yoff -= viewy; + if (plangle != 0) { // Add the view offset, rotated by the plane angle. @@ -429,6 +431,9 @@ visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel, } } + (void)xscale; + (void)yscale; + // This appears to fix the Nimbus Ruins sky bug. if (picnum == skyflatnum && pfloor) { @@ -441,8 +446,6 @@ visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel, hash = visplane_hash(picnum, lightlevel, height); for (check = visplanes[hash]; check; check = check->next) { - if (polyobj != check->polyobj) - continue; if (height == check->height && picnum == check->picnum && lightlevel == check->lightlevel && xoff == check->xoffs && yoff == check->yoffs @@ -450,7 +453,8 @@ visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel, && check->viewx == viewx && check->viewy == viewy && check->viewz == viewz && check->viewangle == viewangle && check->plangle == plangle - && check->slope == slope) + && check->slope == slope + && check->polyobj == polyobj) { return check; } diff --git a/src/r_plane.h b/src/r_plane.h index 917e8b041..831d4a6e1 100644 --- a/src/r_plane.h +++ b/src/r_plane.h @@ -75,8 +75,8 @@ void R_ClearPlanes(void); void R_ClearFFloorClips (void); void R_DrawPlanes(void); -visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel, fixed_t xoff, fixed_t yoff, angle_t plangle, - extracolormap_t *planecolormap, ffloor_t *ffloor, polyobj_t *polyobj, pslope_t *slope); +visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel, fixed_t xoff, fixed_t yoff, fixed_t xscale, fixed_t yscale, + angle_t plangle, extracolormap_t *planecolormap, ffloor_t *ffloor, polyobj_t *polyobj, pslope_t *slope); visplane_t *R_CheckPlane(visplane_t *pl, INT32 start, INT32 stop); void R_ExpandPlane(visplane_t *pl, INT32 start, INT32 stop); void R_PlaneBounds(visplane_t *plane); diff --git a/src/r_segs.c b/src/r_segs.c index 8a8827c10..987eb014c 100644 --- a/src/r_segs.c +++ b/src/r_segs.c @@ -1981,6 +1981,8 @@ void R_StoreWallRange(INT32 start, INT32 stop) //SoM: 3/22/2000: Check floor x and y offsets. || backsector->floorxoffset != frontsector->floorxoffset || backsector->flooryoffset != frontsector->flooryoffset + || backsector->floorxscale != frontsector->floorxscale + || backsector->flooryscale != frontsector->flooryscale || backsector->floorangle != frontsector->floorangle //SoM: 3/22/2000: Prevents bleeding. || (frontsector->heightsec != -1 && frontsector->floorpic != skyflatnum) @@ -2014,6 +2016,8 @@ void R_StoreWallRange(INT32 start, INT32 stop) //SoM: 3/22/2000: Check floor x and y offsets. || backsector->ceilingxoffset != frontsector->ceilingxoffset || backsector->ceilingyoffset != frontsector->ceilingyoffset + || backsector->ceilingxscale != frontsector->ceilingxscale + || backsector->ceilingyscale != frontsector->ceilingyscale || backsector->ceilingangle != frontsector->ceilingangle //SoM: 3/22/2000: Prevents bleeding. || (frontsector->heightsec != -1 && frontsector->ceilingpic != skyflatnum) From da68f4c6693a33f0966c561442cc0046858a256d Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 24 Nov 2023 02:05:08 -0300 Subject: [PATCH 140/227] Sector texture scaling, part 1 --- src/r_plane.c | 29 ++++++++++++++++++++++++----- src/r_plane.h | 1 + 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/r_plane.c b/src/r_plane.c index 6e186b9bf..786ca440c 100644 --- a/src/r_plane.c +++ b/src/r_plane.c @@ -169,6 +169,8 @@ static void R_MapPlane(INT32 y, INT32 x1, INT32 x2) { ds_xstep = FixedMul(planesin, planeheight) / span; ds_ystep = FixedMul(planecos, planeheight) / span; + ds_xstep = FixedMul(currentplane->xscale, ds_xstep); + ds_ystep = FixedMul(currentplane->yscale, ds_ystep); } else ds_xstep = ds_ystep = FRACUNIT; @@ -188,8 +190,8 @@ static void R_MapPlane(INT32 y, INT32 x1, INT32 x2) // to step from those to the proper texture coordinate to start drawing at. // That way, the texture coordinate is always calculated by its position // on the screen and not by its position relative to the edge of the visplane. - ds_xfrac = xoffs + FixedMul(planecos, distance) + (x1 - centerx) * ds_xstep; - ds_yfrac = yoffs - FixedMul(planesin, distance) + (x1 - centerx) * ds_ystep; + ds_xfrac = xoffs + FixedMul(currentplane->xscale, FixedMul(planecos, distance)) + (x1 - centerx) * ds_xstep; + ds_yfrac = yoffs - FixedMul(currentplane->yscale, FixedMul(planesin, distance)) + (x1 - centerx) * ds_ystep; // Water ripple effect if (planeripple.active) @@ -431,8 +433,11 @@ visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel, } } - (void)xscale; - (void)yscale; + if (!slope) + { + xoff = FixedMul(xoff, xscale); + yoff = FixedMul(yoff, yscale); + } // This appears to fix the Nimbus Ruins sky bug. if (picnum == skyflatnum && pfloor) @@ -449,6 +454,7 @@ visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel, if (height == check->height && picnum == check->picnum && lightlevel == check->lightlevel && xoff == check->xoffs && yoff == check->yoffs + && xscale == check->xscale && yscale == check->yscale && planecolormap == check->extra_colormap && check->viewx == viewx && check->viewy == viewy && check->viewz == viewz && check->viewangle == viewangle @@ -474,6 +480,8 @@ visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel, check->maxx = -1; check->xoffs = xoff; check->yoffs = yoff; + check->xscale = xscale; + check->yscale = yscale; check->extra_colormap = planecolormap; check->ffloor = pfloor; check->viewx = viewx; @@ -550,6 +558,8 @@ visplane_t *R_CheckPlane(visplane_t *pl, INT32 start, INT32 stop) new_pl->lightlevel = pl->lightlevel; new_pl->xoffs = pl->xoffs; new_pl->yoffs = pl->yoffs; + new_pl->xscale = pl->xscale; + new_pl->yscale = pl->yscale; new_pl->extra_colormap = pl->extra_colormap; new_pl->ffloor = pl->ffloor; new_pl->viewx = pl->viewx; @@ -816,7 +826,16 @@ void R_SetTiltedSpan(INT32 span) static void R_SetSlopePlaneVectors(visplane_t *pl, INT32 y, fixed_t xoff, fixed_t yoff) { R_SetTiltedSpan(y); - R_SetSlopePlane(pl->slope, pl->viewx, pl->viewy, pl->viewz, xoff, yoff, pl->viewangle, pl->plangle); + + if (pl->xscale != FRACUNIT || pl->yscale != FRACUNIT) + { + R_SetScaledSlopePlane(pl->slope, pl->viewx, pl->viewy, pl->viewz, + FixedDiv(FRACUNIT, pl->xscale), FixedDiv(FRACUNIT, pl->yscale), + xoff, yoff, pl->viewangle, pl->plangle); + } + else + R_SetSlopePlane(pl->slope, pl->viewx, pl->viewy, pl->viewz, xoff, yoff, pl->viewangle, pl->plangle); + R_CalculateSlopeVectors(); } diff --git a/src/r_plane.h b/src/r_plane.h index 831d4a6e1..344e71830 100644 --- a/src/r_plane.h +++ b/src/r_plane.h @@ -49,6 +49,7 @@ typedef struct visplane_s INT32 high, low; // R_PlaneBounds should set these. fixed_t xoffs, yoffs; // Scrolling flats. + fixed_t xscale, yscale; struct ffloor_s *ffloor; polyobj_t *polyobj; From 6f4d1b43ea06ecfd0c71cb2cdb9a482c449d4dc1 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 24 Nov 2023 02:11:59 -0300 Subject: [PATCH 141/227] Fix scaling of walls that are skewed by a slope --- src/r_segs.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/r_segs.c b/src/r_segs.c index 8a8827c10..a8fe4deb5 100644 --- a/src/r_segs.c +++ b/src/r_segs.c @@ -1822,13 +1822,13 @@ void R_StoreWallRange(INT32 start, INT32 stop) else if (linedef->flags & ML_DONTPEGBOTTOM) { rw_midtexturemid = FixedMul(worldbottom, rw_midtexturescaley) + texheight; - rw_midtextureslide = FixedMul(floorfrontslide, rw_midtexturescaley); + rw_midtextureslide = floorfrontslide; } else { // top of texture at top rw_midtexturemid = FixedMul(worldtop, rw_midtexturescaley); - rw_midtextureslide = FixedMul(ceilingfrontslide, rw_midtexturescaley); + rw_midtextureslide = ceilingfrontslide; } } else @@ -1846,13 +1846,13 @@ void R_StoreWallRange(INT32 start, INT32 stop) else if (linedef->flags & ML_DONTPEGBOTTOM) { rw_midtexturemid = FixedMul(worldbottom, rw_midtexturescaley); - rw_midtextureslide = FixedMul(floorfrontslide, rw_midtexturescaley); + rw_midtextureslide = floorfrontslide; } else { // top of texture at top rw_midtexturemid = FixedMul(worldtop, rw_midtexturescaley) + texheight; - rw_midtextureslide = FixedMul(ceilingfrontslide, rw_midtexturescaley); + rw_midtextureslide = ceilingfrontslide; } } From 3764ba3de0aa96f573d1597cebf0578b725f7a18 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 24 Nov 2023 02:17:59 -0300 Subject: [PATCH 142/227] Sector texture scaling, part 2 --- src/hardware/hw_main.c | 48 ++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 3a347196d..369f731b2 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -458,14 +458,18 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool { if (!isceiling) // it's a floor { - scrollx = FIXED_TO_FLOAT(FOFsector->floorxoffset)/fflatwidth; - scrolly = FIXED_TO_FLOAT(FOFsector->flooryoffset)/fflatheight; + fflatwidth /= FixedToFloat(FOFsector->floorxscale); + fflatheight /= FixedToFloat(FOFsector->flooryscale); + scrollx = FixedToFloat(FOFsector->floorxoffset) / fflatwidth; + scrolly = FixedToFloat(FOFsector->flooryoffset) / fflatheight; angle = FOFsector->floorangle; } else // it's a ceiling { - scrollx = FIXED_TO_FLOAT(FOFsector->ceilingxoffset)/fflatwidth; - scrolly = FIXED_TO_FLOAT(FOFsector->ceilingyoffset)/fflatheight; + fflatwidth /= FixedToFloat(FOFsector->ceilingxscale); + fflatheight /= FixedToFloat(FOFsector->ceilingyscale); + scrollx = FixedToFloat(FOFsector->ceilingxoffset) / fflatwidth; + scrolly = FixedToFloat(FOFsector->ceilingyoffset) / fflatheight; angle = FOFsector->ceilingangle; } } @@ -473,14 +477,18 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool { if (!isceiling) // it's a floor { - scrollx = FIXED_TO_FLOAT(gl_frontsector->floorxoffset)/fflatwidth; - scrolly = FIXED_TO_FLOAT(gl_frontsector->flooryoffset)/fflatheight; + fflatwidth /= FixedToFloat(gl_frontsector->floorxscale); + fflatheight /= FixedToFloat(gl_frontsector->flooryscale); + scrollx = FixedToFloat(gl_frontsector->floorxoffset) / fflatwidth; + scrolly = FixedToFloat(gl_frontsector->flooryoffset) / fflatheight; angle = gl_frontsector->floorangle; } else // it's a ceiling { - scrollx = FIXED_TO_FLOAT(gl_frontsector->ceilingxoffset)/fflatwidth; - scrolly = FIXED_TO_FLOAT(gl_frontsector->ceilingyoffset)/fflatheight; + fflatwidth /= FixedToFloat(gl_frontsector->ceilingxscale); + fflatheight /= FixedToFloat(gl_frontsector->ceilingyscale); + scrollx = FixedToFloat(gl_frontsector->ceilingxoffset) / fflatwidth; + scrolly = FixedToFloat(gl_frontsector->ceilingyoffset) / fflatheight; angle = gl_frontsector->ceilingangle; } } @@ -2705,14 +2713,18 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling, { if (!isceiling) // it's a floor { - scrollx = FIXED_TO_FLOAT(FOFsector->floorxoffset)/fflatwidth; - scrolly = FIXED_TO_FLOAT(FOFsector->flooryoffset)/fflatheight; + fflatwidth /= FixedToFloat(FOFsector->floorxscale); + fflatheight /= FixedToFloat(FOFsector->flooryscale); + scrollx = FixedToFloat(FOFsector->floorxoffset) / fflatwidth; + scrolly = FixedToFloat(FOFsector->flooryoffset) / fflatheight; angle = FOFsector->floorangle; } else // it's a ceiling { - scrollx = FIXED_TO_FLOAT(FOFsector->ceilingxoffset)/fflatwidth; - scrolly = FIXED_TO_FLOAT(FOFsector->ceilingyoffset)/fflatheight; + fflatwidth /= FixedToFloat(FOFsector->ceilingxscale); + fflatheight /= FixedToFloat(FOFsector->ceilingyscale); + scrollx = FixedToFloat(FOFsector->ceilingxoffset) / fflatwidth; + scrolly = FixedToFloat(FOFsector->ceilingyoffset) / fflatheight; angle = FOFsector->ceilingangle; } } @@ -2720,14 +2732,18 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling, { if (!isceiling) // it's a floor { - scrollx = FIXED_TO_FLOAT(gl_frontsector->floorxoffset)/fflatwidth; - scrolly = FIXED_TO_FLOAT(gl_frontsector->flooryoffset)/fflatheight; + fflatwidth /= FixedToFloat(gl_frontsector->floorxscale); + fflatheight /= FixedToFloat(gl_frontsector->flooryscale); + scrollx = FixedToFloat(gl_frontsector->floorxoffset) / fflatwidth; + scrolly = FixedToFloat(gl_frontsector->flooryoffset) / fflatheight; angle = gl_frontsector->floorangle; } else // it's a ceiling { - scrollx = FIXED_TO_FLOAT(gl_frontsector->ceilingxoffset)/fflatwidth; - scrolly = FIXED_TO_FLOAT(gl_frontsector->ceilingyoffset)/fflatheight; + fflatwidth /= FixedToFloat(gl_frontsector->ceilingxscale); + fflatheight /= FixedToFloat(gl_frontsector->ceilingyscale); + scrollx = FixedToFloat(gl_frontsector->ceilingxoffset) / fflatwidth; + scrolly = FixedToFloat(gl_frontsector->ceilingyoffset) / fflatheight; angle = gl_frontsector->ceilingangle; } } From b1d7f59feada351f6e64e5cd3c6eccefcc928fa5 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 24 Nov 2023 08:34:51 -0300 Subject: [PATCH 143/227] Delete cachedheight, cacheddistance, cachedxstep and cachedystep --- src/r_plane.c | 53 ++++++++++++-------------------------------------- src/r_plane.h | 4 ---- src/r_splats.c | 34 +++++++++----------------------- 3 files changed, 21 insertions(+), 70 deletions(-) diff --git a/src/r_plane.c b/src/r_plane.c index 786ca440c..d8d66a989 100644 --- a/src/r_plane.c +++ b/src/r_plane.c @@ -83,11 +83,6 @@ static fixed_t planeheight; fixed_t yslopetab[MAXVIDHEIGHT*16]; fixed_t *yslope; -fixed_t cachedheight[MAXVIDHEIGHT]; -fixed_t cacheddistance[MAXVIDHEIGHT]; -fixed_t cachedxstep[MAXVIDHEIGHT]; -fixed_t cachedystep[MAXVIDHEIGHT]; - static fixed_t xoffs, yoffs; static floatv3_t ds_slope_origin, ds_slope_u, ds_slope_v; @@ -159,31 +154,20 @@ static void R_MapPlane(INT32 y, INT32 x1, INT32 x2) planecos = FINECOSINE(angle); planesin = FINESINE(angle); - if (planeheight != cachedheight[y]) + // [RH] Notice that I dumped the caching scheme used by Doom. + // It did not offer any appreciable speedup. + distance = FixedMul(planeheight, yslope[y]); + span = abs(centery - y); + + if (span) // Don't divide by zero { - cachedheight[y] = planeheight; - cacheddistance[y] = distance = FixedMul(planeheight, yslope[y]); - span = abs(centery - y); - - if (span) // Don't divide by zero - { - ds_xstep = FixedMul(planesin, planeheight) / span; - ds_ystep = FixedMul(planecos, planeheight) / span; - ds_xstep = FixedMul(currentplane->xscale, ds_xstep); - ds_ystep = FixedMul(currentplane->yscale, ds_ystep); - } - else - ds_xstep = ds_ystep = FRACUNIT; - - cachedxstep[y] = ds_xstep; - cachedystep[y] = ds_ystep; + ds_xstep = FixedMul(planesin, planeheight) / span; + ds_ystep = FixedMul(planecos, planeheight) / span; + ds_xstep = FixedMul(currentplane->xscale, ds_xstep); + ds_ystep = FixedMul(currentplane->yscale, ds_ystep); } else - { - distance = cacheddistance[y]; - ds_xstep = cachedxstep[y]; - ds_ystep = cachedystep[y]; - } + ds_xstep = ds_ystep = FRACUNIT; // [RH] Instead of using the xtoviewangle array, I calculated the fractional values // at the middle of the screen, then used the calculated ds_xstep and ds_ystep @@ -277,10 +261,7 @@ static void R_MapFogPlane(INT32 y, INT32 x1, INT32 x2) if (x1 >= vid.width) x1 = vid.width - 1; - if (planeheight != cachedheight[y]) - distance = FixedMul(planeheight, yslope[y]); - else - distance = cacheddistance[y]; + distance = FixedMul(planeheight, yslope[y]); pindex = distance >> LIGHTZSHIFT; if (pindex >= MAXLIGHTZ) @@ -363,9 +344,6 @@ void R_ClearPlanes(void) { freehead = &(*freehead)->next; } - - // texture calculation - memset(cachedheight, 0, sizeof (cachedheight)); } static visplane_t *new_visplane(unsigned hash) @@ -1023,13 +1001,6 @@ void R_DrawSinglePlane(visplane_t *pl) } } - // Don't mess with angle on slopes! We'll handle this ourselves later - if (!pl->slope && viewangle != pl->viewangle+pl->plangle) - { - memset(cachedheight, 0, sizeof (cachedheight)); - viewangle = pl->viewangle+pl->plangle; - } - mapfunc = R_MapPlane; if (ds_solidcolor) diff --git a/src/r_plane.h b/src/r_plane.h index 344e71830..38d49d5db 100644 --- a/src/r_plane.h +++ b/src/r_plane.h @@ -63,10 +63,6 @@ extern visplane_t *ceilingplane; // Visplane related. extern INT16 floorclip[MAXVIDWIDTH], ceilingclip[MAXVIDWIDTH]; extern fixed_t frontscale[MAXVIDWIDTH], yslopetab[MAXVIDHEIGHT*16]; -extern fixed_t cachedheight[MAXVIDHEIGHT]; -extern fixed_t cacheddistance[MAXVIDHEIGHT]; -extern fixed_t cachedxstep[MAXVIDHEIGHT]; -extern fixed_t cachedystep[MAXVIDHEIGHT]; extern fixed_t *yslope; extern lighttable_t **planezlight; diff --git a/src/r_splats.c b/src/r_splats.c index 0b482d798..9bfaa6b51 100644 --- a/src/r_splats.c +++ b/src/r_splats.c @@ -391,8 +391,6 @@ static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, visspr if (pSplat->angle) { - memset(cachedheight, 0, sizeof(cachedheight)); - // Add the view offset, rotated by the plane angle. fixed_t a = -pSplat->verts[0].x + vis->viewpoint.x; fixed_t b = -pSplat->verts[0].y + vis->viewpoint.y; @@ -547,29 +545,18 @@ static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, visspr angle_t planecos = FINECOSINE(angle); angle_t planesin = FINESINE(angle); - if (planeheight != cachedheight[y]) + // [RH] Notice that I dumped the caching scheme used by Doom. + // It did not offer any appreciable speedup. + distance = FixedMul(planeheight, yslope[y]); + span = abs(centery - y); + + if (span) // Don't divide by zero { - cachedheight[y] = planeheight; - distance = cacheddistance[y] = FixedMul(planeheight, yslope[y]); - span = abs(centery - y); - - if (span) // Don't divide by zero - { - xstep = FixedMul(planesin, planeheight) / span; - ystep = FixedMul(planecos, planeheight) / span; - } - else - xstep = ystep = FRACUNIT; - - cachedxstep[y] = xstep; - cachedystep[y] = ystep; + xstep = FixedMul(planesin, planeheight) / span; + ystep = FixedMul(planecos, planeheight) / span; } else - { - distance = cacheddistance[y]; - xstep = cachedxstep[y]; - ystep = cachedystep[y]; - } + xstep = ystep = FRACUNIT; ds_xstep = FixedDiv(xstep, pSplat->xscale); ds_ystep = FixedDiv(ystep, pSplat->yscale); @@ -586,9 +573,6 @@ static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, visspr rastertab[y].minx = INT32_MAX; rastertab[y].maxx = INT32_MIN; } - - if (!ds_solidcolor && pSplat->angle && !pSplat->slope) - memset(cachedheight, 0, sizeof(cachedheight)); } static void prepare_rastertab(void) From c5de9bdb7d64663cb9e83b2d9db998d21e9429e0 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 24 Nov 2023 08:47:59 -0300 Subject: [PATCH 144/227] Add ffloor xoffs, yoffs, xscale, yscale to Lua --- src/lua_maplib.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/lua_maplib.c b/src/lua_maplib.c index aca07b1c3..2037dc28c 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -267,8 +267,16 @@ enum ffloor_e { ffloor_topheight, ffloor_toppic, ffloor_toplightlevel, + ffloor_topxoffs, + ffloor_topyoffs, + ffloor_topxscale, + ffloor_topyscale, ffloor_bottomheight, ffloor_bottompic, + ffloor_bottomxoffs, + ffloor_bottomyoffs, + ffloor_bottomxscale, + ffloor_bottomyscale, ffloor_tslope, ffloor_bslope, ffloor_sector, @@ -293,8 +301,16 @@ static const char *const ffloor_opt[] = { "topheight", "toppic", "toplightlevel", + "topxoffs", + "topyoffs", + "topxscale", + "topyscale", "bottomheight", "bottompic", + "bottomxoffs", + "bottomyoffs", + "bottomxscale", + "bottomyscale", "t_slope", "b_slope", "sector", // secnum pushed as control sector userdata @@ -2182,6 +2198,18 @@ static int ffloor_get(lua_State *L) case ffloor_toplightlevel: lua_pushinteger(L, *ffloor->toplightlevel); return 1; + case ffloor_topxoffs: + lua_pushfixed(L, *ffloor->topxoffs); + return 1; + case ffloor_topyoffs: + lua_pushfixed(L, *ffloor->topyoffs); + return 1; + case ffloor_topxscale: + lua_pushfixed(L, *ffloor->topxscale); + return 1; + case ffloor_topyscale: + lua_pushfixed(L, *ffloor->topyscale); + return 1; case ffloor_bottomheight: lua_pushfixed(L, *ffloor->bottomheight); return 1; @@ -2193,6 +2221,18 @@ static int ffloor_get(lua_State *L) lua_pushlstring(L, levelflat->name, i); return 1; } + case ffloor_bottomxoffs: + lua_pushfixed(L, *ffloor->bottomxoffs); + return 1; + case ffloor_bottomyoffs: + lua_pushfixed(L, *ffloor->bottomyoffs); + return 1; + case ffloor_bottomxscale: + lua_pushfixed(L, *ffloor->bottomxscale); + return 1; + case ffloor_bottomyscale: + lua_pushfixed(L, *ffloor->bottomyscale); + return 1; case ffloor_tslope: LUA_PushUserdata(L, *ffloor->t_slope, META_SLOPE); return 1; @@ -2377,6 +2417,18 @@ static int ffloor_set(lua_State *L) case ffloor_toplightlevel: *ffloor->toplightlevel = (INT16)luaL_checkinteger(L, 3); break; + case ffloor_topxoffs: + *ffloor->topxoffs = luaL_checkfixed(L, 3); + break; + case ffloor_topyoffs: + *ffloor->topyoffs = luaL_checkfixed(L, 3); + break; + case ffloor_topxscale: + *ffloor->topxscale = luaL_checkfixed(L, 3); + break; + case ffloor_topyscale: + *ffloor->topyscale = luaL_checkfixed(L, 3); + break; case ffloor_bottomheight: { // bottomheight boolean flag; fixed_t lastpos = *ffloor->bottomheight; @@ -2395,6 +2447,18 @@ static int ffloor_set(lua_State *L) case ffloor_bottompic: *ffloor->bottompic = P_AddLevelFlatRuntime(luaL_checkstring(L, 3)); break; + case ffloor_bottomxoffs: + *ffloor->bottomxoffs = luaL_checkfixed(L, 3); + break; + case ffloor_bottomyoffs: + *ffloor->bottomyoffs = luaL_checkfixed(L, 3); + break; + case ffloor_bottomxscale: + *ffloor->bottomxscale = luaL_checkfixed(L, 3); + break; + case ffloor_bottomyscale: + *ffloor->bottomyscale = luaL_checkfixed(L, 3); + break; case ffloor_fofflags: { ffloortype_e oldflags = ffloor->fofflags; // store FOF's old flags ffloor->fofflags = luaL_checkinteger(L, 3); From b411b9e5235eb3a21a9f7d2531a91a48721e0087 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 24 Nov 2023 12:28:53 -0300 Subject: [PATCH 145/227] Fix potential misalignment with scaled slope textures --- src/r_plane.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/r_plane.c b/src/r_plane.c index d8d66a989..d9dccd89c 100644 --- a/src/r_plane.c +++ b/src/r_plane.c @@ -1037,7 +1037,7 @@ void R_DrawSinglePlane(visplane_t *pl) { mapfunc = R_MapTiltedPlane; - if (!pl->plangle && !ds_solidcolor) + if (!pl->plangle && !ds_solidcolor && pl->xscale == FRACUNIT && pl->yscale == FRACUNIT) { if (ds_powersoftwo) R_AdjustSlopeCoordinates(&pl->slope->o); From 89770d98217fe7eb1b235301285c359ce28fd42c Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 24 Nov 2023 13:44:20 -0300 Subject: [PATCH 146/227] Make sector texture offsets not scale --- src/hardware/hw_main.c | 76 +++++++++++++++++++----------------------- src/r_plane.c | 12 ++----- 2 files changed, 38 insertions(+), 50 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 369f731b2..34e6dc777 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -373,8 +373,9 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool INT32 i; float height; // constant y for all points on the convex flat polygon - float flatxref, flatyref, anglef = 0.0f; + float anglef = 0.0f; float fflatwidth = 64.0f, fflatheight = 64.0f; + float xscale, yscale; float tempxsow, tempytow; float scrollx = 0.0f, scrolly = 0.0f; @@ -409,11 +410,7 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool slope = gl_frontsector->c_slope; } - // Set fixedheight to the slope's height from our viewpoint, if we have a slope - if (slope) - fixedheight = P_GetSlopeZAt(slope, viewx, viewy); - - height = FIXED_TO_FLOAT(fixedheight); + height = FixedToFloat(fixedheight); // Allocate plane-vertex buffer if we need to if (!planeVerts || nrPlaneVerts > numAllocedPlaneVerts) @@ -449,25 +446,21 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool else // set no texture HWR_SetCurrentTexture(NULL); - // reference point for flat texture coord for each vertex around the polygon - flatxref = 0.0; - flatyref = 0.0; - // transform if (FOFsector != NULL) { if (!isceiling) // it's a floor { - fflatwidth /= FixedToFloat(FOFsector->floorxscale); - fflatheight /= FixedToFloat(FOFsector->flooryscale); + xscale = FixedToFloat(FOFsector->floorxscale); + yscale = FixedToFloat(FOFsector->flooryscale); scrollx = FixedToFloat(FOFsector->floorxoffset) / fflatwidth; scrolly = FixedToFloat(FOFsector->flooryoffset) / fflatheight; angle = FOFsector->floorangle; } else // it's a ceiling { - fflatwidth /= FixedToFloat(FOFsector->ceilingxscale); - fflatheight /= FixedToFloat(FOFsector->ceilingyscale); + xscale = FixedToFloat(FOFsector->ceilingxscale); + yscale = FixedToFloat(FOFsector->ceilingyscale); scrollx = FixedToFloat(FOFsector->ceilingxoffset) / fflatwidth; scrolly = FixedToFloat(FOFsector->ceilingyoffset) / fflatheight; angle = FOFsector->ceilingangle; @@ -477,16 +470,16 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool { if (!isceiling) // it's a floor { - fflatwidth /= FixedToFloat(gl_frontsector->floorxscale); - fflatheight /= FixedToFloat(gl_frontsector->flooryscale); + xscale = FixedToFloat(gl_frontsector->floorxscale); + yscale = FixedToFloat(gl_frontsector->flooryscale); scrollx = FixedToFloat(gl_frontsector->floorxoffset) / fflatwidth; scrolly = FixedToFloat(gl_frontsector->flooryoffset) / fflatheight; angle = gl_frontsector->floorangle; } else // it's a ceiling { - fflatwidth /= FixedToFloat(gl_frontsector->ceilingxscale); - fflatheight /= FixedToFloat(gl_frontsector->ceilingyscale); + xscale = FixedToFloat(gl_frontsector->ceilingxscale); + yscale = FixedToFloat(gl_frontsector->ceilingyscale); scrollx = FixedToFloat(gl_frontsector->ceilingxoffset) / fflatwidth; scrolly = FixedToFloat(gl_frontsector->ceilingyoffset) / fflatheight; angle = gl_frontsector->ceilingangle; @@ -497,8 +490,8 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool #define SETUP3DVERT(vert, vx, vy) {\ /* Hurdler: add scrolling texture on floor/ceiling */\ - vert->s = (float)(((vx) / fflatwidth) - flatxref + scrollx);\ - vert->t = (float)(flatyref - ((vy) / fflatheight) + scrolly);\ + vert->s = ((vx) / fflatwidth) + (scrollx / xscale);\ + vert->t = -((vy) / fflatheight) + (scrolly / yscale);\ \ /* Need to rotate before translate */\ if (angle) /* Only needs to be done if there's an altered angle */\ @@ -509,15 +502,18 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool vert->t = (tempxsow * sin(anglef)) + (tempytow * cos(anglef));\ }\ \ - vert->x = (vx);\ - vert->y = height;\ - vert->z = (vy);\ + vert->s *= xscale;\ + vert->t *= yscale;\ \ if (slope)\ {\ - fixedheight = P_GetSlopeZAt(slope, FLOAT_TO_FIXED((vx)), FLOAT_TO_FIXED((vy)));\ - vert->y = FIXED_TO_FLOAT(fixedheight);\ + fixedheight = P_GetSlopeZAt(slope, FloatToFixed((vx)), FloatToFixed((vy)));\ + height = FixedToFloat(fixedheight);\ }\ +\ + vert->x = (vx);\ + vert->y = height;\ + vert->z = (vy);\ } for (i = 0, v3d = planeVerts; i < (INT32)nrPlaneVerts; i++,v3d++,pv++) @@ -2649,8 +2645,8 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling, INT32 i; float height = FIXED_TO_FLOAT(fixedheight); // constant y for all points on the convex flat polygon - float flatxref, flatyref; float fflatwidth = 64.0f, fflatheight = 64.0f; + float xscale, yscale; float scrollx = 0.0f, scrolly = 0.0f; float tempxsow, tempytow, anglef = 0.0f; @@ -2701,11 +2697,6 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling, else // set no texture HWR_SetCurrentTexture(NULL); - // reference point for flat texture coord for each vertex around the polygon - flatxref = 0.0; - flatyref = 0.0; - - // transform v3d = planeVerts; @@ -2713,16 +2704,16 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling, { if (!isceiling) // it's a floor { - fflatwidth /= FixedToFloat(FOFsector->floorxscale); - fflatheight /= FixedToFloat(FOFsector->flooryscale); + xscale = FixedToFloat(FOFsector->floorxscale); + yscale = FixedToFloat(FOFsector->flooryscale); scrollx = FixedToFloat(FOFsector->floorxoffset) / fflatwidth; scrolly = FixedToFloat(FOFsector->flooryoffset) / fflatheight; angle = FOFsector->floorangle; } else // it's a ceiling { - fflatwidth /= FixedToFloat(FOFsector->ceilingxscale); - fflatheight /= FixedToFloat(FOFsector->ceilingyscale); + xscale = FixedToFloat(FOFsector->ceilingxscale); + yscale = FixedToFloat(FOFsector->ceilingyscale); scrollx = FixedToFloat(FOFsector->ceilingxoffset) / fflatwidth; scrolly = FixedToFloat(FOFsector->ceilingyoffset) / fflatheight; angle = FOFsector->ceilingangle; @@ -2732,16 +2723,16 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling, { if (!isceiling) // it's a floor { - fflatwidth /= FixedToFloat(gl_frontsector->floorxscale); - fflatheight /= FixedToFloat(gl_frontsector->flooryscale); + xscale = FixedToFloat(gl_frontsector->floorxscale); + yscale = FixedToFloat(gl_frontsector->flooryscale); scrollx = FixedToFloat(gl_frontsector->floorxoffset) / fflatwidth; scrolly = FixedToFloat(gl_frontsector->flooryoffset) / fflatheight; angle = gl_frontsector->floorangle; } else // it's a ceiling { - fflatwidth /= FixedToFloat(gl_frontsector->ceilingxscale); - fflatheight /= FixedToFloat(gl_frontsector->ceilingyscale); + xscale = FixedToFloat(gl_frontsector->ceilingxscale); + yscale = FixedToFloat(gl_frontsector->ceilingyscale); scrollx = FixedToFloat(gl_frontsector->ceilingxoffset) / fflatwidth; scrolly = FixedToFloat(gl_frontsector->ceilingyoffset) / fflatheight; angle = gl_frontsector->ceilingangle; @@ -2754,8 +2745,8 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling, { // Go from the polysector's original vertex locations // Means the flat is offset based on the original vertex locations - v3d->s = (float)((FIXED_TO_FLOAT(polysector->origVerts[i].x) / fflatwidth) - flatxref + scrollx); - v3d->t = (float)(flatyref - (FIXED_TO_FLOAT(polysector->origVerts[i].y) / fflatheight) + scrolly); + v3d->s = (FixedToFloat(polysector->origVerts[i].x) / fflatwidth) + (scrollx / xscale); + v3d->t = -(FixedToFloat(polysector->origVerts[i].y) / fflatheight) + (scrolly / yscale); // Need to rotate before translate if (angle) // Only needs to be done if there's an altered angle @@ -2767,6 +2758,9 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling, v3d->t = (tempxsow * sin(anglef)) + (tempytow * cos(anglef)); } + v3d->s *= xscale; + v3d->t *= yscale; + v3d->x = FIXED_TO_FLOAT(polysector->vertices[i]->x); v3d->y = height; v3d->z = FIXED_TO_FLOAT(polysector->vertices[i]->y); diff --git a/src/r_plane.c b/src/r_plane.c index d9dccd89c..9c87ecbe4 100644 --- a/src/r_plane.c +++ b/src/r_plane.c @@ -380,8 +380,8 @@ visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel, if (!slope) // Don't mess with this right now if a slope is involved { - xoff += viewx; - yoff -= viewy; + xoff += FixedMul(viewx, xscale); + yoff -= FixedMul(viewy, yscale); if (plangle != 0) { @@ -411,12 +411,6 @@ visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel, } } - if (!slope) - { - xoff = FixedMul(xoff, xscale); - yoff = FixedMul(yoff, yscale); - } - // This appears to fix the Nimbus Ruins sky bug. if (picnum == skyflatnum && pfloor) { @@ -809,7 +803,7 @@ static void R_SetSlopePlaneVectors(visplane_t *pl, INT32 y, fixed_t xoff, fixed_ { R_SetScaledSlopePlane(pl->slope, pl->viewx, pl->viewy, pl->viewz, FixedDiv(FRACUNIT, pl->xscale), FixedDiv(FRACUNIT, pl->yscale), - xoff, yoff, pl->viewangle, pl->plangle); + FixedDiv(xoff, pl->xscale), FixedDiv(yoff, pl->yscale), pl->viewangle, pl->plangle); } else R_SetSlopePlane(pl->slope, pl->viewx, pl->viewy, pl->viewz, xoff, yoff, pl->viewangle, pl->plangle); From 6948537e9e410b1a434e5e3f1c41b1259ba93bdd Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 24 Nov 2023 13:49:44 -0300 Subject: [PATCH 147/227] Initialize xscale and yscale --- src/hardware/hw_main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 34e6dc777..ad6ed327c 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -375,7 +375,7 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool float height; // constant y for all points on the convex flat polygon float anglef = 0.0f; float fflatwidth = 64.0f, fflatheight = 64.0f; - float xscale, yscale; + float xscale = 1.0f, yscale = 1.0f; float tempxsow, tempytow; float scrollx = 0.0f, scrolly = 0.0f; @@ -2646,7 +2646,7 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling, float height = FIXED_TO_FLOAT(fixedheight); // constant y for all points on the convex flat polygon float fflatwidth = 64.0f, fflatheight = 64.0f; - float xscale, yscale; + float xscale = 1.0f, yscale = 1.0f; float scrollx = 0.0f, scrolly = 0.0f; float tempxsow, tempytow, anglef = 0.0f; From 6596fc97acbf32f0db270e796d3ef58a76afdb38 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 24 Nov 2023 13:54:09 -0300 Subject: [PATCH 148/227] Accept '_bottom' instead of just '_bot' to match the UDMF names --- src/lua_maplib.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/lua_maplib.c b/src/lua_maplib.c index 2037dc28c..78b07baaf 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -194,13 +194,17 @@ enum side_e { side_offsety_top, side_offsetx_mid, side_offsety_mid, + side_offsetx_bottom, side_offsetx_bot, + side_offsety_bottom, side_offsety_bot, side_scalex_top, side_scaley_top, side_scalex_mid, side_scaley_mid, + side_scalex_bottom, side_scalex_bot, + side_scaley_bottom, side_scaley_bot, side_toptexture, side_bottomtexture, @@ -220,13 +224,17 @@ static const char *const side_opt[] = { "offsety_top", "offsetx_mid", "offsety_mid", + "offsetx_bottom", "offsetx_bot", + "offsety_bottom", "offsety_bot", "scalex_top", "scaley_top", "scalex_mid", "scaley_mid", + "scalex_bottom", "scalex_bot", + "scaley_bottom", "scaley_bot", "toptexture", "bottomtexture", @@ -1253,9 +1261,11 @@ static int side_get(lua_State *L) case side_offsety_mid: lua_pushfixed(L, side->offsety_mid); return 1; + case side_offsetx_bottom: case side_offsetx_bot: lua_pushfixed(L, side->offsetx_bottom); return 1; + case side_offsety_bottom: case side_offsety_bot: lua_pushfixed(L, side->offsety_bottom); return 1; @@ -1271,9 +1281,11 @@ static int side_get(lua_State *L) case side_scaley_mid: lua_pushfixed(L, side->scaley_mid); return 1; + case side_scalex_bottom: case side_scalex_bot: lua_pushfixed(L, side->scalex_bottom); return 1; + case side_scaley_bottom: case side_scaley_bot: lua_pushfixed(L, side->scaley_bottom); return 1; From 147d5948fc5f3a8fb18fcb097b0a96eaca7a7bf3 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Fri, 24 Nov 2023 13:56:56 -0300 Subject: [PATCH 149/227] Remove 'scalex_bot' and 'scaley_bot' --- src/lua_maplib.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/lua_maplib.c b/src/lua_maplib.c index 78b07baaf..0efce3114 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -203,9 +203,7 @@ enum side_e { side_scalex_mid, side_scaley_mid, side_scalex_bottom, - side_scalex_bot, side_scaley_bottom, - side_scaley_bot, side_toptexture, side_bottomtexture, side_midtexture, @@ -1282,11 +1280,9 @@ static int side_get(lua_State *L) lua_pushfixed(L, side->scaley_mid); return 1; case side_scalex_bottom: - case side_scalex_bot: lua_pushfixed(L, side->scalex_bottom); return 1; case side_scaley_bottom: - case side_scaley_bot: lua_pushfixed(L, side->scaley_bottom); return 1; case side_toptexture: @@ -1372,9 +1368,11 @@ static int side_set(lua_State *L) side->offsety_mid = luaL_checkfixed(L, 3); break; case side_offsetx_bot: + case side_offsetx_bottom: side->offsetx_bottom = luaL_checkfixed(L, 3); break; case side_offsety_bot: + case side_offsety_bottom: side->offsety_bottom = luaL_checkfixed(L, 3); break; case side_scalex_top: @@ -1389,10 +1387,10 @@ static int side_set(lua_State *L) case side_scaley_mid: side->scaley_mid = luaL_checkfixed(L, 3); break; - case side_scalex_bot: + case side_scalex_bottom: side->scalex_bottom = luaL_checkfixed(L, 3); break; - case side_scaley_bot: + case side_scaley_bottom: side->scaley_bottom = luaL_checkfixed(L, 3); break; case side_toptexture: From 3f98755c11bc36cbb735c427f41441878f873d33 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Sat, 25 Nov 2023 15:19:48 -0300 Subject: [PATCH 150/227] Actually remove 'scalex_bot' and 'scaley_bot' --- src/lua_maplib.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lua_maplib.c b/src/lua_maplib.c index 0efce3114..0b998199b 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -231,9 +231,7 @@ static const char *const side_opt[] = { "scalex_mid", "scaley_mid", "scalex_bottom", - "scalex_bot", "scaley_bottom", - "scaley_bot", "toptexture", "bottomtexture", "midtexture", From 87d99fe84ccaa9bb18821b49819dfbd19541fe89 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Sun, 26 Nov 2023 03:56:19 -0300 Subject: [PATCH 151/227] Fix scaling of walls that are skewed by a slope, part 2 --- src/r_segs.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/r_segs.c b/src/r_segs.c index a8fe4deb5..6085bae7c 100644 --- a/src/r_segs.c +++ b/src/r_segs.c @@ -2082,7 +2082,6 @@ void R_StoreWallRange(INT32 start, INT32 stop) } rw_toptexturemid = FixedMul(rw_toptexturemid, rw_toptexturescaley); - rw_toptextureslide = FixedMul(rw_toptextureslide, rw_toptexturescaley); } // check BOTTOM TEXTURE @@ -2123,7 +2122,6 @@ void R_StoreWallRange(INT32 start, INT32 stop) } rw_bottomtexturemid = FixedMul(rw_bottomtexturemid, rw_bottomtexturescaley); - rw_bottomtextureslide = FixedMul(rw_bottomtextureslide, rw_bottomtexturescaley); } rw_toptexturemid += toprowoffset; From 40f57de187896d241f67e810f859c3c7f431e102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Sun, 26 Nov 2023 21:30:54 +0100 Subject: [PATCH 152/227] Add indicator for IPv6-enabled servers --- src/m_menu.c | 2 ++ src/netcode/d_net.h | 1 + src/netcode/i_tcp.c | 5 +++++ 3 files changed, 8 insertions(+) diff --git a/src/m_menu.c b/src/m_menu.c index 629f53d24..e863bc40f 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -11254,6 +11254,8 @@ static void M_DrawConnectMenu(void) V_DrawSmallString(currentMenu->x+202, S_LINEY(i)+8, globalflags, "\x85" "Mod"); if (serverlist[slindex].info.cheatsenabled) V_DrawSmallString(currentMenu->x+222, S_LINEY(i)+8, globalflags, "\x83" "Cheats"); + if (Net_IsNodeIPv6(serverlist[slindex].node)) + V_DrawSmallString(currentMenu->x+252, S_LINEY(i)+8, globalflags, "\x84" "IPv6"); V_DrawSmallString(currentMenu->x, S_LINEY(i)+8, globalflags, va("Ping: %u", (UINT32)LONG(serverlist[slindex].info.time))); diff --git a/src/netcode/d_net.h b/src/netcode/d_net.h index 549f2b93c..2f83939f8 100644 --- a/src/netcode/d_net.h +++ b/src/netcode/d_net.h @@ -70,6 +70,7 @@ boolean HGetPacket(void); void D_SetDoomcom(void); boolean D_CheckNetGame(void); void D_CloseConnection(void); +boolean Net_IsNodeIPv6(INT32 node); void Net_UnAcknowledgePacket(INT32 node); void Net_CloseConnection(INT32 node); void Net_ConnectionTimeout(INT32 node); diff --git a/src/netcode/i_tcp.c b/src/netcode/i_tcp.c index 0b650de49..b6d7724a4 100644 --- a/src/netcode/i_tcp.c +++ b/src/netcode/i_tcp.c @@ -1362,4 +1362,9 @@ boolean I_InitTcpNetwork(void) return ret; } +boolean Net_IsNodeIPv6(INT32 node) +{ + return clientaddress[node].any.sa_family == AF_INET6; +} + #include "i_addrinfo.c" From b5c8db00f836f303ca33319f73969f6226474059 Mon Sep 17 00:00:00 2001 From: LoganAir Date: Sun, 26 Nov 2023 17:44:03 -0500 Subject: [PATCH 153/227] Update mserv.c https://mb.srb2.org is now behind CloudFlare, and the Master Server API v1 can not be accessed for some users due to this change. so I setup a new dual stack DNS record for accessing to the API --- src/mserv.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/mserv.c b/src/mserv.c index 62cda96e4..f83b16d07 100644 --- a/src/mserv.c +++ b/src/mserv.c @@ -61,7 +61,7 @@ static CV_PossibleValue_t masterserver_update_rate_cons_t[] = { {0,NULL} }; -consvar_t cv_masterserver = CVAR_INIT ("masterserver", "https://mb.srb2.org/MS/0", CV_SAVE|CV_CALL, NULL, MasterServer_OnChange); +consvar_t cv_masterserver = CVAR_INIT ("masterserver", "https://ds.ms.srb2.org/MS/0", CV_SAVE|CV_CALL, NULL, MasterServer_OnChange); consvar_t cv_servername = CVAR_INIT ("servername", "SRB2 server", CV_SAVE|CV_NETVAR|CV_CALL|CV_NOINIT|CV_ALLOWLUA, NULL, Update_parameters); consvar_t cv_masterserver_update_rate = CVAR_INIT ("masterserver_update_rate", "15", CV_SAVE|CV_CALL|CV_NOINIT, masterserver_update_rate_cons_t, Update_parameters); @@ -544,6 +544,13 @@ static void MasterServer_OnChange(void) CV_StealthSet(&cv_masterserver, cv_masterserver.defaultvalue); } + if ( + ! cv_masterserver.changed && + strcmp(cv_masterserver.string, "https://mb.srb2.org/MS/0") == 0 + ){ + CV_StealthSet(&cv_masterserver, cv_masterserver.defaultvalue); + } + Set_api(cv_masterserver.string); if (Online()) From 30e48fd555c7d91b0e43fa99034a18a16e484d3f Mon Sep 17 00:00:00 2001 From: bitten2up <575-bitten2up@users.noreply.git.do.srb2.org> Date: Wed, 29 Nov 2023 11:19:53 +0000 Subject: [PATCH 154/227] I_SetSongTrack(int) should be I_SetSongTrack(INT32) --- src/dummy/i_sound.c | 2 +- src/sdl/mixer_sound.c | 2 +- src/sdl/sdl_sound.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dummy/i_sound.c b/src/dummy/i_sound.c index ba0fc6423..436187805 100644 --- a/src/dummy/i_sound.c +++ b/src/dummy/i_sound.c @@ -164,7 +164,7 @@ void I_SetMusicVolume(UINT8 volume) (void)volume; } -boolean I_SetSongTrack(int track) +boolean I_SetSongTrack(INT32 track) { (void)track; return false; diff --git a/src/sdl/mixer_sound.c b/src/sdl/mixer_sound.c index 0a39c7f28..a2a4d3ef0 100644 --- a/src/sdl/mixer_sound.c +++ b/src/sdl/mixer_sound.c @@ -1441,7 +1441,7 @@ void I_SetMusicVolume(UINT8 volume) Mix_VolumeMusic(get_real_volume(music_volume)); } -boolean I_SetSongTrack(int track) +boolean I_SetSongTrack(INT32 track) { #ifdef HAVE_GME // If the specified track is within the number of tracks playing, then change it diff --git a/src/sdl/sdl_sound.c b/src/sdl/sdl_sound.c index 2ca35b954..2705261d6 100644 --- a/src/sdl/sdl_sound.c +++ b/src/sdl/sdl_sound.c @@ -1471,7 +1471,7 @@ void I_SetMusicVolume(UINT8 volume) (void)volume; } -boolean I_SetSongTrack(int track) +boolean I_SetSongTrack(INT32 track) { (void)track; return false; From 2e885d4e6fd69b66c1c5cfd2a973f2353f0840ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Thu, 30 Nov 2023 16:03:44 +0100 Subject: [PATCH 155/227] Add NO_IPV6 check --- src/netcode/i_tcp.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/netcode/i_tcp.c b/src/netcode/i_tcp.c index b6d7724a4..c70290e54 100644 --- a/src/netcode/i_tcp.c +++ b/src/netcode/i_tcp.c @@ -1364,7 +1364,11 @@ boolean I_InitTcpNetwork(void) boolean Net_IsNodeIPv6(INT32 node) { +#ifdef NO_IPV6 + return false; +#else return clientaddress[node].any.sa_family == AF_INET6; +#endif } #include "i_addrinfo.c" From ff492cbdcc0b5e76cff62cf23798c856945dfafb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Thu, 30 Nov 2023 16:29:08 +0100 Subject: [PATCH 156/227] Register both IPv4 and IPv6 on the master server --- src/netcode/http-mserv.c | 90 +++++++++++++++++++++++++++++++++++----- 1 file changed, 80 insertions(+), 10 deletions(-) diff --git a/src/netcode/http-mserv.c b/src/netcode/http-mserv.c index f8bd5da21..10137e67b 100644 --- a/src/netcode/http-mserv.c +++ b/src/netcode/http-mserv.c @@ -35,6 +35,10 @@ Documentation available here. #define Blame( ... ) \ CONS_Printf("\x85" __VA_ARGS__) +#define PROTO_ANY 0 +#define PROTO_V4 1 +#define PROTO_V6 2 + static void MasterServer_Debug_OnChange (void); consvar_t cv_masterserver_timeout = CVAR_INIT @@ -59,12 +63,17 @@ consvar_t cv_masterserver_token = CVAR_INIT static int hms_started; +static boolean hms_allow_ipv6; + static char *hms_api; #ifdef HAVE_THREADS static I_mutex hms_api_mutex; #endif static char *hms_server_token; +#ifndef NO_IPV6 +static char *hms_server_token_ipv6; +#endif static char hms_useragent[512]; @@ -126,7 +135,7 @@ HMS_on_read (char *s, size_t _1, size_t n, void *userdata) } static struct HMS_buffer * -HMS_connect (const char *format, ...) +HMS_connect (int proto, const char *format, ...) { va_list ap; CURL *curl; @@ -136,8 +145,14 @@ HMS_connect (const char *format, ...) size_t token_length; struct HMS_buffer *buffer; +#ifdef NO_IPV6 + if (proto == PROTO_V6) + return NULL; +#endif + if (! hms_started) { + hms_allow_ipv6 = !M_CheckParm("-noipv6"); if (curl_global_init(CURL_GLOBAL_ALL) != 0) { Contact_error(); @@ -219,7 +234,9 @@ HMS_connect (const char *format, ...) curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); #ifndef NO_IPV6 - if (M_CheckParm("-noipv6")) + if (proto == PROTO_V6) + curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6); + if (proto == PROTO_V4) #endif curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); @@ -309,7 +326,7 @@ HMS_fetch_rooms (int joining, int query_id) (void)query_id; - hms = HMS_connect("rooms"); + hms = HMS_connect(PROTO_ANY, "rooms"); if (! hms) return 0; @@ -409,7 +426,7 @@ HMS_register (void) char *title; - hms = HMS_connect("rooms/%d/register", ms_RoomId); + hms = HMS_connect(PROTO_V4, "rooms/%d/register", ms_RoomId); if (! hms) return 0; @@ -441,6 +458,27 @@ HMS_register (void) HMS_end(hms); +#ifndef NO_IPV6 + if (!hms_allow_ipv6) + return ok; + + hms = HMS_connect(PROTO_V6, "rooms/%d/register", ms_RoomId); + + if (! hms) + return 0; + + curl_easy_setopt(hms->curl, CURLOPT_POSTFIELDS, post); + + ok = HMS_do(hms); + + if (ok) + { + hms_server_token_ipv6 = strdup(strtok(hms->buffer, "\n")); + } + + HMS_end(hms); +#endif + return ok; } @@ -450,7 +488,7 @@ HMS_unlist (void) struct HMS_buffer *hms; int ok; - hms = HMS_connect("servers/%s/unlist", hms_server_token); + hms = HMS_connect(PROTO_V4, "servers/%s/unlist", hms_server_token); if (! hms) return 0; @@ -462,6 +500,23 @@ HMS_unlist (void) free(hms_server_token); +#ifndef NO_IPV6 + if (hms_server_token_ipv6 && hms_allow_ipv6) + { + hms = HMS_connect(PROTO_V6, "servers/%s/unlist", hms_server_token_ipv6); + + if (! hms) + return 0; + + curl_easy_setopt(hms->curl, CURLOPT_CUSTOMREQUEST, "POST"); + + ok = HMS_do(hms); + HMS_end(hms); + + free(hms_server_token_ipv6); + } +#endif + return ok; } @@ -475,7 +530,7 @@ HMS_update (void) char *title; - hms = HMS_connect("servers/%s/update", hms_server_token); + hms = HMS_connect(PROTO_V4, "servers/%s/update", hms_server_token); if (! hms) return 0; @@ -494,6 +549,21 @@ HMS_update (void) ok = HMS_do(hms); HMS_end(hms); +#ifndef NO_IPV6 + if (hms_server_token_ipv6 && hms_allow_ipv6) + { + hms = HMS_connect(PROTO_V6, "servers/%s/update", hms_server_token_ipv6); + + if (! hms) + return ok; + + curl_easy_setopt(hms->curl, CURLOPT_POSTFIELDS, post); + + ok = HMS_do(hms); + HMS_end(hms); + } +#endif + return ok; } @@ -505,7 +575,7 @@ HMS_list_servers (void) char *list; char *p; - hms = HMS_connect("servers"); + hms = HMS_connect(PROTO_ANY, "servers"); if (! hms) return; @@ -554,10 +624,10 @@ HMS_fetch_servers (msg_server_t *list, int room_number, int query_id) if (room_number > 0) { - hms = HMS_connect("rooms/%d/servers", room_number); + hms = HMS_connect(PROTO_ANY, "rooms/%d/servers", room_number); } else - hms = HMS_connect("servers"); + hms = HMS_connect(PROTO_ANY, "servers"); if (! hms) return NULL; @@ -661,7 +731,7 @@ HMS_compare_mod_version (char *buffer, size_t buffer_size) char *version; char *version_name; - hms = HMS_connect("versions/%d", MODID); + hms = HMS_connect(PROTO_ANY, "versions/%d", MODID); if (! hms) return 0; From 244ad5b8925c201a82de323b5155776a7c173fe0 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Sun, 3 Dec 2023 00:22:23 -0300 Subject: [PATCH 157/227] Remove CPUID code --- src/doomdef.h | 2 +- src/m_misc.c | 421 +------------------------------------------------- src/screen.c | 50 ------ src/screen.h | 11 -- 4 files changed, 2 insertions(+), 482 deletions(-) diff --git a/src/doomdef.h b/src/doomdef.h index b382d0ecb..4e08b11bf 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -533,7 +533,7 @@ extern char liveeventbackup[256]; #define M_GetText(x) (x) #endif void M_StartupLocale(void); -extern void *(*M_Memcpy)(void* dest, const void* src, size_t n) FUNCNONNULL; +void *M_Memcpy(void* dest, const void* src, size_t n); char *va(const char *format, ...) FUNCPRINTF; char *M_GetToken(const char *inputString); void M_UnGetToken(void); diff --git a/src/m_misc.c b/src/m_misc.c index 923a188c6..ce332910d 100644 --- a/src/m_misc.c +++ b/src/m_misc.c @@ -2207,430 +2207,11 @@ char *sizeu5(size_t num) return sizeu5_buf; } -#if defined (__GNUC__) && defined (__i386__) // from libkwave, under GPL -// Alam: note libkwave memcpy code comes from mplayer's libvo/aclib_template.c, r699 - -/* for small memory blocks (<256 bytes) this version is faster */ -#define small_memcpy(dest,src,n)\ -{\ -register unsigned long int dummy;\ -__asm__ __volatile__(\ - "cld\n\t"\ - "rep; movsb"\ - :"=&D"(dest), "=&S"(src), "=&c"(dummy)\ - :"0" (dest), "1" (src),"2" (n)\ - : "memory", "cc");\ -} -/* linux kernel __memcpy (from: /include/asm/string.h) */ -ATTRINLINE static FUNCINLINE void *__memcpy (void *dest, const void * src, size_t n) +void *M_Memcpy(void *dest, const void *src, size_t n) { - int d0, d1, d2; - - if ( n < 4 ) - { - small_memcpy(dest, src, n); - } - else - { - __asm__ __volatile__ ( - "rep ; movsl;" - "testb $2,%b4;" - "je 1f;" - "movsw;" - "1:\ttestb $1,%b4;" - "je 2f;" - "movsb;" - "2:" - : "=&c" (d0), "=&D" (d1), "=&S" (d2) - :"0" (n/4), "q" (n),"1" ((long) dest),"2" ((long) src) - : "memory"); - } - - return dest; -} - -#define SSE_MMREG_SIZE 16 -#define MMX_MMREG_SIZE 8 - -#define MMX1_MIN_LEN 0x800 /* 2K blocks */ -#define MIN_LEN 0x40 /* 64-byte blocks */ - -/* SSE note: i tried to move 128 bytes a time instead of 64 but it -didn't make any measureable difference. i'm using 64 for the sake of -simplicity. [MF] */ -static /*FUNCTARGET("sse2")*/ void *sse_cpy(void * dest, const void * src, size_t n) -{ - void *retval = dest; - size_t i; - - /* PREFETCH has effect even for MOVSB instruction ;) */ - __asm__ __volatile__ ( - "prefetchnta (%0);" - "prefetchnta 32(%0);" - "prefetchnta 64(%0);" - "prefetchnta 96(%0);" - "prefetchnta 128(%0);" - "prefetchnta 160(%0);" - "prefetchnta 192(%0);" - "prefetchnta 224(%0);" - "prefetchnta 256(%0);" - "prefetchnta 288(%0);" - : : "r" (src) ); - - if (n >= MIN_LEN) - { - register unsigned long int delta; - /* Align destinition to MMREG_SIZE -boundary */ - delta = ((unsigned long int)dest)&(SSE_MMREG_SIZE-1); - if (delta) - { - delta=SSE_MMREG_SIZE-delta; - n -= delta; - small_memcpy(dest, src, delta); - } - i = n >> 6; /* n/64 */ - n&=63; - if (((unsigned long)src) & 15) - /* if SRC is misaligned */ - for (; i>0; i--) - { - __asm__ __volatile__ ( - "prefetchnta 320(%0);" - "prefetchnta 352(%0);" - "movups (%0), %%xmm0;" - "movups 16(%0), %%xmm1;" - "movups 32(%0), %%xmm2;" - "movups 48(%0), %%xmm3;" - "movntps %%xmm0, (%1);" - "movntps %%xmm1, 16(%1);" - "movntps %%xmm2, 32(%1);" - "movntps %%xmm3, 48(%1);" - :: "r" (src), "r" (dest) : "memory"); - src = (const unsigned char *)src + 64; - dest = (unsigned char *)dest + 64; - } - else - /* - Only if SRC is aligned on 16-byte boundary. - It allows to use movaps instead of movups, which required data - to be aligned or a general-protection exception (#GP) is generated. - */ - for (; i>0; i--) - { - __asm__ __volatile__ ( - "prefetchnta 320(%0);" - "prefetchnta 352(%0);" - "movaps (%0), %%xmm0;" - "movaps 16(%0), %%xmm1;" - "movaps 32(%0), %%xmm2;" - "movaps 48(%0), %%xmm3;" - "movntps %%xmm0, (%1);" - "movntps %%xmm1, 16(%1);" - "movntps %%xmm2, 32(%1);" - "movntps %%xmm3, 48(%1);" - :: "r" (src), "r" (dest) : "memory"); - src = ((const unsigned char *)src) + 64; - dest = ((unsigned char *)dest) + 64; - } - /* since movntq is weakly-ordered, a "sfence" - * is needed to become ordered again. */ - __asm__ __volatile__ ("sfence":::"memory"); - /* enables to use FPU */ - __asm__ __volatile__ ("emms":::"memory"); - } - /* - * Now do the tail of the block - */ - if (n) __memcpy(dest, src, n); - return retval; -} - -static FUNCTARGET("mmx") void *mmx2_cpy(void *dest, const void *src, size_t n) -{ - void *retval = dest; - size_t i; - - /* PREFETCH has effect even for MOVSB instruction ;) */ - __asm__ __volatile__ ( - "prefetchnta (%0);" - "prefetchnta 32(%0);" - "prefetchnta 64(%0);" - "prefetchnta 96(%0);" - "prefetchnta 128(%0);" - "prefetchnta 160(%0);" - "prefetchnta 192(%0);" - "prefetchnta 224(%0);" - "prefetchnta 256(%0);" - "prefetchnta 288(%0);" - : : "r" (src)); - - if (n >= MIN_LEN) - { - register unsigned long int delta; - /* Align destinition to MMREG_SIZE -boundary */ - delta = ((unsigned long int)dest)&(MMX_MMREG_SIZE-1); - if (delta) - { - delta=MMX_MMREG_SIZE-delta; - n -= delta; - small_memcpy(dest, src, delta); - } - i = n >> 6; /* n/64 */ - n&=63; - for (; i>0; i--) - { - __asm__ __volatile__ ( - "prefetchnta 320(%0);" - "prefetchnta 352(%0);" - "movq (%0), %%mm0;" - "movq 8(%0), %%mm1;" - "movq 16(%0), %%mm2;" - "movq 24(%0), %%mm3;" - "movq 32(%0), %%mm4;" - "movq 40(%0), %%mm5;" - "movq 48(%0), %%mm6;" - "movq 56(%0), %%mm7;" - "movntq %%mm0, (%1);" - "movntq %%mm1, 8(%1);" - "movntq %%mm2, 16(%1);" - "movntq %%mm3, 24(%1);" - "movntq %%mm4, 32(%1);" - "movntq %%mm5, 40(%1);" - "movntq %%mm6, 48(%1);" - "movntq %%mm7, 56(%1);" - :: "r" (src), "r" (dest) : "memory"); - src = ((const unsigned char *)src) + 64; - dest = ((unsigned char *)dest) + 64; - } - /* since movntq is weakly-ordered, a "sfence" - * is needed to become ordered again. */ - __asm__ __volatile__ ("sfence":::"memory"); - __asm__ __volatile__ ("emms":::"memory"); - } - /* - * Now do the tail of the block - */ - if (n) __memcpy(dest, src, n); - return retval; -} - -static FUNCTARGET("mmx") void *mmx1_cpy(void *dest, const void *src, size_t n) //3DNOW -{ - void *retval = dest; - size_t i; - - /* PREFETCH has effect even for MOVSB instruction ;) */ - __asm__ __volatile__ ( - "prefetch (%0);" - "prefetch 32(%0);" - "prefetch 64(%0);" - "prefetch 96(%0);" - "prefetch 128(%0);" - "prefetch 160(%0);" - "prefetch 192(%0);" - "prefetch 224(%0);" - "prefetch 256(%0);" - "prefetch 288(%0);" - : : "r" (src)); - - if (n >= MMX1_MIN_LEN) - { - register unsigned long int delta; - /* Align destinition to MMREG_SIZE -boundary */ - delta = ((unsigned long int)dest)&(MMX_MMREG_SIZE-1); - if (delta) - { - delta=MMX_MMREG_SIZE-delta; - n -= delta; - small_memcpy(dest, src, delta); - } - i = n >> 6; /* n/64 */ - n&=63; - for (; i>0; i--) - { - __asm__ __volatile__ ( - "prefetch 320(%0);" - "prefetch 352(%0);" - "movq (%0), %%mm0;" - "movq 8(%0), %%mm1;" - "movq 16(%0), %%mm2;" - "movq 24(%0), %%mm3;" - "movq 32(%0), %%mm4;" - "movq 40(%0), %%mm5;" - "movq 48(%0), %%mm6;" - "movq 56(%0), %%mm7;" - "movq %%mm0, (%1);" - "movq %%mm1, 8(%1);" - "movq %%mm2, 16(%1);" - "movq %%mm3, 24(%1);" - "movq %%mm4, 32(%1);" - "movq %%mm5, 40(%1);" - "movq %%mm6, 48(%1);" - "movq %%mm7, 56(%1);" - :: "r" (src), "r" (dest) : "memory"); - src = ((const unsigned char *)src) + 64; - dest = ((unsigned char *)dest) + 64; - } - __asm__ __volatile__ ("femms":::"memory"); // same as mmx_cpy() but with a femms - } - /* - * Now do the tail of the block - */ - if (n) __memcpy(dest, src, n); - return retval; -} -#endif - -// Alam: why? memcpy may be __cdecl/_System and our code may be not the same type -static void *cpu_cpy(void *dest, const void *src, size_t n) -{ - if (src == NULL) - { - CONS_Debug(DBG_MEMORY, "Memcpy from 0x0?!: %p %p %s\n", dest, src, sizeu1(n)); - return dest; - } - - if(dest == NULL) - { - CONS_Debug(DBG_MEMORY, "Memcpy to 0x0?!: %p %p %s\n", dest, src, sizeu1(n)); - return dest; - } - return memcpy(dest, src, n); } -static /*FUNCTARGET("mmx")*/ void *mmx_cpy(void *dest, const void *src, size_t n) -{ -#if defined (_MSC_VER) && defined (_X86_) - _asm - { - mov ecx, [n] - mov esi, [src] - mov edi, [dest] - shr ecx, 6 // mit mmx: 64bytes per iteration - jz lower_64 // if lower than 64 bytes - loop_64: // MMX transfers multiples of 64bytes - movq mm0, 0[ESI] // read sources - movq mm1, 8[ESI] - movq mm2, 16[ESI] - movq mm3, 24[ESI] - movq mm4, 32[ESI] - movq mm5, 40[ESI] - movq mm6, 48[ESI] - movq mm7, 56[ESI] - - movq 0[EDI], mm0 // write destination - movq 8[EDI], mm1 - movq 16[EDI], mm2 - movq 24[EDI], mm3 - movq 32[EDI], mm4 - movq 40[EDI], mm5 - movq 48[EDI], mm6 - movq 56[EDI], mm7 - - add esi, 64 - add edi, 64 - dec ecx - jnz loop_64 - emms // close mmx operation - lower_64:// transfer rest of buffer - mov ebx,esi - sub ebx,src - mov ecx,[n] - sub ecx,ebx - shr ecx, 3 // multiples of 8 bytes - jz lower_8 - loop_8: - movq mm0, [esi] // read source - movq [edi], mm0 // write destination - add esi, 8 - add edi, 8 - dec ecx - jnz loop_8 - emms // close mmx operation - lower_8: - mov ebx,esi - sub ebx,src - mov ecx,[n] - sub ecx,ebx - rep movsb - mov eax, [dest] // return dest - } -#elif defined (__GNUC__) && defined (__i386__) - void *retval = dest; - size_t i; - - if (n >= MMX1_MIN_LEN) - { - register unsigned long int delta; - /* Align destinition to MMREG_SIZE -boundary */ - delta = ((unsigned long int)dest)&(MMX_MMREG_SIZE-1); - if (delta) - { - delta=MMX_MMREG_SIZE-delta; - n -= delta; - small_memcpy(dest, src, delta); - } - i = n >> 6; /* n/64 */ - n&=63; - for (; i>0; i--) - { - __asm__ __volatile__ ( - "movq (%0), %%mm0;" - "movq 8(%0), %%mm1;" - "movq 16(%0), %%mm2;" - "movq 24(%0), %%mm3;" - "movq 32(%0), %%mm4;" - "movq 40(%0), %%mm5;" - "movq 48(%0), %%mm6;" - "movq 56(%0), %%mm7;" - "movq %%mm0, (%1);" - "movq %%mm1, 8(%1);" - "movq %%mm2, 16(%1);" - "movq %%mm3, 24(%1);" - "movq %%mm4, 32(%1);" - "movq %%mm5, 40(%1);" - "movq %%mm6, 48(%1);" - "movq %%mm7, 56(%1);" - :: "r" (src), "r" (dest) : "memory"); - src = ((const unsigned char *)src) + 64; - dest = ((unsigned char *)dest) + 64; - } - __asm__ __volatile__ ("emms":::"memory"); - } - /* - * Now do the tail of the block - */ - if (n) __memcpy(dest, src, n); - return retval; -#else - return cpu_cpy(dest, src, n); -#endif -} - -void *(*M_Memcpy)(void* dest, const void* src, size_t n) = cpu_cpy; - -/** Memcpy that uses MMX, 3DNow, MMXExt or even SSE - * Do not use on overlapped memory, use memmove for that - */ -void M_SetupMemcpy(void) -{ -#if defined (__GNUC__) && defined (__i386__) - if (R_SSE2) - M_Memcpy = sse_cpy; - else if (R_MMXExt) - M_Memcpy = mmx2_cpy; - else if (R_3DNow) - M_Memcpy = mmx1_cpy; - else -#endif - if (R_MMX) - M_Memcpy = mmx_cpy; -#if 0 - M_Memcpy = cpu_cpy; -#endif -} - /** Return the appropriate message for a file error or end of file. */ const char *M_FileError(FILE *fp) diff --git a/src/screen.c b/src/screen.c index 3c50ec67e..ca59b251d 100644 --- a/src/screen.c +++ b/src/screen.c @@ -98,14 +98,6 @@ UINT8 *scr_borderpatch; // flat used to fill the reduced view borders set at ST_ // Short and Tall sky drawer, for the current color mode void (*walldrawerfunc)(void); -boolean R_486 = false; -boolean R_586 = false; -boolean R_MMX = false; -boolean R_SSE = false; -boolean R_3DNow = false; -boolean R_MMXExt = false; -boolean R_SSE2 = false; - void SCR_SetDrawFuncs(void) { // @@ -225,48 +217,6 @@ void SCR_SetMode(void) // void SCR_Startup(void) { - const CPUInfoFlags *RCpuInfo = I_CPUInfo(); - if (!M_CheckParm("-NOCPUID") && RCpuInfo) - { -#if defined (__i386__) || defined (_M_IX86) || defined (__WATCOMC__) - R_486 = true; -#endif - if (RCpuInfo->RDTSC) - R_586 = true; - if (RCpuInfo->MMX) - R_MMX = true; - if (RCpuInfo->AMD3DNow) - R_3DNow = true; - if (RCpuInfo->MMXExt) - R_MMXExt = true; - if (RCpuInfo->SSE) - R_SSE = true; - if (RCpuInfo->SSE2) - R_SSE2 = true; - CONS_Printf("CPU Info: 486: %i, 586: %i, MMX: %i, 3DNow: %i, MMXExt: %i, SSE2: %i\n", R_486, R_586, R_MMX, R_3DNow, R_MMXExt, R_SSE2); - } - - if (M_CheckParm("-486")) - R_486 = true; - if (M_CheckParm("-586")) - R_586 = true; - if (M_CheckParm("-MMX")) - R_MMX = true; - if (M_CheckParm("-3DNow")) - R_3DNow = true; - if (M_CheckParm("-MMXExt")) - R_MMXExt = true; - - if (M_CheckParm("-SSE")) - R_SSE = true; - if (M_CheckParm("-noSSE")) - R_SSE = false; - - if (M_CheckParm("-SSE2")) - R_SSE2 = true; - - M_SetupMemcpy(); - if (dedicated) { V_Init(); diff --git a/src/screen.h b/src/screen.h index 46c1b99c6..e4c1006c3 100644 --- a/src/screen.h +++ b/src/screen.h @@ -172,17 +172,6 @@ extern void (*spanfunc)(void); extern void (*spanfuncs[SPANDRAWFUNC_MAX])(void); extern void (*spanfuncs_npo2[SPANDRAWFUNC_MAX])(void); -// ----- -// CPUID -// ----- -extern boolean R_ASM; -extern boolean R_486; -extern boolean R_586; -extern boolean R_MMX; -extern boolean R_3DNow; -extern boolean R_MMXExt; -extern boolean R_SSE2; - // ---------------- // screen variables // ---------------- From 5257b498eaa25c1579832f0f1980721e55f4c103 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Sun, 3 Dec 2023 01:21:39 -0300 Subject: [PATCH 158/227] Remove useless check --- src/r_plane.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/r_plane.c b/src/r_plane.c index 4695034bd..6714c2a6d 100644 --- a/src/r_plane.c +++ b/src/r_plane.c @@ -445,7 +445,6 @@ visplane_t *R_FindPlane(sector_t *sector, fixed_t height, INT32 picnum, INT32 li && lightlevel == check->lightlevel && xoff == check->xoffs && yoff == check->yoffs && planecolormap == check->extra_colormap - && !pfloor && !check->ffloor && check->viewx == viewx && check->viewy == viewy && check->viewz == viewz && check->viewangle == viewangle && check->plangle == plangle From eca16a811e8fdd82f4eb525664db964a17ec7a82 Mon Sep 17 00:00:00 2001 From: Logan-A Date: Tue, 5 Dec 2023 21:03:01 -0500 Subject: [PATCH 159/227] comptime.sh is a bash shell, so it should have eol=lf --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index c2e507352..9f0850930 100644 --- a/.gitattributes +++ b/.gitattributes @@ -10,6 +10,7 @@ /src/Make*.cfg text=auto /src/CMakeLists.txt text=auto *.mk -whitespace text=auto +/comptime.sh text eol=lf # Windows EOL *.cs -crlf -whitespace *.bat -crlf -whitespace From 5867b7355ec2d9d895a7cfd280b4f1aea5ce6073 Mon Sep 17 00:00:00 2001 From: Logan-A Date: Tue, 5 Dec 2023 22:54:59 -0500 Subject: [PATCH 160/227] add compnote support to comptime add compnote support to comptime --- comptime.bat | 7 +++++-- comptime.sh | 8 +++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/comptime.bat b/comptime.bat index 0c7ea06d6..77879d5ee 100644 --- a/comptime.bat +++ b/comptime.bat @@ -1,6 +1,7 @@ @echo off set BRA=Unknown set REV=illegal +set GL1=Dummy copy nul: /b +%1\comptime.c tmp.$$$ > nul move tmp.$$$ %1\comptime.c > nul @@ -13,8 +14,9 @@ goto filwri :gitrev set GIT=%2 if "%GIT%"=="" set GIT=git -for /f "usebackq" %%s in (`%GIT% rev-parse --abbrev-ref HEAD`) do @set BRA=%%s -for /f "usebackq" %%s in (`%GIT% rev-parse HEAD`) do @set REV=%%s +for /f "tokens=* usebackq" %%s in (`%GIT% rev-parse --abbrev-ref HEAD`) do @set BRA=%%s +for /f "tokens=* usebackq" %%s in (`%GIT% rev-parse HEAD`) do @set REV=%%s +for /f "tokens=* usebackq" %%s in (`%GIT% log -1 --format^=%%s`) do @set GL1=%%s set REV=%REV:~0,8% goto filwri @@ -30,3 +32,4 @@ echo // by the %0 batch file >> %1\comptime.h echo // >> %1\comptime.h echo const char* compbranch = "%BRA%"; >> %1\comptime.h echo const char* comprevision = "%REV%"; >> %1\comptime.h +echo const char* compnote = "%GL1%"; >> %1\comptime.h diff --git a/comptime.sh b/comptime.sh index ce771f631..e37ba6ad7 100755 --- a/comptime.sh +++ b/comptime.sh @@ -12,24 +12,26 @@ version() { // const char* compbranch = "$1"; const char* comprevision = "$2"; +const char* compnote = "$3"; EOF } versiongit() { gitbranch="$(git rev-parse --abbrev-ref HEAD)" gitversion="$(git rev-parse HEAD | cut -c -8)" - version "$gitbranch" "$gitversion"; + gitsubject="$(git log -1 --format=%s)" + version "$gitbranch" "$gitversion" "$gitsubject"; exit 0 } versionsvn() { svnrevision="$(svnversion -n "$1")" - version "Subversion" "r$svnrevision"; + version "Subversion" "r$svnrevision" "dummy"; exit 0 } versionfake() { - version "Unknown" "illegal"; + version "Unknown" "illegal" "dummy"; } compversion() { From e1562ad4c51b9d77d25a92d5f8e45b2fdf4604bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Wed, 6 Dec 2023 17:00:27 +0100 Subject: [PATCH 161/227] Fix crash when trying to send to an unreachable client --- src/netcode/i_tcp.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/netcode/i_tcp.c b/src/netcode/i_tcp.c index 0b650de49..bde3a0e8d 100644 --- a/src/netcode/i_tcp.c +++ b/src/netcode/i_tcp.c @@ -83,6 +83,10 @@ #undef ETIMEDOUT #endif #define ETIMEDOUT WSAETIMEDOUT + #ifdef EHOSTUNREACH + #undef EHOSTUNREACH + #endif + #define EHOSTUNREACH WSAEHOSTUNREACH #ifndef IOC_VENDOR #define IOC_VENDOR 0x18000000 #endif @@ -678,7 +682,7 @@ static void SOCK_Send(void) if (c == ERRSOCKET) { int e = errno; // save error code so it can't be modified later - if (e != ECONNREFUSED && e != EWOULDBLOCK) + if (e != ECONNREFUSED && e != EWOULDBLOCK && e != EHOSTUNREACH) I_Error("SOCK_Send, error sending to node %d (%s) #%u: %s", doomcom->remotenode, SOCK_GetNodeAddress(doomcom->remotenode), e, strerror(e)); } From 0131cdb03d790810445ae7a3d6947b0ab44ceeb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Thu, 14 Dec 2023 16:30:04 +0100 Subject: [PATCH 162/227] Fix IPv6 bans being ignored when loading banlist --- src/netcode/commands.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/netcode/commands.c b/src/netcode/commands.c index 4b67198ba..e7d51437e 100644 --- a/src/netcode/commands.c +++ b/src/netcode/commands.c @@ -79,7 +79,7 @@ static void Ban_Clear(void) void Ban_Load_File(boolean warning) { FILE *f; - const char *address, *mask; + char *address, *mask; char buffer[MAX_WADPATH]; if (!I_ClearBans) @@ -100,6 +100,14 @@ void Ban_Load_File(boolean warning) { address = strtok(buffer, " \t\r\n"); mask = strtok(NULL, " \t\r\n"); + if (address[0] == '[') + { + size_t len; + address++; + len = strlen(address); + if (address[len-1] == ']') + address[len-1] = '\0'; + } I_SetBanAddress(address, mask); From 59aa6fdad4f89279f5106a4be0fdc7588117c706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Thu, 14 Dec 2023 17:00:20 +0100 Subject: [PATCH 163/227] Fix ban mask being ignored on IPv6 addresses --- src/netcode/i_tcp.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/netcode/i_tcp.c b/src/netcode/i_tcp.c index c70290e54..d91583e25 100644 --- a/src/netcode/i_tcp.c +++ b/src/netcode/i_tcp.c @@ -404,6 +404,20 @@ static const char *SOCK_GetBanMask(size_t ban) return NULL; } +#ifdef HAVE_IPV6 +static boolean SOCK_cmpipv6(mysockaddr_t *a, mysockaddr_t *b, UINT8 mask) +{ + UINT8 bitmask; + I_Assert(mask <= 128); + if (memcmp(&a->ip6.sin6_addr, &b->ip6.sin6_addr, mask / 8) != 0) + return false; + if (mask % 8 == 0) + return true; + bitmask = 255 << (mask % 8); + return (a->ip6.sin6_addr.s6_addr[mask / 8] & bitmask) == (b->ip6.sin6_addr.s6_addr[mask / 8] & bitmask); +} +#endif + static boolean SOCK_cmpaddr(mysockaddr_t *a, mysockaddr_t *b, UINT8 mask) { UINT32 bitmask = INADDR_NONE; @@ -416,7 +430,7 @@ static boolean SOCK_cmpaddr(mysockaddr_t *a, mysockaddr_t *b, UINT8 mask) && (b->ip4.sin_port == 0 || (a->ip4.sin_port == b->ip4.sin_port)); #ifdef HAVE_IPV6 else if (b->any.sa_family == AF_INET6) - return !memcmp(&a->ip6.sin6_addr, &b->ip6.sin6_addr, sizeof(b->ip6.sin6_addr)) + return SOCK_cmpipv6(a, b, mask) && (b->ip6.sin6_port == 0 || (a->ip6.sin6_port == b->ip6.sin6_port)); #endif else From fe87760c8c89f14186cb1c6395fe8fe0948dba33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Thu, 14 Dec 2023 17:15:07 +0100 Subject: [PATCH 164/227] Fix buffer overflow in SV_SendPlayerInfo --- src/netcode/server_connection.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/netcode/server_connection.c b/src/netcode/server_connection.c index 258d4cce4..faff7e8dd 100644 --- a/src/netcode/server_connection.c +++ b/src/netcode/server_connection.c @@ -164,7 +164,7 @@ static void SV_SendPlayerInfo(INT32 node) for (UINT8 i = 0; i < MAXPLAYERS; i++) { - if (!netnodes[playernode[i]].ingame) + if (playernode[i] == UINT8_MAX || !netnodes[playernode[i]].ingame) { netbuffer->u.playerinfo[i].num = 255; // This slot is empty. continue; From 54ba225164605bb1bc69aa33468d1861e715e28f Mon Sep 17 00:00:00 2001 From: Zwip-Zwap Zapony Date: Thu, 14 Dec 2023 21:42:43 +0100 Subject: [PATCH 165/227] Update player->quittime while a server is idling --- src/netcode/d_clisrv.c | 33 +++++++++++++++++++++++++++++++++ src/p_tick.c | 16 +--------------- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/netcode/d_clisrv.c b/src/netcode/d_clisrv.c index 7804b068f..de22d601b 100644 --- a/src/netcode/d_clisrv.c +++ b/src/netcode/d_clisrv.c @@ -116,6 +116,8 @@ consvar_t cv_playbackspeed = CVAR_INIT ("playbackspeed", "1", 0, playbackspeed_c consvar_t cv_idletime = CVAR_INIT ("idletime", "0", CV_SAVE, CV_Unsigned, NULL); consvar_t cv_dedicatedidletime = CVAR_INIT ("dedicatedidletime", "10", CV_SAVE, CV_Unsigned, NULL); +static INT32 D_NumNodes(boolean skiphost); + void ResetNode(INT32 node) { memset(&netnodes[node], 0, sizeof(*netnodes)); @@ -1275,6 +1277,7 @@ static void UpdatePingTable(void) } } +// Handle idle and disconnected player timers static void IdleUpdate(void) { INT32 i; @@ -1297,7 +1300,26 @@ static void IdleUpdate(void) } } else + { players[i].lastinputtime = 0; + + if (players[i].quittime && playeringame[i]) + { + players[i].quittime++; + + if (players[i].quittime == 30 * TICRATE && G_TagGametype()) + P_CheckSurvivors(); + + if (server && players[i].quittime >= (tic_t)FixedMul(cv_rejointimeout.value, 60 * TICRATE) + && !(players[i].quittime % TICRATE)) + { + if (D_NumNodes(true) > 0) + SendKick(i, KICK_MSG_PLAYER_QUIT); + else // If the server is empty, don't send a NetXCmd - that would wake an idling dedicated server + CL_RemovePlayer(i, KICK_MSG_PLAYER_QUIT); + } + } + } } } @@ -1631,6 +1653,17 @@ INT32 D_NumBots(void) return num; } +// Returns the number of currently-connected nodes in a netgame +// Not necessarily equivalent to D_NumPlayers() minus D_NumBots() +static INT32 D_NumNodes(boolean skiphost) +{ + INT32 num = 0, ix; + for (ix = skiphost ? 1 : 0; ix < MAXNETNODES; ix++) + if (netnodes[ix].ingame) + num++; + return num; +} + // // Consistancy diff --git a/src/p_tick.c b/src/p_tick.c index 1bc7b78bf..3934890f1 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -690,25 +690,11 @@ void P_Ticker(boolean run) { INT32 i; - // Increment jointime and quittime even if paused + // Increment jointime even if paused for (i = 0; i < MAXPLAYERS; i++) if (playeringame[i]) - { players[i].jointime++; - if (players[i].quittime) - { - players[i].quittime++; - - if (players[i].quittime == 30 * TICRATE && G_TagGametype()) - P_CheckSurvivors(); - - if (server && players[i].quittime >= (tic_t)FixedMul(cv_rejointimeout.value, 60 * TICRATE) - && !(players[i].quittime % TICRATE)) - SendKick(i, KICK_MSG_PLAYER_QUIT); - } - } - if (objectplacing) { if (OP_FreezeObjectplace()) From bc96645f0b7583ced290a89c136a3651a49be406 Mon Sep 17 00:00:00 2001 From: Alam Ed Arias Date: Thu, 14 Dec 2023 20:15:50 -0500 Subject: [PATCH 166/227] GitLabCI: use new builder image, srb2ci --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4e284ce65..49dd9bdf5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,7 @@ variables: GIT_CLONE_PATH: $CI_BUILDS_DIR/$CI_CONCURRENT_ID/$CI_PROJECT_PATH default: - image: debian:stable-slim + image: git.do.srb2.org:5050/stjr/srb2ci/srb2ci:stable cache: - key: ccache-$CI_PROJECT_PATH_SLUG-$CI_JOB_NAME_SLUG From 1756fabef0de34f80bd9b5eba545f467af5c1b97 Mon Sep 17 00:00:00 2001 From: spherallic Date: Fri, 22 Dec 2023 11:24:56 +0100 Subject: [PATCH 167/227] Convert UDMF colormap light/fade alpha to new 0-255 range --- src/p_setup.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/p_setup.c b/src/p_setup.c index 6974eab53..442db9430 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -2976,22 +2976,26 @@ static void P_LoadTextmap(void) P_InitializeSector(sc); if (textmap_colormap.used) { - INT32 rgba = P_ColorToRGBA(textmap_colormap.lightcolor, textmap_colormap.lightalpha); - INT32 fadergba = P_ColorToRGBA(textmap_colormap.fadecolor, textmap_colormap.fadealpha); + // Convert alpha values from old 0-25 (A-Z) range to 0-255 range + UINT8 lightalpha = (textmap_colormap.lightalpha * 102) / 10; + UINT8 fadealpha = (textmap_colormap.fadealpha * 102) / 10; + + INT32 rgba = P_ColorToRGBA(textmap_colormap.lightcolor, lightalpha); + INT32 fadergba = P_ColorToRGBA(textmap_colormap.fadecolor, fadealpha); sc->extra_colormap = sc->spawn_extra_colormap = R_CreateColormap(rgba, fadergba, textmap_colormap.fadestart, textmap_colormap.fadeend, textmap_colormap.flags); } if (textmap_planefloor.defined == (PD_A|PD_B|PD_C|PD_D)) - { + { sc->f_slope = MakeViaEquationConstants(textmap_planefloor.a, textmap_planefloor.b, textmap_planefloor.c, textmap_planefloor.d); sc->hasslope = true; - } + } if (textmap_planeceiling.defined == (PD_A|PD_B|PD_C|PD_D)) - { + { sc->c_slope = MakeViaEquationConstants(textmap_planeceiling.a, textmap_planeceiling.b, textmap_planeceiling.c, textmap_planeceiling.d); sc->hasslope = true; - } + } TextmapFixFlatOffsets(sc); } From 7893d08407ba5b11f5dbad213c84180ced6179c0 Mon Sep 17 00:00:00 2001 From: spherallic Date: Fri, 22 Dec 2023 14:23:24 +0100 Subject: [PATCH 168/227] Fix realmapnamep not being set with map * and + --- src/g_game.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index f819cbd9a..578dc7c6c 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -5387,17 +5387,19 @@ INT32 G_FindMapByNameOrCode(const char *mapname, char **realmapnamep) if (mapnamelen == 1) { if (mapname[0] == '*') // current map - return gamemap; + { + usemapcode = true; + newmapnum = gamemap; + } else if (mapname[0] == '+' && mapheaderinfo[gamemap-1]) // next map { + usemapcode = true; newmapnum = mapheaderinfo[gamemap-1]->nextlevel; if (newmapnum < 1 || newmapnum > NUMMAPS) { CONS_Alert(CONS_ERROR, M_GetText("NextLevel (%d) is not a valid map.\n"), newmapnum); return 0; } - else - return newmapnum; } } else if (mapnamelen == 2)/* maybe two digit code */ From d79d22de63c5b84e3e0f4d737af81f74321dd80d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Sat, 23 Dec 2023 11:05:14 +0100 Subject: [PATCH 169/227] (Hopefully) Fix chatbug --- src/netcode/d_net.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/netcode/d_net.c b/src/netcode/d_net.c index cfb1963b9..5a2e229d3 100644 --- a/src/netcode/d_net.c +++ b/src/netcode/d_net.c @@ -313,9 +313,9 @@ static void RemoveAck(INT32 i) } // We have got a packet, proceed the ack request and ack return -static boolean Processackpak(void) +static int Processackpak(void) { - boolean goodpacket = true; + int goodpacket = 0; node_t *node = &nodes[doomcom->remotenode]; // Received an ack return, so remove the ack in the list @@ -340,7 +340,7 @@ static boolean Processackpak(void) { DEBFILE(va("Discard(1) ack %d (duplicated)\n", ack)); duppacket++; - goodpacket = false; // Discard packet (duplicate) + goodpacket = 1; // Discard packet (duplicate) } else { @@ -350,10 +350,10 @@ static boolean Processackpak(void) { DEBFILE(va("Discard(2) ack %d (duplicated)\n", ack)); duppacket++; - goodpacket = false; // Discard packet (duplicate) + goodpacket = 1; // Discard packet (duplicate) break; } - if (goodpacket) + if (goodpacket == 0) { // Is a good packet so increment the acknowledge number, // Then search for a "hole" in the queue @@ -414,12 +414,13 @@ static boolean Processackpak(void) else // Buffer full discard packet, sender will resend it { // We can admit the packet but we will not detect the duplication after :( DEBFILE("no more freeackret\n"); - goodpacket = false; + goodpacket = 2; } } } } } + // return values: 0 = ok, 1 = duplicate, 2 = out of order return goodpacket; } @@ -1069,6 +1070,7 @@ boolean HGetPacket(void) while(true) { //nodejustjoined = I_NetGet(); + int goodpacket; I_NetGet(); if (doomcom->remotenode == -1) // No packet received @@ -1114,8 +1116,15 @@ boolean HGetPacket(void) }*/ // Proceed the ack and ackreturn field - if (!Processackpak()) + goodpacket = Processackpak(); + if (goodpacket != 0) + { + // resend the ACK in case the previous ACK didn't reach the client. + // prevents the client's netbuffer from locking up. + if (goodpacket == 1) + Net_SendAcks(doomcom->remotenode); continue; // discarded (duplicated) + } // A packet with just ackreturn if (netbuffer->packettype == PT_NOTHING) From ddba6e80f7af16d712670c87efdcc3a30d319e4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Sat, 23 Dec 2023 13:45:16 +0100 Subject: [PATCH 170/227] Handle player state is P_MobjSetState --- src/b_bot.c | 4 +- src/f_finale.c | 2 +- src/lua_mobjlib.c | 5 +- src/m_cheat.c | 6 +- src/p_enemy.c | 6 +- src/p_inter.c | 26 ++--- src/p_local.h | 1 - src/p_map.c | 28 +++--- src/p_mobj.c | 20 ++-- src/p_spec.c | 14 +-- src/p_telept.c | 4 +- src/p_user.c | 242 +++++++++++++++++++++++----------------------- src/r_skins.c | 2 +- 13 files changed, 177 insertions(+), 183 deletions(-) diff --git a/src/b_bot.c b/src/b_bot.c index 033288a86..d5d5ab925 100644 --- a/src/b_bot.c +++ b/src/b_bot.c @@ -584,11 +584,11 @@ void B_RespawnBot(INT32 playernum) P_SetOrigin(tails, x, y, z); if (player->charability == CA_FLY) { - P_SetPlayerMobjState(tails, S_PLAY_FLY); + P_SetMobjState(tails, S_PLAY_FLY); tails->player->powers[pw_tailsfly] = (UINT16)-1; } else - P_SetPlayerMobjState(tails, S_PLAY_FALL); + P_SetMobjState(tails, S_PLAY_FALL); P_SetScale(tails, sonic->scale); tails->destscale = sonic->destscale; } diff --git a/src/f_finale.c b/src/f_finale.c index 68e9c3216..5dc18115c 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -4524,7 +4524,7 @@ void F_TextPromptDrawer(void) if (players[j].mo->state == states+S_PLAY_STND && players[j].mo->tics != -1)\ players[j].mo->tics++;\ else if (players[j].mo->state == states+S_PLAY_WAIT)\ - P_SetPlayerMobjState(players[j].mo, S_PLAY_STND);\ + P_SetMobjState(players[j].mo, S_PLAY_STND);\ }\ } diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c index 19f30b70e..d38e85a21 100644 --- a/src/lua_mobjlib.c +++ b/src/lua_mobjlib.c @@ -641,10 +641,7 @@ static int mobj_set(lua_State *L) mo->tics = luaL_checkinteger(L, 3); break; case mobj_state: // set state by enum - if (mo->player) - P_SetPlayerMobjState(mo, luaL_checkinteger(L, 3)); - else - P_SetMobjState(mo, luaL_checkinteger(L, 3)); + P_SetMobjState(mo, luaL_checkinteger(L, 3)); break; case mobj_flags: // special handling for MF_NOBLOCKMAP and MF_NOSECTOR { diff --git a/src/m_cheat.c b/src/m_cheat.c index 2bcf43ad1..e61db2c2e 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -1019,7 +1019,7 @@ static void OP_CycleThings(INT32 amt) if (players[0].mo->eflags & MFE_VERTICALFLIP) // correct z when flipped players[0].mo->z += players[0].mo->height - FixedMul(mobjinfo[op_currentthing].height, players[0].mo->scale); players[0].mo->height = FixedMul(mobjinfo[op_currentthing].height, players[0].mo->scale); - P_SetPlayerMobjState(players[0].mo, S_OBJPLACE_DUMMY); + P_SetMobjState(players[0].mo, S_OBJPLACE_DUMMY); op_currentdoomednum = mobjinfo[op_currentthing].doomednum; } @@ -1528,7 +1528,7 @@ void Command_ObjectPlace_f(void) else OP_CycleThings(0); // sets all necessary height values without cycling op_currentthing - P_SetPlayerMobjState(players[0].mo, S_OBJPLACE_DUMMY); + P_SetMobjState(players[0].mo, S_OBJPLACE_DUMMY); } // Or are we leaving it instead? else @@ -1542,7 +1542,7 @@ void Command_ObjectPlace_f(void) // If still in dummy state, get out of it. if (players[0].mo->state == &states[S_OBJPLACE_DUMMY]) - P_SetPlayerMobjState(players[0].mo, op_oldstate); + P_SetMobjState(players[0].mo, op_oldstate); // Reset everything back to how it was before we entered objectplace. P_UnsetThingPosition(players[0].mo); diff --git a/src/p_enemy.c b/src/p_enemy.c index 93c828fbe..e27e7060e 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -9780,7 +9780,7 @@ void A_SetObjectState(mobj_t *actor) if (!target->player) P_SetMobjState(target, locvar1); else - P_SetPlayerMobjState(target, locvar1); + P_SetMobjState(target, locvar1); } } @@ -13609,7 +13609,7 @@ static boolean PIT_DustDevilLaunch(mobj_t *thing) player->powers[pw_carry] = CR_DUSTDEVIL; player->powers[pw_nocontrol] = 2; P_SetTarget(&thing->tracer, dustdevil); - P_SetPlayerMobjState(thing, S_PLAY_PAIN); + P_SetMobjState(thing, S_PLAY_PAIN); if (dist > dragamount) { @@ -13632,7 +13632,7 @@ static boolean PIT_DustDevilLaunch(mobj_t *thing) player->powers[pw_nocontrol] = 0; P_SetTarget(&thing->tracer, NULL); S_StartSound(thing, sfx_wdjump); - P_SetPlayerMobjState(thing, S_PLAY_FALL); + P_SetMobjState(thing, S_PLAY_FALL); } thing->momz = thrust; diff --git a/src/p_inter.c b/src/p_inter.c index c3811cbe4..27e838617 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -526,7 +526,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) else if (player->pflags & PF_GLIDING && !P_IsObjectOnGround(toucher)) { player->pflags &= ~(PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE); - P_SetPlayerMobjState(toucher, S_PLAY_FALL); + P_SetMobjState(toucher, S_PLAY_FALL); toucher->momz += P_MobjFlip(toucher) * (player->speed >> 3); toucher->momx = 7*toucher->momx>>3; toucher->momy = 7*toucher->momy>>3; @@ -1246,7 +1246,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) P_ResetPlayer(player); - P_SetPlayerMobjState(toucher, S_PLAY_FALL); + P_SetMobjState(toucher, S_PLAY_FALL); } } return; @@ -1553,7 +1553,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (player->pflags & PF_GLIDING && !P_IsObjectOnGround(toucher)) { player->pflags &= ~(PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE); - P_SetPlayerMobjState(toucher, S_PLAY_FALL); + P_SetMobjState(toucher, S_PLAY_FALL); toucher->momz += P_MobjFlip(toucher) * (player->speed >> 3); toucher->momx = 7*toucher->momx>>3; toucher->momy = 7*toucher->momy>>3; @@ -1604,7 +1604,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (player->pflags & PF_GLIDING && !P_IsObjectOnGround(toucher)) { player->pflags &= ~(PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE); - P_SetPlayerMobjState(toucher, S_PLAY_FALL); + P_SetMobjState(toucher, S_PLAY_FALL); toucher->momz += P_MobjFlip(toucher) * (player->speed >> 3); toucher->momx = 7*toucher->momx>>3; toucher->momy = 7*toucher->momy>>3; @@ -1640,7 +1640,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) special->momx = special->momy = 0; // Buenos Dias Mandy - P_SetPlayerMobjState(toucher, S_PLAY_STUN); + P_SetMobjState(toucher, S_PLAY_STUN); player->pflags &= ~PF_APPLYAUTOBRAKE; P_ResetPlayer(player); player->drawangle = special->angle + ANGLE_180; @@ -1719,7 +1719,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) { player->powers[pw_carry] = CR_MACESPIN; S_StartSound(toucher, sfx_spin); - P_SetPlayerMobjState(toucher, S_PLAY_ROLL); + P_SetMobjState(toucher, S_PLAY_ROLL); } else player->powers[pw_carry] = CR_GENERIC; @@ -1780,7 +1780,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) { if (player->bot && player->bot != BOT_MPAI && toucher->state-states != S_PLAY_GASP) S_StartSound(toucher, special->info->deathsound); // Force it to play a sound for bots - P_SetPlayerMobjState(toucher, S_PLAY_GASP); + P_SetMobjState(toucher, S_PLAY_GASP); P_ResetPlayer(player); } @@ -1847,7 +1847,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) toucher->momz = toucher->tracer->momz + P_AproxDistance(toucher->tracer->momx, toucher->tracer->momy)/2; P_ResetPlayer(player); player->pflags &= ~PF_APPLYAUTOBRAKE; - P_SetPlayerMobjState(toucher, S_PLAY_FALL); + P_SetMobjState(toucher, S_PLAY_FALL); P_SetTarget(&toucher->tracer->target, NULL); P_KillMobj(toucher->tracer, toucher, special, 0); P_SetTarget(&toucher->tracer, NULL); @@ -3059,9 +3059,9 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget else if (target->player) { if (damagetype == DMG_DROWNED || damagetype == DMG_SPACEDROWN) - P_SetPlayerMobjState(target, target->info->xdeathstate); + P_SetMobjState(target, target->info->xdeathstate); else - P_SetPlayerMobjState(target, target->info->deathstate); + P_SetMobjState(target, target->info->deathstate); } else #ifdef DEBUG_NULL_DEATHSTATE @@ -3115,7 +3115,7 @@ static void P_NiGHTSDamage(mobj_t *target, mobj_t *source) } player->powers[pw_flashing] = flashingtics; - P_SetPlayerMobjState(target, S_PLAY_NIGHTS_STUN); + P_SetMobjState(target, S_PLAY_NIGHTS_STUN); S_StartSound(target, sfx_nghurt); player->mo->spriteroll = 0; @@ -3336,7 +3336,7 @@ static void P_KillPlayer(player_t *player, mobj_t *source, INT32 damage) if (!player->spectator) player->mo->flags2 &= ~MF2_DONTDRAW; - P_SetPlayerMobjState(player->mo, player->mo->info->deathstate); + P_SetMobjState(player->mo, player->mo->info->deathstate); if ((gametyperules & GTR_TEAMFLAGS) && (player->gotflag & (GF_REDFLAG|GF_BLUEFLAG))) { P_PlayerFlagBurst(player, false); @@ -3410,7 +3410,7 @@ static void P_SuperDamage(player_t *player, mobj_t *inflictor, mobj_t *source, I P_InstaThrust(player->mo, ang, fallbackspeed); - P_SetPlayerMobjState(player->mo, S_PLAY_STUN); + P_SetMobjState(player->mo, S_PLAY_STUN); P_ResetPlayer(player); diff --git a/src/p_local.h b/src/p_local.h index ba033dc06..baa6bd753 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -296,7 +296,6 @@ void P_PrecipitationEffects(void); void P_RemoveMobj(mobj_t *th); boolean P_MobjWasRemoved(mobj_t *th); void P_RemoveSavegameMobj(mobj_t *th); -boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state); boolean P_SetMobjState(mobj_t *mobj, statenum_t state); void P_RunShields(void); void P_RunOverlays(void); diff --git a/src/p_map.c b/src/p_map.c index 6a152c563..b1a339ac0 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -264,7 +264,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object) UINT8 secondjump = object->player->secondjump; UINT16 tailsfly = object->player->powers[pw_tailsfly]; if (object->player->pflags & PF_GLIDING) - P_SetPlayerMobjState(object, S_PLAY_FALL); + P_SetMobjState(object, S_PLAY_FALL); P_ResetPlayer(object->player); object->player->pflags |= pflags; object->player->secondjump = secondjump; @@ -403,7 +403,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object) } if (object->player->pflags & PF_GLIDING) - P_SetPlayerMobjState(object, S_PLAY_FALL); + P_SetMobjState(object, S_PLAY_FALL); if ((spring->info->painchance == 3)) { if (!(pflags = (object->player->pflags & PF_SPINNING)) && @@ -411,11 +411,11 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object) || (spring->flags2 & MF2_AMBUSH))) { pflags = PF_SPINNING; - P_SetPlayerMobjState(object, S_PLAY_ROLL); + P_SetMobjState(object, S_PLAY_ROLL); S_StartSound(object, sfx_spin); } else - P_SetPlayerMobjState(object, S_PLAY_ROLL); + P_SetMobjState(object, S_PLAY_ROLL); } else { @@ -424,7 +424,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object) pflags = object->player->pflags & (PF_STARTJUMP | PF_JUMPED | PF_NOJUMPDAMAGE | PF_SPINNING | PF_THOKKED | PF_BOUNCING); // I still need these. if (wasSpindashing) // Ensure we're in the rolling state, and not spindash. - P_SetPlayerMobjState(object, S_PLAY_ROLL); + P_SetMobjState(object, S_PLAY_ROLL); if (object->player->charability == CA_GLIDEANDCLIMB && object->player->skidtime && (pflags & PF_JUMPED)) { @@ -439,7 +439,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object) if (spring->info->painchance == 1) // For all those ancient, SOC'd abilities. { object->player->pflags |= P_GetJumpFlags(object->player); - P_SetPlayerMobjState(object, S_PLAY_JUMP); + P_SetMobjState(object, S_PLAY_JUMP); } else if ((spring->info->painchance == 2) || ((spring->info->painchance != 3) && (pflags & PF_BOUNCING))) // Adding momentum only. { @@ -456,16 +456,16 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object) object->player->secondjump = secondjump; } else if (object->player->dashmode >= DASHMODE_THRESHOLD) - P_SetPlayerMobjState(object, S_PLAY_DASH); + P_SetMobjState(object, S_PLAY_DASH); else if (P_IsObjectOnGround(object)) - P_SetPlayerMobjState(object, (horizspeed >= FixedMul(object->player->runspeed, object->scale)) ? S_PLAY_RUN : S_PLAY_WALK); + P_SetMobjState(object, (horizspeed >= FixedMul(object->player->runspeed, object->scale)) ? S_PLAY_RUN : S_PLAY_WALK); else - P_SetPlayerMobjState(object, (object->momz > 0) ? S_PLAY_SPRING : S_PLAY_FALL); + P_SetMobjState(object, (object->momz > 0) ? S_PLAY_SPRING : S_PLAY_FALL); } else if (P_MobjFlip(object)*vertispeed > 0) - P_SetPlayerMobjState(object, S_PLAY_SPRING); + P_SetMobjState(object, S_PLAY_SPRING); else - P_SetPlayerMobjState(object, S_PLAY_FALL); + P_SetMobjState(object, S_PLAY_FALL); } else if (horizspeed && object->tracer @@ -547,7 +547,7 @@ static void P_DoFanAndGasJet(mobj_t *spring, mobj_t *object) if (p && !p->powers[pw_tailsfly] && !p->powers[pw_carry]) // doesn't reset anim for Tails' flight { P_ResetPlayer(p); - P_SetPlayerMobjState(object, S_PLAY_FALL); + P_SetMobjState(object, S_PLAY_FALL); P_SetTarget(&object->tracer, spring); p->powers[pw_carry] = CR_FAN; } @@ -565,7 +565,7 @@ static void P_DoFanAndGasJet(mobj_t *spring, mobj_t *object) { P_ResetPlayer(p); if (p->panim != PA_FALL) - P_SetPlayerMobjState(object, S_PLAY_FALL); + P_SetMobjState(object, S_PLAY_FALL); } break; default: @@ -1047,7 +1047,7 @@ static boolean PIT_CheckThing(mobj_t *thing) thing->flags2 &= ~MF2_DONTDRAW; // don't leave the rock invisible if it was flashing prior to boarding P_SetTarget(&thing->tracer, tmthing); P_ResetPlayer(tmthing->player); - P_SetPlayerMobjState(tmthing, S_PLAY_WALK); + P_SetMobjState(tmthing, S_PLAY_WALK); tmthing->player->powers[pw_carry] = CR_ROLLOUT; P_SetTarget(&tmthing->tracer, thing); if (!P_IsObjectOnGround(thing)) diff --git a/src/p_mobj.c b/src/p_mobj.c index 1b2065920..621de3a42 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -177,7 +177,7 @@ static void P_CyclePlayerMobjState(mobj_t *mobj) // you can cycle through multiple states in a tic if (!mobj->tics && mobj->state) - if (!P_SetPlayerMobjState(mobj, mobj->state->nextstate)) + if (!P_SetMobjState(mobj, mobj->state->nextstate)) return; // freed itself } } @@ -188,7 +188,7 @@ static void P_CyclePlayerMobjState(mobj_t *mobj) // // Separate from P_SetMobjState because of the pw_flashing check and Super states // -boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state) +static boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state) { state_t *st; player_t *player = mobj->player; @@ -514,10 +514,8 @@ boolean P_SetMobjState(mobj_t *mobj, statenum_t state) statenum_t i = state; // initial state statenum_t tempstate[NUMSTATES]; // for use with recursion -#ifdef PARANOIA if (mobj->player != NULL) - I_Error("P_SetMobjState used for player mobj. Use P_SetPlayerMobjState instead!\n(State called: %d)", state); -#endif + P_SetPlayerMobjState(mobj, state); if (recursion++) // if recursion detected, memset(seenstate = tempstate, 0, sizeof tempstate); // clear state table @@ -1648,7 +1646,7 @@ static void P_XYFriction(mobj_t *mo, fixed_t oldx, fixed_t oldy) { // if in a walking frame, stop moving if (player->panim == PA_WALK) - P_SetPlayerMobjState(mo, S_PLAY_STND); + P_SetMobjState(mo, S_PLAY_STND); mo->momx = player->cmomx; mo->momy = player->cmomy; } @@ -2974,7 +2972,7 @@ void P_PlayerZMovement(mobj_t *mo) } // Get up if you fell. if (mo->player->panim == PA_PAIN) - P_SetPlayerMobjState(mo, S_PLAY_WALK); + P_SetMobjState(mo, S_PLAY_WALK); if (!mo->standingslope && (mo->eflags & MFE_VERTICALFLIP ? tmceilingslope : tmfloorslope)) { // Handle landing on slope during Z movement @@ -3957,7 +3955,7 @@ static void P_PlayerMobjThinker(mobj_t *mobj) { mobj->player->secondjump = 0; mobj->player->powers[pw_tailsfly] = 0; - P_SetPlayerMobjState(mobj, S_PLAY_WALK); + P_SetMobjState(mobj, S_PLAY_WALK); } #endif mobj->eflags &= ~MFE_JUSTHITFLOOR; @@ -7549,7 +7547,7 @@ static void P_RosySceneryThink(mobj_t *mobj) if (stat == S_ROSY_HUG) { if (player->panim != PA_IDLE) - P_SetPlayerMobjState(mobj->target, S_PLAY_STND); + P_SetMobjState(mobj->target, S_PLAY_STND); player->pflags |= PF_STASIS; } @@ -11771,9 +11769,9 @@ void P_MovePlayerToSpawn(INT32 playernum, mapthing_t *mthing) mobj->flags2 |= MF2_OBJECTFLIP; } if (mthing->args[0]) - P_SetPlayerMobjState(mobj, S_PLAY_FALL); + P_SetMobjState(mobj, S_PLAY_FALL); else if (metalrecording) - P_SetPlayerMobjState(mobj, S_PLAY_WAIT); + P_SetMobjState(mobj, S_PLAY_WAIT); } else z = floor; diff --git a/src/p_spec.c b/src/p_spec.c index 4263a4fc7..131a58d20 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2735,7 +2735,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) mo->player->rmomx = mo->player->rmomy = 1; mo->player->cmomx = mo->player->cmomy = 0; P_ResetPlayer(mo->player); - P_SetPlayerMobjState(mo, S_PLAY_STND); + P_SetMobjState(mo, S_PLAY_STND); // Reset bot too. if (bot) { @@ -2746,7 +2746,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) bot->player->rmomx = bot->player->rmomy = 1; bot->player->cmomx = bot->player->cmomy = 0; P_ResetPlayer(bot->player); - P_SetPlayerMobjState(bot, S_PLAY_STND); + P_SetMobjState(bot, S_PLAY_STND); } } break; @@ -4566,7 +4566,7 @@ static void P_ProcessSpeedPad(player_t *player, sector_t *sector, sector_t *rove if (!(player->pflags & PF_SPINNING)) player->pflags |= PF_SPINNING; - P_SetPlayerMobjState(player->mo, S_PLAY_ROLL); + P_SetMobjState(player->mo, S_PLAY_ROLL); } player->powers[pw_flashing] = TICRATE/3; @@ -4744,7 +4744,7 @@ static void P_ProcessZoomTube(player_t *player, mtag_t sectag, boolean end) if (player->mo->state-states != S_PLAY_ROLL) { - P_SetPlayerMobjState(player->mo, S_PLAY_ROLL); + P_SetMobjState(player->mo, S_PLAY_ROLL); S_StartSound(player->mo, sfx_spin); } } @@ -4958,7 +4958,7 @@ static void P_ProcessRopeHang(player_t *player, mtag_t sectag) player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_BOUNCING|PF_SLIDING|PF_CANCARRY); player->climbing = 0; P_SetThingPosition(player->mo); - P_SetPlayerMobjState(player->mo, S_PLAY_RIDE); + P_SetMobjState(player->mo, S_PLAY_RIDE); } static boolean P_SectorHasSpecial(sector_t *sec) @@ -5017,7 +5017,7 @@ static void P_EvaluateSpecialFlags(player_t *player, sector_t *sector, sector_t if (!player->powers[pw_carry]) { P_ResetPlayer(player); - P_SetPlayerMobjState(player->mo, S_PLAY_FALL); + P_SetMobjState(player->mo, S_PLAY_FALL); P_SetTarget(&player->mo->tracer, player->mo); player->powers[pw_carry] = CR_FAN; } @@ -5032,7 +5032,7 @@ static void P_EvaluateSpecialFlags(player_t *player, sector_t *sector, sector_t if (!(player->pflags & PF_SPINNING)) { player->pflags |= PF_SPINNING; - P_SetPlayerMobjState(player->mo, S_PLAY_ROLL); + P_SetMobjState(player->mo, S_PLAY_ROLL); S_StartAttackSound(player->mo, sfx_spin); if (abs(player->rmomx) < FixedMul(5*FRACUNIT, player->mo->scale) diff --git a/src/p_telept.c b/src/p_telept.c index 66b05ff01..32669edaf 100644 --- a/src/p_telept.c +++ b/src/p_telept.c @@ -96,7 +96,7 @@ void P_MixUp(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle, P_ClearStarPost(starpostnum); P_ResetPlayer(thing->player); - P_SetPlayerMobjState(thing, S_PLAY_STND); + P_SetMobjState(thing, S_PLAY_STND); P_FlashPal(thing->player, PAL_MIXUP, 10); } @@ -153,7 +153,7 @@ boolean P_Teleport(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle thing->player->rmomx = thing->player->rmomy = 0; thing->player->speed = 0; P_ResetPlayer(thing->player); - P_SetPlayerMobjState(thing, S_PLAY_STND); + P_SetMobjState(thing, S_PLAY_STND); thing->reactiontime = TICRATE/2; // don't move for about half a second thing->player->drawangle = angle; diff --git a/src/p_user.c b/src/p_user.c index a856d2592..f54eb4e6b 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -694,7 +694,7 @@ static void P_DeNightserizePlayer(player_t *player) else if (player == &players[secondarydisplayplayer]) localaiming2 = 0; - P_SetPlayerMobjState(player->mo, S_PLAY_FALL); + P_SetMobjState(player->mo, S_PLAY_FALL); // If in a special stage, add some preliminary exit time. if (G_IsSpecialStage(gamemap)) @@ -948,7 +948,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime) P_RunNightserizeExecutors(player->mo); player->powers[pw_carry] = CR_NIGHTSMODE; - P_SetPlayerMobjState(player->mo, S_PLAY_NIGHTS_TRANS1); + P_SetMobjState(player->mo, S_PLAY_NIGHTS_TRANS1); } pflags_t P_GetJumpFlags(player_t *player) @@ -994,7 +994,7 @@ void P_DoPlayerPain(player_t *player, mobj_t *source, mobj_t *inflictor) fixed_t fallbackspeed; P_ResetPlayer(player); - P_SetPlayerMobjState(player->mo, player->mo->info->painstate); + P_SetMobjState(player->mo, player->mo->info->painstate); if (player->mo->eflags & MFE_VERTICALFLIP) player->mo->z--; @@ -1345,7 +1345,7 @@ void P_DoSuperTransformation(player_t *player, boolean giverings) player->mo->momx = player->mo->momy = player->mo->momz = player->cmomx = player->cmomy = player->rmomx = player->rmomy = 0; // Transformation animation - P_SetPlayerMobjState(player->mo, S_PLAY_SUPER_TRANS1); + P_SetMobjState(player->mo, S_PLAY_SUPER_TRANS1); if (giverings && player->rings < 50) player->rings = 50; @@ -1398,7 +1398,7 @@ static void P_DoSuperDetransformation(player_t *player) player->powers[pw_flashing] = flashingtics-1; if (player->mo->sprite2 & FF_SPR2SUPER) - P_SetPlayerMobjState(player->mo, player->mo->state-states); + P_SetMobjState(player->mo, player->mo->state-states); // Inform the netgame that the champion has fallen in the heat of battle. if (!G_CoopGametype()) @@ -2265,12 +2265,12 @@ void P_DoPlayerExit(player_t *player) { player->climbing = 0; player->pflags |= P_GetJumpFlags(player); - P_SetPlayerMobjState(player->mo, S_PLAY_JUMP); + P_SetMobjState(player->mo, S_PLAY_JUMP); } else if (player->pflags & PF_STARTDASH) { player->pflags &= ~PF_STARTDASH; - P_SetPlayerMobjState(player->mo, S_PLAY_STND); + P_SetMobjState(player->mo, S_PLAY_STND); } player->powers[pw_underwater] = 0; player->powers[pw_spacetime] = 0; @@ -2359,7 +2359,7 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff) if (!(player->pflags & PF_STARTDASH) && player->panim != PA_ROLL && player->panim != PA_ETC && player->panim != PA_ABILITY && player->panim != PA_ABILITY2) { - P_SetPlayerMobjState(player->mo, S_PLAY_ROLL); + P_SetMobjState(player->mo, S_PLAY_ROLL); S_StartSound(player->mo, sfx_spin); } } @@ -2368,7 +2368,7 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff) if (dorollstuff) { player->skidtime = TICRATE; - P_SetPlayerMobjState(player->mo, S_PLAY_GLIDE); + P_SetMobjState(player->mo, S_PLAY_GLIDE); P_SpawnSkidDust(player, player->mo->radius, true); // make sure the player knows they landed player->mo->tics = -1; } @@ -2381,7 +2381,7 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff) if (player->mo->state-states != S_PLAY_GLIDE_LANDING) { P_ResetPlayer(player); - P_SetPlayerMobjState(player->mo, S_PLAY_GLIDE_LANDING); + P_SetMobjState(player->mo, S_PLAY_GLIDE_LANDING); player->pflags |= PF_STASIS; if (player->speed > FixedMul(player->runspeed, player->mo->scale)) player->skidtime += player->mo->tics; @@ -2402,7 +2402,7 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff) if (player->mo->state-states != S_PLAY_MELEE_LANDING) { mobjtype_t type = player->revitem; - P_SetPlayerMobjState(player->mo, S_PLAY_MELEE_LANDING); + P_SetMobjState(player->mo, S_PLAY_MELEE_LANDING); player->mo->tics = (player->mo->movefactor == FRACUNIT) ? TICRATE/2 : (FixedDiv(35<<(FRACBITS-1), FixedSqrt(player->mo->movefactor)))>>FRACBITS; S_StartSound(player->mo, sfx_s3k8b); player->pflags |= PF_FULLSTASIS; @@ -2465,28 +2465,28 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff) if (player->cmomx || player->cmomy) { if (player->charflags & SF_DASHMODE && player->dashmode >= DASHMODE_THRESHOLD && player->panim != PA_DASH) - P_SetPlayerMobjState(player->mo, S_PLAY_DASH); + P_SetMobjState(player->mo, S_PLAY_DASH); else if (player->speed >= runspd && (player->panim != PA_RUN || player->mo->state-states == S_PLAY_FLOAT_RUN)) - P_SetPlayerMobjState(player->mo, S_PLAY_RUN); + P_SetMobjState(player->mo, S_PLAY_RUN); else if ((player->rmomx || player->rmomy) && (player->panim != PA_WALK || player->mo->state-states == S_PLAY_FLOAT)) - P_SetPlayerMobjState(player->mo, S_PLAY_WALK); + P_SetMobjState(player->mo, S_PLAY_WALK); else if (!player->rmomx && !player->rmomy && player->panim != PA_IDLE) - P_SetPlayerMobjState(player->mo, S_PLAY_STND); + P_SetMobjState(player->mo, S_PLAY_STND); } else { if (player->charflags & SF_DASHMODE && player->dashmode >= DASHMODE_THRESHOLD && player->panim != PA_DASH) - P_SetPlayerMobjState(player->mo, S_PLAY_DASH); + P_SetMobjState(player->mo, S_PLAY_DASH); else if (player->speed >= runspd && (player->panim != PA_RUN || player->mo->state-states == S_PLAY_FLOAT_RUN)) - P_SetPlayerMobjState(player->mo, S_PLAY_RUN); + P_SetMobjState(player->mo, S_PLAY_RUN); else if ((player->mo->momx || player->mo->momy) && (player->panim != PA_WALK || player->mo->state-states == S_PLAY_FLOAT)) - P_SetPlayerMobjState(player->mo, S_PLAY_WALK); + P_SetMobjState(player->mo, S_PLAY_WALK); else if (!player->mo->momx && !player->mo->momy && player->panim != PA_IDLE) - P_SetPlayerMobjState(player->mo, S_PLAY_STND); + P_SetMobjState(player->mo, S_PLAY_STND); } } @@ -2516,7 +2516,7 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff) ? 6*FRACUNIT/5 : 5*FRACUNIT/2, false); - P_SetPlayerMobjState(player->mo, S_PLAY_FALL); + P_SetMobjState(player->mo, S_PLAY_FALL); player->mo->momx = player->mo->momy = 0; clipmomz = false; } @@ -3648,9 +3648,9 @@ static void P_DoClimbing(player_t *player) if (player->climbing && climb && (player->mo->momx || player->mo->momy || player->mo->momz) && player->mo->state-states != S_PLAY_CLIMB) - P_SetPlayerMobjState(player->mo, S_PLAY_CLIMB); + P_SetMobjState(player->mo, S_PLAY_CLIMB); else if ((!(player->mo->momx || player->mo->momy || player->mo->momz) || !climb) && player->mo->state-states != S_PLAY_CLING) - P_SetPlayerMobjState(player->mo, S_PLAY_CLING); + P_SetMobjState(player->mo, S_PLAY_CLING); if (!floorclimb) { @@ -3665,14 +3665,14 @@ static void P_DoClimbing(player_t *player) player->climbing = 0; player->pflags |= P_GetJumpFlags(player); - P_SetPlayerMobjState(player->mo, S_PLAY_JUMP); + P_SetMobjState(player->mo, S_PLAY_JUMP); } if (skyclimber) { player->climbing = 0; player->pflags |= P_GetJumpFlags(player); - P_SetPlayerMobjState(player->mo, S_PLAY_JUMP); + P_SetMobjState(player->mo, S_PLAY_JUMP); } } @@ -3683,15 +3683,15 @@ static void P_DoClimbing(player_t *player) if (player->climbing && climb && (player->mo->momx || player->mo->momy || player->mo->momz) && player->mo->state-states != S_PLAY_CLIMB) - P_SetPlayerMobjState(player->mo, S_PLAY_CLIMB); + P_SetMobjState(player->mo, S_PLAY_CLIMB); else if ((!(player->mo->momx || player->mo->momy || player->mo->momz) || !climb) && player->mo->state-states != S_PLAY_CLING) - P_SetPlayerMobjState(player->mo, S_PLAY_CLING); + P_SetMobjState(player->mo, S_PLAY_CLING); if (cmd->buttons & BT_SPIN && !(player->pflags & PF_JUMPSTASIS)) { player->climbing = 0; player->pflags |= P_GetJumpFlags(player); - P_SetPlayerMobjState(player->mo, S_PLAY_JUMP); + P_SetMobjState(player->mo, S_PLAY_JUMP); P_SetObjectMomZ(player->mo, 4*FRACUNIT, false); P_Thrust(player->mo, player->mo->angle, FixedMul(-4*FRACUNIT, player->mo->scale)); } @@ -3707,12 +3707,12 @@ static void P_DoClimbing(player_t *player) } if (player->climbing == 0) - P_SetPlayerMobjState(player->mo, S_PLAY_JUMP); + P_SetMobjState(player->mo, S_PLAY_JUMP); if (player->climbing && P_IsObjectOnGround(player->mo)) { P_ResetPlayer(player); - P_SetPlayerMobjState(player->mo, S_PLAY_STND); + P_SetMobjState(player->mo, S_PLAY_STND); } } @@ -4074,10 +4074,10 @@ teeterdone: if (teeter) { if (player->panim == PA_IDLE) - P_SetPlayerMobjState(player->mo, S_PLAY_EDGE); + P_SetMobjState(player->mo, S_PLAY_EDGE); } else if (player->panim == PA_EDGE) - P_SetPlayerMobjState(player->mo, S_PLAY_STND); + P_SetMobjState(player->mo, S_PLAY_STND); } // @@ -4602,7 +4602,7 @@ void P_DoJump(player_t *player, boolean soundandstate) if (!player->spectator) S_StartSound(player->mo, sfx_jump); // Play jump sound! - P_SetPlayerMobjState(player->mo, S_PLAY_JUMP); + P_SetMobjState(player->mo, S_PLAY_JUMP); } } @@ -4672,7 +4672,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd) player->mo->momy = player->cmomy; player->pflags |= (PF_SPINDOWN|PF_STARTDASH|PF_SPINNING); player->dashspeed = player->mindash; - P_SetPlayerMobjState(player->mo, S_PLAY_SPINDASH); + P_SetMobjState(player->mo, S_PLAY_SPINDASH); if (!player->spectator) S_StartSound(player->mo, sfx_spndsh); // Make the rev sound! } @@ -4682,7 +4682,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd) if (player->speed > 5*player->mo->scale) { player->pflags &= ~PF_STARTDASH; - P_SetPlayerMobjState(player->mo, S_PLAY_ROLL); + P_SetMobjState(player->mo, S_PLAY_ROLL); S_StartSound(player->mo, sfx_spin); break; } @@ -4715,7 +4715,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd) || !canstand) && !(player->pflags & (PF_SPINDOWN|PF_SPINNING))) { player->pflags |= (PF_SPINDOWN|PF_SPINNING); - P_SetPlayerMobjState(player->mo, S_PLAY_ROLL); + P_SetMobjState(player->mo, S_PLAY_ROLL); if (!player->spectator) S_StartSound(player->mo, sfx_spin); } @@ -4731,12 +4731,12 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd) { if (player->dashspeed) { - P_SetPlayerMobjState(player->mo, S_PLAY_ROLL); + P_SetMobjState(player->mo, S_PLAY_ROLL); P_InstaThrust(player->mo, player->mo->angle, (player->speed = FixedMul(player->dashspeed, player->mo->scale))); // catapult forward ho!! } else { - P_SetPlayerMobjState(player->mo, S_PLAY_STND); + P_SetMobjState(player->mo, S_PLAY_STND); player->pflags &= ~PF_SPINNING; } @@ -4768,7 +4768,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd) { mobj_t *bullet; - P_SetPlayerMobjState(player->mo, S_PLAY_FIRE); + P_SetMobjState(player->mo, S_PLAY_FIRE); #define zpos(posmo) (posmo->z + (posmo->height - mobjinfo[player->revitem].height)/2) if (lockon) @@ -4812,7 +4812,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd) P_DoJump(player, false); player->pflags &= ~PF_STARTJUMP; player->mo->momz = FixedMul(player->mo->momz, 3*FRACUNIT/2); // NOT 1.5 times the jump height, but 2.25 times. - P_SetPlayerMobjState(player->mo, S_PLAY_TWINSPIN); + P_SetMobjState(player->mo, S_PLAY_TWINSPIN); S_StartSound(player->mo, sfx_s3k8b); } else @@ -4838,7 +4838,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd) } player->mo->momx += player->cmomx; player->mo->momy += player->cmomy; - P_SetPlayerMobjState(player->mo, S_PLAY_MELEE); + P_SetMobjState(player->mo, S_PLAY_MELEE); player->powers[pw_strong] = STR_MELEE; S_StartSound(player->mo, sfx_s3k42); } @@ -4862,7 +4862,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd) { player->skidtime = 0; player->pflags &= ~PF_SPINNING; - P_SetPlayerMobjState(player->mo, S_PLAY_STND); + P_SetMobjState(player->mo, S_PLAY_STND); player->mo->momx = player->cmomx; player->mo->momy = player->cmomy; } @@ -4911,13 +4911,13 @@ void P_DoJumpShield(player_t *player) #undef limitangle #undef numangles player->pflags &= ~PF_NOJUMPDAMAGE; - P_SetPlayerMobjState(player->mo, S_PLAY_ROLL); + P_SetMobjState(player->mo, S_PLAY_ROLL); S_StartSound(player->mo, sfx_s3k45); } else { player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE); - P_SetPlayerMobjState(player->mo, S_PLAY_FALL); + P_SetMobjState(player->mo, S_PLAY_FALL); S_StartSound(player->mo, sfx_wdjump); } } @@ -4934,9 +4934,9 @@ void P_DoBubbleBounce(player_t *player) P_MobjCheckWater(player->mo); P_DoJump(player, false); if (player->charflags & SF_NOJUMPSPIN) - P_SetPlayerMobjState(player->mo, S_PLAY_FALL); + P_SetMobjState(player->mo, S_PLAY_FALL); else - P_SetPlayerMobjState(player->mo, S_PLAY_ROLL); + P_SetMobjState(player->mo, S_PLAY_ROLL); player->pflags |= PF_THOKKED; player->pflags &= ~PF_STARTJUMP; player->secondjump = UINT8_MAX; @@ -4973,7 +4973,7 @@ void P_DoAbilityBounce(player_t *player, boolean changemomz) } S_StartSound(player->mo, sfx_boingf); - P_SetPlayerMobjState(player->mo, S_PLAY_BOUNCE_LANDING); + P_SetMobjState(player->mo, S_PLAY_BOUNCE_LANDING); player->pflags |= PF_BOUNCING|PF_THOKKED; } @@ -5086,7 +5086,7 @@ static void P_DoTwinSpin(player_t *player) player->pflags |= P_GetJumpFlags(player) | PF_THOKKED; S_StartSound(player->mo, sfx_s3k42); player->mo->frame = 0; - P_SetPlayerMobjState(player->mo, S_PLAY_TWINSPIN); + P_SetMobjState(player->mo, S_PLAY_TWINSPIN); player->powers[pw_strong] = STR_TWINSPIN; } @@ -5160,7 +5160,7 @@ static boolean P_PlayerShieldThink(player_t *player, ticcmd_t *cmd, mobj_t *lock { player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, lockonshield->x, lockonshield->y); player->pflags &= ~PF_NOJUMPDAMAGE; - P_SetPlayerMobjState(player->mo, S_PLAY_ROLL); + P_SetMobjState(player->mo, S_PLAY_ROLL); S_StartSound(player->mo, sfx_s3k40); player->homing = 3*TICRATE; } @@ -5184,7 +5184,7 @@ static boolean P_PlayerShieldThink(player_t *player, ticcmd_t *cmd, mobj_t *lock player->mo->momx -= (player->mo->momx/3); player->mo->momy -= (player->mo->momy/3); player->pflags &= ~PF_NOJUMPDAMAGE; - P_SetPlayerMobjState(player->mo, S_PLAY_ROLL); + P_SetMobjState(player->mo, S_PLAY_ROLL); S_StartSound(player->mo, sfx_s3k44); } player->secondjump = 0; @@ -5197,7 +5197,7 @@ static boolean P_PlayerShieldThink(player_t *player, ticcmd_t *cmd, mobj_t *lock P_Thrust(player->mo, player->mo->angle, FixedMul(30*FRACUNIT - FixedSqrt(FixedDiv(player->speed, player->mo->scale)), player->mo->scale)); player->drawangle = player->mo->angle; player->pflags &= ~(PF_NOJUMPDAMAGE|PF_SPINNING); - P_SetPlayerMobjState(player->mo, S_PLAY_ROLL); + P_SetMobjState(player->mo, S_PLAY_ROLL); S_StartSound(player->mo, sfx_s3k43); default: break; @@ -5259,9 +5259,9 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd) if (player->panim != PA_RUN && player->panim != PA_WALK) { if (player->speed >= FixedMul(player->runspeed, player->mo->scale)) - P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT_RUN); + P_SetMobjState(player->mo, S_PLAY_FLOAT_RUN); else - P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT); + P_SetMobjState(player->mo, S_PLAY_FLOAT); } player->mo->momz = 0; @@ -5392,13 +5392,13 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd) P_SetTarget(&player->mo->target, P_SetTarget(&player->mo->tracer, lockonthok)); if (lockonthok) { - P_SetPlayerMobjState(player->mo, S_PLAY_ROLL); + P_SetMobjState(player->mo, S_PLAY_ROLL); player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, lockonthok->x, lockonthok->y); player->homing = 3*TICRATE; } else { - P_SetPlayerMobjState(player->mo, S_PLAY_FALL); + P_SetMobjState(player->mo, S_PLAY_FALL); player->pflags &= ~PF_JUMPED; player->mo->height = P_GetPlayerHeight(player); } @@ -5435,7 +5435,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd) ; // Can't do anything if you're a fish out of water! else if (!(player->pflags & PF_THOKKED) && !(player->powers[pw_tailsfly])) { - P_SetPlayerMobjState(player->mo, S_PLAY_FLY); // Change to the flying animation + P_SetMobjState(player->mo, S_PLAY_FLY); // Change to the flying animation player->powers[pw_tailsfly] = tailsflytics + 1; // Set the fly timer @@ -5468,7 +5468,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd) player->pflags |= PF_GLIDING|PF_THOKKED; player->glidetime = 0; - P_SetPlayerMobjState(player->mo, S_PLAY_GLIDE); + P_SetMobjState(player->mo, S_PLAY_GLIDE); if (playerspeed < glidespeed) P_Thrust(player->mo, player->mo->angle, glidespeed - playerspeed); player->pflags &= ~(PF_JUMPED|PF_SPINNING|PF_STARTDASH); @@ -5489,11 +5489,11 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd) if (!(player->pflags & PF_THOKKED) || player->charflags & SF_MULTIABILITY) { if (player->charflags & SF_DASHMODE && player->dashmode >= DASHMODE_THRESHOLD) - P_SetPlayerMobjState(player->mo, S_PLAY_DASH); + P_SetMobjState(player->mo, S_PLAY_DASH); else if (player->speed >= FixedMul(player->runspeed, player->mo->scale)) - P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT_RUN); + P_SetMobjState(player->mo, S_PLAY_FLOAT_RUN); else - P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT); + P_SetMobjState(player->mo, S_PLAY_FLOAT); player->pflags |= PF_THOKKED; player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_SPINNING); player->secondjump = 1; @@ -5529,7 +5529,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd) case CA_BOUNCE: if (!(player->pflags & PF_THOKKED) || player->charflags & SF_MULTIABILITY) { - P_SetPlayerMobjState(player->mo, S_PLAY_BOUNCE); + P_SetMobjState(player->mo, S_PLAY_BOUNCE); player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_SPINNING); player->pflags |= PF_THOKKED|PF_BOUNCING; player->powers[pw_strong] = STR_BOUNCE; @@ -5619,7 +5619,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd) if (!(player->mo->tracer->flags & MF_BOSS)) player->pflags &= ~PF_THOKKED; - P_SetPlayerMobjState(player->mo, S_PLAY_SPRING); + P_SetMobjState(player->mo, S_PLAY_SPRING); player->pflags |= PF_NOJUMPDAMAGE; } } @@ -5667,7 +5667,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd) else player->secondjump = 2; - P_SetPlayerMobjState(player->mo, S_PLAY_FALL); + P_SetMobjState(player->mo, S_PLAY_FALL); } // If letting go of the jump button while still on ascent, cut the jump height. @@ -5787,7 +5787,7 @@ static void P_2dMovement(player_t *player) else if (player->exiting) { player->pflags &= ~PF_GLIDING; - P_SetPlayerMobjState(player->mo, S_PLAY_WALK); + P_SetMobjState(player->mo, S_PLAY_WALK); player->skidtime = 0; } } @@ -5796,7 +5796,7 @@ static void P_2dMovement(player_t *player) if (player->pflags & PF_SPINNING && !player->exiting) { player->pflags &= ~PF_SPINNING; - P_SetPlayerMobjState(player->mo, S_PLAY_STND); + P_SetMobjState(player->mo, S_PLAY_STND); } } @@ -5960,7 +5960,7 @@ static void P_3dMovement(player_t *player) else if (player->exiting) { player->pflags &= ~PF_GLIDING; - P_SetPlayerMobjState(player->mo, S_PLAY_WALK); + P_SetMobjState(player->mo, S_PLAY_WALK); player->skidtime = 0; } } @@ -5969,7 +5969,7 @@ static void P_3dMovement(player_t *player) if (player->pflags & PF_SPINNING && !player->exiting) { player->pflags &= ~PF_SPINNING; - P_SetPlayerMobjState(player->mo, S_PLAY_STND); + P_SetMobjState(player->mo, S_PLAY_STND); } } @@ -6011,7 +6011,7 @@ static void P_3dMovement(player_t *player) if (player->pflags & PF_SLIDING) cmd->forwardmove = 0; else if (onground && player->mo->state == states+S_PLAY_PAIN) - P_SetPlayerMobjState(player->mo, S_PLAY_WALK); + P_SetMobjState(player->mo, S_PLAY_WALK); player->aiming = cmd->aiming<mo->momx || player->mo->momy || player->mo->momz) { if (player->mo->state != &states[S_PLAY_NIGHTS_PULL]) - P_SetPlayerMobjState(player->mo, S_PLAY_NIGHTS_PULL); + P_SetMobjState(player->mo, S_PLAY_NIGHTS_PULL); } else if (player->mo->state != &states[S_PLAY_NIGHTS_ATTACK]) { S_StartSound(player->mo, sfx_spin); - P_SetPlayerMobjState(player->mo, S_PLAY_NIGHTS_ATTACK); + P_SetMobjState(player->mo, S_PLAY_NIGHTS_ATTACK); } } else @@ -6829,7 +6829,7 @@ static void P_DoNiGHTSCapsule(player_t *player) if (!(player->pflags & PF_JUMPED) && !(player->pflags & PF_SPINNING)) player->pflags |= PF_JUMPED; if (player->panim != PA_ROLL) - P_SetPlayerMobjState(player->mo, S_PLAY_ROLL); + P_SetMobjState(player->mo, S_PLAY_ROLL); } if (!(player->charflags & SF_NONIGHTSROTATION)) @@ -7308,14 +7308,14 @@ static void P_NiGHTSMovement(player_t *player) if (!(player->charflags & SF_NONIGHTSROTATION) && player->mo->momz) { if (player->mo->state != &states[S_PLAY_NIGHTS_DRILL]) - P_SetPlayerMobjState(player->mo, S_PLAY_NIGHTS_DRILL); + P_SetMobjState(player->mo, S_PLAY_NIGHTS_DRILL); player->mo->spriteroll = ANGLE_90; } else #endif { if (player->mo->state != &states[S_PLAY_NIGHTS_FLOAT]) - P_SetPlayerMobjState(player->mo, S_PLAY_NIGHTS_FLOAT); + P_SetMobjState(player->mo, S_PLAY_NIGHTS_FLOAT); player->drawangle += ANGLE_22h; } @@ -7639,7 +7639,7 @@ static void P_NiGHTSMovement(player_t *player) } if (player->mo->state != &states[flystate]) - P_SetPlayerMobjState(player->mo, flystate); + P_SetMobjState(player->mo, flystate); if (player->charflags & SF_NONIGHTSROTATION) player->mo->spriteroll = 0; @@ -7905,13 +7905,13 @@ static void P_SkidStuff(player_t *player) player->skidtime = 0; player->pflags &= ~(PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE); player->pflags |= PF_THOKKED; - P_SetPlayerMobjState(player->mo, S_PLAY_FALL); + P_SetMobjState(player->mo, S_PLAY_FALL); } // Get up and brush yourself off, idiot. else if (player->glidetime > 15 || !(player->cmd.buttons & BT_JUMP)) { P_ResetPlayer(player); - P_SetPlayerMobjState(player->mo, S_PLAY_GLIDE_LANDING); + P_SetMobjState(player->mo, S_PLAY_GLIDE_LANDING); player->pflags |= PF_STASIS; if (player->speed > FixedMul(player->runspeed, player->mo->scale)) player->skidtime += player->mo->tics; @@ -7956,7 +7956,7 @@ static void P_SkidStuff(player_t *player) if (dang > ANGLE_157h) { if (player->mo->state-states != S_PLAY_SKID) - P_SetPlayerMobjState(player->mo, S_PLAY_SKID); + P_SetMobjState(player->mo, S_PLAY_SKID); player->mo->tics = player->skidtime = (player->mo->movefactor == FRACUNIT) ? TICRATE/2 : (FixedDiv(35<<(FRACBITS-1), FixedSqrt(player->mo->movefactor)))>>FRACBITS; S_StartSound(player->mo, sfx_skid); } @@ -8188,63 +8188,63 @@ void P_MovePlayer(player_t *player) { // If the player is in dashmode, here's their peelout. if (player->charflags & SF_DASHMODE && player->dashmode >= DASHMODE_THRESHOLD && player->panim == PA_RUN && !player->skidtime && (onground || ((player->charability == CA_FLOAT || player->charability == CA_SLOWFALL) && player->secondjump == 1) || player->powers[pw_super])) - P_SetPlayerMobjState (player->mo, S_PLAY_DASH); + P_SetMobjState (player->mo, S_PLAY_DASH); // If the player is moving fast enough, // break into a run! else if (player->speed >= runspd && player->panim == PA_WALK && !player->skidtime && (onground || ((player->charability == CA_FLOAT || player->charability == CA_SLOWFALL) && player->secondjump == 1) || player->powers[pw_super])) { if (!onground) - P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT_RUN); + P_SetMobjState(player->mo, S_PLAY_FLOAT_RUN); else - P_SetPlayerMobjState(player->mo, S_PLAY_RUN); + P_SetMobjState(player->mo, S_PLAY_RUN); } // Floating at slow speeds has its own special animation. else if ((((player->charability == CA_FLOAT || player->charability == CA_SLOWFALL) && player->secondjump == 1) || player->powers[pw_super]) && player->panim == PA_IDLE && !onground) - P_SetPlayerMobjState (player->mo, S_PLAY_FLOAT); + P_SetMobjState (player->mo, S_PLAY_FLOAT); // Otherwise, just walk. else if ((player->rmomx || player->rmomy) && player->panim == PA_IDLE) - P_SetPlayerMobjState (player->mo, S_PLAY_WALK); + P_SetMobjState (player->mo, S_PLAY_WALK); } // If your peelout animation is playing, and you're // going too slow, switch back to the run. if (player->charflags & SF_DASHMODE && player->panim == PA_DASH && player->dashmode < DASHMODE_THRESHOLD) - P_SetPlayerMobjState(player->mo, S_PLAY_RUN); + P_SetMobjState(player->mo, S_PLAY_RUN); // If your running animation is playing, and you're // going too slow, switch back to the walking frames. if (player->panim == PA_RUN && player->speed < runspd) { if (!onground && (((player->charability == CA_FLOAT || player->charability == CA_SLOWFALL) && player->secondjump == 1) || player->powers[pw_super])) - P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT); + P_SetMobjState(player->mo, S_PLAY_FLOAT); else - P_SetPlayerMobjState(player->mo, S_PLAY_WALK); + P_SetMobjState(player->mo, S_PLAY_WALK); } // Correct floating when ending up on the ground. if (onground) { if (player->mo->state-states == S_PLAY_FLOAT) - P_SetPlayerMobjState(player->mo, S_PLAY_WALK); + P_SetMobjState(player->mo, S_PLAY_WALK); else if (player->mo->state-states == S_PLAY_FLOAT_RUN) - P_SetPlayerMobjState(player->mo, S_PLAY_RUN); + P_SetMobjState(player->mo, S_PLAY_RUN); } // If Springing (or nojumpspinning), but travelling DOWNWARD, change back! if ((player->panim == PA_SPRING && P_MobjFlip(player->mo)*player->mo->momz < 0) || ((((player->charflags & SF_NOJUMPSPIN) && (player->pflags & PF_JUMPED) && player->panim == PA_JUMP)) && (P_MobjFlip(player->mo)*player->mo->momz < 0))) - P_SetPlayerMobjState(player->mo, S_PLAY_FALL); + P_SetMobjState(player->mo, S_PLAY_FALL); // If doing an air animation but on the ground, change back! else if (onground && (player->panim == PA_SPRING || player->panim == PA_FALL || player->panim == PA_RIDE || player->panim == PA_JUMP) && !player->mo->momz) - P_SetPlayerMobjState(player->mo, S_PLAY_STND); + P_SetMobjState(player->mo, S_PLAY_STND); // If you are stopped and are still walking, stand still! if (!player->mo->momx && !player->mo->momy && !player->mo->momz && player->panim == PA_WALK) - P_SetPlayerMobjState(player->mo, S_PLAY_STND); + P_SetMobjState(player->mo, S_PLAY_STND); ////////////////// //GAMEPLAY STUFF// @@ -8256,18 +8256,18 @@ void P_MovePlayer(player_t *player) { player->pflags &= ~(PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE|PF_THOKKED|PF_SHIELDABILITY); player->secondjump = 0; - P_SetPlayerMobjState(player->mo, S_PLAY_STND); + P_SetMobjState(player->mo, S_PLAY_STND); } if ((!(player->charability == CA_GLIDEANDCLIMB) || player->gotflag) // If you can't glide, then why the heck would you be gliding? && (player->pflags & PF_GLIDING || player->climbing)) { if (onground) - P_SetPlayerMobjState(player->mo, S_PLAY_WALK); + P_SetMobjState(player->mo, S_PLAY_WALK); else { player->pflags |= P_GetJumpFlags(player); - P_SetPlayerMobjState(player->mo, S_PLAY_JUMP); + P_SetMobjState(player->mo, S_PLAY_JUMP); } player->pflags &= ~PF_GLIDING; player->glidetime = 0; @@ -8278,11 +8278,11 @@ void P_MovePlayer(player_t *player) && (player->pflags & PF_BOUNCING)) { if (onground) - P_SetPlayerMobjState(player->mo, S_PLAY_WALK); + P_SetMobjState(player->mo, S_PLAY_WALK); else { player->pflags |= P_GetJumpFlags(player); - P_SetPlayerMobjState(player->mo, S_PLAY_JUMP); + P_SetMobjState(player->mo, S_PLAY_JUMP); } player->pflags &= ~PF_BOUNCING; } @@ -8399,18 +8399,18 @@ void P_MovePlayer(player_t *player) { P_ResetPlayer(player); // down, stop gliding. if (onground) - P_SetPlayerMobjState(player->mo, S_PLAY_WALK); + P_SetMobjState(player->mo, S_PLAY_WALK); else if (player->charflags & SF_MULTIABILITY) { player->pflags |= P_GetJumpFlags(player); - P_SetPlayerMobjState(player->mo, S_PLAY_JUMP); + P_SetMobjState(player->mo, S_PLAY_JUMP); } else { player->pflags |= PF_THOKKED; player->mo->momx >>= 1; player->mo->momy >>= 1; - P_SetPlayerMobjState(player->mo, S_PLAY_FALL); + P_SetMobjState(player->mo, S_PLAY_FALL); } } } @@ -8427,23 +8427,23 @@ void P_MovePlayer(player_t *player) P_ResetPlayer(player); // down, stop bouncing. player->pflags |= PF_THOKKED; if (onground) - P_SetPlayerMobjState(player->mo, S_PLAY_WALK); + P_SetMobjState(player->mo, S_PLAY_WALK); else if (player->charflags & SF_MULTIABILITY) { player->pflags |= P_GetJumpFlags(player); - P_SetPlayerMobjState(player->mo, S_PLAY_JUMP); + P_SetMobjState(player->mo, S_PLAY_JUMP); } else { player->mo->momx >>= 1; player->mo->momy >>= 1; player->mo->momz >>= 1; - P_SetPlayerMobjState(player->mo, S_PLAY_FALL); + P_SetMobjState(player->mo, S_PLAY_FALL); } } } else if (player->mo->state-states == S_PLAY_BOUNCE) - P_SetPlayerMobjState(player->mo, S_PLAY_FALL); + P_SetMobjState(player->mo, S_PLAY_FALL); // If you're running fast enough, you can create splashes as you run in shallow water. if (!player->climbing @@ -8487,11 +8487,11 @@ void P_MovePlayer(player_t *player) || player->mo->state-states == S_PLAY_FLY_TIRED) { if (onground) - P_SetPlayerMobjState(player->mo, S_PLAY_WALK); + P_SetMobjState(player->mo, S_PLAY_WALK); else { player->pflags |= P_GetJumpFlags(player); - P_SetPlayerMobjState(player->mo, S_PLAY_JUMP); + P_SetMobjState(player->mo, S_PLAY_JUMP); } } player->powers[pw_tailsfly] = 0; @@ -8525,7 +8525,7 @@ void P_MovePlayer(player_t *player) if (P_MobjFlip(player->mo)*player->mo->momz < FixedMul(5*actionspd, player->mo->scale)) P_SetObjectMomZ(player->mo, actionspd/2, true); - P_SetPlayerMobjState(player->mo, player->mo->state->nextstate); + P_SetMobjState(player->mo, player->mo->state->nextstate); player->fly1--; } @@ -8553,7 +8553,7 @@ void P_MovePlayer(player_t *player) { // Tails-gets-tired Stuff if (player->panim == PA_ABILITY && player->mo->state-states != S_PLAY_FLY_TIRED) - P_SetPlayerMobjState(player->mo, S_PLAY_FLY_TIRED); + P_SetMobjState(player->mo, S_PLAY_FLY_TIRED); if (player->charability == CA_FLY && (leveltime % 10 == 0) && player->mo->state-states == S_PLAY_FLY_TIRED @@ -8670,7 +8670,7 @@ void P_MovePlayer(player_t *player) // Make sure you're not teetering when you shouldn't be. if (player->panim == PA_EDGE && (player->mo->momx || player->mo->momy || player->mo->momz)) - P_SetPlayerMobjState(player->mo, S_PLAY_STND); + P_SetMobjState(player->mo, S_PLAY_STND); // Check for teeter! if (!(player->mo->momz || player->mo->momx || player->mo->momy) && !(player->mo->eflags & MFE_GOOWATER) @@ -8736,7 +8736,7 @@ void P_MovePlayer(player_t *player) if (!atspinheight) { player->pflags |= PF_SPINNING; - P_SetPlayerMobjState(player->mo, S_PLAY_ROLL); + P_SetMobjState(player->mo, S_PLAY_ROLL); } else if (player->mo->ceilingz - player->mo->floorz < player->mo->height) { @@ -8917,7 +8917,7 @@ static void P_DoRopeHang(player_t *player) if (player->cmd.buttons & BT_SPIN && !(player->pflags & PF_STASIS)) // Drop off of the rope { player->pflags |= (P_GetJumpFlags(player)|PF_SPINDOWN); - P_SetPlayerMobjState(player->mo, S_PLAY_JUMP); + P_SetMobjState(player->mo, S_PLAY_JUMP); P_SetTarget(&player->mo->tracer, NULL); player->powers[pw_carry] = CR_NONE; @@ -8926,7 +8926,7 @@ static void P_DoRopeHang(player_t *player) } if (player->mo->state-states != S_PLAY_RIDE) - P_SetPlayerMobjState(player->mo, S_PLAY_RIDE); + P_SetMobjState(player->mo, S_PLAY_RIDE); // If not allowed to move, we're done here. if (!speed) @@ -8982,7 +8982,7 @@ static void P_DoRopeHang(player_t *player) if (player->mo->tracer->flags & MF_SLIDEME) { player->pflags |= P_GetJumpFlags(player); - P_SetPlayerMobjState(player->mo, S_PLAY_JUMP); + P_SetMobjState(player->mo, S_PLAY_JUMP); } P_SetTarget(&player->mo->tracer, NULL); @@ -11180,7 +11180,7 @@ static void P_MinecartThink(player_t *player) if (player->mo->state-states != S_PLAY_STND) { - P_SetPlayerMobjState(player->mo, S_PLAY_STND); + P_SetMobjState(player->mo, S_PLAY_STND); player->mo->tics = -1; } @@ -11958,7 +11958,7 @@ void P_PlayerThink(player_t *player) { P_DoZoomTube(player); if (!(player->panim == PA_ROLL)) - P_SetPlayerMobjState(player->mo, S_PLAY_ROLL); + P_SetMobjState(player->mo, S_PLAY_ROLL); } player->rmomx = player->rmomy = 0; // no actual momentum from your controls P_ResetScore(player); @@ -12117,7 +12117,7 @@ void P_PlayerThink(player_t *player) { statenum_t stat = player->mo->state-states; if (stat == S_PLAY_WAIT) - P_SetPlayerMobjState(player->mo, S_PLAY_STND); + P_SetMobjState(player->mo, S_PLAY_STND); else if (stat == S_PLAY_STND && player->mo->tics != -1) player->mo->tics++; } @@ -12144,7 +12144,7 @@ void P_PlayerThink(player_t *player) else if (!player->skidtime && !(player->mo->eflags & MFE_GOOWATER) && !(player->pflags & (PF_JUMPED|PF_SPINNING|PF_SLIDING)) && !(player->charflags & SF_NOSKID) && P_AproxDistance(player->mo->momx, player->mo->momy) >= FixedMul(player->runspeed, player->mo->scale)) // modified from player->runspeed/2 'cuz the skid was just TOO frequent ngl { if (player->mo->state-states != S_PLAY_SKID) - P_SetPlayerMobjState(player->mo, S_PLAY_SKID); + P_SetMobjState(player->mo, S_PLAY_SKID); player->mo->tics = player->skidtime = (player->mo->movefactor == FRACUNIT) ? TICRATE/2 : (FixedDiv(35<<(FRACBITS-1), FixedSqrt(player->mo->movefactor)))>>FRACBITS; if (P_IsLocalPlayer(player)) // the sound happens way more frequently now, so give co-op players' ears a brake... @@ -12321,7 +12321,7 @@ void P_PlayerThink(player_t *player) if (!player->powers[pw_flashing]) { if (player->mo->state != &states[S_PLAY_STND]) - P_SetPlayerMobjState(player->mo, S_PLAY_STND); + P_SetMobjState(player->mo, S_PLAY_STND); else player->mo->tics = 2; } @@ -12682,7 +12682,7 @@ void P_PlayerAfterThink(player_t *player) S_StartSound(NULL, sfx_wepchg); if ((player->pflags & PF_SLIDING) && ((player->pflags & (PF_JUMPED|PF_NOJUMPDAMAGE)) != PF_JUMPED)) - P_SetPlayerMobjState(player->mo, player->mo->info->painstate); + P_SetMobjState(player->mo, player->mo->info->painstate); /* if (player->powers[pw_carry] == CR_NONE && player->mo->tracer && !player->homing) P_SetTarget(&player->mo->tracer, NULL); @@ -12743,7 +12743,7 @@ void P_PlayerAfterThink(player_t *player) if (player->powers[pw_carry] == CR_PLAYER) { if (player->mo->state-states != S_PLAY_RIDE) - P_SetPlayerMobjState(player->mo, S_PLAY_RIDE); + P_SetMobjState(player->mo, S_PLAY_RIDE); if (tails->player && (tails->skin && ((skin_t *)(tails->skin))->sprites[SPR2_SWIM].numframes) && (tails->eflags & MFE_UNDERWATER)) tails->player->powers[pw_tailsfly] = 0; } @@ -12768,7 +12768,7 @@ void P_PlayerAfterThink(player_t *player) player->mo->momx = player->mo->momy = player->mo->momz = 0; P_SetThingPosition(player->mo); if (player->mo->state-states != S_PLAY_RIDE) - P_SetPlayerMobjState(player->mo, S_PLAY_RIDE); + P_SetMobjState(player->mo, S_PLAY_RIDE); // Controllable missile if (item->type == MT_BLACKEGGMAN_MISSILE) @@ -12889,7 +12889,7 @@ void P_PlayerAfterThink(player_t *player) if (player->panim == PA_IDLE && (mo->momx || mo->momy)) { - P_SetPlayerMobjState(player->mo, S_PLAY_WALK); + P_SetMobjState(player->mo, S_PLAY_WALK); } if (player->panim == PA_WALK && mo->tics > walktics) @@ -12945,7 +12945,7 @@ void P_PlayerAfterThink(player_t *player) P_KillMobj(ptera, player->mo, player->mo, 0); P_SetObjectMomZ(player->mo, 12*FRACUNIT, false); player->pflags |= PF_APPLYAUTOBRAKE|PF_JUMPED|PF_THOKKED; - P_SetPlayerMobjState(player->mo, S_PLAY_ROLL); + P_SetMobjState(player->mo, S_PLAY_ROLL); break; } @@ -12966,7 +12966,7 @@ void P_PlayerAfterThink(player_t *player) ptera->cvmem >>= 1; if (player->mo->state-states != S_PLAY_FALL) - P_SetPlayerMobjState(player->mo, S_PLAY_FALL); + P_SetMobjState(player->mo, S_PLAY_FALL); break; dropoff: diff --git a/src/r_skins.c b/src/r_skins.c index 308cee8d6..fbc2a30e1 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -392,7 +392,7 @@ static void SetSkin(player_t *player, INT32 skinnum) P_SetScale(player->mo, player->mo->scale); player->mo->radius = radius; - P_SetPlayerMobjState(player->mo, player->mo->state-states); // Prevent visual errors when switching between skins with differing number of frames + P_SetMobjState(player->mo, player->mo->state-states); // Prevent visual errors when switching between skins with differing number of frames } } From ac34fdf1290b31e772c88dee3b866e6950511554 Mon Sep 17 00:00:00 2001 From: Zwip-Zwap Zapony Date: Sat, 23 Dec 2023 13:14:47 +0000 Subject: [PATCH 171/227] Apply 1 suggestion(s) to 1 file(s) --- src/p_mobj.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index 621de3a42..2a627ce01 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -515,7 +515,7 @@ boolean P_SetMobjState(mobj_t *mobj, statenum_t state) statenum_t tempstate[NUMSTATES]; // for use with recursion if (mobj->player != NULL) - P_SetPlayerMobjState(mobj, state); + return P_SetPlayerMobjState(mobj, state); if (recursion++) // if recursion detected, memset(seenstate = tempstate, 0, sizeof tempstate); // clear state table From 6a4b26a04c8e90acf19a0effba17333a3e0420fe Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Sat, 23 Dec 2023 16:30:45 -0300 Subject: [PATCH 172/227] Fix #1158 --- src/r_draw.c | 19 ++-- src/r_draw.h | 7 +- src/r_draw8.c | 160 ++++++++++++++++----------------- src/r_draw8_npo2.c | 144 +++++++++++++++--------------- src/r_main.c | 13 --- src/r_plane.c | 214 ++++++++++++++++++++++++--------------------- src/r_plane.h | 5 -- src/r_splats.c | 4 +- 8 files changed, 271 insertions(+), 295 deletions(-) diff --git a/src/r_draw.c b/src/r_draw.c index 671767b21..643f843d3 100644 --- a/src/r_draw.c +++ b/src/r_draw.c @@ -106,14 +106,13 @@ fixed_t ds_xfrac, ds_yfrac, ds_xstep, ds_ystep; INT32 ds_waterofs, ds_bgofs; UINT16 ds_flatwidth, ds_flatheight; -boolean ds_powersoftwo, ds_solidcolor; +boolean ds_powersoftwo, ds_solidcolor, ds_fog; UINT8 *ds_source; // points to the start of a flat UINT8 *ds_transmap; // one of the translucency tables // Vectors for Software's tilted slope drawers -floatv3_t *ds_su, *ds_sv, *ds_sz; -floatv3_t *ds_sup, *ds_svp, *ds_szp; +floatv3_t ds_su, ds_sv, ds_sz, ds_slopelight; float focallengthf, zeroheight; /** \brief Variable flat sizes @@ -906,13 +905,15 @@ static void R_CalcTiltedLighting(fixed_t start, fixed_t end) } } +#define PLANELIGHTFLOAT (BASEVIDWIDTH * BASEVIDWIDTH / vid.width / zeroheight / 21.0f * FIXED_TO_FLOAT(fovtan)) + // Lighting is simple. It's just linear interpolation from start to end -#define CALC_SLOPE_LIGHT { \ - float planelightfloat = PLANELIGHTFLOAT; \ - float lightstart, lightend; \ - lightend = (iz + ds_szp->x*width) * planelightfloat; \ - lightstart = iz * planelightfloat; \ - R_CalcTiltedLighting(FloatToFixed(lightstart), FloatToFixed(lightend)); \ +static void R_CalcSlopeLight(void) +{ + float iz = ds_slopelight.z + ds_slopelight.y * (centery - ds_y) + ds_slopelight.x * (ds_x1 - centerx); + float lightstart = iz * PLANELIGHTFLOAT; + float lightend = (iz + ds_slopelight.x * (ds_x2 - ds_x1)) * PLANELIGHTFLOAT; + R_CalcTiltedLighting(FloatToFixed(lightstart), FloatToFixed(lightend)); } // ========================================================================== diff --git a/src/r_draw.h b/src/r_draw.h index 0103ed827..0f08a48bf 100644 --- a/src/r_draw.h +++ b/src/r_draw.h @@ -61,7 +61,7 @@ extern fixed_t ds_xfrac, ds_yfrac, ds_xstep, ds_ystep; extern INT32 ds_waterofs, ds_bgofs; extern UINT16 ds_flatwidth, ds_flatheight; -extern boolean ds_powersoftwo, ds_solidcolor; +extern boolean ds_powersoftwo, ds_solidcolor, ds_fog; extern UINT8 *ds_source; extern UINT8 *ds_transmap; @@ -71,8 +71,7 @@ typedef struct { } floatv3_t; // Vectors for Software's tilted slope drawers -extern floatv3_t *ds_su, *ds_sv, *ds_sz; -extern floatv3_t *ds_sup, *ds_svp, *ds_szp; +extern floatv3_t ds_su, ds_sv, ds_sz, ds_slopelight; extern float focallengthf, zeroheight; // Variable flat sizes @@ -178,8 +177,6 @@ void R_Draw2sMultiPatchTranslucentColumn_8(void); void R_DrawFogColumn_8(void); void R_DrawColumnShadowed_8(void); -#define PLANELIGHTFLOAT (BASEVIDWIDTH * BASEVIDWIDTH / vid.width / zeroheight / 21.0f * FIXED_TO_FLOAT(fovtan)) - void R_DrawSpan_8(void); void R_DrawTranslucentSpan_8(void); void R_DrawTiltedSpan_8(void); diff --git a/src/r_draw8.c b/src/r_draw8.c index b80a47984..fe7d321df 100644 --- a/src/r_draw8.c +++ b/src/r_draw8.c @@ -676,12 +676,11 @@ void R_DrawTiltedSpan_8(void) double endz, endu, endv; UINT32 stepu, stepv; - iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx); + iz = ds_sz.z + ds_sz.y*(centery-ds_y) + ds_sz.x*(ds_x1-centerx); + uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx); + vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx); - CALC_SLOPE_LIGHT - - uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx); - vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx); + R_CalcSlopeLight(); dest = ylookup[ds_y] + columnofs[ds_x1]; source = ds_source; @@ -700,18 +699,18 @@ void R_DrawTiltedSpan_8(void) *dest = colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]]; dest++; - iz += ds_szp->x; - uz += ds_sup->x; - vz += ds_svp->x; + iz += ds_sz.x; + uz += ds_su.x; + vz += ds_sv.x; } while (--width >= 0); #else startz = 1.f/iz; startu = uz*startz; startv = vz*startz; - izstep = ds_szp->x * SPANSIZE; - uzstep = ds_sup->x * SPANSIZE; - vzstep = ds_svp->x * SPANSIZE; + izstep = ds_sz.x * SPANSIZE; + uzstep = ds_su.x * SPANSIZE; + vzstep = ds_sv.x * SPANSIZE; //x1 = 0; width++; @@ -753,9 +752,9 @@ void R_DrawTiltedSpan_8(void) else { double left = width; - iz += ds_szp->x * left; - uz += ds_sup->x * left; - vz += ds_svp->x * left; + iz += ds_sz.x * left; + uz += ds_su.x * left; + vz += ds_sv.x * left; endz = 1.f/iz; endu = uz*endz; @@ -799,12 +798,11 @@ void R_DrawTiltedTranslucentSpan_8(void) double endz, endu, endv; UINT32 stepu, stepv; - iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx); + iz = ds_sz.z + ds_sz.y*(centery-ds_y) + ds_sz.x*(ds_x1-centerx); + uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx); + vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx); - CALC_SLOPE_LIGHT - - uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx); - vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx); + R_CalcSlopeLight(); dest = ylookup[ds_y] + columnofs[ds_x1]; source = ds_source; @@ -822,18 +820,18 @@ void R_DrawTiltedTranslucentSpan_8(void) colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps); *dest = *(ds_transmap + (colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]] << 8) + *dest); dest++; - iz += ds_szp->x; - uz += ds_sup->x; - vz += ds_svp->x; + iz += ds_sz.x; + uz += ds_su.x; + vz += ds_sv.x; } while (--width >= 0); #else startz = 1.f/iz; startu = uz*startz; startv = vz*startz; - izstep = ds_szp->x * SPANSIZE; - uzstep = ds_sup->x * SPANSIZE; - vzstep = ds_svp->x * SPANSIZE; + izstep = ds_sz.x * SPANSIZE; + uzstep = ds_su.x * SPANSIZE; + vzstep = ds_sv.x * SPANSIZE; //x1 = 0; width++; @@ -875,9 +873,9 @@ void R_DrawTiltedTranslucentSpan_8(void) else { double left = width; - iz += ds_szp->x * left; - uz += ds_sup->x * left; - vz += ds_svp->x * left; + iz += ds_sz.x * left; + uz += ds_su.x * left; + vz += ds_sv.x * left; endz = 1.f/iz; endu = uz*endz; @@ -922,12 +920,11 @@ void R_DrawTiltedWaterSpan_8(void) double endz, endu, endv; UINT32 stepu, stepv; - iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx); + iz = ds_sz.z + ds_sz.y*(centery-ds_y) + ds_sz.x*(ds_x1-centerx); + uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx); + vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx); - CALC_SLOPE_LIGHT - - uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx); - vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx); + R_CalcSlopeLight(); dest = ylookup[ds_y] + columnofs[ds_x1]; dsrc = screens[1] + (ds_y+ds_bgofs)*vid.width + ds_x1; @@ -946,18 +943,18 @@ void R_DrawTiltedWaterSpan_8(void) colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps); *dest = *(ds_transmap + (colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]] << 8) + *dsrc++); dest++; - iz += ds_szp->x; - uz += ds_sup->x; - vz += ds_svp->x; + iz += ds_sz.x; + uz += ds_su.x; + vz += ds_sv.x; } while (--width >= 0); #else startz = 1.f/iz; startu = uz*startz; startv = vz*startz; - izstep = ds_szp->x * SPANSIZE; - uzstep = ds_sup->x * SPANSIZE; - vzstep = ds_svp->x * SPANSIZE; + izstep = ds_sz.x * SPANSIZE; + uzstep = ds_su.x * SPANSIZE; + vzstep = ds_sv.x * SPANSIZE; //x1 = 0; width++; @@ -999,9 +996,9 @@ void R_DrawTiltedWaterSpan_8(void) else { double left = width; - iz += ds_szp->x * left; - uz += ds_sup->x * left; - vz += ds_svp->x * left; + iz += ds_sz.x * left; + uz += ds_su.x * left; + vz += ds_sv.x * left; endz = 1.f/iz; endu = uz*endz; @@ -1044,12 +1041,11 @@ void R_DrawTiltedSplat_8(void) double endz, endu, endv; UINT32 stepu, stepv; - iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx); + iz = ds_sz.z + ds_sz.y*(centery-ds_y) + ds_sz.x*(ds_x1-centerx); + uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx); + vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx); - CALC_SLOPE_LIGHT - - uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx); - vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx); + R_CalcSlopeLight(); dest = ylookup[ds_y] + columnofs[ds_x1]; source = ds_source; @@ -1071,18 +1067,18 @@ void R_DrawTiltedSplat_8(void) *dest = colormap[val]; dest++; - iz += ds_szp->x; - uz += ds_sup->x; - vz += ds_svp->x; + iz += ds_sz.x; + uz += ds_su.x; + vz += ds_sv.x; } while (--width >= 0); #else startz = 1.f/iz; startu = uz*startz; startv = vz*startz; - izstep = ds_szp->x * SPANSIZE; - uzstep = ds_sup->x * SPANSIZE; - vzstep = ds_svp->x * SPANSIZE; + izstep = ds_sz.x * SPANSIZE; + uzstep = ds_su.x * SPANSIZE; + vzstep = ds_sv.x * SPANSIZE; //x1 = 0; width++; @@ -1128,9 +1124,9 @@ void R_DrawTiltedSplat_8(void) else { double left = width; - iz += ds_szp->x * left; - uz += ds_sup->x * left; - vz += ds_svp->x * left; + iz += ds_sz.x * left; + uz += ds_su.x * left; + vz += ds_sv.x * left; endz = 1.f/iz; endu = uz*endz; @@ -1613,9 +1609,9 @@ void R_DrawTiltedFloorSprite_8(void) double endz, endu, endv; UINT32 stepu, stepv; - iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx); - uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx); - vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx); + iz = ds_sz.z + ds_sz.y*(centery-ds_y) + ds_sz.x*(ds_x1-centerx); + uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx); + vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx); dest = ylookup[ds_y] + columnofs[ds_x1]; source = (UINT16 *)ds_source; @@ -1626,9 +1622,9 @@ void R_DrawTiltedFloorSprite_8(void) startu = uz*startz; startv = vz*startz; - izstep = ds_szp->x * SPANSIZE; - uzstep = ds_sup->x * SPANSIZE; - vzstep = ds_svp->x * SPANSIZE; + izstep = ds_sz.x * SPANSIZE; + uzstep = ds_su.x * SPANSIZE; + vzstep = ds_sv.x * SPANSIZE; //x1 = 0; width++; @@ -1673,9 +1669,9 @@ void R_DrawTiltedFloorSprite_8(void) else { double left = width; - iz += ds_szp->x * left; - uz += ds_sup->x * left; - vz += ds_svp->x * left; + iz += ds_sz.x * left; + uz += ds_su.x * left; + vz += ds_sv.x * left; endz = 1.f/iz; endu = uz*endz; @@ -1722,9 +1718,9 @@ void R_DrawTiltedTranslucentFloorSprite_8(void) double endz, endu, endv; UINT32 stepu, stepv; - iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx); - uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx); - vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx); + iz = ds_sz.z + ds_sz.y*(centery-ds_y) + ds_sz.x*(ds_x1-centerx); + uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx); + vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx); dest = ylookup[ds_y] + columnofs[ds_x1]; source = (UINT16 *)ds_source; @@ -1735,9 +1731,9 @@ void R_DrawTiltedTranslucentFloorSprite_8(void) startu = uz*startz; startv = vz*startz; - izstep = ds_szp->x * SPANSIZE; - uzstep = ds_sup->x * SPANSIZE; - vzstep = ds_svp->x * SPANSIZE; + izstep = ds_sz.x * SPANSIZE; + uzstep = ds_su.x * SPANSIZE; + vzstep = ds_sv.x * SPANSIZE; //x1 = 0; width++; @@ -1782,9 +1778,9 @@ void R_DrawTiltedTranslucentFloorSprite_8(void) else { double left = width; - iz += ds_szp->x * left; - uz += ds_sup->x * left; - vz += ds_svp->x * left; + iz += ds_sz.x * left; + uz += ds_su.x * left; + vz += ds_sv.x * left; endz = 1.f/iz; endu = uz*endz; @@ -2013,9 +2009,7 @@ void R_DrawTiltedFogSpan_8(void) UINT8 *dest = ylookup[ds_y] + columnofs[ds_x1]; - double iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx); - - CALC_SLOPE_LIGHT + R_CalcSlopeLight(); do { @@ -2067,9 +2061,7 @@ void R_DrawTiltedSolidColorSpan_8(void) UINT8 source = ds_source[0]; UINT8 *dest = ylookup[ds_y] + columnofs[ds_x1]; - double iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx); - - CALC_SLOPE_LIGHT + R_CalcSlopeLight(); do { @@ -2088,9 +2080,7 @@ void R_DrawTiltedTransSolidColorSpan_8(void) UINT8 source = ds_source[0]; UINT8 *dest = ylookup[ds_y] + columnofs[ds_x1]; - double iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx); - - CALC_SLOPE_LIGHT + R_CalcSlopeLight(); do { @@ -2131,9 +2121,7 @@ void R_DrawTiltedWaterSolidColorSpan_8(void) UINT8 *dest = ylookup[ds_y] + columnofs[ds_x1]; UINT8 *dsrc = screens[1] + (ds_y+ds_bgofs)*vid.width + ds_x1; - double iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx); - - CALC_SLOPE_LIGHT + R_CalcSlopeLight(); do { diff --git a/src/r_draw8_npo2.c b/src/r_draw8_npo2.c index 91f3b06c4..78cde8a2c 100644 --- a/src/r_draw8_npo2.c +++ b/src/r_draw8_npo2.c @@ -114,12 +114,11 @@ void R_DrawTiltedSpan_NPO2_8(void) struct libdivide_u32_t x_divider = libdivide_u32_gen(ds_flatwidth); struct libdivide_u32_t y_divider = libdivide_u32_gen(ds_flatheight); - iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx); + iz = ds_sz.z + ds_sz.y*(centery-ds_y) + ds_sz.x*(ds_x1-centerx); + uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx); + vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx); - CALC_SLOPE_LIGHT - - uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx); - vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx); + R_CalcSlopeLight(); dest = ylookup[ds_y] + columnofs[ds_x1]; source = ds_source; @@ -154,18 +153,18 @@ void R_DrawTiltedSpan_NPO2_8(void) *dest = colormap[source[((y * ds_flatwidth) + x)]]; } dest++; - iz += ds_szp->x; - uz += ds_sup->x; - vz += ds_svp->x; + iz += ds_sz.x; + uz += ds_su.x; + vz += ds_sv.x; } while (--width >= 0); #else startz = 1.f/iz; startu = uz*startz; startv = vz*startz; - izstep = ds_szp->x * SPANSIZE; - uzstep = ds_sup->x * SPANSIZE; - vzstep = ds_svp->x * SPANSIZE; + izstep = ds_sz.x * SPANSIZE; + uzstep = ds_su.x * SPANSIZE; + vzstep = ds_sv.x * SPANSIZE; //x1 = 0; width++; @@ -239,9 +238,9 @@ void R_DrawTiltedSpan_NPO2_8(void) else { double left = width; - iz += ds_szp->x * left; - uz += ds_sup->x * left; - vz += ds_svp->x * left; + iz += ds_sz.x * left; + uz += ds_su.x * left; + vz += ds_sv.x * left; endz = 1.f/iz; endu = uz*endz; @@ -304,12 +303,11 @@ void R_DrawTiltedTranslucentSpan_NPO2_8(void) struct libdivide_u32_t x_divider = libdivide_u32_gen(ds_flatwidth); struct libdivide_u32_t y_divider = libdivide_u32_gen(ds_flatheight); - iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx); + iz = ds_sz.z + ds_sz.y*(centery-ds_y) + ds_sz.x*(ds_x1-centerx); + uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx); + vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx); - CALC_SLOPE_LIGHT - - uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx); - vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx); + R_CalcSlopeLight(); dest = ylookup[ds_y] + columnofs[ds_x1]; source = ds_source; @@ -343,18 +341,18 @@ void R_DrawTiltedTranslucentSpan_NPO2_8(void) *dest = *(ds_transmap + (colormap[source[((y * ds_flatwidth) + x)]] << 8) + *dest); } dest++; - iz += ds_szp->x; - uz += ds_sup->x; - vz += ds_svp->x; + iz += ds_sz.x; + uz += ds_su.x; + vz += ds_sv.x; } while (--width >= 0); #else startz = 1.f/iz; startu = uz*startz; startv = vz*startz; - izstep = ds_szp->x * SPANSIZE; - uzstep = ds_sup->x * SPANSIZE; - vzstep = ds_svp->x * SPANSIZE; + izstep = ds_sz.x * SPANSIZE; + uzstep = ds_su.x * SPANSIZE; + vzstep = ds_sv.x * SPANSIZE; //x1 = 0; width++; @@ -428,9 +426,9 @@ void R_DrawTiltedTranslucentSpan_NPO2_8(void) else { double left = width; - iz += ds_szp->x * left; - uz += ds_sup->x * left; - vz += ds_svp->x * left; + iz += ds_sz.x * left; + uz += ds_su.x * left; + vz += ds_sv.x * left; endz = 1.f/iz; endu = uz*endz; @@ -492,12 +490,11 @@ void R_DrawTiltedSplat_NPO2_8(void) struct libdivide_u32_t x_divider = libdivide_u32_gen(ds_flatwidth); struct libdivide_u32_t y_divider = libdivide_u32_gen(ds_flatheight); - iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx); + iz = ds_sz.z + ds_sz.y*(centery-ds_y) + ds_sz.x*(ds_x1-centerx); + uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx); + vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx); - CALC_SLOPE_LIGHT - - uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx); - vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx); + R_CalcSlopeLight(); dest = ylookup[ds_y] + columnofs[ds_x1]; source = ds_source; @@ -536,18 +533,18 @@ void R_DrawTiltedSplat_NPO2_8(void) *dest = colormap[val]; dest++; - iz += ds_szp->x; - uz += ds_sup->x; - vz += ds_svp->x; + iz += ds_sz.x; + uz += ds_su.x; + vz += ds_sv.x; } while (--width >= 0); #else startz = 1.f/iz; startu = uz*startz; startv = vz*startz; - izstep = ds_szp->x * SPANSIZE; - uzstep = ds_sup->x * SPANSIZE; - vzstep = ds_svp->x * SPANSIZE; + izstep = ds_sz.x * SPANSIZE; + uzstep = ds_su.x * SPANSIZE; + vzstep = ds_sv.x * SPANSIZE; //x1 = 0; width++; @@ -625,9 +622,9 @@ void R_DrawTiltedSplat_NPO2_8(void) else { double left = width; - iz += ds_szp->x * left; - uz += ds_sup->x * left; - vz += ds_svp->x * left; + iz += ds_sz.x * left; + uz += ds_su.x * left; + vz += ds_sv.x * left; endz = 1.f/iz; endu = uz*endz; @@ -970,9 +967,9 @@ void R_DrawTiltedFloorSprite_NPO2_8(void) struct libdivide_u32_t x_divider = libdivide_u32_gen(ds_flatwidth); struct libdivide_u32_t y_divider = libdivide_u32_gen(ds_flatheight); - iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx); - uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx); - vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx); + iz = ds_sz.z + ds_sz.y*(centery-ds_y) + ds_sz.x*(ds_x1-centerx); + uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx); + vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx); dest = ylookup[ds_y] + columnofs[ds_x1]; source = (UINT16 *)ds_source; @@ -983,9 +980,9 @@ void R_DrawTiltedFloorSprite_NPO2_8(void) startu = uz*startz; startv = vz*startz; - izstep = ds_szp->x * SPANSIZE; - uzstep = ds_sup->x * SPANSIZE; - vzstep = ds_svp->x * SPANSIZE; + izstep = ds_sz.x * SPANSIZE; + uzstep = ds_su.x * SPANSIZE; + vzstep = ds_sv.x * SPANSIZE; //x1 = 0; width++; @@ -1060,9 +1057,9 @@ void R_DrawTiltedFloorSprite_NPO2_8(void) else { double left = width; - iz += ds_szp->x * left; - uz += ds_sup->x * left; - vz += ds_svp->x * left; + iz += ds_sz.x * left; + uz += ds_su.x * left; + vz += ds_sv.x * left; endz = 1.f/iz; endu = uz*endz; @@ -1126,9 +1123,9 @@ void R_DrawTiltedTranslucentFloorSprite_NPO2_8(void) struct libdivide_u32_t x_divider = libdivide_u32_gen(ds_flatwidth); struct libdivide_u32_t y_divider = libdivide_u32_gen(ds_flatheight); - iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx); - uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx); - vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx); + iz = ds_sz.z + ds_sz.y*(centery-ds_y) + ds_sz.x*(ds_x1-centerx); + uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx); + vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx); dest = ylookup[ds_y] + columnofs[ds_x1]; source = (UINT16 *)ds_source; @@ -1139,9 +1136,9 @@ void R_DrawTiltedTranslucentFloorSprite_NPO2_8(void) startu = uz*startz; startv = vz*startz; - izstep = ds_szp->x * SPANSIZE; - uzstep = ds_sup->x * SPANSIZE; - vzstep = ds_svp->x * SPANSIZE; + izstep = ds_sz.x * SPANSIZE; + uzstep = ds_su.x * SPANSIZE; + vzstep = ds_sv.x * SPANSIZE; //x1 = 0; width++; @@ -1216,9 +1213,9 @@ void R_DrawTiltedTranslucentFloorSprite_NPO2_8(void) else { double left = width; - iz += ds_szp->x * left; - uz += ds_sup->x * left; - vz += ds_svp->x * left; + iz += ds_sz.x * left; + uz += ds_su.x * left; + vz += ds_sv.x * left; endz = 1.f/iz; endu = uz*endz; @@ -1411,12 +1408,11 @@ void R_DrawTiltedWaterSpan_NPO2_8(void) struct libdivide_u32_t x_divider = libdivide_u32_gen(ds_flatwidth); struct libdivide_u32_t y_divider = libdivide_u32_gen(ds_flatheight); - iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx); + iz = ds_sz.z + ds_sz.y*(centery-ds_y) + ds_sz.x*(ds_x1-centerx); + uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx); + vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx); - CALC_SLOPE_LIGHT - - uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx); - vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx); + R_CalcSlopeLight(); dest = ylookup[ds_y] + columnofs[ds_x1]; dsrc = screens[1] + (ds_y+ds_bgofs)*vid.width + ds_x1; @@ -1451,18 +1447,18 @@ void R_DrawTiltedWaterSpan_NPO2_8(void) *dest = *(ds_transmap + (colormap[source[((y * ds_flatwidth) + x)]] << 8) + *dsrc++); } dest++; - iz += ds_szp->x; - uz += ds_sup->x; - vz += ds_svp->x; + iz += ds_sz.x; + uz += ds_su.x; + vz += ds_sv.x; } while (--width >= 0); #else startz = 1.f/iz; startu = uz*startz; startv = vz*startz; - izstep = ds_szp->x * SPANSIZE; - uzstep = ds_sup->x * SPANSIZE; - vzstep = ds_svp->x * SPANSIZE; + izstep = ds_sz.x * SPANSIZE; + uzstep = ds_su.x * SPANSIZE; + vzstep = ds_sv.x * SPANSIZE; //x1 = 0; width++; @@ -1536,9 +1532,9 @@ void R_DrawTiltedWaterSpan_NPO2_8(void) else { double left = width; - iz += ds_szp->x * left; - uz += ds_sup->x * left; - vz += ds_svp->x * left; + iz += ds_sz.x * left; + uz += ds_su.x * left; + vz += ds_sv.x * left; endz = 1.f/iz; endu = uz*endz; diff --git a/src/r_main.c b/src/r_main.c index 0cfccab8c..8c071ebad 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -957,16 +957,6 @@ void R_ExecuteSetViewSize(void) dy = FixedMul(abs(dy), fovtan); yslopetab[i] = FixedDiv(centerx*FRACUNIT, dy); } - - if (ds_su) - Z_Free(ds_su); - if (ds_sv) - Z_Free(ds_sv); - if (ds_sz) - Z_Free(ds_sz); - - ds_su = ds_sv = ds_sz = NULL; - ds_sup = ds_svp = ds_szp = NULL; } memset(scalelight, 0xFF, sizeof(scalelight)); @@ -1012,9 +1002,6 @@ void R_Init(void) R_InitViewBorder(); R_SetViewSize(); // setsizeneeded is set true - //I_OutputMsg("\nR_InitPlanes"); - R_InitPlanes(); - // this is now done by SCR_Recalc() at the first mode set //I_OutputMsg("\nR_InitLightTables"); R_InitLightTables(); diff --git a/src/r_plane.c b/src/r_plane.c index 9c87ecbe4..08e147c89 100644 --- a/src/r_plane.c +++ b/src/r_plane.c @@ -84,16 +84,14 @@ fixed_t yslopetab[MAXVIDHEIGHT*16]; fixed_t *yslope; static fixed_t xoffs, yoffs; -static floatv3_t ds_slope_origin, ds_slope_u, ds_slope_v; +static floatv3_t slope_origin, slope_u, slope_v; +static floatv3_t slope_lightu, slope_lightv; -// -// R_InitPlanes -// Only at game startup. -// -void R_InitPlanes(void) -{ - // FIXME: unused -} +static void CalcSlopePlaneVectors(visplane_t *pl, fixed_t xoff, fixed_t yoff); +static void CalcSlopeLightVectors(pslope_t *slope, fixed_t xpos, fixed_t ypos, fixed_t height, float ang, angle_t plangle); + +static void DoSlopeCrossProducts(void); +static void DoSlopeLightCrossProduct(void); // // Water ripple effect @@ -224,9 +222,9 @@ static void R_MapTiltedPlane(INT32 y, INT32 x1, INT32 x2) { ds_bgofs = R_CalculateRippleOffset(y); - ds_sup = &ds_su[y]; - ds_svp = &ds_sv[y]; - ds_szp = &ds_sz[y]; + R_CalculatePlaneRipple(currentplane->viewangle + currentplane->plangle); + + CalcSlopePlaneVectors(currentplane, (xoffs + planeripple.xfrac), (yoffs + planeripple.yfrac)); ds_bgofs >>= FRACBITS; @@ -672,8 +670,6 @@ static INT64 R_GetSlopeZAt(const pslope_t *slope, fixed_t x, fixed_t y) // Sets the texture origin vector of the sloped plane. static void R_SetSlopePlaneOrigin(pslope_t *slope, fixed_t xpos, fixed_t ypos, fixed_t zpos, fixed_t xoff, fixed_t yoff, fixed_t angle) { - floatv3_t *p = &ds_slope_origin; - INT64 vx = (INT64)xpos + (INT64)xoff; INT64 vy = (INT64)ypos - (INT64)yoff; @@ -681,125 +677,157 @@ static void R_SetSlopePlaneOrigin(pslope_t *slope, fixed_t xpos, fixed_t ypos, f float vyf = vy / (float)FRACUNIT; float ang = ANG2RAD(ANGLE_270 - angle); - // p is the texture origin in view space + // slope_origin is the texture origin in view space // Don't add in the offsets at this stage, because doing so can result in // errors if the flat is rotated. - p->x = vxf * cos(ang) - vyf * sin(ang); - p->z = vxf * sin(ang) + vyf * cos(ang); - p->y = (R_GetSlopeZAt(slope, -xoff, yoff) - zpos) / (float)FRACUNIT; + slope_origin.x = vxf * cos(ang) - vyf * sin(ang); + slope_origin.z = vxf * sin(ang) + vyf * cos(ang); + slope_origin.y = (R_GetSlopeZAt(slope, -xoff, yoff) - zpos) / (float)FRACUNIT; } // This function calculates all of the vectors necessary for drawing a sloped plane. void R_SetSlopePlane(pslope_t *slope, fixed_t xpos, fixed_t ypos, fixed_t zpos, fixed_t xoff, fixed_t yoff, angle_t angle, angle_t plangle) { - // Potentially override other stuff for now cus we're mean. :< But draw a slope plane! // I copied ZDoom's code and adapted it to SRB2... -Red - floatv3_t *m = &ds_slope_v, *n = &ds_slope_u; - fixed_t height, temp; + fixed_t height, z_at_xy; float ang; R_SetSlopePlaneOrigin(slope, xpos, ypos, zpos, xoff, yoff, angle); height = P_GetSlopeZAt(slope, xpos, ypos); zeroheight = FixedToFloat(height - zpos); - // m is the v direction vector in view space ang = ANG2RAD(ANGLE_180 - (angle + plangle)); - m->x = cos(ang); - m->z = sin(ang); - // n is the u direction vector in view space - n->x = sin(ang); - n->z = -cos(ang); + CalcSlopeLightVectors(slope, xpos, ypos, height, ang, plangle); + + if (ds_solidcolor || ds_fog) + { + DoSlopeLightCrossProduct(); + return; + } + + // slope_v is the v direction vector in view space + slope_v.x = cos(ang); + slope_v.z = sin(ang); + + // slope_u is the u direction vector in view space + slope_u.x = sin(ang); + slope_u.z = -cos(ang); plangle >>= ANGLETOFINESHIFT; - temp = P_GetSlopeZAt(slope, xpos + FINESINE(plangle), ypos + FINECOSINE(plangle)); - m->y = FixedToFloat(temp - height); - temp = P_GetSlopeZAt(slope, xpos + FINECOSINE(plangle), ypos - FINESINE(plangle)); - n->y = FixedToFloat(temp - height); + z_at_xy = P_GetSlopeZAt(slope, xpos + FINESINE(plangle), ypos + FINECOSINE(plangle)); + slope_v.y = FixedToFloat(z_at_xy - height); + z_at_xy = P_GetSlopeZAt(slope, xpos + FINECOSINE(plangle), ypos - FINESINE(plangle)); + slope_u.y = FixedToFloat(z_at_xy - height); + + DoSlopeCrossProducts(); + DoSlopeLightCrossProduct(); } // This function calculates all of the vectors necessary for drawing a sloped and scaled plane. void R_SetScaledSlopePlane(pslope_t *slope, fixed_t xpos, fixed_t ypos, fixed_t zpos, fixed_t xs, fixed_t ys, fixed_t xoff, fixed_t yoff, angle_t angle, angle_t plangle) { - floatv3_t *m = &ds_slope_v, *n = &ds_slope_u; - fixed_t height, temp; + fixed_t height, z_at_xy; - float xscale = FixedToFloat(xs); - float yscale = FixedToFloat(ys); float ang; R_SetSlopePlaneOrigin(slope, xpos, ypos, zpos, xoff, yoff, angle); height = P_GetSlopeZAt(slope, xpos, ypos); zeroheight = FixedToFloat(height - zpos); - // m is the v direction vector in view space ang = ANG2RAD(ANGLE_180 - (angle + plangle)); - m->x = yscale * cos(ang); - m->z = yscale * sin(ang); + + CalcSlopeLightVectors(slope, xpos, ypos, height, ang, plangle); + + if (ds_solidcolor || ds_fog) + { + DoSlopeLightCrossProduct(); + return; + } + + float xscale = FixedToFloat(xs); + float yscale = FixedToFloat(ys); + + // m is the v direction vector in view space + slope_v.x = yscale * cos(ang); + slope_v.z = yscale * sin(ang); // n is the u direction vector in view space - n->x = xscale * sin(ang); - n->z = -xscale * cos(ang); + slope_u.x = xscale * sin(ang); + slope_u.z = -xscale * cos(ang); ang = ANG2RAD(plangle); - temp = P_GetSlopeZAt(slope, xpos + FloatToFixed(yscale * sin(ang)), ypos + FloatToFixed(yscale * cos(ang))); - m->y = FixedToFloat(temp - height); - temp = P_GetSlopeZAt(slope, xpos + FloatToFixed(xscale * cos(ang)), ypos - FloatToFixed(xscale * sin(ang))); - n->y = FixedToFloat(temp - height); + z_at_xy = P_GetSlopeZAt(slope, xpos + FloatToFixed(yscale * sin(ang)), ypos + FloatToFixed(yscale * cos(ang))); + slope_v.y = FixedToFloat(z_at_xy - height); + z_at_xy = P_GetSlopeZAt(slope, xpos + FloatToFixed(xscale * cos(ang)), ypos - FloatToFixed(xscale * sin(ang))); + slope_u.y = FixedToFloat(z_at_xy - height); + + DoSlopeCrossProducts(); + DoSlopeLightCrossProduct(); } -void R_CalculateSlopeVectors(void) +static void CalcSlopeLightVectors(pslope_t *slope, fixed_t xpos, fixed_t ypos, fixed_t height, float ang, angle_t plangle) +{ + fixed_t z_at_xy; + + slope_lightv.x = cos(ang); + slope_lightv.z = sin(ang); + + slope_lightu.x = sin(ang); + slope_lightu.z = -cos(ang); + + plangle >>= ANGLETOFINESHIFT; + z_at_xy = P_GetSlopeZAt(slope, xpos + FINESINE(plangle), ypos + FINECOSINE(plangle)); + slope_lightv.y = FixedToFloat(z_at_xy - height); + z_at_xy = P_GetSlopeZAt(slope, xpos + FINECOSINE(plangle), ypos - FINESINE(plangle)); + slope_lightu.y = FixedToFloat(z_at_xy - height); +} + +// Eh. I tried making this stuff fixed-point and it exploded on me. Here's a macro for the only floating-point vector function I recall using. +#define CROSS(d, v1, v2) \ +d.x = (v1.y * v2.z) - (v1.z * v2.y);\ +d.y = (v1.z * v2.x) - (v1.x * v2.z);\ +d.z = (v1.x * v2.y) - (v1.y * v2.x) + +static void DoSlopeCrossProducts(void) { float sfmult = 65536.f; - // Eh. I tried making this stuff fixed-point and it exploded on me. Here's a macro for the only floating-point vector function I recall using. -#define CROSS(d, v1, v2) \ -d->x = (v1.y * v2.z) - (v1.z * v2.y);\ -d->y = (v1.z * v2.x) - (v1.x * v2.z);\ -d->z = (v1.x * v2.y) - (v1.y * v2.x) - CROSS(ds_sup, ds_slope_origin, ds_slope_v); - CROSS(ds_svp, ds_slope_origin, ds_slope_u); - CROSS(ds_szp, ds_slope_v, ds_slope_u); -#undef CROSS + CROSS(ds_su, slope_origin, slope_v); + CROSS(ds_sv, slope_origin, slope_u); + CROSS(ds_sz, slope_v, slope_u); - ds_sup->z *= focallengthf; - ds_svp->z *= focallengthf; - ds_szp->z *= focallengthf; + ds_su.z *= focallengthf; + ds_sv.z *= focallengthf; + ds_sz.z *= focallengthf; if (ds_solidcolor) return; // Premultiply the texture vectors with the scale factors if (ds_powersoftwo) - sfmult *= (1 << nflatshiftup); + sfmult *= 1 << nflatshiftup; - ds_sup->x *= sfmult; - ds_sup->y *= sfmult; - ds_sup->z *= sfmult; - ds_svp->x *= sfmult; - ds_svp->y *= sfmult; - ds_svp->z *= sfmult; + ds_su.x *= sfmult; + ds_su.y *= sfmult; + ds_su.z *= sfmult; + ds_sv.x *= sfmult; + ds_sv.y *= sfmult; + ds_sv.z *= sfmult; } -void R_SetTiltedSpan(INT32 span) +static void DoSlopeLightCrossProduct(void) { - if (ds_su == NULL) - ds_su = Z_Malloc(sizeof(*ds_su) * vid.height, PU_STATIC, NULL); - if (ds_sv == NULL) - ds_sv = Z_Malloc(sizeof(*ds_sv) * vid.height, PU_STATIC, NULL); - if (ds_sz == NULL) - ds_sz = Z_Malloc(sizeof(*ds_sz) * vid.height, PU_STATIC, NULL); + CROSS(ds_slopelight, slope_lightv, slope_lightu); - ds_sup = &ds_su[span]; - ds_svp = &ds_sv[span]; - ds_szp = &ds_sz[span]; + ds_slopelight.z *= focallengthf; } -static void R_SetSlopePlaneVectors(visplane_t *pl, INT32 y, fixed_t xoff, fixed_t yoff) -{ - R_SetTiltedSpan(y); +#undef CROSS - if (pl->xscale != FRACUNIT || pl->yscale != FRACUNIT) +static void CalcSlopePlaneVectors(visplane_t *pl, fixed_t xoff, fixed_t yoff) +{ + if (!ds_fog && (pl->xscale != FRACUNIT || pl->yscale != FRACUNIT)) { R_SetScaledSlopePlane(pl->slope, pl->viewx, pl->viewy, pl->viewz, FixedDiv(FRACUNIT, pl->xscale), FixedDiv(FRACUNIT, pl->yscale), @@ -807,8 +835,6 @@ static void R_SetSlopePlaneVectors(visplane_t *pl, INT32 y, fixed_t xoff, fixed_ } else R_SetSlopePlane(pl->slope, pl->viewx, pl->viewy, pl->viewz, xoff, yoff, pl->viewangle, pl->plangle); - - R_CalculateSlopeVectors(); } static inline void R_AdjustSlopeCoordinates(vector3_t *origin) @@ -845,7 +871,6 @@ void R_DrawSinglePlane(visplane_t *pl) INT32 light = 0; INT32 x, stop; ffloor_t *rover; - boolean fog = false; INT32 spanfunctype = BASEDRAWFUNC; void (*mapfunc)(INT32, INT32, INT32); @@ -859,6 +884,8 @@ void R_DrawSinglePlane(visplane_t *pl) return; } + ds_powersoftwo = ds_solidcolor = ds_fog = false; + planeripple.active = false; if (pl->polyobj) @@ -923,13 +950,13 @@ void R_DrawSinglePlane(visplane_t *pl) } else if (pl->ffloor->fofflags & FOF_FOG) { - fog = true; + ds_fog = true; spanfunctype = SPANDRAWFUNC_FOG; light = (pl->lightlevel >> LIGHTSEGSHIFT); } else light = (pl->lightlevel >> LIGHTSEGSHIFT); - if (pl->ffloor->fofflags & FOF_RIPPLE && !fog) + if (pl->ffloor->fofflags & FOF_RIPPLE && !ds_fog) { planeripple.active = true; @@ -957,9 +984,7 @@ void R_DrawSinglePlane(visplane_t *pl) light = (pl->lightlevel >> LIGHTSEGSHIFT); } - ds_powersoftwo = ds_solidcolor = false; - - if (fog) + if (ds_fog) { // Since all fog planes do is apply a colormap, it's not required // to know any information about their textures. @@ -1025,7 +1050,7 @@ void R_DrawSinglePlane(visplane_t *pl) if (pl->slope) { - if (fog) + if (ds_fog) mapfunc = R_MapTiltedFogPlane; else { @@ -1040,21 +1065,10 @@ void R_DrawSinglePlane(visplane_t *pl) } } - if (planeripple.active) - { + if (!ds_fog && planeripple.active) planeheight = abs(P_GetSlopeZAt(pl->slope, pl->viewx, pl->viewy) - pl->viewz); - - R_PlaneBounds(pl); - - for (x = pl->high; x < pl->low; x++) - { - ds_bgofs = R_CalculateRippleOffset(x); - R_CalculatePlaneRipple(pl->viewangle + pl->plangle); - R_SetSlopePlaneVectors(pl, x, (xoffs + planeripple.xfrac), (yoffs + planeripple.yfrac)); - } - } else - R_SetSlopePlaneVectors(pl, 0, xoffs, yoffs); + CalcSlopePlaneVectors(pl, xoffs, yoffs); switch (spanfunctype) { diff --git a/src/r_plane.h b/src/r_plane.h index 38d49d5db..5ce49e3cc 100644 --- a/src/r_plane.h +++ b/src/r_plane.h @@ -67,7 +67,6 @@ extern fixed_t frontscale[MAXVIDWIDTH], yslopetab[MAXVIDHEIGHT*16]; extern fixed_t *yslope; extern lighttable_t **planezlight; -void R_InitPlanes(void); void R_ClearPlanes(void); void R_ClearFFloorClips (void); @@ -84,10 +83,6 @@ void R_DrawSinglePlane(visplane_t *pl); // Calculates the slope vectors needed for tilted span drawing. void R_SetSlopePlane(pslope_t *slope, fixed_t xpos, fixed_t ypos, fixed_t zpos, fixed_t xoff, fixed_t yoff, angle_t angle, angle_t plangle); void R_SetScaledSlopePlane(pslope_t *slope, fixed_t xpos, fixed_t ypos, fixed_t zpos, fixed_t xs, fixed_t ys, fixed_t xoff, fixed_t yoff, angle_t angle, angle_t plangle); -void R_CalculateSlopeVectors(void); - -// Sets the slope vector pointers for the current tilted span. -void R_SetTiltedSpan(INT32 span); typedef struct planemgr_s { diff --git a/src/r_splats.c b/src/r_splats.c index 9bfaa6b51..e9665e84a 100644 --- a/src/r_splats.c +++ b/src/r_splats.c @@ -369,7 +369,7 @@ static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, visspr ds_flatwidth = pSplat->width; ds_flatheight = pSplat->height; - ds_powersoftwo = ds_solidcolor = false; + ds_powersoftwo = ds_solidcolor = ds_fog = false; if (R_CheckSolidColorFlat()) ds_solidcolor = true; @@ -381,9 +381,7 @@ static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, visspr if (pSplat->slope) { - R_SetTiltedSpan(0); R_SetScaledSlopePlane(pSplat->slope, vis->viewpoint.x, vis->viewpoint.y, vis->viewpoint.z, pSplat->xscale, pSplat->yscale, -pSplat->verts[0].x, pSplat->verts[0].y, vis->viewpoint.angle, pSplat->angle); - R_CalculateSlopeVectors(); } else if (!ds_solidcolor) { From e1e24cf53777bc8921cc1a88ad081f9d55a50f9a Mon Sep 17 00:00:00 2001 From: Hanicef Date: Mon, 25 Dec 2023 14:31:44 +0100 Subject: [PATCH 173/227] Make execinfo.h optional (fixes musl libc build) --- src/Makefile.d/features.mk | 2 +- src/sdl/i_system.c | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Makefile.d/features.mk b/src/Makefile.d/features.mk index 8b713718c..29587302f 100644 --- a/src/Makefile.d/features.mk +++ b/src/Makefile.d/features.mk @@ -5,7 +5,7 @@ passthru_opts+=\ NO_IPV6 NOHW NOMD5 NOPOSTPROCESSING\ MOBJCONSISTANCY PACKETDROP ZDEBUG\ - HAVE_MINIUPNPC\ + HAVE_MINIUPNPC NOEXECINFO\ # build with debugging information ifdef DEBUGMODE diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index 450237149..b1bde5226 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -138,7 +138,9 @@ typedef LPVOID (WINAPI *p_MapViewOfFile) (HANDLE, DWORD, DWORD, DWORD, SIZE_T); #endif #if defined (__unix__) || defined(__APPLE__) || defined (UNIXCOMMON) +#ifndef NOEXECINFO #include +#endif #include #define UNIXBACKTRACE #endif @@ -269,13 +271,17 @@ UINT8 keyboard_started = false; static void write_backtrace(INT32 signal) { int fd = -1; +#ifndef NOEXECINFO size_t size; +#endif time_t rawtime; struct tm timeinfo; ssize_t junk; enum { BT_SIZE = 1024, STR_SIZE = 32 }; +#ifndef NOEXECINFO void *array[BT_SIZE]; +#endif char timestr[STR_SIZE]; const char *error = "An error occurred within SRB2! Send this stack trace to someone who can help!\n"; @@ -308,12 +314,14 @@ static void write_backtrace(INT32 signal) CRASHLOG_WRITE(strsignal(signal)); CRASHLOG_WRITE("\n"); // Newline for the signal name +#ifndef NOEXECINFO CRASHLOG_STDERR_WRITE("\nBacktrace:\n"); // Flood the output and log with the backtrace size = backtrace(array, BT_SIZE); backtrace_symbols_fd(array, size, fd); backtrace_symbols_fd(array, size, STDERR_FILENO); +#endif CRASHLOG_WRITE("\n"); // Write another newline to the log so it looks nice :) (void)junk; From dbce1493eed0cef7280a440376d0b7767c48f3a9 Mon Sep 17 00:00:00 2001 From: Alam Ed Arias Date: Mon, 25 Dec 2023 16:37:06 -0500 Subject: [PATCH 174/227] GitLab CI: Alpine 3 build job with GCC --- .gitlab-ci.yml | 131 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 49dd9bdf5..2b574ef39 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,6 +19,11 @@ default: - apt-cache unprotect: true + - key: apk-$CI_JOB_IMAGE + paths: + - apk-cache + unprotect: true + before_script: - - | # debconf @@ -503,3 +508,129 @@ Debian testing musl: CC: musl-gcc LDD: musl-ldd LDFLAGS: -Wl,-fuse-ld=gold + +Alpine 3 GCC: + stage: build + + when: manual + + image: alpine:3 + + allow_failure: true + + artifacts: + name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-Apline-3-gcc" + + before_script: + - - | + # apk_cache + echo -e "\e[0Ksection_start:`date +%s`:apk_cache[collapsed=true]\r\e[0KUpdating APK listing" + - export APK_CACHE_DIR=`pwd`/apk-cache + - mkdir --parents --verbose $APK_CACHE_DIR/ + - ln -s /etc/apk/cache $APK_CACHE_DIR + - | + # apk_cache + echo -e "\e[0Ksection_end:`date +%s`:apk_cache\r\e[0K" + + - - | + # apk_update + echo -e "\e[0Ksection_start:`date +%s`:apk_update[collapsed=true]\r\e[0KUpdating APK listing" + - apk update + - | + # apk_update + echo -e "\e[0Ksection_end:`date +%s`:apk_update\r\e[0K" + + - - | + # apk_upgrade + echo -e "\e[0Ksection_start:`date +%s`:apk_upgrade[collapsed=true]\r\e[0KUpdating existing packages" + - apk upgrade + - | + # apk_update + echo -e "\e[0Ksection_end:`date +%s`:apk_upgrade\r\e[0K" + + - - | + # apk_common + echo -e "\e[0Ksection_start:`date +%s`:apk_common[collapsed=true]\r\e[0KInstalling common packages" + - apk add make git ccache nasm + - | + # apk_common + echo -e "\e[0Ksection_end:`date +%s`:apk_common\r\e[0K" + + - - | + # ccache_config + echo -e "\e[0Ksection_start:`date +%s`:ccache_config[collapsed=true]\r\e[0KSetting up ccache config" + - mkdir --parents --verbose ~/.ccache/ + - touch ~/.ccache/ccache.conf + - | + # cache.conf + echo Adding ccache configution option + - | + # base_dir + echo base_dir = $PWD | tee --append ~/.ccache/ccache.conf + - | + # cache_dir + echo cache_dir = $PWD/ccache | tee --append ~/.ccache/ccache.conf + - | + # compiler_check + echo compiler_check = content | tee --append ~/.ccache/ccache.conf + - | + # stats_log + echo stats_log = $PWD/ccache_statslog | tee --append ~/.ccache/ccache.conf + - | + # max_size + echo max_size = 50M | tee --append ~/.ccache/ccache.conf + - | + # ccache_config + echo -e "\e[0Ksection_end:`date +%s`:ccache_config\r\e[0K" + + - - | + # cache_reset + echo -e "\e[0Ksection_start:`date +%s`:ccache_reset[collapsed=true]\r\e[0KResetting ccache statistics" + - ccache --zero-stats + - ccache --show-stats + - | + # ccache_reset + echo -e "\e[0Ksection_end:`date +%s`:ccache_reset\r\e[0K" + + script: + - - | + # apk_toolchain + echo -e "\e[0Ksection_start:`date +%s`:apk_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages" + - apk add gcc + - | + # apk_toolchain + echo -e "\e[0Ksection_end:`date +%s`:apk_toolchain\r\e[0K" + + - - | + # apk_development + echo -e "\e[0Ksection_start:`date +%s`:apk_development[collapsed=true]\r\e[0KInstalling development packages" + - apk add musl-dev sdl2_mixer-dev libpng-dev curl-dev libgme-dev libopenmpt-dev + - | + # apk_development + echo -e "\e[0Ksection_end:`date +%s`:apk_development\r\e[0K" + + - - | + # make + echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" + - make --directory=src --keep-going CCACHE=1 ERRORMODE=1 NONX86=1 || make --directory=src --keep-going CCACHE=1 ERRORMODE=1 NONX86=1 + - | + # make + echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" + + after_script: + - - | + # apk_clean + echo -e "\e[0Ksection_start:`date +%s`:apk_clean[collapsed=true]\r\e[0KCleaning of unneeded APK packages" + - apk cache clean + - | + # apk_clean + echo -e "\e[0Ksection_end:`date +%s`:apk_clean\r\e[0K" + + - - | + # ccache_stats + echo -e "\e[0Ksection_start:`date +%s`:ccache_stats[collapsed=true]\r\e[0Kccache statistics:" + - ccache --show-stats --verbose + - ccache --show-log-stats --verbose + - | + # ccahe_stats + echo -e "\e[0Ksection_end:`date +%s`:ccache_stats\r\e[0K" From 13cfc5ef597f994b9100a21a1f0b2c6acca8fe75 Mon Sep 17 00:00:00 2001 From: Hanicef Date: Mon, 25 Dec 2023 14:31:44 +0100 Subject: [PATCH 175/227] Make execinfo.h optional (fixes musl libc build) --- src/Makefile.d/features.mk | 2 +- src/sdl/i_system.c | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Makefile.d/features.mk b/src/Makefile.d/features.mk index a66779c07..20963adac 100644 --- a/src/Makefile.d/features.mk +++ b/src/Makefile.d/features.mk @@ -5,7 +5,7 @@ passthru_opts+=\ NONET NO_IPV6 NOHW NOMD5 NOPOSTPROCESSING\ MOBJCONSISTANCY PACKETDROP ZDEBUG\ - HAVE_MINIUPNPC\ + HAVE_MINIUPNPC NOEXECINFO\ # build with debugging information ifdef DEBUGMODE diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index 2a26f3f50..dd0833f20 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -138,7 +138,9 @@ typedef LPVOID (WINAPI *p_MapViewOfFile) (HANDLE, DWORD, DWORD, DWORD, SIZE_T); #endif #if defined (__unix__) || defined(__APPLE__) || defined (UNIXCOMMON) +#ifndef NOEXECINFO #include +#endif #include #define UNIXBACKTRACE #endif @@ -268,13 +270,17 @@ UINT8 keyboard_started = false; static void write_backtrace(INT32 signal) { int fd = -1; +#ifndef NOEXECINFO size_t size; +#endif time_t rawtime; struct tm timeinfo; ssize_t junk; enum { BT_SIZE = 1024, STR_SIZE = 32 }; +#ifndef NOEXECINFO void *array[BT_SIZE]; +#endif char timestr[STR_SIZE]; const char *error = "An error occurred within SRB2! Send this stack trace to someone who can help!\n"; @@ -307,12 +313,14 @@ static void write_backtrace(INT32 signal) CRASHLOG_WRITE(strsignal(signal)); CRASHLOG_WRITE("\n"); // Newline for the signal name +#ifndef NOEXECINFO CRASHLOG_STDERR_WRITE("\nBacktrace:\n"); // Flood the output and log with the backtrace size = backtrace(array, BT_SIZE); backtrace_symbols_fd(array, size, fd); backtrace_symbols_fd(array, size, STDERR_FILENO); +#endif CRASHLOG_WRITE("\n"); // Write another newline to the log so it looks nice :) (void)junk; From 90f2170451a5a4b3c36b380020dcfb9c6bb30c0a Mon Sep 17 00:00:00 2001 From: Alam Ed Arias Date: Mon, 25 Dec 2023 16:51:59 -0500 Subject: [PATCH 176/227] GitLab CI: work with busybox's tee --- .gitlab-ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2b574ef39..25ad113a7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -566,19 +566,19 @@ Alpine 3 GCC: echo Adding ccache configution option - | # base_dir - echo base_dir = $PWD | tee --append ~/.ccache/ccache.conf + echo base_dir = $PWD | tee -a ~/.ccache/ccache.conf - | # cache_dir - echo cache_dir = $PWD/ccache | tee --append ~/.ccache/ccache.conf + echo cache_dir = $PWD/ccache | tee -a ~/.ccache/ccache.conf - | # compiler_check - echo compiler_check = content | tee --append ~/.ccache/ccache.conf + echo compiler_check = content | tee -a ~/.ccache/ccache.conf - | # stats_log - echo stats_log = $PWD/ccache_statslog | tee --append ~/.ccache/ccache.conf + echo stats_log = $PWD/ccache_statslog | tee -a ~/.ccache/ccache.conf - | # max_size - echo max_size = 50M | tee --append ~/.ccache/ccache.conf + echo max_size = 50M | tee -a ~/.ccache/ccache.conf - | # ccache_config echo -e "\e[0Ksection_end:`date +%s`:ccache_config\r\e[0K" From d678237a5c48d92c25a95235b5f6f6cd1f0b5171 Mon Sep 17 00:00:00 2001 From: Logan Aerl Arias Date: Mon, 25 Dec 2023 21:59:28 +0000 Subject: [PATCH 177/227] Update .gitlab-ci.yml file add NOEXECINFO=1 to make --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 25ad113a7..16179b6ba 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -612,7 +612,7 @@ Alpine 3 GCC: - - | # make echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" - - make --directory=src --keep-going CCACHE=1 ERRORMODE=1 NONX86=1 || make --directory=src --keep-going CCACHE=1 ERRORMODE=1 NONX86=1 + - make --directory=src --keep-going CCACHE=1 ERRORMODE=1 NONX86=1 NOEXECINFO=1 || make --directory=src --keep-going CCACHE=1 ERRORMODE=1 NONX86=1 NOEXECINFO=1 - | # make echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" From e1f2cbd8ad178496ec3a012366b3c432f4bd71f0 Mon Sep 17 00:00:00 2001 From: Alam Ed Arias Date: Mon, 25 Dec 2023 17:21:34 -0500 Subject: [PATCH 178/227] GitLab-CI: fixup artifact exposes --- .gitlab-ci.yml | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 16179b6ba..f288b36c9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -172,6 +172,10 @@ Debian testing GCC: allow_failure: true artifacts: + paths: + - "bin/" + - "src/comptime.h" + expose_as: "testing-gcc" name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-testing-gcc" variables: @@ -406,6 +410,10 @@ Debian stable Clang: allow_failure: true artifacts: + paths: + - "bin/" + - "src/comptime.h" + expose_as: "clang" name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-clang" variables: @@ -447,6 +455,10 @@ Debian stable musl: allow_failure: true artifacts: + paths: + - "bin/" + - "src/comptime.h" + expose_as: "musl" name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-musl" variables: @@ -486,6 +498,10 @@ Debian testing Clang: image: debian:testing-slim artifacts: + paths: + - "bin/" + - "src/comptime.h" + expose_as: "testing-clang" name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-testing-clang" variables: @@ -502,6 +518,10 @@ Debian testing musl: image: debian:testing-slim artifacts: + paths: + - "bin/" + - "src/comptime.h" + expose_as: "testing-musl" name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-testing-musl" variables: @@ -519,7 +539,11 @@ Alpine 3 GCC: allow_failure: true artifacts: - name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-Apline-3-gcc" + paths: + - "bin/" + - "src/comptime.h" + expose_as: "Apline-3" + name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-Apline-3" before_script: - - | From 78af4afc0631fb8a14ebbc2095f7b6d0464d580c Mon Sep 17 00:00:00 2001 From: Alam Ed Arias Date: Mon, 25 Dec 2023 17:27:50 -0500 Subject: [PATCH 179/227] GitLab-CI: builders may reuse jobs, force remake links --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f288b36c9..8bfe4fb36 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -551,7 +551,7 @@ Alpine 3 GCC: echo -e "\e[0Ksection_start:`date +%s`:apk_cache[collapsed=true]\r\e[0KUpdating APK listing" - export APK_CACHE_DIR=`pwd`/apk-cache - mkdir --parents --verbose $APK_CACHE_DIR/ - - ln -s /etc/apk/cache $APK_CACHE_DIR + - ln -sf /etc/apk/cache $APK_CACHE_DIR - | # apk_cache echo -e "\e[0Ksection_end:`date +%s`:apk_cache\r\e[0K" From 90edfa562df3c7bafd42e29bb87e320ebc67b35d Mon Sep 17 00:00:00 2001 From: Alam Ed Arias Date: Mon, 25 Dec 2023 17:37:29 -0500 Subject: [PATCH 180/227] GitLib-CI: let use this build job to keep us from breaking non-glibc systems --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8bfe4fb36..46fa6584d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -532,7 +532,7 @@ Debian testing musl: Alpine 3 GCC: stage: build - when: manual + when: on_success image: alpine:3 From 8374a1628c26ba648a94d3b181091e69a29312b4 Mon Sep 17 00:00:00 2001 From: Logan-A Date: Mon, 25 Dec 2023 19:52:32 -0500 Subject: [PATCH 181/227] added NOEXECINFO to cMake added NOEXECINFO to cMake --- CMakeLists.txt | 1 + src/CMakeLists.txt | 5 +++++ src/Makefile | 1 + 3 files changed, 7 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 80a3bdcd6..8803620e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,7 @@ option(SRB2_CONFIG_ERRORMODE "Compile C code with warnings treated as errors." O option(SRB2_CONFIG_DEBUGMODE "Compile with PARANOIA, ZDEBUG, RANGECHECK and PACKETDROP defined." OFF) option(SRB2_CONFIG_MOBJCONSISTANCY "Compile with MOBJCONSISTANCY defined." OFF) option(SRB2_CONFIG_PACKETDROP "Compile with PACKETDROP defined." OFF) +option(SRB2_CONFIG_EXECINFO "Enable stack trace dump support." ON) option(SRB2_CONFIG_ZDEBUG "Compile with ZDEBUG defined." OFF) # SRB2_CONFIG_PROFILEMODE is probably superceded by some CMake setting. option(SRB2_CONFIG_PROFILEMODE "Compile for profiling (GCC only)." OFF) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b926b3b7a..7916b26c6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -380,6 +380,11 @@ endif() if(SRB2_CONFIG_PACKETDROP) target_compile_definitions(SRB2SDL2 PRIVATE -DPACKETDROP) endif() +if(SRB2_CONFIG_EXECINFO) +else() + target_compile_definitions(SRB2SDL2 PRIVATE -DNOEXECINFO) + message(STATUS "You have disabled stack trace dump support") +endif() if(SRB2_CONFIG_ZDEBUG) target_compile_definitions(SRB2SDL2 PRIVATE -DZDEBUG) endif() diff --git a/src/Makefile b/src/Makefile index 539c2fa74..9fc87132e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -67,6 +67,7 @@ # NOPOSTPROCESSING=1 - ? # MOBJCONSISTANCY=1 - ?? # PACKETDROP=1 - ?? +# NOEXECINFO=1 - Disable stack trace dump support # DEBUGMODE=1 - Enable various debugging capabilities. # Also disables optimizations. # NOZLIB=1 - Disable some compression capability. Implies From 6c701a93120d4f48ab170df1e4a0f558dd686aee Mon Sep 17 00:00:00 2001 From: Logan Aerl Arias Date: Tue, 26 Dec 2023 19:21:09 +0000 Subject: [PATCH 182/227] Revert "Merge branch 'native-keyboard-layout-support' into 'next'" This reverts merge request !1952 --- src/console.c | 19 +++++++----- src/d_event.h | 1 - src/d_main.c | 6 ++-- src/hu_stuff.c | 60 ++++++++++++++++-------------------- src/m_menu.c | 76 ++++++++++++++++++++-------------------------- src/sdl/i_system.c | 1 - src/sdl/i_video.c | 16 ---------- 7 files changed, 74 insertions(+), 105 deletions(-) diff --git a/src/console.c b/src/console.c index cdb1fc196..01b90ebc3 100644 --- a/src/console.c +++ b/src/console.c @@ -937,7 +937,7 @@ boolean CON_Responder(event_t *ev) return false; // let go keyup events, don't eat them - if (ev->type != ev_keydown && ev->type != ev_text && ev->type != ev_console) + if (ev->type != ev_keydown && ev->type != ev_console) { if (ev->key == gamecontrol[GC_CONSOLE][0] || ev->key == gamecontrol[GC_CONSOLE][1]) consdown = false; @@ -964,7 +964,7 @@ boolean CON_Responder(event_t *ev) // check other keys only if console prompt is active if (!consoleready && key < NUMINPUTS) // metzgermeister: boundary check!! { - if (ev->type == ev_keydown && !menuactive && bindtable[key]) + if (! menuactive && bindtable[key]) { COM_BufAddText(bindtable[key]); COM_BufAddText("\n"); @@ -981,12 +981,6 @@ boolean CON_Responder(event_t *ev) } } - if (ev->type == ev_text) - { - CON_InputAddChar(key); - return true; - } - // Always eat ctrl/shift/alt if console open, so the menu doesn't get ideas if (key == KEY_LSHIFT || key == KEY_RSHIFT || key == KEY_LCTRL || key == KEY_RCTRL @@ -1301,12 +1295,21 @@ boolean CON_Responder(event_t *ev) else if (key == KEY_KPADSLASH) key = '/'; + if (key >= 'a' && key <= 'z') + { + if (capslock ^ shiftdown) + key = shiftxform[key]; + } + else if (shiftdown) + key = shiftxform[key]; + // enter a char into the command prompt if (key < 32 || key > 127) return true; if (input_sel != input_cur) CON_InputDelSelection(); + CON_InputAddChar(key); return true; } diff --git a/src/d_event.h b/src/d_event.h index 7743d8609..5aa435060 100644 --- a/src/d_event.h +++ b/src/d_event.h @@ -22,7 +22,6 @@ typedef enum { ev_keydown, ev_keyup, - ev_text, ev_console, ev_mouse, ev_joystick, diff --git a/src/d_main.c b/src/d_main.c index bc821cf71..274e4ceb3 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -192,19 +192,19 @@ void D_ProcessEvents(void) ev = &events[eventtail]; // Set mouse buttons early in case event is eaten later - if (ev->type == ev_keydown || ev->type == ev_keyup || ev->type == ev_text) + if (ev->type == ev_keydown || ev->type == ev_keyup) { // Mouse buttons if ((UINT32)(ev->key - KEY_MOUSE1) < MOUSEBUTTONS) { - if (ev->type == ev_keydown || ev->type == ev_text) + if (ev->type == ev_keydown) mouse.buttons |= 1 << (ev->key - KEY_MOUSE1); else mouse.buttons &= ~(1 << (ev->key - KEY_MOUSE1)); } else if ((UINT32)(ev->key - KEY_2MOUSE1) < MOUSEBUTTONS) { - if (ev->type == ev_keydown || ev->type == ev_text) + if (ev->type == ev_keydown) mouse2.buttons |= 1 << (ev->key - KEY_2MOUSE1); else mouse2.buttons &= ~(1 << (ev->key - KEY_2MOUSE1)); diff --git a/src/hu_stuff.c b/src/hu_stuff.c index 0a2b71aec..6178298d2 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -79,7 +79,6 @@ patch_t *nto_font[NT_FONTSIZE]; static player_t *plr; boolean chat_on; // entering a chat message? -boolean chat_on_first_event; // blocker for first chat input event static char w_chat[HU_MAXMSGLEN + 1]; static size_t c_input = 0; // let's try to make the chat input less shitty. static boolean headsupactive = false; @@ -1040,7 +1039,7 @@ boolean HU_Responder(event_t *ev) { INT32 c=0; - if (ev->type != ev_keydown && ev->type != ev_text) + if (ev->type != ev_keydown) return false; // only KeyDown events now... @@ -1069,15 +1068,11 @@ boolean HU_Responder(event_t *ev) if (!chat_on) { - if (ev->type == ev_text) - return false; - // enter chat mode if ((ev->key == gamecontrol[GC_TALKKEY][0] || ev->key == gamecontrol[GC_TALKKEY][1]) && netgame && !OLD_MUTE) // check for old chat mute, still let the players open the chat incase they want to scroll otherwise. { chat_on = true; - chat_on_first_event = false; w_chat[0] = 0; teamtalk = false; chat_scrollmedown = true; @@ -1088,7 +1083,6 @@ boolean HU_Responder(event_t *ev) && netgame && !OLD_MUTE) { chat_on = true; - chat_on_first_event = false; w_chat[0] = 0; teamtalk = G_GametypeHasTeams(); // Don't teamtalk if we don't have teams. chat_scrollmedown = true; @@ -1098,31 +1092,6 @@ boolean HU_Responder(event_t *ev) } else // if chat_on { - if (!chat_on_first_event) - { - // since the text event is sent immediately after the keydown event, - // we need to make sure that nothing is displayed once the chat - // opens, otherwise a 't' would be outputted. - chat_on_first_event = true; - return true; - } - - if (ev->type == ev_text) - { - if ((c < HU_FONTSTART || c > HU_FONTEND || !hu_font[c-HU_FONTSTART]) - && c != ' ') // Allow spaces, of course - { - return false; - } - - if (CHAT_MUTE || strlen(w_chat) >= HU_MAXMSGLEN) - return true; - - memmove(&w_chat[c_input + 1], &w_chat[c_input], strlen(w_chat) - c_input + 1); - w_chat[c_input] = c; - c_input++; - return true; - } // Ignore modifier keys // Note that we do this here so users can still set @@ -1132,8 +1101,23 @@ boolean HU_Responder(event_t *ev) || ev->key == KEY_LALT || ev->key == KEY_RALT) return true; + c = (INT32)ev->key; + + // I know this looks very messy but this works. If it ain't broke, don't fix it! + // shift LETTERS to uppercase if we have capslock or are holding shift + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) + { + if (shiftdown ^ capslock) + c = shiftxform[c]; + } + else // if we're holding shift we should still shift non letter symbols + { + if (shiftdown) + c = shiftxform[c]; + } + // pasting. pasting is cool. chat is a bit limited, though :( - if (c == 'v' && ctrldown) + if ((c == 'v' || c == 'V') && ctrldown) { const char *paste; size_t chatlen; @@ -1201,6 +1185,16 @@ boolean HU_Responder(event_t *ev) else c_input++; } + else if ((c >= HU_FONTSTART && c <= HU_FONTEND && hu_font[c-HU_FONTSTART]) + || c == ' ') // Allow spaces, of course + { + if (CHAT_MUTE || strlen(w_chat) >= HU_MAXMSGLEN) + return true; + + memmove(&w_chat[c_input + 1], &w_chat[c_input], strlen(w_chat) - c_input + 1); + w_chat[c_input] = c; + c_input++; + } else if (c == KEY_BACKSPACE) { if (CHAT_MUTE || c_input <= 0) diff --git a/src/m_menu.c b/src/m_menu.c index 9aae59445..72d4547b0 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -3177,42 +3177,40 @@ boolean M_Responder(event_t *ev) } else if (menuactive) { - if (ev->type == ev_keydown || ev->type == ev_text) + if (ev->type == ev_keydown) { + keydown++; ch = ev->key; - if (ev->type == ev_keydown) + + // added 5-2-98 remap virtual keys (mouse & joystick buttons) + switch (ch) { - keydown++; - // added 5-2-98 remap virtual keys (mouse & joystick buttons) - switch (ch) - { - case KEY_MOUSE1: - case KEY_JOY1: - ch = KEY_ENTER; - break; - case KEY_JOY1 + 3: - ch = 'n'; - break; - case KEY_MOUSE1 + 1: - case KEY_JOY1 + 1: - ch = KEY_ESCAPE; - break; - case KEY_JOY1 + 2: - ch = KEY_BACKSPACE; - break; - case KEY_HAT1: - ch = KEY_UPARROW; - break; - case KEY_HAT1 + 1: - ch = KEY_DOWNARROW; - break; - case KEY_HAT1 + 2: - ch = KEY_LEFTARROW; - break; - case KEY_HAT1 + 3: - ch = KEY_RIGHTARROW; - break; - } + case KEY_MOUSE1: + case KEY_JOY1: + ch = KEY_ENTER; + break; + case KEY_JOY1 + 3: + ch = 'n'; + break; + case KEY_MOUSE1 + 1: + case KEY_JOY1 + 1: + ch = KEY_ESCAPE; + break; + case KEY_JOY1 + 2: + ch = KEY_BACKSPACE; + break; + case KEY_HAT1: + ch = KEY_UPARROW; + break; + case KEY_HAT1 + 1: + ch = KEY_DOWNARROW; + break; + case KEY_HAT1 + 2: + ch = KEY_LEFTARROW; + break; + case KEY_HAT1 + 3: + ch = KEY_RIGHTARROW; + break; } } else if (ev->type == ev_joystick && ev->key == 0 && joywait < I_GetTime()) @@ -3374,11 +3372,8 @@ boolean M_Responder(event_t *ev) // Handle menuitems which need a specific key handling if (routine && (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_KEYHANDLER) { - // ignore ev_keydown events if the key maps to a character, since - // the ev_text event will follow immediately after in that case. - if (ev->type == ev_keydown && ch >= 32 && ch <= 127) - return true; - + if (shiftdown && ch >= 32 && ch <= 127) + ch = shiftxform[ch]; routine(ch); return true; } @@ -3420,11 +3415,6 @@ boolean M_Responder(event_t *ev) { if ((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_STRING) { - // ignore ev_keydown events if the key maps to a character, since - // the ev_text event will follow immediately after in that case. - if (ev->type == ev_keydown && ch >= 32 && ch <= 127) - return false; - if (M_ChangeStringCvar(ch)) return true; else diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index 847806270..986647e72 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -652,7 +652,6 @@ void I_GetConsoleEvents(void) else if (tty_con.cursor < sizeof (tty_con.buffer)) { // push regular character - ev.type = ev_text; ev.key = tty_con.buffer[tty_con.cursor] = key; tty_con.cursor++; // print the current line (this is differential) diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c index d3a602c05..a70e5a860 100644 --- a/src/sdl/i_video.c +++ b/src/sdl/i_video.c @@ -693,19 +693,6 @@ static void Impl_HandleKeyboardEvent(SDL_KeyboardEvent evt, Uint32 type) if (event.key) D_PostEvent(&event); } -static void Impl_HandleTextEvent(SDL_TextInputEvent evt) -{ - event_t event; - event.type = ev_text; - if (evt.text[1] != '\0') - { - // limit ourselves to ASCII for now, we can add UTF-8 support later - return; - } - event.key = evt.text[0]; - D_PostEvent(&event); -} - static void Impl_HandleMouseMotionEvent(SDL_MouseMotionEvent evt) { static boolean firstmove = true; @@ -954,9 +941,6 @@ void I_GetEvent(void) case SDL_KEYDOWN: Impl_HandleKeyboardEvent(evt.key, evt.type); break; - case SDL_TEXTINPUT: - Impl_HandleTextEvent(evt.text); - break; case SDL_MOUSEMOTION: //if (!mouseMotionOnce) Impl_HandleMouseMotionEvent(evt.motion); From 3ae6a99ac70753c3bb8943b726e4f4c2a94bf61e Mon Sep 17 00:00:00 2001 From: Logan Aerl Arias Date: Tue, 26 Dec 2023 19:32:40 +0000 Subject: [PATCH 183/227] Revert "Merge branch 'allow-multiple-admin-passwords' into 'next'" This reverts merge request !2201 --- src/netcode/d_clisrv.c | 27 +++++++++++---------------- src/netcode/d_clisrv.h | 4 ++-- src/netcode/d_netcmd.c | 35 ++++------------------------------- src/netcode/d_netcmd.h | 1 - 4 files changed, 17 insertions(+), 50 deletions(-) diff --git a/src/netcode/d_clisrv.c b/src/netcode/d_clisrv.c index d222920c3..ac0b42d49 100644 --- a/src/netcode/d_clisrv.c +++ b/src/netcode/d_clisrv.c @@ -90,8 +90,8 @@ INT16 consistancy[BACKUPTICS]; // true when a player is connecting or disconnecting so that the gameplay has stopped in its tracks boolean hu_stopped = false; -UINT8 (*adminpassmd5)[16]; -UINT32 adminpasscount = 0; +UINT8 adminpassmd5[16]; +boolean adminpasswordset = false; tic_t neededtic; SINT8 servernode = 0; // the number of the server node @@ -862,31 +862,26 @@ static void PT_Login(SINT8 node, INT32 netconsole) #ifndef NOMD5 UINT8 finalmd5[16];/* Well, it's the cool thing to do? */ - UINT32 i; if (doomcom->datalength < 16)/* ignore partial sends */ return; - if (adminpasscount == 0) + if (!adminpasswordset) { CONS_Printf(M_GetText("Password from %s failed (no password set).\n"), player_names[netconsole]); return; } - for (i = 0; i < adminpasscount; i++) + // Do the final pass to compare with the sent md5 + D_MD5PasswordPass(adminpassmd5, 16, va("PNUM%02d", netconsole), &finalmd5); + + if (!memcmp(netbuffer->u.md5sum, finalmd5, 16)) { - // Do the final pass to compare with the sent md5 - D_MD5PasswordPass(adminpassmd5[i], 16, va("PNUM%02d", netconsole), &finalmd5); - - if (!memcmp(netbuffer->u.md5sum, finalmd5, 16)) - { - CONS_Printf(M_GetText("%s passed authentication.\n"), player_names[netconsole]); - COM_BufInsertText(va("promote %d\n", netconsole)); // do this immediately - return; - } + CONS_Printf(M_GetText("%s passed authentication.\n"), player_names[netconsole]); + COM_BufInsertText(va("promote %d\n", netconsole)); // do this immediately } - - CONS_Printf(M_GetText("Password from %s failed.\n"), player_names[netconsole]); + else + CONS_Printf(M_GetText("Password from %s failed.\n"), player_names[netconsole]); #else (void)netconsole; #endif diff --git a/src/netcode/d_clisrv.h b/src/netcode/d_clisrv.h index f9cbf8876..092878421 100644 --- a/src/netcode/d_clisrv.h +++ b/src/netcode/d_clisrv.h @@ -128,8 +128,8 @@ tic_t GetLag(INT32 node); void D_MD5PasswordPass(const UINT8 *buffer, size_t len, const char *salt, void *dest); -extern UINT8 (*adminpassmd5)[16]; -extern UINT32 adminpasscount; +extern UINT8 adminpassmd5[16]; +extern boolean adminpasswordset; extern boolean hu_stopped; diff --git a/src/netcode/d_netcmd.c b/src/netcode/d_netcmd.c index ef1ef9aeb..2b445a835 100644 --- a/src/netcode/d_netcmd.c +++ b/src/netcode/d_netcmd.c @@ -150,7 +150,6 @@ static void Command_Clearscores_f(void); // Remote Administration static void Command_Changepassword_f(void); -static void Command_Clearpassword_f(void); static void Command_Login_f(void); static void Got_Verification(UINT8 **cp, INT32 playernum); static void Got_Removal(UINT8 **cp, INT32 playernum); @@ -469,7 +468,6 @@ void D_RegisterServerCommands(void) // Remote Administration COM_AddCommand("password", Command_Changepassword_f, COM_LUA); - COM_AddCommand("clearpassword", Command_Clearpassword_f, COM_LUA); COM_AddCommand("login", Command_Login_f, COM_LUA); // useful in dedicated to kick off remote admin COM_AddCommand("promote", Command_Verify_f, COM_LUA); RegisterNetXCmd(XD_VERIFIED, Got_Verification); @@ -2846,15 +2844,8 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum) void D_SetPassword(const char *pw) { - adminpassmd5 = Z_Realloc(adminpassmd5, sizeof(*adminpassmd5) * ++adminpasscount, PU_STATIC, NULL); - D_MD5PasswordPass((const UINT8 *)pw, strlen(pw), BASESALT, &adminpassmd5[adminpasscount-1]); -} - -void D_ClearPassword(void) -{ - Z_Free(adminpassmd5); - adminpassmd5 = NULL; - adminpasscount = 0; + D_MD5PasswordPass((const UINT8 *)pw, strlen(pw), BASESALT, &adminpassmd5); + adminpasswordset = true; } // Remote Administration @@ -2872,30 +2863,12 @@ static void Command_Changepassword_f(void) if (COM_Argc() != 2) { - CONS_Printf(M_GetText("password : add remote admin password\n")); + CONS_Printf(M_GetText("password : change remote admin password\n")); return; } D_SetPassword(COM_Argv(1)); - CONS_Printf(M_GetText("Password added.\n")); -#endif -} - -// Remote Administration -static void Command_Clearpassword_f(void) -{ -#ifdef NOMD5 - // If we have no MD5 support then completely disable XD_LOGIN responses for security. - CONS_Alert(CONS_NOTICE, "Remote administration commands are not supported in this build.\n"); -#else - if (client) // cannot change remotely - { - CONS_Printf(M_GetText("Only the server can use this.\n")); - return; - } - - D_ClearPassword(); - CONS_Printf(M_GetText("Passwords cleared.\n")); + CONS_Printf(M_GetText("Password set.\n")); #endif } diff --git a/src/netcode/d_netcmd.h b/src/netcode/d_netcmd.h index 0f2a1f92b..4849079d0 100644 --- a/src/netcode/d_netcmd.h +++ b/src/netcode/d_netcmd.h @@ -209,7 +209,6 @@ void ClearAdminPlayers(void); void RemoveAdminPlayer(INT32 playernum); void ItemFinder_OnChange(void); void D_SetPassword(const char *pw); -void D_ClearPassword(void); // used for the player setup menu UINT8 CanChangeSkin(INT32 playernum); From 80af2a27d4f14d9b0ab6a1f7655f9c3e94be0332 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Tue, 26 Dec 2023 17:14:30 -0300 Subject: [PATCH 184/227] Fix -Warray-bounds warning in lua_hooklib.c --- src/lua_hooklib.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c index 0fc25ee6c..38815a06c 100644 --- a/src/lua_hooklib.c +++ b/src/lua_hooklib.c @@ -76,12 +76,12 @@ static boolean mobj_hook_available(int hook_type, mobjtype_t mobj_type) ); } -static int hook_in_list +static unsigned hook_in_list ( const char * const name, const char * const * const list ){ - int type; + unsigned type; for (type = 0; list[type] != NULL; ++type) { @@ -200,7 +200,7 @@ static void add_hook_ref(lua_State *L, int idx) static int lib_addHook(lua_State *L) { const char * name; - int type; + unsigned type; if (!lua_lumploading) return luaL_error(L, "This function cannot be called from within a hook or coroutine!"); From fa0c5cfd04e62038684febd1cf42fd70232b228b Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Tue, 26 Dec 2023 17:20:57 -0300 Subject: [PATCH 185/227] Revert 3ae6a99ac70753c3bb8943b726e4f4c2a94bf61e --- src/netcode/d_clisrv.c | 27 ++++++++++++++++----------- src/netcode/d_clisrv.h | 4 ++-- src/netcode/d_netcmd.c | 35 +++++++++++++++++++++++++++++++---- src/netcode/d_netcmd.h | 1 + 4 files changed, 50 insertions(+), 17 deletions(-) diff --git a/src/netcode/d_clisrv.c b/src/netcode/d_clisrv.c index ac0b42d49..d222920c3 100644 --- a/src/netcode/d_clisrv.c +++ b/src/netcode/d_clisrv.c @@ -90,8 +90,8 @@ INT16 consistancy[BACKUPTICS]; // true when a player is connecting or disconnecting so that the gameplay has stopped in its tracks boolean hu_stopped = false; -UINT8 adminpassmd5[16]; -boolean adminpasswordset = false; +UINT8 (*adminpassmd5)[16]; +UINT32 adminpasscount = 0; tic_t neededtic; SINT8 servernode = 0; // the number of the server node @@ -862,26 +862,31 @@ static void PT_Login(SINT8 node, INT32 netconsole) #ifndef NOMD5 UINT8 finalmd5[16];/* Well, it's the cool thing to do? */ + UINT32 i; if (doomcom->datalength < 16)/* ignore partial sends */ return; - if (!adminpasswordset) + if (adminpasscount == 0) { CONS_Printf(M_GetText("Password from %s failed (no password set).\n"), player_names[netconsole]); return; } - // Do the final pass to compare with the sent md5 - D_MD5PasswordPass(adminpassmd5, 16, va("PNUM%02d", netconsole), &finalmd5); - - if (!memcmp(netbuffer->u.md5sum, finalmd5, 16)) + for (i = 0; i < adminpasscount; i++) { - CONS_Printf(M_GetText("%s passed authentication.\n"), player_names[netconsole]); - COM_BufInsertText(va("promote %d\n", netconsole)); // do this immediately + // Do the final pass to compare with the sent md5 + D_MD5PasswordPass(adminpassmd5[i], 16, va("PNUM%02d", netconsole), &finalmd5); + + if (!memcmp(netbuffer->u.md5sum, finalmd5, 16)) + { + CONS_Printf(M_GetText("%s passed authentication.\n"), player_names[netconsole]); + COM_BufInsertText(va("promote %d\n", netconsole)); // do this immediately + return; + } } - else - CONS_Printf(M_GetText("Password from %s failed.\n"), player_names[netconsole]); + + CONS_Printf(M_GetText("Password from %s failed.\n"), player_names[netconsole]); #else (void)netconsole; #endif diff --git a/src/netcode/d_clisrv.h b/src/netcode/d_clisrv.h index 092878421..f9cbf8876 100644 --- a/src/netcode/d_clisrv.h +++ b/src/netcode/d_clisrv.h @@ -128,8 +128,8 @@ tic_t GetLag(INT32 node); void D_MD5PasswordPass(const UINT8 *buffer, size_t len, const char *salt, void *dest); -extern UINT8 adminpassmd5[16]; -extern boolean adminpasswordset; +extern UINT8 (*adminpassmd5)[16]; +extern UINT32 adminpasscount; extern boolean hu_stopped; diff --git a/src/netcode/d_netcmd.c b/src/netcode/d_netcmd.c index 2b445a835..ef1ef9aeb 100644 --- a/src/netcode/d_netcmd.c +++ b/src/netcode/d_netcmd.c @@ -150,6 +150,7 @@ static void Command_Clearscores_f(void); // Remote Administration static void Command_Changepassword_f(void); +static void Command_Clearpassword_f(void); static void Command_Login_f(void); static void Got_Verification(UINT8 **cp, INT32 playernum); static void Got_Removal(UINT8 **cp, INT32 playernum); @@ -468,6 +469,7 @@ void D_RegisterServerCommands(void) // Remote Administration COM_AddCommand("password", Command_Changepassword_f, COM_LUA); + COM_AddCommand("clearpassword", Command_Clearpassword_f, COM_LUA); COM_AddCommand("login", Command_Login_f, COM_LUA); // useful in dedicated to kick off remote admin COM_AddCommand("promote", Command_Verify_f, COM_LUA); RegisterNetXCmd(XD_VERIFIED, Got_Verification); @@ -2844,8 +2846,15 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum) void D_SetPassword(const char *pw) { - D_MD5PasswordPass((const UINT8 *)pw, strlen(pw), BASESALT, &adminpassmd5); - adminpasswordset = true; + adminpassmd5 = Z_Realloc(adminpassmd5, sizeof(*adminpassmd5) * ++adminpasscount, PU_STATIC, NULL); + D_MD5PasswordPass((const UINT8 *)pw, strlen(pw), BASESALT, &adminpassmd5[adminpasscount-1]); +} + +void D_ClearPassword(void) +{ + Z_Free(adminpassmd5); + adminpassmd5 = NULL; + adminpasscount = 0; } // Remote Administration @@ -2863,12 +2872,30 @@ static void Command_Changepassword_f(void) if (COM_Argc() != 2) { - CONS_Printf(M_GetText("password : change remote admin password\n")); + CONS_Printf(M_GetText("password : add remote admin password\n")); return; } D_SetPassword(COM_Argv(1)); - CONS_Printf(M_GetText("Password set.\n")); + CONS_Printf(M_GetText("Password added.\n")); +#endif +} + +// Remote Administration +static void Command_Clearpassword_f(void) +{ +#ifdef NOMD5 + // If we have no MD5 support then completely disable XD_LOGIN responses for security. + CONS_Alert(CONS_NOTICE, "Remote administration commands are not supported in this build.\n"); +#else + if (client) // cannot change remotely + { + CONS_Printf(M_GetText("Only the server can use this.\n")); + return; + } + + D_ClearPassword(); + CONS_Printf(M_GetText("Passwords cleared.\n")); #endif } diff --git a/src/netcode/d_netcmd.h b/src/netcode/d_netcmd.h index 4849079d0..0f2a1f92b 100644 --- a/src/netcode/d_netcmd.h +++ b/src/netcode/d_netcmd.h @@ -209,6 +209,7 @@ void ClearAdminPlayers(void); void RemoveAdminPlayer(INT32 playernum); void ItemFinder_OnChange(void); void D_SetPassword(const char *pw); +void D_ClearPassword(void); // used for the player setup menu UINT8 CanChangeSkin(INT32 playernum); From a266fbb6a055e2aec20a931200a3604f7e0a1033 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Tue, 26 Dec 2023 17:53:38 -0300 Subject: [PATCH 186/227] Revert 6c701a93120d4f48ab170df1e4a0f558dd686aee --- src/console.c | 19 +++++------- src/d_event.h | 1 + src/d_main.c | 6 ++-- src/hu_stuff.c | 60 ++++++++++++++++++++---------------- src/m_menu.c | 76 ++++++++++++++++++++++++++-------------------- src/sdl/i_system.c | 1 + src/sdl/i_video.c | 16 ++++++++++ 7 files changed, 105 insertions(+), 74 deletions(-) diff --git a/src/console.c b/src/console.c index 01b90ebc3..cdb1fc196 100644 --- a/src/console.c +++ b/src/console.c @@ -937,7 +937,7 @@ boolean CON_Responder(event_t *ev) return false; // let go keyup events, don't eat them - if (ev->type != ev_keydown && ev->type != ev_console) + if (ev->type != ev_keydown && ev->type != ev_text && ev->type != ev_console) { if (ev->key == gamecontrol[GC_CONSOLE][0] || ev->key == gamecontrol[GC_CONSOLE][1]) consdown = false; @@ -964,7 +964,7 @@ boolean CON_Responder(event_t *ev) // check other keys only if console prompt is active if (!consoleready && key < NUMINPUTS) // metzgermeister: boundary check!! { - if (! menuactive && bindtable[key]) + if (ev->type == ev_keydown && !menuactive && bindtable[key]) { COM_BufAddText(bindtable[key]); COM_BufAddText("\n"); @@ -981,6 +981,12 @@ boolean CON_Responder(event_t *ev) } } + if (ev->type == ev_text) + { + CON_InputAddChar(key); + return true; + } + // Always eat ctrl/shift/alt if console open, so the menu doesn't get ideas if (key == KEY_LSHIFT || key == KEY_RSHIFT || key == KEY_LCTRL || key == KEY_RCTRL @@ -1295,21 +1301,12 @@ boolean CON_Responder(event_t *ev) else if (key == KEY_KPADSLASH) key = '/'; - if (key >= 'a' && key <= 'z') - { - if (capslock ^ shiftdown) - key = shiftxform[key]; - } - else if (shiftdown) - key = shiftxform[key]; - // enter a char into the command prompt if (key < 32 || key > 127) return true; if (input_sel != input_cur) CON_InputDelSelection(); - CON_InputAddChar(key); return true; } diff --git a/src/d_event.h b/src/d_event.h index 5aa435060..7743d8609 100644 --- a/src/d_event.h +++ b/src/d_event.h @@ -22,6 +22,7 @@ typedef enum { ev_keydown, ev_keyup, + ev_text, ev_console, ev_mouse, ev_joystick, diff --git a/src/d_main.c b/src/d_main.c index 274e4ceb3..bc821cf71 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -192,19 +192,19 @@ void D_ProcessEvents(void) ev = &events[eventtail]; // Set mouse buttons early in case event is eaten later - if (ev->type == ev_keydown || ev->type == ev_keyup) + if (ev->type == ev_keydown || ev->type == ev_keyup || ev->type == ev_text) { // Mouse buttons if ((UINT32)(ev->key - KEY_MOUSE1) < MOUSEBUTTONS) { - if (ev->type == ev_keydown) + if (ev->type == ev_keydown || ev->type == ev_text) mouse.buttons |= 1 << (ev->key - KEY_MOUSE1); else mouse.buttons &= ~(1 << (ev->key - KEY_MOUSE1)); } else if ((UINT32)(ev->key - KEY_2MOUSE1) < MOUSEBUTTONS) { - if (ev->type == ev_keydown) + if (ev->type == ev_keydown || ev->type == ev_text) mouse2.buttons |= 1 << (ev->key - KEY_2MOUSE1); else mouse2.buttons &= ~(1 << (ev->key - KEY_2MOUSE1)); diff --git a/src/hu_stuff.c b/src/hu_stuff.c index 6178298d2..0a2b71aec 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -79,6 +79,7 @@ patch_t *nto_font[NT_FONTSIZE]; static player_t *plr; boolean chat_on; // entering a chat message? +boolean chat_on_first_event; // blocker for first chat input event static char w_chat[HU_MAXMSGLEN + 1]; static size_t c_input = 0; // let's try to make the chat input less shitty. static boolean headsupactive = false; @@ -1039,7 +1040,7 @@ boolean HU_Responder(event_t *ev) { INT32 c=0; - if (ev->type != ev_keydown) + if (ev->type != ev_keydown && ev->type != ev_text) return false; // only KeyDown events now... @@ -1068,11 +1069,15 @@ boolean HU_Responder(event_t *ev) if (!chat_on) { + if (ev->type == ev_text) + return false; + // enter chat mode if ((ev->key == gamecontrol[GC_TALKKEY][0] || ev->key == gamecontrol[GC_TALKKEY][1]) && netgame && !OLD_MUTE) // check for old chat mute, still let the players open the chat incase they want to scroll otherwise. { chat_on = true; + chat_on_first_event = false; w_chat[0] = 0; teamtalk = false; chat_scrollmedown = true; @@ -1083,6 +1088,7 @@ boolean HU_Responder(event_t *ev) && netgame && !OLD_MUTE) { chat_on = true; + chat_on_first_event = false; w_chat[0] = 0; teamtalk = G_GametypeHasTeams(); // Don't teamtalk if we don't have teams. chat_scrollmedown = true; @@ -1092,6 +1098,31 @@ boolean HU_Responder(event_t *ev) } else // if chat_on { + if (!chat_on_first_event) + { + // since the text event is sent immediately after the keydown event, + // we need to make sure that nothing is displayed once the chat + // opens, otherwise a 't' would be outputted. + chat_on_first_event = true; + return true; + } + + if (ev->type == ev_text) + { + if ((c < HU_FONTSTART || c > HU_FONTEND || !hu_font[c-HU_FONTSTART]) + && c != ' ') // Allow spaces, of course + { + return false; + } + + if (CHAT_MUTE || strlen(w_chat) >= HU_MAXMSGLEN) + return true; + + memmove(&w_chat[c_input + 1], &w_chat[c_input], strlen(w_chat) - c_input + 1); + w_chat[c_input] = c; + c_input++; + return true; + } // Ignore modifier keys // Note that we do this here so users can still set @@ -1101,23 +1132,8 @@ boolean HU_Responder(event_t *ev) || ev->key == KEY_LALT || ev->key == KEY_RALT) return true; - c = (INT32)ev->key; - - // I know this looks very messy but this works. If it ain't broke, don't fix it! - // shift LETTERS to uppercase if we have capslock or are holding shift - if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) - { - if (shiftdown ^ capslock) - c = shiftxform[c]; - } - else // if we're holding shift we should still shift non letter symbols - { - if (shiftdown) - c = shiftxform[c]; - } - // pasting. pasting is cool. chat is a bit limited, though :( - if ((c == 'v' || c == 'V') && ctrldown) + if (c == 'v' && ctrldown) { const char *paste; size_t chatlen; @@ -1185,16 +1201,6 @@ boolean HU_Responder(event_t *ev) else c_input++; } - else if ((c >= HU_FONTSTART && c <= HU_FONTEND && hu_font[c-HU_FONTSTART]) - || c == ' ') // Allow spaces, of course - { - if (CHAT_MUTE || strlen(w_chat) >= HU_MAXMSGLEN) - return true; - - memmove(&w_chat[c_input + 1], &w_chat[c_input], strlen(w_chat) - c_input + 1); - w_chat[c_input] = c; - c_input++; - } else if (c == KEY_BACKSPACE) { if (CHAT_MUTE || c_input <= 0) diff --git a/src/m_menu.c b/src/m_menu.c index 72d4547b0..9aae59445 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -3177,40 +3177,42 @@ boolean M_Responder(event_t *ev) } else if (menuactive) { - if (ev->type == ev_keydown) + if (ev->type == ev_keydown || ev->type == ev_text) { - keydown++; ch = ev->key; - - // added 5-2-98 remap virtual keys (mouse & joystick buttons) - switch (ch) + if (ev->type == ev_keydown) { - case KEY_MOUSE1: - case KEY_JOY1: - ch = KEY_ENTER; - break; - case KEY_JOY1 + 3: - ch = 'n'; - break; - case KEY_MOUSE1 + 1: - case KEY_JOY1 + 1: - ch = KEY_ESCAPE; - break; - case KEY_JOY1 + 2: - ch = KEY_BACKSPACE; - break; - case KEY_HAT1: - ch = KEY_UPARROW; - break; - case KEY_HAT1 + 1: - ch = KEY_DOWNARROW; - break; - case KEY_HAT1 + 2: - ch = KEY_LEFTARROW; - break; - case KEY_HAT1 + 3: - ch = KEY_RIGHTARROW; - break; + keydown++; + // added 5-2-98 remap virtual keys (mouse & joystick buttons) + switch (ch) + { + case KEY_MOUSE1: + case KEY_JOY1: + ch = KEY_ENTER; + break; + case KEY_JOY1 + 3: + ch = 'n'; + break; + case KEY_MOUSE1 + 1: + case KEY_JOY1 + 1: + ch = KEY_ESCAPE; + break; + case KEY_JOY1 + 2: + ch = KEY_BACKSPACE; + break; + case KEY_HAT1: + ch = KEY_UPARROW; + break; + case KEY_HAT1 + 1: + ch = KEY_DOWNARROW; + break; + case KEY_HAT1 + 2: + ch = KEY_LEFTARROW; + break; + case KEY_HAT1 + 3: + ch = KEY_RIGHTARROW; + break; + } } } else if (ev->type == ev_joystick && ev->key == 0 && joywait < I_GetTime()) @@ -3372,8 +3374,11 @@ boolean M_Responder(event_t *ev) // Handle menuitems which need a specific key handling if (routine && (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_KEYHANDLER) { - if (shiftdown && ch >= 32 && ch <= 127) - ch = shiftxform[ch]; + // ignore ev_keydown events if the key maps to a character, since + // the ev_text event will follow immediately after in that case. + if (ev->type == ev_keydown && ch >= 32 && ch <= 127) + return true; + routine(ch); return true; } @@ -3415,6 +3420,11 @@ boolean M_Responder(event_t *ev) { if ((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_STRING) { + // ignore ev_keydown events if the key maps to a character, since + // the ev_text event will follow immediately after in that case. + if (ev->type == ev_keydown && ch >= 32 && ch <= 127) + return false; + if (M_ChangeStringCvar(ch)) return true; else diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index 986647e72..847806270 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -652,6 +652,7 @@ void I_GetConsoleEvents(void) else if (tty_con.cursor < sizeof (tty_con.buffer)) { // push regular character + ev.type = ev_text; ev.key = tty_con.buffer[tty_con.cursor] = key; tty_con.cursor++; // print the current line (this is differential) diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c index a70e5a860..d3a602c05 100644 --- a/src/sdl/i_video.c +++ b/src/sdl/i_video.c @@ -693,6 +693,19 @@ static void Impl_HandleKeyboardEvent(SDL_KeyboardEvent evt, Uint32 type) if (event.key) D_PostEvent(&event); } +static void Impl_HandleTextEvent(SDL_TextInputEvent evt) +{ + event_t event; + event.type = ev_text; + if (evt.text[1] != '\0') + { + // limit ourselves to ASCII for now, we can add UTF-8 support later + return; + } + event.key = evt.text[0]; + D_PostEvent(&event); +} + static void Impl_HandleMouseMotionEvent(SDL_MouseMotionEvent evt) { static boolean firstmove = true; @@ -941,6 +954,9 @@ void I_GetEvent(void) case SDL_KEYDOWN: Impl_HandleKeyboardEvent(evt.key, evt.type); break; + case SDL_TEXTINPUT: + Impl_HandleTextEvent(evt.text); + break; case SDL_MOUSEMOTION: //if (!mouseMotionOnce) Impl_HandleMouseMotionEvent(evt.motion); From 1f65c5507419ded1ec143e5d52f3f4532b8e81de Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Tue, 26 Dec 2023 18:29:41 -0300 Subject: [PATCH 187/227] Don't add text to the console on the same frame it's being toggled Resolves #1161 --- src/console.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/console.c b/src/console.c index cdb1fc196..ece1e9727 100644 --- a/src/console.c +++ b/src/console.c @@ -983,7 +983,8 @@ boolean CON_Responder(event_t *ev) if (ev->type == ev_text) { - CON_InputAddChar(key); + if (!consoletoggle) + CON_InputAddChar(key); return true; } From 2ef5ea86f3956f6a34fe6860ae1180fd4d2e63d0 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Tue, 26 Dec 2023 18:31:11 -0300 Subject: [PATCH 188/227] Don't toggle the console if shift is held --- src/console.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/console.c b/src/console.c index ece1e9727..706ed6633 100644 --- a/src/console.c +++ b/src/console.c @@ -952,7 +952,7 @@ boolean CON_Responder(event_t *ev) if (modeattacking || metalrecording || marathonmode) return false; - if (key == gamecontrol[GC_CONSOLE][0] || key == gamecontrol[GC_CONSOLE][1]) + if ((key == gamecontrol[GC_CONSOLE][0] || key == gamecontrol[GC_CONSOLE][1]) && !shiftdown) { if (consdown) // ignore repeat return true; From 5d7ee3974fe376cc49e40a8f66cecb4477622f68 Mon Sep 17 00:00:00 2001 From: Alam Ed Arias Date: Tue, 26 Dec 2023 17:49:34 -0500 Subject: [PATCH 189/227] Build: error on array bounds --- src/CMakeLists.txt | 2 +- src/Makefile.d/versions.mk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7916b26c6..80dff5043 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -281,7 +281,7 @@ target_compile_options(SRB2SDL2 PRIVATE $<$,4.5.0>: -Wlogical-op - -Wno-error=array-bounds + #-Wno-error=array-bounds > $<$,4.6.0>: diff --git a/src/Makefile.d/versions.mk b/src/Makefile.d/versions.mk index d2877b374..2523d7f3c 100644 --- a/src/Makefile.d/versions.mk +++ b/src/Makefile.d/versions.mk @@ -116,7 +116,7 @@ ifdef GCC43 #WFLAGS+=-Wno-error=clobbered endif ifdef GCC44 - WFLAGS+=-Wno-error=array-bounds +#WFLAGS+=-Wno-error=array-bounds endif ifdef GCC46 WFLAGS+=-Wno-error=suggest-attribute=noreturn From 8329b21b81a8e21c21cc80f0bdf85f57a17f8164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Wed, 27 Dec 2023 22:35:00 +0100 Subject: [PATCH 190/227] Fix dummy build --- src/dummy/i_system.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/dummy/i_system.c b/src/dummy/i_system.c index 70e1ef4ec..fe33cfe3e 100644 --- a/src/dummy/i_system.c +++ b/src/dummy/i_system.c @@ -1,6 +1,7 @@ #include "../doomdef.h" #include "../doomtype.h" #include "../i_system.h" +#include "../i_time.h" FILE *logstream = NULL; @@ -19,6 +20,11 @@ void I_Sleep(UINT32 ms) (void)ms; } +void I_SleepDuration(precise_t duration) +{ + (void)duration; +} + precise_t I_GetPreciseTime(void) { return 0; From 646d1b0ea6025082eb880ddfb6ff40bb4836cc33 Mon Sep 17 00:00:00 2001 From: LJ Sonic Date: Thu, 28 Dec 2023 15:17:14 +0100 Subject: [PATCH 191/227] Fix Lua taglists methods not working --- src/lua_taglib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lua_taglib.c b/src/lua_taglib.c index 9e73a050c..a040a7efc 100644 --- a/src/lua_taglib.c +++ b/src/lua_taglib.c @@ -228,7 +228,7 @@ static int taglist_get(lua_State *L) } else { - lua_getmetatable(L, 1); + lua_getglobal(L, "taglist"); lua_replace(L, 1); lua_rawget(L, 1); return 1; From b3ef2b3344e0f510f4f24dd6f3c21326ca2e5eda Mon Sep 17 00:00:00 2001 From: LJ Sonic Date: Thu, 28 Dec 2023 17:32:36 +0100 Subject: [PATCH 192/227] Support loadfile in folder add-ons too --- src/blua/lbaselib.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/blua/lbaselib.c b/src/blua/lbaselib.c index 452f101c4..2bb3d9cf0 100644 --- a/src/blua/lbaselib.c +++ b/src/blua/lbaselib.c @@ -275,7 +275,7 @@ static int luaB_dofile (lua_State *L) { int n = lua_gettop(L); if (!W_FileHasFolders(wadfiles[numwadfiles - 1])) - luaL_error(L, "dofile() only works with PK3 files"); + luaL_error(L, "dofile() only works with PK3 files and folders"); snprintf(fullfilename, sizeof(fullfilename), "Lua/%s", filename); lumpnum = W_CheckNumForFullNamePK3(fullfilename, numwadfiles - 1, 0); @@ -293,8 +293,8 @@ static int luaB_loadfile (lua_State *L) { char fullfilename[256]; UINT16 lumpnum; - if (wadfiles[numwadfiles - 1]->type != RET_PK3) - luaL_error(L, "loadfile() only works with PK3 files"); + if (!W_FileHasFolders(wadfiles[numwadfiles - 1])) + luaL_error(L, "loadfile() only works with PK3 files and folders"); snprintf(fullfilename, sizeof(fullfilename), "Lua/%s", filename); lumpnum = W_CheckNumForFullNamePK3(fullfilename, numwadfiles - 1, 0); From 849455bba72bc80aabb9303582f6130b134af611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Thu, 28 Dec 2023 21:52:48 +0100 Subject: [PATCH 193/227] Fix one too many player count on dedicated servers --- src/netcode/server_connection.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/netcode/server_connection.c b/src/netcode/server_connection.c index faff7e8dd..bfbe30a08 100644 --- a/src/netcode/server_connection.c +++ b/src/netcode/server_connection.c @@ -109,7 +109,7 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime) netbuffer->u.serverinfo.leveltime = (tic_t)LONG(leveltime); // Exclude bots from both counts - netbuffer->u.serverinfo.numberofplayer = (UINT8)D_NumNodes(); + netbuffer->u.serverinfo.numberofplayer = (UINT8)(D_NumNodes() - (dedicated ? 1 : 0)); netbuffer->u.serverinfo.maxplayer = (UINT8)(cv_maxplayers.value - D_NumBots()); netbuffer->u.serverinfo.refusereason = GetRefuseReason(node); From cdacb95846945bbb06b4911e544054459facd57a Mon Sep 17 00:00:00 2001 From: Logan-A Date: Sat, 30 Dec 2023 11:35:35 -0500 Subject: [PATCH 194/227] update MiniUPnPc from 1.6 to 2.2.5 --- libs/miniupnpc/.gitattributes | 26 + libs/miniupnpc/CMakeLists.txt | 426 ++++--- libs/miniupnpc/Changelog.txt | 347 +++++- libs/miniupnpc/LICENSE | 40 +- libs/miniupnpc/MANIFEST.in | 11 +- libs/miniupnpc/Makefile | 434 +++++-- libs/miniupnpc/Makefile.mingw | 182 ++- libs/miniupnpc/README | 51 +- libs/miniupnpc/VERSION | 2 +- libs/miniupnpc/apiversions.txt | 178 +++ libs/miniupnpc/bsdqueue.h | 531 --------- libs/miniupnpc/codelength.h | 24 - libs/miniupnpc/connecthostport.h | 17 - libs/miniupnpc/declspec.h | 15 - libs/miniupnpc/external-ip.sh | 4 +- libs/miniupnpc/{ => include}/igd_desc_parse.h | 13 +- libs/miniupnpc/{ => include}/miniupnpc.h | 88 +- libs/miniupnpc/include/miniupnpc_declspec.h | 21 + libs/miniupnpc/{ => include}/miniupnpctypes.h | 12 +- libs/miniupnpc/include/miniwget.h | 27 + .../{ => include}/portlistingparse.h | 26 +- libs/miniupnpc/{ => include}/upnpcommands.h | 173 ++- libs/miniupnpc/include/upnpdev.h | 44 + libs/miniupnpc/{ => include}/upnperrors.h | 14 +- libs/miniupnpc/{ => include}/upnpreplyparse.h | 29 +- libs/miniupnpc/java/JavaBridgeTest.java | 8 +- libs/miniupnpc/java/testjava.bat | 8 + libs/miniupnpc/java/testjava.sh | 8 +- libs/miniupnpc/man3/miniupnpc.3 | 16 +- libs/miniupnpc/mingw32/libminiupnpc.a | Bin 2028512 -> 63762 bytes libs/miniupnpc/mingw64/libminiupnpc.a | Bin 135706 -> 72024 bytes libs/miniupnpc/minissdpc.c | 132 --- libs/miniupnpc/minissdpc.h | 15 - libs/miniupnpc/miniupnpc-config.cmake | 6 + libs/miniupnpc/miniupnpc.c | 943 --------------- libs/miniupnpc/miniupnpc.def | 6 + libs/miniupnpc/miniupnpc.pc.in | 18 + libs/miniupnpc/miniupnpc.rc | 36 + libs/miniupnpc/miniupnpcstrings.h.cmake | 12 +- libs/miniupnpc/miniupnpcstrings.h.in | 16 +- libs/miniupnpc/miniwget.h | 30 - libs/miniupnpc/msvc/genminiupnpcstrings.vbs | 112 ++ libs/miniupnpc/msvc/miniupnpc.vcproj | 24 +- libs/miniupnpc/msvc/miniupnpc.vcxproj | 215 ++++ libs/miniupnpc/msvc/miniupnpc.vcxproj.filters | 133 +++ libs/miniupnpc/msvc/miniupnpc_vs2010.sln | 36 + libs/miniupnpc/msvc/miniupnpc_vs2010.vcxproj | 209 ++++ .../msvc/miniupnpc_vs2010.vcxproj.filters | 133 +++ libs/miniupnpc/msvc/miniupnpc_vs2015.sln | 38 + libs/miniupnpc/msvc/upnpc-static.vcproj | 4 +- libs/miniupnpc/msvc/upnpc-static.vcxproj | 184 +++ .../msvc/upnpc-static.vcxproj.filters | 22 + .../msvc/upnpc-static_vs2010.vcxproj | 176 +++ libs/miniupnpc/pymoduletest.py | 79 +- libs/miniupnpc/receivedata.c | 81 -- libs/miniupnpc/receivedata.h | 17 - libs/miniupnpc/setup.py | 42 +- libs/miniupnpc/setupmingw32.py | 42 +- libs/miniupnpc/src/addr_is_reserved.c | 79 ++ libs/miniupnpc/src/addr_is_reserved.h | 14 + libs/miniupnpc/src/codelength.h | 54 + libs/miniupnpc/{ => src}/connecthostport.c | 147 ++- libs/miniupnpc/src/connecthostport.h | 20 + libs/miniupnpc/{ => src}/igd_desc_parse.c | 36 +- libs/miniupnpc/src/listdevices.c | 197 ++++ libs/miniupnpc/{ => src}/minihttptestserver.c | 343 +++++- libs/miniupnpc/{ => src}/minisoap.c | 36 +- libs/miniupnpc/{ => src}/minisoap.h | 12 +- libs/miniupnpc/src/minissdpc.c | 1019 +++++++++++++++++ libs/miniupnpc/src/minissdpc.h | 58 + libs/miniupnpc/src/miniupnpc.c | 698 +++++++++++ libs/miniupnpc/src/miniupnpc_socketdef.h | 44 + libs/miniupnpc/{ => src}/miniupnpcmodule.c | 309 ++++- libs/miniupnpc/{ => src}/miniwget.c | 416 ++++--- libs/miniupnpc/src/miniwget_private.h | 15 + libs/miniupnpc/{ => src}/minixml.c | 27 +- libs/miniupnpc/{ => src}/minixml.h | 8 +- libs/miniupnpc/{ => src}/minixmlvalid.c | 13 +- libs/miniupnpc/{ => src}/portlistingparse.c | 36 +- libs/miniupnpc/src/receivedata.c | 105 ++ libs/miniupnpc/src/receivedata.h | 21 + libs/miniupnpc/src/testaddr_is_reserved.c | 46 + libs/miniupnpc/src/testigddescparse.c | 187 +++ libs/miniupnpc/{ => src}/testminiwget.c | 19 +- libs/miniupnpc/{ => src}/testminixml.c | 25 +- libs/miniupnpc/src/testportlistingparse.c | 151 +++ libs/miniupnpc/src/testupnpreplyparse.c | 115 ++ libs/miniupnpc/{ => src}/upnpc.c | 464 +++++--- libs/miniupnpc/{ => src}/upnpcommands.c | 355 ++++-- libs/miniupnpc/src/upnpdev.c | 23 + libs/miniupnpc/{ => src}/upnperrors.c | 39 +- libs/miniupnpc/{ => src}/upnpreplyparse.c | 115 +- libs/miniupnpc/src/win32_snprintf.h | 71 ++ .../testdesc/linksys_WAG200G_desc.values | 14 + .../testdesc/linksys_WAG200G_desc.xml | 110 ++ .../testdesc/new_LiveBox_desc.values | 20 + libs/miniupnpc/testdesc/new_LiveBox_desc.xml | 90 ++ libs/miniupnpc/testigddescparse.c | 64 -- libs/miniupnpc/testminiwget.sh | 88 +- .../DeletePortMapping.namevalue | 3 + .../testreplyparse/DeletePortMapping.xml | 6 + .../GetExternalIPAddress.namevalue | 2 + .../testreplyparse/GetExternalIPAddress.xml | 2 + .../GetSpecificPortMappingEntryReq.namevalue | 3 + .../GetSpecificPortMappingEntryReq.xml | 3 + .../GetSpecificPortMappingEntryResp.namevalue | 5 + .../GetSpecificPortMappingEntryResp.xml | 2 + .../SetDefaultConnectionService.namevalue | 1 + .../SetDefaultConnectionService.xml | 1 + libs/miniupnpc/testreplyparse/readme.txt | 7 + libs/miniupnpc/testupnpigd.py | 48 +- libs/miniupnpc/testupnpreplyparse.c | 44 - libs/miniupnpc/testupnpreplyparse.sh | 17 + libs/miniupnpc/updateminiupnpcstrings.sh | 26 +- libs/miniupnpc/wingenminiupnpcstrings.c | 43 +- 115 files changed, 8290 insertions(+), 3162 deletions(-) create mode 100644 libs/miniupnpc/.gitattributes create mode 100644 libs/miniupnpc/apiversions.txt delete mode 100644 libs/miniupnpc/bsdqueue.h delete mode 100644 libs/miniupnpc/codelength.h delete mode 100644 libs/miniupnpc/connecthostport.h delete mode 100644 libs/miniupnpc/declspec.h rename libs/miniupnpc/{ => include}/igd_desc_parse.h (84%) rename libs/miniupnpc/{ => include}/miniupnpc.h (52%) create mode 100644 libs/miniupnpc/include/miniupnpc_declspec.h rename libs/miniupnpc/{ => include}/miniupnpctypes.h (56%) create mode 100644 libs/miniupnpc/include/miniwget.h rename libs/miniupnpc/{ => include}/portlistingparse.h (76%) rename libs/miniupnpc/{ => include}/upnpcommands.h (61%) create mode 100644 libs/miniupnpc/include/upnpdev.h rename libs/miniupnpc/{ => include}/upnperrors.h (52%) rename libs/miniupnpc/{ => include}/upnpreplyparse.h (71%) create mode 100644 libs/miniupnpc/java/testjava.bat delete mode 100644 libs/miniupnpc/minissdpc.c delete mode 100644 libs/miniupnpc/minissdpc.h create mode 100644 libs/miniupnpc/miniupnpc-config.cmake delete mode 100644 libs/miniupnpc/miniupnpc.c create mode 100644 libs/miniupnpc/miniupnpc.pc.in create mode 100644 libs/miniupnpc/miniupnpc.rc delete mode 100644 libs/miniupnpc/miniwget.h create mode 100644 libs/miniupnpc/msvc/genminiupnpcstrings.vbs create mode 100644 libs/miniupnpc/msvc/miniupnpc.vcxproj create mode 100644 libs/miniupnpc/msvc/miniupnpc.vcxproj.filters create mode 100644 libs/miniupnpc/msvc/miniupnpc_vs2010.sln create mode 100644 libs/miniupnpc/msvc/miniupnpc_vs2010.vcxproj create mode 100644 libs/miniupnpc/msvc/miniupnpc_vs2010.vcxproj.filters create mode 100644 libs/miniupnpc/msvc/miniupnpc_vs2015.sln create mode 100644 libs/miniupnpc/msvc/upnpc-static.vcxproj create mode 100644 libs/miniupnpc/msvc/upnpc-static.vcxproj.filters create mode 100644 libs/miniupnpc/msvc/upnpc-static_vs2010.vcxproj delete mode 100644 libs/miniupnpc/receivedata.c delete mode 100644 libs/miniupnpc/receivedata.h create mode 100644 libs/miniupnpc/src/addr_is_reserved.c create mode 100644 libs/miniupnpc/src/addr_is_reserved.h create mode 100644 libs/miniupnpc/src/codelength.h rename libs/miniupnpc/{ => src}/connecthostport.c (62%) create mode 100644 libs/miniupnpc/src/connecthostport.h rename libs/miniupnpc/{ => src}/igd_desc_parse.c (83%) create mode 100644 libs/miniupnpc/src/listdevices.c rename libs/miniupnpc/{ => src}/minihttptestserver.c (56%) rename libs/miniupnpc/{ => src}/minisoap.c (81%) rename libs/miniupnpc/{ => src}/minisoap.h (51%) create mode 100644 libs/miniupnpc/src/minissdpc.c create mode 100644 libs/miniupnpc/src/minissdpc.h create mode 100644 libs/miniupnpc/src/miniupnpc.c create mode 100644 libs/miniupnpc/src/miniupnpc_socketdef.h rename libs/miniupnpc/{ => src}/miniupnpcmodule.c (61%) rename libs/miniupnpc/{ => src}/miniwget.c (50%) create mode 100644 libs/miniupnpc/src/miniwget_private.h rename libs/miniupnpc/{ => src}/minixml.c (88%) rename libs/miniupnpc/{ => src}/minixml.h (83%) rename libs/miniupnpc/{ => src}/minixmlvalid.c (94%) rename libs/miniupnpc/{ => src}/portlistingparse.c (81%) create mode 100644 libs/miniupnpc/src/receivedata.c create mode 100644 libs/miniupnpc/src/receivedata.h create mode 100644 libs/miniupnpc/src/testaddr_is_reserved.c create mode 100644 libs/miniupnpc/src/testigddescparse.c rename libs/miniupnpc/{ => src}/testminiwget.c (70%) rename libs/miniupnpc/{ => src}/testminixml.c (82%) create mode 100644 libs/miniupnpc/src/testportlistingparse.c create mode 100644 libs/miniupnpc/src/testupnpreplyparse.c rename libs/miniupnpc/{ => src}/upnpc.c (59%) rename libs/miniupnpc/{ => src}/upnpcommands.c (79%) create mode 100644 libs/miniupnpc/src/upnpdev.c rename libs/miniupnpc/{ => src}/upnperrors.c (69%) rename libs/miniupnpc/{ => src}/upnpreplyparse.c (58%) create mode 100644 libs/miniupnpc/src/win32_snprintf.h create mode 100644 libs/miniupnpc/testdesc/linksys_WAG200G_desc.values create mode 100644 libs/miniupnpc/testdesc/linksys_WAG200G_desc.xml create mode 100644 libs/miniupnpc/testdesc/new_LiveBox_desc.values create mode 100644 libs/miniupnpc/testdesc/new_LiveBox_desc.xml delete mode 100644 libs/miniupnpc/testigddescparse.c create mode 100644 libs/miniupnpc/testreplyparse/DeletePortMapping.namevalue create mode 100644 libs/miniupnpc/testreplyparse/DeletePortMapping.xml create mode 100644 libs/miniupnpc/testreplyparse/GetExternalIPAddress.namevalue create mode 100644 libs/miniupnpc/testreplyparse/GetExternalIPAddress.xml create mode 100644 libs/miniupnpc/testreplyparse/GetSpecificPortMappingEntryReq.namevalue create mode 100644 libs/miniupnpc/testreplyparse/GetSpecificPortMappingEntryReq.xml create mode 100644 libs/miniupnpc/testreplyparse/GetSpecificPortMappingEntryResp.namevalue create mode 100644 libs/miniupnpc/testreplyparse/GetSpecificPortMappingEntryResp.xml create mode 100644 libs/miniupnpc/testreplyparse/SetDefaultConnectionService.namevalue create mode 100644 libs/miniupnpc/testreplyparse/SetDefaultConnectionService.xml create mode 100644 libs/miniupnpc/testreplyparse/readme.txt delete mode 100644 libs/miniupnpc/testupnpreplyparse.c create mode 100644 libs/miniupnpc/testupnpreplyparse.sh diff --git a/libs/miniupnpc/.gitattributes b/libs/miniupnpc/.gitattributes new file mode 100644 index 000000000..513fc8491 --- /dev/null +++ b/libs/miniupnpc/.gitattributes @@ -0,0 +1,26 @@ +/*.cmake text=auto +/*.py text=auto +/*.rc -crlf -whitespace +/*.sh text eol=lf +/*.txt text=auto +/CMakeLists.txt text=auto +/LICENSE text=auto +/Makefile text=auto +/Makefile.mingw text=auto +/include/*.h text=auto +/java/*.bat -crlf -whitespace +/java/*.java text=auto +/java/*.sh text eol=lf +/man3/miniupnpc.3 eol=lf +/msvc/*.sln -crlf -whitespace +/msvc/*.vbs -crlf -whitespace +/msvc/*.vcproj -crlf -whitespace +/msvc/*.vcxproj* -crlf -whitespace +/src/*.c text=auto +/src/*.h text=auto +/testdesc/*.values text=auto +/testdesc/*.xml text=auto +/testreplyparse/*.namevalue text=auto +/testreplyparse/*.txt text=auto +/testreplyparse/*.xml text=auto +MANIFEST.in eol=lf diff --git a/libs/miniupnpc/CMakeLists.txt b/libs/miniupnpc/CMakeLists.txt index 082b653a9..65eb89d08 100644 --- a/libs/miniupnpc/CMakeLists.txt +++ b/libs/miniupnpc/CMakeLists.txt @@ -1,172 +1,308 @@ -cmake_minimum_required (VERSION 2.6) +cmake_minimum_required(VERSION 3.12 FATAL_ERROR) -project (miniupnpc C) -set (MINIUPNPC_VERSION 1.5) -set (MINIUPNPC_API_VERSION 8) +project (miniupnpc + VERSION 2.2.5 + DESCRIPTION "UPnP IGD client lightweight library" + HOMEPAGE_URL https://miniupnp.tuxfamily.org/ + LANGUAGES C) -if (NOT CMAKE_BUILD_TYPE) - if (WIN32) - set (DEFAULT_BUILD_TYPE MinSizeRel) - else (WIN32) - set (DEFAULT_BUILD_TYPE RelWithDebInfo) - endif(WIN32) - set (CMAKE_BUILD_TYPE ${DEFAULT_BUILD_TYPE} CACHE STRING - "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." - FORCE) -endif() +set (MINIUPNPC_API_VERSION 17) option (UPNPC_BUILD_STATIC "Build static library" TRUE) option (UPNPC_BUILD_SHARED "Build shared library" TRUE) -if (NOT WIN32) - option (UPNPC_BUILD_TESTS "Build test executables" TRUE) -endif (NOT WIN32) +option (UPNPC_BUILD_TESTS "Build test executables" TRUE) +option (UPNPC_BUILD_SAMPLE "Build sample executables" TRUE) option (NO_GETADDRINFO "Define NO_GETADDRINFO" FALSE) - -mark_as_advanced (NO_GETADDRINFO) - -if (NO_GETADDRINFO) - add_definitions (-DNO_GETADDRINFO) -endif (NO_GETADDRINFO) - -if (NOT WIN32) - add_definitions (-DMINIUPNPC_SET_SOCKET_TIMEOUT) -else (NOT WIN32) - add_definitions (-D_WIN32_WINNT=0x0501) # XP or higher for getnameinfo and friends -endif (NOT WIN32) - -if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") - add_definitions (-DMACOSX -D_DARWIN_C_SOURCE) -endif () - -# Set compiler specific build flags -if (CMAKE_COMPILER_IS_GNUC) - # Set our own default flags at first run. - if (NOT CONFIGURED) - - if (NOT CMAKE_SYSTEM_NAME STREQUAL "AmigaOS") - set (_PIC -fPIC) - endif (CMAKE_SYSTEM_NAME STREQUAL "AmigaOS") - - set (CMAKE_C_FLAGS "${_PIC} -Wall $ENV{CFLAGS}" # CMAKE_C_FLAGS gets appended to the other C flags - CACHE STRING "Flags used by the C compiler during normal builds." FORCE) - set (CMAKE_C_FLAGS_DEBUG "-g -DDDEBUG" - CACHE STRING "Flags used by the C compiler during debug builds." FORCE) - set (CMAKE_C_FLAGS_RELEASE "-O2 -DNDEBUG" - CACHE STRING "Flags used by the C compiler during release builds." FORCE) - set (CMAKE_C_FLAGS_RELWITHDEBINFO "-O2 -g -DNDEBUG" - CACHE STRING "Flags used by the C compiler during release builds." FORCE) - set (CMAKE_C_FLAGS_MINSIZEREL "-Os -DNDEBUG" - CACHE STRING "Flags used by the C compiler during release builds." FORCE) - - endif (NOT CONFIGURED) -endif () - -configure_file (${CMAKE_SOURCE_DIR}/miniupnpcstrings.h.cmake ${CMAKE_BINARY_DIR}/miniupnpcstrings.h) -include_directories (${CMAKE_BINARY_DIR}) - -set (MINIUPNPC_SOURCES - igd_desc_parse.c - miniupnpc.c - minixml.c - minisoap.c - miniwget.c - upnpc.c - upnpcommands.c - upnpreplyparse.c - upnperrors.c - connecthostport.c - portlistingparse.c -) - -if (NOT WIN32 AND NOT CMAKE_SYSTEM_NAME STREQUAL "AmigaOS") - set (MINIUPNPC_SOURCES ${MINIUPNPC_SOURCES} minissdpc.c) -endif (NOT WIN32 AND NOT CMAKE_SYSTEM_NAME STREQUAL "AmigaOS") - -if (WIN32) - set_source_files_properties (${MINIUPNPC_SOURCES} PROPERTIES - COMPILE_DEFINITIONS STATICLIB - COMPILE_DEFINITIONS MINIUPNP_EXPORTS - ) -endif (WIN32) - -if (WIN32) - find_library (WINSOCK2_LIBRARY NAMES ws2_32 WS2_32 Ws2_32) - find_library (IPHLPAPI_LIBRARY NAMES iphlpapi) - set (LDLIBS ${WINSOCK2_LIBRARY} ${IPHLPAPI_LIBRARY} ${LDLIBS}) -#elseif (CMAKE_SYSTEM_NAME STREQUAL "Solaris") -# find_library (SOCKET_LIBRARY NAMES socket) -# find_library (NSL_LIBRARY NAMES nsl) -# find_library (RESOLV_LIBRARY NAMES resolv) -# set (LDLIBS ${SOCKET_LIBRARY} ${NSL_LIBRARY} ${RESOLV_LIBRARY} ${LDLIBS}) -endif (WIN32) +option (UPNPC_NO_INSTALL "Disable installation" FALSE) if (NOT UPNPC_BUILD_STATIC AND NOT UPNPC_BUILD_SHARED) message (FATAL "Both shared and static libraries are disabled!") -endif (NOT UPNPC_BUILD_STATIC AND NOT UPNPC_BUILD_SHARED) +endif () + +include(GNUInstallDirs) + +# Interface library for compile definitions, flags and option +add_library(miniupnpc-private INTERFACE) + +if (NO_GETADDRINFO) + target_compile_definitions(miniupnpc-private INTERFACE NO_GETADDRINFO) +endif () + +if (NOT WIN32) + target_compile_definitions(miniupnpc-private INTERFACE + MINIUPNPC_SET_SOCKET_TIMEOUT + _BSD_SOURCE _DEFAULT_SOURCE) + if (NOT APPLE AND NOT CMAKE_SYSTEM_NAME MATCHES ".*BSD" AND NOT CMAKE_SYSTEM_NAME STREQUAL "SunOS") + # add_definitions (-D_POSIX_C_SOURCE=200112L) + target_compile_definitions(miniupnpc-private INTERFACE _XOPEN_SOURCE=600) + endif () + if (CMAKE_SYSTEM_NAME STREQUAL "NetBSD") + target_compile_definitions(miniupnpc-private INTERFACE _NETBSD_SOURCE) + endif () +else () + target_compile_definitions(miniupnpc-private INTERFACE _WIN32_WINNT=0x0501) # XP or higher for getnameinfo and friends +endif () + +if (APPLE) + target_compile_definitions(miniupnpc-private INTERFACE _DARWIN_C_SOURCE) +endif () + +# Set compiler specific build flags +if (CMAKE_COMPILER_IS_GNUCC AND NOT CMAKE_SYSTEM_NAME STREQUAL "AmigaOS") + set(CMAKE_POSITION_INDEPENDENT_CODE ON) + target_compile_options(miniupnpc-private INTERFACE -Wall) +endif () + +# Suppress noise warnings +if (MSVC) + target_compile_definitions(miniupnpc-private INTERFACE _CRT_SECURE_NO_WARNINGS _WINSOCK_DEPRECATED_NO_WARNINGS) +endif() + +configure_file (${CMAKE_CURRENT_SOURCE_DIR}/miniupnpcstrings.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/miniupnpcstrings.h) +target_include_directories(miniupnpc-private INTERFACE $) + +set (MINIUPNPC_SOURCES + src/igd_desc_parse.c + src/miniupnpc.c + src/minixml.c + src/minisoap.c + src/minissdpc.c + src/miniwget.c + src/upnpcommands.c + src/upnpdev.c + src/upnpreplyparse.c + src/upnperrors.c + src/connecthostport.c + src/portlistingparse.c + src/receivedata.c + src/addr_is_reserved.c + ${CMAKE_CURRENT_BINARY_DIR}/miniupnpcstrings.h +) + +if (WIN32) + target_link_libraries(miniupnpc-private INTERFACE ws2_32 iphlpapi) +elseif (CMAKE_SYSTEM_NAME STREQUAL "SunOS") + target_link_libraries(miniupnpc-private INTERFACE socket nsl resolv) + find_library (SOCKET_LIBRARY NAMES socket) + find_library (NSL_LIBRARY NAMES nsl) + find_library (RESOLV_LIBRARY NAMES resolv) + set (LDLIBS ${SOCKET_LIBRARY} ${NSL_LIBRARY} ${RESOLV_LIBRARY} ${LDLIBS}) +elseif (HAIKU) + target_link_libraries(miniupnpc-private INTERFACE network) + find_library (SOCKET_LIBRARY NAMES network) + find_library (NSL_LIBRARY NAMES network) + find_library (RESOLV_LIBRARY NAMES network) + set (LDLIBS ${SOCKET_LIBRARY} ${NSL_LIBRARY} ${RESOLV_LIBRARY} ${LDLIBS}) +endif () + if (UPNPC_BUILD_STATIC) - add_library (upnpc-static STATIC ${MINIUPNPC_SOURCES}) - set_target_properties (upnpc-static PROPERTIES OUTPUT_NAME "miniupnpc") - target_link_libraries (upnpc-static ${LDLIBS}) - set (UPNPC_INSTALL_TARGETS ${UPNPC_INSTALL_TARGETS} upnpc-static) - set (UPNPC_LIBRARY_TARGET upnpc-static) -endif (UPNPC_BUILD_STATIC) + add_library (libminiupnpc-static STATIC ${MINIUPNPC_SOURCES}) + target_include_directories (libminiupnpc-static PUBLIC + $ + $) + if (NOT UPNPC_BUILD_SHARED) + add_library (miniupnpc::miniupnpc ALIAS libminiupnpc-static) + endif() + set_target_properties (libminiupnpc-static PROPERTIES EXPORT_NAME miniupnpc) + if (WIN32 AND NOT MINGW) + set_target_properties (libminiupnpc-static PROPERTIES OUTPUT_NAME "libminiupnpc") + else() + set_target_properties (libminiupnpc-static PROPERTIES OUTPUT_NAME "miniupnpc") + endif() + target_link_libraries (libminiupnpc-static PRIVATE miniupnpc-private) + target_include_directories(libminiupnpc-static INTERFACE $) + target_compile_definitions(libminiupnpc-static PUBLIC MINIUPNP_STATICLIB) + + if (NOT UPNPC_NO_INSTALL) + install (TARGETS miniupnpc-private EXPORT miniupnpc-private) + + install (EXPORT miniupnpc-private + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/miniupnpc" + NAMESPACE miniupnpc::) + + install (TARGETS libminiupnpc-static + EXPORT libminiupnpc-static + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + + install (EXPORT libminiupnpc-static + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/miniupnpc" + NAMESPACE miniupnpc::) + endif() + + if (UPNPC_BUILD_SAMPLE) + add_executable (upnpc-static src/upnpc.c) + target_link_libraries (upnpc-static PRIVATE libminiupnpc-static) + target_include_directories(upnpc-static PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + if (NOT UPNPC_NO_INSTALL) + install (TARGETS upnpc-static + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + endif() + endif () +endif () if (UPNPC_BUILD_SHARED) - add_library (upnpc-shared SHARED ${MINIUPNPC_SOURCES}) - set_target_properties (upnpc-shared PROPERTIES OUTPUT_NAME "miniupnpc") - set_target_properties (upnpc-shared PROPERTIES VERSION ${MINIUPNPC_VERSION}) - set_target_properties (upnpc-shared PROPERTIES SOVERSION ${MINIUPNPC_API_VERSION}) - target_link_libraries (upnpc-shared ${LDLIBS}) - set (UPNPC_INSTALL_TARGETS ${UPNPC_INSTALL_TARGETS} upnpc-shared) - set (UPNPC_LIBRARY_TARGET upnpc-shared) -endif (UPNPC_BUILD_SHARED) + add_library (libminiupnpc-shared SHARED ${MINIUPNPC_SOURCES}) + target_include_directories (libminiupnpc-shared PUBLIC + $ + $) + add_library (miniupnpc::miniupnpc ALIAS libminiupnpc-shared) + set_target_properties (libminiupnpc-shared PROPERTIES EXPORT_NAME miniupnpc) + set_target_properties (libminiupnpc-shared PROPERTIES OUTPUT_NAME "miniupnpc") + set_target_properties (libminiupnpc-shared PROPERTIES VERSION ${PROJECT_VERSION}) + set_target_properties (libminiupnpc-shared PROPERTIES SOVERSION ${MINIUPNPC_API_VERSION}) + target_link_libraries (libminiupnpc-shared PRIVATE miniupnpc-private) + target_compile_definitions(libminiupnpc-shared PRIVATE MINIUPNP_EXPORTS) + + target_include_directories(libminiupnpc-shared INTERFACE $) + if (WIN32) + target_link_libraries(libminiupnpc-shared INTERFACE ws2_32 iphlpapi) + endif() + + if (NOT UPNPC_NO_INSTALL) + install (TARGETS libminiupnpc-shared + EXPORT libminiupnpc-shared + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + + install (EXPORT libminiupnpc-shared + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/miniupnpc" + NAMESPACE miniupnpc::) + endif() + + if (UPNPC_BUILD_SAMPLE) + add_executable (upnpc-shared src/upnpc.c) + target_link_libraries (upnpc-shared PRIVATE libminiupnpc-shared) + target_include_directories(upnpc-shared PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + if (NOT UPNPC_NO_INSTALL) + install (TARGETS upnpc-shared + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + endif() + endif () + + add_executable (listdevices src/listdevices.c) + target_link_libraries (listdevices PRIVATE libminiupnpc-shared) + target_include_directories(listdevices PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + if (NOT UPNPC_NO_INSTALL) + install (TARGETS listdevices + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + endif() +endif () if (UPNPC_BUILD_TESTS) - add_executable (testminixml testminixml.c minixml.c igd_desc_parse.c) - target_link_libraries (testminixml ${LDLIBS}) + add_library(miniupnpc-tests INTERFACE) + target_link_libraries(miniupnpc-tests INTERFACE miniupnpc-private) + target_compile_definitions(miniupnpc-tests INTERFACE MINIUPNP_STATICLIB) - add_executable (minixmlvalid minixmlvalid.c minixml.c) - target_link_libraries (minixmlvalid ${LDLIBS}) + add_executable (testminixml src/testminixml.c src/minixml.c src/igd_desc_parse.c) + target_include_directories (testminixml PRIVATE + $) + target_link_libraries (testminixml PRIVATE miniupnpc-tests) - add_executable (testupnpreplyparse testupnpreplyparse.c - minixml.c upnpreplyparse.c) - target_link_libraries (testupnpreplyparse ${LDLIBS}) + add_executable (minixmlvalid src/minixmlvalid.c src/minixml.c) + target_link_libraries (minixmlvalid PRIVATE miniupnpc-tests) - add_executable (testigddescparse testigddescparse.c - igd_desc_parse.c minixml.c miniupnpc.c miniwget.c minissdpc.c - upnpcommands.c upnpreplyparse.c minisoap.c connecthostport.c - portlistingparse.c + add_executable (testupnpreplyparse src/testupnpreplyparse.c + src/minixml.c src/upnpreplyparse.c) + target_include_directories (testupnpreplyparse PRIVATE + $) + target_link_libraries (testupnpreplyparse PRIVATE miniupnpc-tests) + + add_executable (testigddescparse src/testigddescparse.c + src/igd_desc_parse.c src/minixml.c src/miniupnpc.c src/miniwget.c src/minissdpc.c + src/upnpcommands.c src/upnpreplyparse.c src/minisoap.c src/connecthostport.c + src/portlistingparse.c src/receivedata.c src/addr_is_reserved.c ) - target_link_libraries (testigddescparse ${LDLIBS}) + target_include_directories (testigddescparse PRIVATE + $) + target_link_libraries (testigddescparse PRIVATE miniupnpc-tests) - add_executable (testminiwget testminiwget.c - miniwget.c miniupnpc.c minisoap.c upnpcommands.c minissdpc.c - upnpreplyparse.c minixml.c igd_desc_parse.c connecthostport.c - portlistingparse.c + add_executable (testminiwget src/testminiwget.c + src/miniwget.c src/miniupnpc.c src/minisoap.c src/upnpcommands.c src/minissdpc.c + src/upnpreplyparse.c src/minixml.c src/igd_desc_parse.c src/connecthostport.c + src/portlistingparse.c src/receivedata.c src/addr_is_reserved.c ) - target_link_libraries (testminiwget ${LDLIBS}) + target_include_directories (testminiwget PRIVATE + $) + target_link_libraries (testminiwget PRIVATE miniupnpc-tests) + + add_executable (testaddr_is_reserved src/testaddr_is_reserved.c + src/addr_is_reserved.c + ) + target_link_libraries (testaddr_is_reserved PRIVATE miniupnpc-tests) + + add_executable (testportlistingparse src/testportlistingparse.c + src/minixml.c src/portlistingparse.c) + target_include_directories (testportlistingparse PRIVATE + $) + target_link_libraries (testportlistingparse PRIVATE miniupnpc-tests) + + add_executable (minihttptestserver src/minihttptestserver.c) # set (UPNPC_INSTALL_TARGETS ${UPNPC_INSTALL_TARGETS} testminixml minixmlvalid testupnpreplyparse testigddescparse testminiwget) -endif (UPNPC_BUILD_TESTS) + include(CTest) + add_test(NAME validateminixml + COMMAND minixmlvalid) + add_test(NAME validateminiwget + COMMAND ${CMAKE_SOURCE_DIR}/testminiwget.sh) + set_property(TEST validateminiwget + PROPERTY ENVIRONMENT + TESTSERVER=${CMAKE_BINARY_DIR}/minihttptestserver + TESTMINIWGET=${CMAKE_BINARY_DIR}/testminiwget) + add_test(NAME validateupnpreplyparse + COMMAND ${CMAKE_SOURCE_DIR}/testupnpreplyparse.sh + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + set_property(TEST validateupnpreplyparse + PROPERTY ENVIRONMENT + TESTUPNPREPLYPARSE=${CMAKE_BINARY_DIR}/testupnpreplyparse) + add_test(NAME validateportlistingparse + COMMAND testportlistingparse) + add_test(NAME validateigddescparse1 + COMMAND testigddescparse new_LiveBox_desc.xml new_LiveBox_desc.values + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/testdesc) + add_test(NAME validateigddescparse2 + COMMAND testigddescparse linksys_WAG200G_desc.xml linksys_WAG200G_desc.values + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/testdesc) + add_test(NAME validateaddr_is_reserved + COMMAND testaddr_is_reserved) +endif () -install (TARGETS ${UPNPC_INSTALL_TARGETS} - RUNTIME DESTINATION bin - LIBRARY DESTINATION lib${LIB_SUFFIX} - ARCHIVE DESTINATION lib${LIB_SUFFIX} -) -install (FILES - miniupnpc.h - miniwget.h - upnpcommands.h - igd_desc_parse.h - upnpreplyparse.h - upnperrors.h - declspec.h - DESTINATION include/miniupnpc -) +configure_file(miniupnpc.pc.in miniupnpc.pc @ONLY) -set (CONFIGURED YES CACHE INTERNAL "") +if (NOT UPNPC_NO_INSTALL) + install (FILES + include/miniupnpc.h + include/miniwget.h + include/upnpcommands.h + include/igd_desc_parse.h + include/upnpreplyparse.h + include/upnperrors.h + include/upnpdev.h + include/miniupnpctypes.h + include/portlistingparse.h + include/miniupnpc_declspec.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/miniupnpc + ) -# vim: ts=2:sw=2 + install(FILES miniupnpc-config.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/miniupnpc + ) + + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/miniupnpc.pc + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig + ) + + install(FILES man3/miniupnpc.3 + DESTINATION ${CMAKE_INSTALL_MANDIR}/man3 + ) + + install(FILES external-ip.sh + TYPE BIN + ) +endif() + +# vim: ts=2:sw=2:expandtab diff --git a/libs/miniupnpc/Changelog.txt b/libs/miniupnpc/Changelog.txt index 70b656498..595b4f93d 100644 --- a/libs/miniupnpc/Changelog.txt +++ b/libs/miniupnpc/Changelog.txt @@ -1,6 +1,343 @@ -$Id: Changelog.txt,v 1.152 2011/07/25 18:02:11 nanard Exp $ +$Id: Changelog.txt,v 1.256 2023/06/11 23:23:29 nanard Exp $ miniUPnP client Changelog. +2023/06/05: + GetListOfPortMappings NewStartPort 0 => 1 + +2023/05/30: + CheckPinholeWorking is optional + add 60x errors from UPnP Device Architecture + +2023/01/04: + cmake: install binaries, man pages and external-ip.sh + +VERSION 2.2.4 : released 2022/10/21 + +2022/02/20: + upnpc: use of @ to replace local lan address + +2021/11/09: + python module : Allow to specify the root description url + +VERSION 2.2.3 : released 2021/09/28 + +2021/08/13: + Change directory structure : include/ and src/ directories. + +VERSION 2.2.2 : released 2021/03/03 + +2021/01/15: + miniupnpcmodule.c: throw an exception in UPnP_discover() + +2020/12/30: + Fix usage of IP_MULTICAST_IF with struct ip_mreqn + +VERSION 2.2.1 : released 2020/12/20 + +2020/11/30: + Add miniupnpc.rc for .dll description + +VERSION 2.2.0 : released 2020/11/09 + +2020/09/24: + Check properly for reserved IP addresses + +2020/09/23: + prevent infinite loop in upnpDiscover() + +2020/02/16: + Add Haiku support + +2019/10/22: + testminiwget.sh can use either "ip addr" or "ifconfig -a + +2019/10/13: + fix UPNP_GetValidIGD() when several devices are found + which are reachable from != local address + +2019/08/24: + Allow Remote Host on upnpc command line + fix error 708 description in strupnperror() + +2019/04/05: + Fix memory leak in upnpreplyparse.c with NewPortListing element + +2019/03/10: + connecthostport.c: Code simplification, error trace fix + +2019/01/23: + set timeout for select() in connecthostport() + +2018/10/31: + miniupnpcmodule.c: check return of WSAStartup() + +2018/07/14: + Fix and improve MSVC project : + Add Dll configurations + improve genminiupnpcstrings.vbs + +2018/06/18: + Fixes for windows 64-bits. + +VERSION 2.1 : released 2018/05/07 + +2018/05/07: + CMake Modernize and cleanup CMakeLists.txt + Update MS Visual Studio projects + +2018/04/30: + listdevices: show devices sorted by XML desc URL + +2018/04/26: + Small fix in miniupnpcmodule.c (python module) + Support cross compiling in Makefile.mingw + +2018/04/06: + Use SOCKET type instead of int (for Win64 compilation) + Increments API_VERSION to 17 + +2018/02/22: + Disable usage of MiniSSDPd when using -m option + +2017/12/11: + Fix buffer over run in minixml.c + Fix uninitialized variable access in upnpreplyparse.c + +2017/05/05: + Fix CVE-2017-8798 Thanks to tin/Team OSTStrom + +2016/11/11: + check strlen before memcmp in XML parsing portlistingparse.c + fix build under SOLARIS and CYGWIN + +2016/10/11: + Add python 3 compatibility to IGD test + +VERSION 2.0 : released 2016/04/19 + +2016/01/24: + change miniwget to return HTTP status code + increments API_VERSION to 16 + +2016/01/22: + Improve UPNPIGD_IsConnected() to check if WAN address is not private. + parse HTTP response status line in miniwget.c + +2015/10/26: + snprintf() overflow check. check overflow in simpleUPnPcommand2() + +2015/10/25: + fix compilation with old macs + fix compilation with mingw32 (for Appveyor) + fix python module for python <= 2.3 + +2015/10/08: + Change sameport to localport + see https://github.com/miniupnp/miniupnp/pull/120 + increments API_VERSION to 15 + +2015/09/15: + Fix buffer overflow in igd_desc_parse.c/IGDstartelt() + Discovered by Aleksandar Nikolic of Cisco Talos + +2015/08/28: + move ssdpDiscoverDevices() to minissdpc.c + +2015/08/27: + avoid unix socket leak in getDevicesFromMiniSSDPD() + +2015/08/16: + Also accept "Up" as ConnectionStatus value + +2015/07/23: + split getDevicesFromMiniSSDPD + add ttl argument to upnpDiscover() functions + increments API_VERSION to 14 + +2015/07/22: + Read USN from SSDP messages. + +2015/07/15: + Check malloc/calloc + +2015/06/16: + update getDevicesFromMiniSSDPD() to process longer minissdpd + responses + +2015/05/22: + add searchalltypes param to upnpDiscoverDevices() + increments API_VERSION to 13 + +2015/04/30: + upnpc: output version on the terminal + +2015/04/27: + _BSD_SOURCE is deprecated in favor of _DEFAULT_SOURCE + fix CMakeLists.txt COMPILE_DEFINITIONS + fix getDevicesFromMiniSSDPD() not setting scope_id + improve -r command of upnpc command line tool + +2014/11/17: + search all : + upnpDiscoverDevices() / upnpDiscoverAll() functions + listdevices executable + increment API_VERSION to 12 + validate igd_desc_parse + +2014/11/13: + increment API_VERSION to 11 + +2014/11/05: + simplified function GetUPNPUrls() + +2014/09/11: + use remoteHost arg of DeletePortMapping + +2014/09/06: + Fix python3 build + +2014/07/01: + Fix parsing of IGD2 root descriptions + +2014/06/10: + rename LIBSPEC to MINIUPNP_LIBSPEC + +2014/05/15: + Add support for IGD2 AddAnyPortMapping and DeletePortMappingRange + +2014/02/05: + handle EINPROGRESS after connect() + +2014/02/03: + minixml now handle XML comments + +VERSION 1.9 : released 2014/01/31 + +2014/01/31: + added argument remoteHost to UPNP_GetSpecificPortMappingEntry() + increment API_VERSION to 10 + +2013/12/09: + --help and -h arguments in upnpc.c + +2013/10/07: + fixed potential buffer overrun in miniwget.c + Modified UPNP_GetValidIGD() to check for ExternalIpAddress + +2013/08/01: + define MAXHOSTNAMELEN if not already done + +2013/06/06: + update upnpreplyparse to allow larger values (128 chars instead of 64) + +2013/05/14: + Update upnpreplyparse to take into account "empty" elements + validate upnpreplyparse.c code with "make check" + +2013/05/03: + Fix Solaris build thanks to Maciej Małecki + +2013/04/27: + Fix testminiwget.sh for BSD + +2013/03/23: + Fixed Makefile for *BSD + +2013/03/11: + Update Makefile to use JNAerator version 0.11 + +2013/02/11: + Fix testminiwget.sh for use with dash + Use $(DESTDIR) in Makefile + +VERSION 1.8 : released 2013/02/06 + +2012/10/16: + fix testminiwget with no IPv6 support + +2012/09/27: + Rename all include guards to not clash with C99 + (7.1.3 Reserved identifiers). + +2012/08/30: + Added -e option to upnpc program (set description for port mappings) + +2012/08/29: + Python 3 support (thanks to Christopher Foo) + +2012/08/11: + Fix a memory link in UPNP_GetValidIGD() + Try to handle scope id in link local IPv6 URL under MS Windows + +2012/07/20: + Disable HAS_IP_MREQN on DragonFly BSD + +2012/06/28: + GetUPNPUrls() now inserts scope into link-local IPv6 addresses + +2012/06/23: + More error return checks in upnpc.c + #define MINIUPNPC_GET_SRC_ADDR enables receivedata() to get scope_id + parseURL() now parses IPv6 addresses scope + new parameter for miniwget() : IPv6 address scope + increment API_VERSION to 9 + +2012/06/20: + fixed CMakeLists.txt + +2012/05/29 + Improvements in testminiwget.sh + +VERSION 1.7 : released 2012/05/24 + +2012/05/01: + Cleanup settings of CFLAGS in Makefile + Fix signed/unsigned integer comparaisons + +2012/04/20: + Allow to specify protocol with TCP or UDP for -A option + +2012/04/09: + Only try to fetch XML description once in UPNP_GetValidIGD() + Added -ansi flag to compilation, and fixed C++ comments to ANSI C comments. + +2012/04/05: + minor improvements to minihttptestserver.c + +2012/03/15: + upnperrors.c returns valid error string for unrecognized error codes + +2012/03/08: + make minihttptestserver listen on loopback interface instead of 0.0.0.0 + +2012/01/25: + Maven installation thanks to Alexey Kuznetsov + +2012/01/21: + Replace WIN32 macro by _WIN32 + +2012/01/19: + Fixes in java wrappers thanks to Alexey Kuznetsov : + https://github.com/axet/miniupnp/tree/fix-javatest/miniupnpc + Make and install .deb packages (python) thanks to Alexey Kuznetsov : + https://github.com/axet/miniupnp/tree/feature-debbuild/miniupnpc + +2012/01/07: + The multicast interface can now be specified by name with IPv4. + +2012/01/02: + Install man page + +2011/11/25: + added header to Port Mappings list in upnpc.c + +2011/10/09: + Makefile : make clean now removes jnaerator generated files. + MINIUPNPC_VERSION in miniupnpc.h (updated by make) + +2011/09/12: + added rootdescURL to UPNPUrls structure. + VERSION 1.6 : released 2011/07/25 2011/07/25: @@ -236,7 +573,7 @@ VERSION 1.2 : small modif to make Clang happy :) 2008/07/17: - #define SOAPPREFIX "s" in miniupnpc.c in order to remove SOAP-ENV... + #define SOAPPREFIX "s" in miniupnpc.c in order to remove SOAP-ENV... 2008/07/14: include declspec.h in installation (to /usr/include/miniupnpc) @@ -258,7 +595,7 @@ VERSION 1.1 : improved python module error/exception reporting. 2008/04/23: - Completely rewrite igd_desc_parse.c in order to be compatible with + Completely rewrite igd_desc_parse.c in order to be compatible with Linksys WAG200G Added testigddescparse updated python module @@ -281,7 +618,7 @@ VERSION 1.0 : improved make install :) 2007/12/22: - Adding upnperrors.c/h to provide a strupnperror() function + Adding upnperrors.c/h to provide a strupnperror() function used to translate UPnP error codes to string. 2007/12/19: @@ -363,7 +700,7 @@ VERSION 1.0 : upnpc now displays external ip address with -s or -l 2007/04/11: - changed MINIUPNPC_URL_MAXSIZE to 128 to accomodate the "BT Voyager 210" + changed MINIUPNPC_URL_MAXSIZE to 128 to accommodate the "BT Voyager 210" 2007/03/19: cleanup in miniwget.c diff --git a/libs/miniupnpc/LICENSE b/libs/miniupnpc/LICENSE index 2434c86e4..6eff8d268 100644 --- a/libs/miniupnpc/LICENSE +++ b/libs/miniupnpc/LICENSE @@ -1,27 +1,29 @@ -MiniUPnPc -Copyright (c) 2005-2011, Thomas BERNARD +BSD 3-Clause License + +Copyright (c) 2005-2023, Thomas BERNARD All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * The name of the author may not be used to endorse or promote products - derived from this software without specific prior written permission. +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/libs/miniupnpc/MANIFEST.in b/libs/miniupnpc/MANIFEST.in index 54b86f95e..84102e728 100644 --- a/libs/miniupnpc/MANIFEST.in +++ b/libs/miniupnpc/MANIFEST.in @@ -1,5 +1,10 @@ include README -include miniupnpcmodule.c +include VERSION +include LICENSE +include src/miniupnpcmodule.c include setup.py -include *.h -include libminiupnpc.a +include Makefile +include src/*.[ch] +include include/*.h +include *.h.in +include *.sh diff --git a/libs/miniupnpc/Makefile b/libs/miniupnpc/Makefile index 2f46e6d19..a19363ad5 100644 --- a/libs/miniupnpc/Makefile +++ b/libs/miniupnpc/Makefile @@ -1,214 +1,396 @@ -# $Id: Makefile,v 1.81 2011/06/21 15:24:14 nanard Exp $ +# $Id: Makefile,v 1.148 2022/10/19 22:46:06 nanard Exp $ # MiniUPnP Project # http://miniupnp.free.fr/ -# (c) 2005-2011 Thomas Bernard +# https://miniupnp.tuxfamily.org/ +# https://github.com/miniupnp/miniupnp +# (c) 2005-2022 Thomas Bernard # to install use : -# $ PREFIX=/tmp/dummylocation make install +# $ make DESTDIR=/tmp/dummylocation install # or # $ INSTALLPREFIX=/usr/local make install -# or -# make install (will go to /usr/bin, /usr/lib, etc...) -OS = $(shell uname -s) +# or +# $ make install (default INSTALLPREFIX is /usr) +OS = $(shell $(CC) -dumpmachine) +VERSION = $(shell cat VERSION) + +ifneq (, $(findstring darwin, $(OS))) +JARSUFFIX=mac +LIBTOOL ?= $(shell which libtool) +endif +ifneq (, $(findstring linux, $(OS))) +JARSUFFIX=linux +endif +ifneq (, $(findstring mingw, $(OS))$(findstring cygwin, $(OS))$(findstring msys, $(OS))) +JARSUFFIX=win32 +endif + +HAVE_IPV6 ?= yes +export HAVE_IPV6 + +# directories +INCDIR = include +SRCDIR = src +BUILD = build + CC ?= gcc #AR = gar -#CFLAGS = -O -Wall -g -DDEBUG -CFLAGS ?= -O -Wall -DNDEBUG -DMINIUPNPC_SET_SOCKET_TIMEOUT -Wstrict-prototypes -# -DNO_GETADDRINFO +#CFLAGS = -O -g +# to debug : +ASANFLAGS = -fsanitize=address -fsanitize=undefined -fsanitize=leak +#CFLAGS = -g -ggdb -O0 $(ASANFLAGS) -fno-omit-frame-pointer +#CPPFLAGS += -DDEBUG +#LDFLAGS += $(ASANFLAGS) +CFLAGS ?= -O +CFLAGS += -Wall +CFLAGS += -W -Wstrict-prototypes +CFLAGS += -fno-common +CPPFLAGS += -I$(BUILD) +CPPFLAGS += -DMINIUPNPC_SET_SOCKET_TIMEOUT +CPPFLAGS += -DMINIUPNPC_GET_SRC_ADDR +CPPFLAGS += -D_BSD_SOURCE +CPPFLAGS += -D_DEFAULT_SOURCE +ifneq (, $(findstring netbsd, $(OS))) +CPPFLAGS += -D_NETBSD_SOURCE +endif +ifeq (, $(findstring freebsd, $(OS))$(findstring darwin, $(OS))) +#CPPFLAGS += -D_POSIX_C_SOURCE=200112L +CPPFLAGS += -D_XOPEN_SOURCE=600 +endif +#CFLAGS += -ansi +#CPPFLAGS += -DNO_GETADDRINFO + +DEPFLAGS = -MM -MG + +MKDIR = mkdir -p INSTALL = install SH = /bin/sh JAVA = java # see http://code.google.com/p/jnaerator/ -JNAERATOR = jnaerator-0.9.3.jar -#following libs are needed on Solaris -#LDLIBS=-lsocket -lnsl -lresolv +#JNAERATOR = jnaerator-0.9.7.jar +#JNAERATOR = jnaerator-0.9.8-shaded.jar +#JNAERATORARGS = -library miniupnpc +#JNAERATOR = jnaerator-0.10-shaded.jar +#JNAERATOR = jnaerator-0.11-shaded.jar +# https://repo1.maven.org/maven2/com/nativelibs4java/jnaerator/0.12/jnaerator-0.12-shaded.jar +JNAERATOR = jnaerator-0.12-shaded.jar +JNAERATORARGS = -mode StandaloneJar -runtime JNAerator -library miniupnpc +#JNAERATORBASEURL = http://jnaerator.googlecode.com/files/ +JNAERATORBASEURL = https://repo1.maven.org/maven2/com/nativelibs4java/jnaerator/0.12 + +ifneq (, $(findstring sun, $(OS))$(findstring solaris, $(OS))) + LDLIBS=-lsocket -lnsl -lresolv + CPPFLAGS += -D__EXTENSIONS__ + CFLAGS += -std=c99 +endif # APIVERSION is used to build SONAME -APIVERSION = 8 +APIVERSION = 17 -SRCS = igd_desc_parse.c miniupnpc.c minixml.c minisoap.c miniwget.c \ - upnpc.c upnpcommands.c upnpreplyparse.c testminixml.c \ - minixmlvalid.c testupnpreplyparse.c minissdpc.c \ - upnperrors.c testigddescparse.c testminiwget.c \ - connecthostport.c portlistringparse.c receivedata.c +SRCS = $(wildcard $(SRCDIR)/*.c) -LIBOBJS = miniwget.o minixml.o igd_desc_parse.o minisoap.o \ +LIBOBJS = $(addprefix $(BUILD)/,miniwget.o minixml.o igd_desc_parse.o minisoap.o \ miniupnpc.o upnpreplyparse.o upnpcommands.o upnperrors.o \ - connecthostport.o portlistingparse.o receivedata.o + connecthostport.o portlistingparse.o receivedata.o upnpdev.o \ + addr_is_reserved.o) -ifneq ($(OS), AmigaOS) -CFLAGS := -fPIC $(CFLAGS) -LIBOBJS := $(LIBOBJS) minissdpc.o -endif +BUILDINCLUDES = $(addprefix $(BUILD)/, miniupnpcstrings.h) -OBJS = $(patsubst %.c,%.o,$(SRCS)) +OBJS = $(patsubst $(SRCDIR)/%.c,$(BUILD)/%.o,$(SRCS)) +DEPS = $(patsubst $(SRCDIR)/%.c,$(BUILD)/%.d,$(SRCS)) # HEADERS to install -HEADERS = miniupnpc.h miniwget.h upnpcommands.h igd_desc_parse.h \ - upnpreplyparse.h upnperrors.h miniupnpctypes.h \ - portlistingparse.h \ - declspec.h +CPPFLAGS += -I$(INCDIR) +HEADERS = $(wildcard $(INCDIR)/*.h) # library names -LIBRARY = libminiupnpc.a -ifeq ($(OS), Darwin) - SHAREDLIBRARY = libminiupnpc.dylib - SONAME = $(basename $(SHAREDLIBRARY)).$(APIVERSION).dylib - CFLAGS := -DMACOSX -D_DARWIN_C_SOURCE $(CFLAGS) -else - SHAREDLIBRARY = libminiupnpc.so - SONAME = $(SHAREDLIBRARY).$(APIVERSION) +LIBRARY = $(BUILD)/libminiupnpc.a +ifneq (, $(findstring darwin, $(OS))) + SHAREDLIBRARY = $(BUILD)/libminiupnpc.dylib + SONAME = $(notdir $(basename $(SHAREDLIBRARY))).$(APIVERSION).dylib + CPPFLAGS += -D_DARWIN_C_SOURCE +else +ifeq ($(JARSUFFIX), win32) + SHAREDLIBRARY = $(BUILD)/miniupnpc.dll +else + # Linux/BSD/etc. + SHAREDLIBRARY = $(BUILD)/libminiupnpc.so + SONAME = $(notdir $(SHAREDLIBRARY)).$(APIVERSION) +endif endif -EXECUTABLES = upnpc-static -EXECUTABLES_ADDTESTS = testminixml minixmlvalid testupnpreplyparse \ - testigddescparse testminiwget +EXECUTABLES = $(addprefix $(BUILD)/, upnpc-static listdevices) +EXECUTABLES_ADDTESTS = $(addprefix $(BUILD)/, testminixml minixmlvalid \ + testupnpreplyparse testigddescparse testminiwget testportlistingparse) -TESTMINIXMLOBJS = minixml.o igd_desc_parse.o testminixml.o +TESTMINIXMLOBJS = $(addprefix $(BUILD)/, minixml.o igd_desc_parse.o testminixml.o) -TESTMINIWGETOBJS = miniwget.o testminiwget.o connecthostport.o receivedata.o +TESTMINIWGETOBJS = $(addprefix $(BUILD)/, miniwget.o testminiwget.o connecthostport.o receivedata.o) -TESTUPNPREPLYPARSE = testupnpreplyparse.o minixml.o upnpreplyparse.o +TESTUPNPREPLYPARSE = $(addprefix $(BUILD)/, testupnpreplyparse.o minixml.o upnpreplyparse.o) -TESTIGDDESCPARSE = testigddescparse.o igd_desc_parse.o minixml.o \ +TESTPORTLISTINGPARSE = $(addprefix $(BUILD)/, testportlistingparse.o minixml.o portlistingparse.o) + +TESTADDR_IS_RESERVED = $(addprefix $(BUILD)/, testaddr_is_reserved.o addr_is_reserved.o) + +TESTIGDDESCPARSE = $(addprefix $(BUILD)/, testigddescparse.o igd_desc_parse.o minixml.o \ miniupnpc.o miniwget.o upnpcommands.o upnpreplyparse.o \ minisoap.o connecthostport.o receivedata.o \ - portlistingparse.o + portlistingparse.o addr_is_reserved.o) -ifneq ($(OS), AmigaOS) -EXECUTABLES := $(EXECUTABLES) upnpc-shared -TESTMINIWGETOBJS := $(TESTMINIWGETOBJS) minissdpc.o -TESTIGDDESCPARSE := $(TESTIGDDESCPARSE) minissdpc.o +ifeq (, $(findstring amiga, $(OS))) +ifeq (, $(findstring mingw, $(OS))$(findstring cygwin, $(OS))$(findstring msys, $(OS))) +CFLAGS += -fPIC +endif +EXECUTABLES += $(BUILD)/upnpc-shared +TESTMINIWGETOBJS += $(BUILD)/minissdpc.o +TESTIGDDESCPARSE += $(BUILD)/minissdpc.o +LIBOBJS += $(BUILD)/minissdpc.o endif +LIBDIR ?= lib # install directories -INSTALLPREFIX ?= $(PREFIX)/usr +ifeq ($(strip $(PREFIX)),) +INSTALLPREFIX ?= /usr +else +INSTALLPREFIX ?= $(PREFIX) +endif INSTALLDIRINC = $(INSTALLPREFIX)/include/miniupnpc -INSTALLDIRLIB = $(INSTALLPREFIX)/lib +INSTALLDIRLIB = $(INSTALLPREFIX)/$(LIBDIR) INSTALLDIRBIN = $(INSTALLPREFIX)/bin +INSTALLDIRMAN = $(INSTALLPREFIX)/share/man +PKGCONFIGDIR = $(INSTALLDIRLIB)/pkgconfig FILESTOINSTALL = $(LIBRARY) $(EXECUTABLES) -ifneq ($(OS), AmigaOS) -FILESTOINSTALL := $(FILESTOINSTALL) $(SHAREDLIBRARY) +ifeq (, $(findstring amiga, $(OS))) +FILESTOINSTALL += $(SHAREDLIBRARY) $(BUILD)/miniupnpc.pc endif -.PHONY: install clean depend all check everything \ - installpythonmodule -# validateminixml validateminiwget +.PHONY: install clean depend all check test everything \ + installpythonmodule updateversion all: $(LIBRARY) $(EXECUTABLES) -check: validateminixml validateminiwget +test: check + +check: validateminixml validateminiwget validateupnpreplyparse \ + validateportlistingparse validateigddescparse validateaddr_is_reserved everything: all $(EXECUTABLES_ADDTESTS) -pythonmodule: $(LIBRARY) miniupnpcmodule.c setup.py - python setup.py build +pythonmodule: $(LIBRARY) $(SRCDIR)/miniupnpcmodule.c setup.py + MAKE=$(MAKE) python setup.py build touch $@ installpythonmodule: pythonmodule - python setup.py install + MAKE=$(MAKE) python setup.py install -validateminixml: minixmlvalid - @echo "minixml validation test" - ./minixmlvalid +pythonmodule3: $(LIBRARY) $(SRCDIR)/miniupnpcmodule.c setup.py + MAKE=$(MAKE) python3 setup.py build touch $@ -validateminiwget: testminiwget minihttptestserver testminiwget.sh +installpythonmodule3: pythonmodule3 + MAKE=$(MAKE) python3 setup.py install + +validateminixml: $(BUILD)/minixmlvalid + @echo "minixml validation test" + ./$< + touch $@ + +validateminiwget: testminiwget.sh $(BUILD)/testminiwget $(BUILD)/minihttptestserver @echo "miniwget validation test" - ./testminiwget.sh + ./$< + touch $@ + +validateupnpreplyparse: testupnpreplyparse.sh $(BUILD)/testupnpreplyparse + @echo "upnpreplyparse validation test" + ./$< + touch $@ + +validateportlistingparse: $(BUILD)/testportlistingparse + @echo "portlistingparse validation test" + ./$< + touch $@ + +validateigddescparse: $(BUILD)/testigddescparse + @echo "igd desc parse validation test" + ./$< testdesc/new_LiveBox_desc.xml testdesc/new_LiveBox_desc.values + ./$< testdesc/linksys_WAG200G_desc.xml testdesc/linksys_WAG200G_desc.values + touch $@ + +validateaddr_is_reserved: $(BUILD)/testaddr_is_reserved + @echo "addr_is_reserved() validation test" + ./$< touch $@ clean: - $(RM) $(LIBRARY) $(SHAREDLIBRARY) $(EXECUTABLES) $(OBJS) miniupnpcstrings.h + $(RM) $(LIBRARY) $(SHAREDLIBRARY) $(EXECUTABLES) $(OBJS) $(BUILDINCLUDES) + $(RM) $(EXECUTABLES_ADDTESTS) # clean python stuff - $(RM) pythonmodule validateminixml + $(RM) pythonmodule pythonmodule3 + $(RM) validateminixml validateminiwget validateupnpreplyparse + $(RM) validateigddescparse + $(RM) minihttptestserver + $(RM) testaddr_is_reserved $(RM) -r build/ dist/ #python setup.py clean + # clean jnaerator stuff + $(RM) _jnaerator.* java/miniupnpc_$(OS).jar -install: $(FILESTOINSTALL) - $(INSTALL) -d $(INSTALLDIRINC) - $(INSTALL) -m 644 $(HEADERS) $(INSTALLDIRINC) - $(INSTALL) -d $(INSTALLDIRLIB) - $(INSTALL) -m 644 $(LIBRARY) $(INSTALLDIRLIB) -ifneq ($(OS), AmigaOS) - $(INSTALL) -m 644 $(SHAREDLIBRARY) $(INSTALLDIRLIB)/$(SONAME) - ln -fs $(SONAME) $(INSTALLDIRLIB)/$(SHAREDLIBRARY) +distclean: clean + $(RM) $(JNAERATOR) java/*.jar java/*.class out.errors.txt + +updateversion: include/miniupnpc.h + cp $< $<.bak + sed 's/\(.*MINIUPNPC_API_VERSION\s\+\)[0-9]\+/\1$(APIVERSION)/' < $<.bak > $< + +install: updateversion $(FILESTOINSTALL) + $(INSTALL) -d $(DESTDIR)$(INSTALLDIRINC) + $(INSTALL) -m 644 $(HEADERS) $(DESTDIR)$(INSTALLDIRINC) + $(INSTALL) -d $(DESTDIR)$(INSTALLDIRLIB) + $(INSTALL) -m 644 $(LIBRARY) $(DESTDIR)$(INSTALLDIRLIB) +ifeq (, $(findstring amiga, $(OS))) + $(INSTALL) -m 644 $(SHAREDLIBRARY) $(DESTDIR)$(INSTALLDIRLIB)/$(SONAME) + ln -fs $(SONAME) $(DESTDIR)$(INSTALLDIRLIB)/$(notdir $(SHAREDLIBRARY)) + $(INSTALL) -d $(DESTDIR)$(PKGCONFIGDIR) + $(INSTALL) -m 644 $(BUILD)/miniupnpc.pc $(DESTDIR)$(PKGCONFIGDIR) endif - $(INSTALL) -d $(INSTALLDIRBIN) -ifeq ($(OS), AmigaOS) - $(INSTALL) -m 755 upnpc-static $(INSTALLDIRBIN)/upnpc + $(INSTALL) -d $(DESTDIR)$(INSTALLDIRBIN) +ifneq (, $(findstring amiga, $(OS))) + $(INSTALL) -m 755 $(BUILD)/upnpc-static $(DESTDIR)$(INSTALLDIRBIN)/upnpc else - $(INSTALL) -m 755 upnpc-shared $(INSTALLDIRBIN)/upnpc + $(INSTALL) -m 755 $(BUILD)/upnpc-shared $(DESTDIR)$(INSTALLDIRBIN)/upnpc endif - $(INSTALL) -m 755 external-ip.sh $(INSTALLDIRBIN)/external-ip + $(INSTALL) -m 755 external-ip.sh $(DESTDIR)$(INSTALLDIRBIN)/external-ip +ifeq (, $(findstring amiga, $(OS))) + $(INSTALL) -d $(DESTDIR)$(INSTALLDIRMAN)/man3 + $(INSTALL) -m 644 man3/miniupnpc.3 $(DESTDIR)$(INSTALLDIRMAN)/man3/miniupnpc.3 +ifneq (, $(findstring linux, $(OS))) + gzip -f $(DESTDIR)$(INSTALLDIRMAN)/man3/miniupnpc.3 +endif +endif + +install-static: updateversion $(FILESTOINSTALL) + $(INSTALL) -d $(DESTDIR)$(INSTALLDIRINC) + $(INSTALL) -m 644 $(HEADERS) $(DESTDIR)$(INSTALLDIRINC) + $(INSTALL) -d $(DESTDIR)$(INSTALLDIRLIB) + $(INSTALL) -m 644 $(LIBRARY) $(DESTDIR)$(INSTALLDIRLIB) + $(INSTALL) -d $(DESTDIR)$(INSTALLDIRBIN) + $(INSTALL) -m 755 external-ip.sh $(DESTDIR)$(INSTALLDIRBIN)/external-ip cleaninstall: - $(RM) -r $(INSTALLDIRINC) - $(RM) $(INSTALLDIRLIB)/$(LIBRARY) - $(RM) $(INSTALLDIRLIB)/$(SHAREDLIBRARY) + $(RM) -r $(DESTDIR)$(INSTALLDIRINC) + $(RM) $(DESTDIR)$(INSTALLDIRLIB)/$(LIBRARY) + $(RM) $(DESTDIR)$(INSTALLDIRLIB)/$(SHAREDLIBRARY) -depend: - makedepend -Y -- $(CFLAGS) -- $(SRCS) 2>/dev/null +$(BUILD)/miniupnpc.pc: VERSION + @$(MKDIR) $(@D) + $(RM) $@ + echo "prefix=$(INSTALLPREFIX)" >> $@ + echo "exec_prefix=\$${prefix}" >> $@ + echo "libdir=\$${exec_prefix}/$(LIBDIR)" >> $@ + echo "includedir=\$${prefix}/include" >> $@ + echo "" >> $@ + echo "Name: miniUPnPc" >> $@ + echo "Description: UPnP IGD client lightweight library" >> $@ + echo "URL: https://miniupnp.tuxfamily.org/" >> $@ + echo "Version: $(VERSION)" >> $@ + echo "Libs: -L\$${libdir} -lminiupnpc" >> $@ + echo "Cflags: -I\$${includedir}" >> $@ + +depend: $(DEPS) $(LIBRARY): $(LIBOBJS) +ifneq (, $(findstring darwin, $(OS))) + $(LIBTOOL) -static -o $@ $? +else $(AR) crs $@ $? +endif $(SHAREDLIBRARY): $(LIBOBJS) -ifeq ($(OS), Darwin) - $(CC) -dynamiclib $(LDFLAGS) -Wl,-install_name,$(SONAME) -o $@ $^ +ifneq (, $(findstring darwin, $(OS))) +# $(CC) -dynamiclib $(LDFLAGS) -Wl,-install_name,$(SONAME) -o $@ $^ + $(CC) -dynamiclib $(LDFLAGS) -Wl,-install_name,$(INSTALLDIRLIB)/$(SONAME) -o $@ $^ else $(CC) -shared $(LDFLAGS) -Wl,-soname,$(SONAME) -o $@ $^ endif -upnpc-static: upnpc.o $(LIBRARY) $(LDLIBS) - $(CC) $(LDFLAGS) -o $@ $^ +$(BUILD)/%.o: $(SRCDIR)/%.c $(BUILD)/%.d + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< -upnpc-shared: upnpc.o $(SHAREDLIBRARY) $(LDLIBS) - $(CC) $(LDFLAGS) -o $@ $^ +$(DEPS): $(BUILDINCLUDES) -testminixml: $(TESTMINIXMLOBJS) +$(BUILD)/%.d: $(SRCDIR)/%.c + @$(MKDIR) $(@D) + $(CC) $(CPPFLAGS) $(DEPFLAGS) -MT $@ -o $@ $< -testminiwget: $(TESTMINIWGETOBJS) +$(BUILD)/upnpc-static: $(BUILD)/upnpc.o $(LIBRARY) + $(CC) $(LDFLAGS) -o $@ $^ $(LOADLIBES) $(LDLIBS) -minixmlvalid: minixml.o minixmlvalid.o +$(BUILD)/upnpc-shared: $(BUILD)/upnpc.o $(SHAREDLIBRARY) + $(CC) $(LDFLAGS) -o $@ $^ $(LOADLIBES) $(LDLIBS) -testupnpreplyparse: $(TESTUPNPREPLYPARSE) +$(BUILD)/listdevices: $(BUILD)/listdevices.o $(LIBRARY) -testigddescparse: $(TESTIGDDESCPARSE) +$(BUILD)/testminixml: $(TESTMINIXMLOBJS) -miniupnpcstrings.h: miniupnpcstrings.h.in updateminiupnpcstrings.sh - $(SH) updateminiupnpcstrings.sh +$(BUILD)/testminiwget: $(TESTMINIWGETOBJS) -jar: $(SHAREDLIBRARY) - $(JAVA) -jar $(JNAERATOR) -library miniupnpc miniupnpc.h declspec.h upnpcommands.h upnpreplyparse.h igd_desc_parse.h miniwget.h upnperrors.h $(SHAREDLIBRARY) -package fr.free.miniupnp -o . -jar java/miniupnpc_$(OS).jar -v +$(BUILD)/minixmlvalid: $(addprefix $(BUILD)/, minixml.o minixmlvalid.o) + +$(BUILD)/testupnpreplyparse: $(TESTUPNPREPLYPARSE) + +$(BUILD)/testigddescparse: $(TESTIGDDESCPARSE) + +$(BUILD)/testportlistingparse: $(TESTPORTLISTINGPARSE) + +$(BUILD)/testaddr_is_reserved: $(TESTADDR_IS_RESERVED) + +$(BUILD)/miniupnpcstrings.h: miniupnpcstrings.h.in updateminiupnpcstrings.sh VERSION + @$(MKDIR) $(@D) + $(SH) updateminiupnpcstrings.sh $@ $< + +# ftp tool supplied with OpenBSD can download files from http. +jnaerator-%.jar: + wget $(JNAERATORBASEURL)/$@ || \ + curl -o $@ $(JNAERATORBASEURL)/$@ || \ + ftp $(JNAERATORBASEURL)/$@ + +jar: $(SHAREDLIBRARY) $(JNAERATOR) + $(JAVA) -jar $(JNAERATOR) $(JNAERATORARGS) \ + miniupnpc.h miniupnpc_declspec.h upnpcommands.h upnpreplyparse.h \ + igd_desc_parse.h miniwget.h upnperrors.h $(SHAREDLIBRARY) \ + -package fr.free.miniupnp -o . -jar java/miniupnpc_$(JARSUFFIX).jar -v + +mvn_install: + mvn install:install-file -Dfile=java/miniupnpc_$(JARSUFFIX).jar \ + -DgroupId=com.github \ + -DartifactId=miniupnp \ + -Dversion=$(VERSION) \ + -Dpackaging=jar \ + -Dclassifier=$(JARSUFFIX) \ + -DgeneratePom=true \ + -DcreateChecksum=true + +# make .deb packages +deb: /usr/share/pyshared/stdeb all + (python setup.py --command-packages=stdeb.command bdist_deb) + +# install .deb packages +ideb: + (sudo dpkg -i deb_dist/*.deb) + +/usr/share/pyshared/stdeb: /usr/share/doc/python-all-dev + (sudo apt-get install python-stdeb) + +/usr/share/doc/python-all-dev: + (sudo apt-get install python-all-dev) minihttptestserver: minihttptestserver.o -# DO NOT DELETE THIS LINE -- make depend depends on it. +print-%: + @echo "$* = $($*)" -igd_desc_parse.o: igd_desc_parse.h -miniupnpc.o: miniupnpc.h declspec.h igd_desc_parse.h minissdpc.h miniwget.h -miniupnpc.o: minisoap.h minixml.h upnpcommands.h upnpreplyparse.h -miniupnpc.o: portlistingparse.h miniupnpctypes.h connecthostport.h -miniupnpc.o: receivedata.h -minixml.o: minixml.h -minisoap.o: minisoap.h miniupnpcstrings.h -miniwget.o: miniupnpcstrings.h miniwget.h declspec.h connecthostport.h -miniwget.o: receivedata.h -upnpc.o: miniwget.h declspec.h miniupnpc.h igd_desc_parse.h upnpcommands.h -upnpc.o: upnpreplyparse.h portlistingparse.h miniupnpctypes.h upnperrors.h -upnpcommands.o: upnpcommands.h upnpreplyparse.h portlistingparse.h declspec.h -upnpcommands.o: miniupnpctypes.h miniupnpc.h igd_desc_parse.h -upnpreplyparse.o: upnpreplyparse.h minixml.h -testminixml.o: minixml.h igd_desc_parse.h -minixmlvalid.o: minixml.h -testupnpreplyparse.o: upnpreplyparse.h -minissdpc.o: minissdpc.h miniupnpc.h declspec.h igd_desc_parse.h codelength.h -upnperrors.o: upnperrors.h declspec.h upnpcommands.h upnpreplyparse.h -upnperrors.o: portlistingparse.h miniupnpctypes.h miniupnpc.h -upnperrors.o: igd_desc_parse.h -testigddescparse.o: igd_desc_parse.h minixml.h miniupnpc.h declspec.h -testminiwget.o: miniwget.h declspec.h -connecthostport.o: connecthostport.h -receivedata.o: receivedata.h +ifneq ($(MAKECMDGOALS),clean) +-include $(DEPS) +endif diff --git a/libs/miniupnpc/Makefile.mingw b/libs/miniupnpc/Makefile.mingw index 2b66211a7..8c7f1c1cb 100644 --- a/libs/miniupnpc/Makefile.mingw +++ b/libs/miniupnpc/Makefile.mingw @@ -1,93 +1,181 @@ -# $Id: Makefile.mingw,v 1.16 2011/04/18 17:39:31 nanard Exp $ +# $Id: Makefile.mingw,v 1.35 2022/10/16 05:28:15 nanard Exp $ # Miniupnp project. -# http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ -# (c) 2005-2011 Thomas Bernard +# http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ +# (c) 2005-2020 Thomas Bernard # This Makefile is made for MinGW # -CC = gcc +# To cross compile on a *nix machine : +# make -f Makefile.mingw DLLWRAP=mingw32-dllwrap CC=mingw32-gcc AR=mingw32-ar WINDRES=mingw32-windres +# +SRCDIR = src +INCDIR = include + +CC ?= gcc +SETUP_COMPILER_FLAG?= DLLWRAP = dllwrap -#CFLAGS = -Wall -g -DDEBUG -D_WIN32_WINNT=0X501 -CFLAGS = -Wall -Os -DNDEBUG -D_WIN32_WINNT=0X501 -LDLIBS = -lws2_32 -liphlpapi +WINDRES = windres SH = /bin/sh -# -lwsock32 -# -liphlpapi is used for GetBestRoute() +ZIP = zip +ifeq ($(OS),Windows_NT) +RM = del +else +RM = rm -f +endif + +CFLAGS ?= -Os +CFLAGS += -Wall +CFLAGS += -W -Wstrict-prototypes + +CPPFLAGS += -DNDEBUG -D_WIN32_WINNT=0x501 +CPPFLAGS += -Iinclude +CPPFLAGS += -I. + +# -liphlpapi is needed for GetBestRoute() and GetIpAddrTable() +LDLIBS = -lws2_32 -liphlpapi + PYTHON=\utils\python25\python OBJS=miniwget.o minixml.o igd_desc_parse.o minisoap.o \ + minissdpc.o \ miniupnpc.o upnpreplyparse.o upnpcommands.o upnperrors.o \ - connecthostport.o portlistingparse.o receivedata.o -OBJSDLL=$(addprefix dll/, $(OBJS)) + connecthostport.o portlistingparse.o receivedata.o \ + upnpdev.o addr_is_reserved.o +OBJSDLL=$(addprefix dll-, $(OBJS)) winres.o +BINARIES=upnpc-static.exe upnpc-shared.exe \ + listdevices-static.exe listdevices-shared.exe \ + miniupnpc.dll libminiupnpc.a \ + testminixml.exe +ifneq ($(GITHUB_SHA),) +COMMITREF=$(GITHUB_SHA) +else +ifneq ($(CI_COMMIT_SHORT_SHA),) +COMMITREF=$(CI_COMMIT_SHORT_SHA) +else +COMMITREF=$(shell git rev-parse --short HEAD) +endif +endif +DISTFILE:=$(shell echo "miniupnpc-bin-win32-`cat VERSION`-$(COMMITREF).zip") -all: init upnpc-static upnpc-shared testminixml libminiupnpc.a miniupnpc.dll +LIBDIR ?= lib +# install directories +ifeq ($(strip $(PREFIX)),) +INSTALLPREFIX ?= /usr +else +INSTALLPREFIX ?= $(PREFIX) +endif -init: - mkdir dll - echo init > init +.PHONY: all dist clean + +all: $(BINARIES) + +dist: $(DISTFILE) clean: - $(RM) upnpc testminixml *.o - $(RM) dll\*.o + $(RM) miniupnpcstrings.h + $(RM) *.o $(RM) *.exe - $(RM) miniupnpc.dll + $(RM) miniupnpc.dll miniupnpc.lib miniupnpc.dll.def $(RM) libminiupnpc.a + $(RM) $(DISTFILE) + +$(DISTFILE): $(BINARIES) + $(ZIP) $@ *.exe *.dll *.lib *.def *.a LICENSE README Changelog.txt libminiupnpc.a: $(OBJS) $(AR) cr $@ $? pythonmodule: libminiupnpc.a - $(PYTHON) setupmingw32.py build --compiler=mingw32 + $(PYTHON) setupmingw32.py build $(SETUP_COMPILER_FLAG) $(PYTHON) setupmingw32.py install --skip-build + $(PYTHON) setupmingw32.py bdist_wheel --skip-build -miniupnpc.dll: libminiupnpc.a $(OBJSDLL) - $(DLLWRAP) -k --driver-name "$(CC)" \ +miniupnpc.dll: miniupnpc.def $(OBJSDLL) + $(DLLWRAP) -k --driver-name $(CC) \ --def miniupnpc.def \ --output-def miniupnpc.dll.def \ --implib miniupnpc.lib -o $@ \ $(OBJSDLL) $(LDLIBS) miniupnpc.lib: miniupnpc.dll - echo $@ generated with $< -dll/upnpc.o: upnpc.o - echo $@ generated with $< +%.o: $(SRCDIR)/%.c + $(CC) $(CFLAGS) $(CPPFLAGS) -DMINIUPNP_STATICLIB -c -o $@ $< -.c.o: - $(CC) $(CFLAGS) $(CPPFLAGS) -DSTATICLIB -c -o $@ $< - $(CC) $(CFLAGS) $(CPPFLAGS) -DMINIUPNP_EXPORTS -c -o dll/$@ $< +dll-%.o: $(SRCDIR)/%.c + $(CC) $(CFLAGS) $(CPPFLAGS) -DMINIUPNP_EXPORTS -c -o $@ $< -upnpc.o: - $(CC) $(CFLAGS) $(CPPFLAGS) -DSTATICLIB -c -o $@ $< - $(CC) $(CFLAGS) $(CPPFLAGS) -c -o dll/$@ $< +%-shared.o: $(SRCDIR)/%.c + $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $< -upnpc-static: upnpc.o libminiupnpc.a +# --enable-stdcall-fixup +%-static.exe: %.o libminiupnpc.a + $(CC) -static -o $@ $^ $(LDLIBS) + +%-shared.exe: %-shared.o miniupnpc.lib $(CC) -o $@ $^ $(LDLIBS) -upnpc-shared: dll/upnpc.o miniupnpc.lib - $(CC) -o $@ $^ $(LDLIBS) +# To make miniupnpcstrings.h from miniupnpcstrings.h.in we either +# use a custom executable (if running make under windows) or use +# sed (if cross compiling from another platform). +ifeq ($(OS),Windows_NT) +wingenminiupnpcstrings.exe: wingenminiupnpcstrings.c + $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $^ -wingenminiupnpcstrings: wingenminiupnpcstrings.o +miniupnpcstrings.h: miniupnpcstrings.h.in wingenminiupnpcstrings.exe VERSION + .\wingenminiupnpcstrings.exe $< $@ rc_version.h -wingenminiupnpcstrings.o: wingenminiupnpcstrings.c +rc_version.h: miniupnpcstrings.h +else +miniupnpcstrings.h: miniupnpcstrings.h.in VERSION + sed 's|OS_STRING ".*"|OS_STRING "Windows/Mingw32"|' $< | \ + sed 's|MINIUPNPC_VERSION_STRING ".*"|MINIUPNPC_VERSION_STRING "$(shell cat VERSION)"|' > $@ -miniupnpcstrings.h: miniupnpcstrings.h.in wingenminiupnpcstrings - -$(SH) updateminiupnpcstrings.sh - -wingenminiupnpcstrings $< $@ +rc_version.h: VERSION + echo "#define LIBMINIUPNPC_DOTTED_VERSION \"$(shell cat VERSION)\"" > $@.tmp + echo "#define LIBMINIUPNPC_MAJOR_VERSION $(shell cat VERSION|cut -d. -f1)" >> $@.tmp + echo "#define LIBMINIUPNPC_MINOR_VERSION $(shell cat VERSION|cut -d. -f2)" >> $@.tmp + echo "#define LIBMINIUPNPC_MICRO_VERSION $(shell cat VERSION|cut -d. -f3)" >> $@.tmp + mv $@.tmp $@ +endif -minixml.o: minixml.c minixml.h miniupnpcstrings.h +miniupnpc.pc: VERSION + $(RM) $@ + echo "prefix=$(INSTALLPREFIX)" >> $@ + echo "exec_prefix=\$${prefix}" >> $@ + echo "libdir=\$${exec_prefix}/$(LIBDIR)" >> $@ + echo "includedir=\$${prefix}/include" >> $@ + echo "" >> $@ + echo "Name: miniUPnPc" >> $@ + echo "Description: UPnP IGD client lightweight library" >> $@ + echo "Version: $(shell cat VERSION)" >> $@ + echo "Libs: -L\$${libdir} -lminiupnpc" >> $@ + echo "Cflags: -I\$${includedir}" >> $@ -upnpc.o: upnpc.c miniwget.h minisoap.h miniupnpc.h igd_desc_parse.h upnpreplyparse.h upnpcommands.h upnperrors.h +winres.o: miniupnpc.rc rc_version.h + $(WINDRES) -D INTERNAL_NAME=\\\"miniupnpc.dll\\0\\\" -i $< -o $@ -miniwget.o: miniwget.c miniwget.h miniupnpcstrings.h connecthostport.h +testminixml.exe: testminixml.o minixml.o igd_desc_parse.o + $(CC) -static -o $@ $^ -minisoap.o: minisoap.c minisoap.h miniupnpcstrings.h +minixml.o: $(SRCDIR)/minixml.c $(SRCDIR)/minixml.h -miniupnpc.o: miniupnpc.c miniupnpc.h minisoap.h miniwget.h minixml.h +upnpc.o: include/miniwget.h $(SRCDIR)/minisoap.h include/miniupnpc.h include/igd_desc_parse.h +upnpc.o: include/upnpreplyparse.h include/upnpcommands.h include/upnperrors.h miniupnpcstrings.h -igd_desc_parse.o: igd_desc_parse.c igd_desc_parse.h +miniwget.o: $(SRCDIR)/miniwget.c include/miniwget.h miniupnpcstrings.h $(SRCDIR)/connecthostport.h -testminixml: minixml.o igd_desc_parse.o testminixml.c +minisoap.o: $(SRCDIR)/minisoap.c $(SRCDIR)/minisoap.h miniupnpcstrings.h -upnpreplyparse.o: upnpreplyparse.c upnpreplyparse.h minixml.h +miniupnpc.o: $(SRCDIR)/miniupnpc.c include/miniupnpc.h $(SRCDIR)/minisoap.h \ + include/miniwget.h $(SRCDIR)/minixml.h $(SRCDIR)/addr_is_reserved.h -upnpcommands.o: upnpcommands.c upnpcommands.h upnpreplyparse.h miniupnpc.h portlistingparse.h +igd_desc_parse.o: $(SRCDIR)/igd_desc_parse.c include/igd_desc_parse.h + +upnpreplyparse.o: $(SRCDIR)/upnpreplyparse.c include/upnpreplyparse.h $(SRCDIR)/minixml.h + +upnpcommands.o: $(SRCDIR)/upnpcommands.c include/upnpcommands.h include/upnpreplyparse.h \ + include/miniupnpc.h include/portlistingparse.h + +minissdpc.o: $(SRCDIR)/minissdpc.c $(SRCDIR)/minissdpc.h $(SRCDIR)/receivedata.h + +upnpdev.o: $(SRCDIR)/upnpdev.c include/upnpdev.h diff --git a/libs/miniupnpc/README b/libs/miniupnpc/README index 12c7fed35..aa8ad2024 100644 --- a/libs/miniupnpc/README +++ b/libs/miniupnpc/README @@ -1,18 +1,20 @@ Project: miniupnp -Project web page: http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ +Project web page: http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ +github: https://github.com/miniupnp/miniupnp Author: Thomas Bernard -Copyright (c) 2005-2011 Thomas Bernard +Copyright (c) 2005-2023 Thomas Bernard This software is subject to the conditions detailed in the LICENSE file provided within this distribution. -For the comfort of Win32 users, bsdqueue.h is included in the distribution. -Its licence is included in the header of the file. -bsdqueue.h is a copy of the sys/queue.h of an OpenBSD system. -* miniupnp Client * +* miniUPnP Client - miniUPnPc * To compile, simply run 'gmake' (could be 'make' on your system). Under win32, to compile with MinGW, type "mingw32make.bat". +MS Visual C solution and project files are supplied in the msvc/ subdirectory. +The miniupnpc library is available as a static library or as a DLL : +define MINIUPNP_STATICLIB if you want to link against the static library. + The compilation is known to work under linux, FreeBSD, OpenBSD, MacOS X, AmigaOS and cygwin. The official AmigaOS4.1 SDK was used for AmigaOS4 and GeekGadgets for AmigaOS3. @@ -23,7 +25,7 @@ To install the library and headers on the system use : > make install > exit -alternatively, to install in a specific location, use : +alternatively, to install into a specific location, use : > INSTALLPREFIX=/usr/local make install upnpc.c is a sample client using the libminiupnpc. @@ -31,6 +33,7 @@ To use the libminiupnpc in your application, link it with libminiupnpc.a (or .so) and use the following functions found in miniupnpc.h, upnpcommands.h and miniwget.h : - upnpDiscover() +- UPNP_GetValidIGD() - miniwget() - parserootdesc() - GetUPNPUrls() @@ -41,9 +44,10 @@ and -lminiupnpc for the link Discovery process is speeded up when MiniSSDPd is running on the machine. + * Python module * -you can build a python module with 'make pythonmodule' +you can build a python module with 'make pythonmodule' and install it with 'make installpythonmodule'. setup.py (and setupmingw32.py) are included in the distribution. @@ -55,5 +59,34 @@ If you are using libminiupnpc in your application, please send me an email ! For any question, you can use the web forum : -http://miniupnp.tuxfamily.org/forum/ +https://miniupnp.tuxfamily.org/forum/ +Bugs should be reported on GitHub : +https://github.com/miniupnp/miniupnp/issues + +* Linux firewall configuration for UPnP clients * + +Due to how UPnP protocol is designed, unicast responses to UPnP multicast client +requests are not tracked by Linux netfilter. And therefore netfilter executes +default action for them (which is in most cases DROP response packet). + +To workaround this limitation, custom ipset hash table can be used. It is +supported since Linux kernel >= 2.6.39. + +Rules for IPv4: +$ ipset create upnp hash:ip,port timeout 3 +$ iptables -A OUTPUT -d 239.255.255.250/32 -p udp -m udp --dport 1900 -j SET --add-set upnp src,src --exist +$ iptables -A INPUT -p udp -m set --match-set upnp dst,dst -j ACCEPT +$ iptables -A INPUT -d 239.255.255.250/32 -p udp -m udp --dport 1900 -j ACCEPT + +Rules for IPv6: +$ ipset create upnp6 hash:ip,port timeout 3 family inet6 +$ ip6tables -A OUTPUT -d ff02::c/128 -p udp -m udp --dport 1900 -j SET --add-set upnp6 src,src --exist +$ ip6tables -A OUTPUT -d ff05::c/128 -p udp -m udp --dport 1900 -j SET --add-set upnp6 src,src --exist +$ ip6tables -A INPUT -p udp -m set --match-set upnp6 dst,dst -j ACCEPT +$ ip6tables -A INPUT -d ff02::c/128 -p udp -m udp --dport 1900 -j ACCEPT +$ ip6tables -A INPUT -d ff05::c/128 -p udp -m udp --dport 1900 -j ACCEPT + +Detailed description is available on: +https://serverfault.com/a/911286 +https://unix.stackexchange.com/a/444804 diff --git a/libs/miniupnpc/VERSION b/libs/miniupnpc/VERSION index 810ee4e91..21bb5e156 100644 --- a/libs/miniupnpc/VERSION +++ b/libs/miniupnpc/VERSION @@ -1 +1 @@ -1.6 +2.2.5 diff --git a/libs/miniupnpc/apiversions.txt b/libs/miniupnpc/apiversions.txt new file mode 100644 index 000000000..d6a0bc3fc --- /dev/null +++ b/libs/miniupnpc/apiversions.txt @@ -0,0 +1,178 @@ +$Id: apiversions.txt,v 1.10 2018/04/06 10:53:13 nanard Exp $ + +Differences in API between miniUPnPc versions + +API version 17 + change struct UPNPDev + move getHTTPResponse() to miniwget_private.h + updated macro : + #define MINIUPNPC_API_VERSION 17 + +API version 16 + added "status_code" argument to getHTTPResponse(), miniwget() and miniwget_getaddr() + updated macro : + #define MINIUPNPC_API_VERSION 16 + +API version 15 + changed "sameport" argument of upnpDiscover() upnpDiscoverAll() upnpDiscoverDevice() + to "localport". When 0 or 1, behaviour is not changed, but it can take + any other value between 2 and 65535 + Existing programs should be compatible + updated macro : + #define MINIUPNPC_API_VERSION 15 + +API version 14 +miniupnpc.h + add ttl argument to upnpDiscover() upnpDiscoverAll() upnpDiscoverDevice() + upnpDiscoverDevices() + getDevicesFromMiniSSDPD() : + connectToMiniSSDPD() / disconnectFromMiniSSDPD() + requestDevicesFromMiniSSDPD() / receiveDevicesFromMiniSSDPD() + updated macro : + #define MINIUPNPC_API_VERSION 14 + +API version 13 +miniupnpc.h: + add searchalltype param to upnpDiscoverDevices() function + updated macro : + #define MINIUPNPC_API_VERSION 13 + +API version 12 +miniupnpc.h : + add upnpDiscoverAll() / upnpDiscoverDevice() / upnpDiscoverDevices() + functions + updated macros : + #define MINIUPNPC_API_VERSION 12 + +API version 11 + +upnpreplyparse.h / portlistingparse.h : + removed usage of sys/queue.h / bsdqueue.h + +miniupnpc.h: + updated macros : + #define MINIUPNPC_API_VERSION 11 + +====================== miniUPnPc version 1.9 ====================== +API version 10 + +upnpcommands.h: + added argument remoteHost to UPNP_GetSpecificPortMappingEntry() + +miniupnpc.h: + updated macros : + #define MINIUPNPC_VERSION "1.9" + #define MINIUPNPC_API_VERSION 10 + +====================== miniUPnPc version 1.8 ====================== +API version 9 + +miniupnpc.h: + updated macros : + #define MINIUPNPC_VERSION "1.8" + #define MINIUPNPC_API_VERSION 9 + added "unsigned int scope_id;" to struct UPNPDev + added scope_id argument to GetUPNPUrls() + + + +====================== miniUPnPc version 1.7 ====================== +API version 8 + +miniupnpc.h : + add new macros : + #define MINIUPNPC_VERSION "1.7" + #define MINIUPNPC_API_VERSION 8 + add rootdescURL to struct UPNPUrls + + + +====================== miniUPnPc version 1.6 ====================== +API version 8 + +Adding support for IPv6. +igd_desc_parse.h : + struct IGDdatas_service : + add char presentationurl[MINIUPNPC_URL_MAXSIZE]; + struct IGDdatas : + add struct IGDdatas_service IPv6FC; +miniupnpc.h : + new macros : + #define UPNPDISCOVER_SUCCESS (0) + #define UPNPDISCOVER_UNKNOWN_ERROR (-1) + #define UPNPDISCOVER_SOCKET_ERROR (-101) + #define UPNPDISCOVER_MEMORY_ERROR (-102) + simpleUPnPcommand() prototype changed (but is normaly not used by API users) + add arguments ipv6 and error to upnpDiscover() : + struct UPNPDev * + upnpDiscover(int delay, const char * multicastif, + const char * minissdpdsock, int sameport, + int ipv6, + int * error); + add controlURL_6FC member to struct UPNPUrls : + struct UPNPUrls { + char * controlURL; + char * ipcondescURL; + char * controlURL_CIF; + char * controlURL_6FC; + }; + +upnpcommands.h : + add leaseDuration argument to UPNP_AddPortMapping() + add desc, enabled and leaseDuration arguments to UPNP_GetSpecificPortMappingEntry() + add UPNP_GetListOfPortMappings() function (IGDv2) + add IGDv2 IPv6 related functions : + UPNP_GetFirewallStatus() + UPNP_GetOutboundPinholeTimeout() + UPNP_AddPinhole() + UPNP_UpdatePinhole() + UPNP_DeletePinhole() + UPNP_CheckPinholeWorking() + UPNP_GetPinholePackets() + + + +====================== miniUPnPc version 1.5 ====================== +API version 5 + +new function : +int UPNPIGD_IsConnected(struct UPNPUrls *, struct IGDdatas *); +new macro in upnpcommands.h : +#define UPNPCOMMAND_HTTP_ERROR + +====================== miniUPnPc version 1.4 ====================== +Same API as version 1.3 + +====================== miniUPnPc version 1.3 ====================== +API version 4 + +Use UNSIGNED_INTEGER type for +UPNP_GetTotalBytesSent(), UPNP_GetTotalBytesReceived(), +UPNP_GetTotalPacketsSent(), UPNP_GetTotalPacketsReceived() +Add remoteHost argument to UPNP_AddPortMapping() and UPNP_DeletePortMapping() + +====================== miniUPnPc version 1.2 ====================== +API version 3 + +added sameport argument to upnpDiscover() +struct UPNPDev * +upnpDiscover(int delay, const char * multicastif, + const char * minissdpdsock, int sameport); + +====================== miniUPnPc Version 1.1 ====================== +Same API as 1.0 + + +====================== miniUPnPc Version 1.0 ====================== +API version 2 + + +struct UPNPDev { + struct UPNPDev * pNext; + char * descURL; + char * st; + char buffer[2]; +}; +struct UPNPDev * upnpDiscover(int delay, const char * multicastif, + const char * minissdpdsock); + diff --git a/libs/miniupnpc/bsdqueue.h b/libs/miniupnpc/bsdqueue.h deleted file mode 100644 index 1fe0599f5..000000000 --- a/libs/miniupnpc/bsdqueue.h +++ /dev/null @@ -1,531 +0,0 @@ -/* $OpenBSD: queue.h,v 1.31 2005/11/25 08:06:25 otto Exp $ */ -/* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */ - -/* - * Copyright (c) 1991, 1993 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * @(#)queue.h 8.5 (Berkeley) 8/20/94 - */ - -#ifndef _SYS_QUEUE_H_ -#define _SYS_QUEUE_H_ - -/* - * This file defines five types of data structures: singly-linked lists, - * lists, simple queues, tail queues, and circular queues. - * - * - * A singly-linked list is headed by a single forward pointer. The elements - * are singly linked for minimum space and pointer manipulation overhead at - * the expense of O(n) removal for arbitrary elements. New elements can be - * added to the list after an existing element or at the head of the list. - * Elements being removed from the head of the list should use the explicit - * macro for this purpose for optimum efficiency. A singly-linked list may - * only be traversed in the forward direction. Singly-linked lists are ideal - * for applications with large datasets and few or no removals or for - * implementing a LIFO queue. - * - * A list is headed by a single forward pointer (or an array of forward - * pointers for a hash table header). The elements are doubly linked - * so that an arbitrary element can be removed without a need to - * traverse the list. New elements can be added to the list before - * or after an existing element or at the head of the list. A list - * may only be traversed in the forward direction. - * - * A simple queue is headed by a pair of pointers, one the head of the - * list and the other to the tail of the list. The elements are singly - * linked to save space, so elements can only be removed from the - * head of the list. New elements can be added to the list before or after - * an existing element, at the head of the list, or at the end of the - * list. A simple queue may only be traversed in the forward direction. - * - * A tail queue is headed by a pair of pointers, one to the head of the - * list and the other to the tail of the list. The elements are doubly - * linked so that an arbitrary element can be removed without a need to - * traverse the list. New elements can be added to the list before or - * after an existing element, at the head of the list, or at the end of - * the list. A tail queue may be traversed in either direction. - * - * A circle queue is headed by a pair of pointers, one to the head of the - * list and the other to the tail of the list. The elements are doubly - * linked so that an arbitrary element can be removed without a need to - * traverse the list. New elements can be added to the list before or after - * an existing element, at the head of the list, or at the end of the list. - * A circle queue may be traversed in either direction, but has a more - * complex end of list detection. - * - * For details on the use of these macros, see the queue(3) manual page. - */ - -#ifdef QUEUE_MACRO_DEBUG -#define _Q_INVALIDATE(a) (a) = ((void *)-1) -#else -#define _Q_INVALIDATE(a) -#endif - -/* - * Singly-linked List definitions. - */ -#define SLIST_HEAD(name, type) \ -struct name { \ - struct type *slh_first; /* first element */ \ -} - -#define SLIST_HEAD_INITIALIZER(head) \ - { NULL } - -#ifdef SLIST_ENTRY -#undef SLIST_ENTRY -#endif - -#define SLIST_ENTRY(type) \ -struct { \ - struct type *sle_next; /* next element */ \ -} - -/* - * Singly-linked List access methods. - */ -#define SLIST_FIRST(head) ((head)->slh_first) -#define SLIST_END(head) NULL -#define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head)) -#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) - -#define SLIST_FOREACH(var, head, field) \ - for((var) = SLIST_FIRST(head); \ - (var) != SLIST_END(head); \ - (var) = SLIST_NEXT(var, field)) - -#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \ - for ((varp) = &SLIST_FIRST((head)); \ - ((var) = *(varp)) != SLIST_END(head); \ - (varp) = &SLIST_NEXT((var), field)) - -/* - * Singly-linked List functions. - */ -#define SLIST_INIT(head) { \ - SLIST_FIRST(head) = SLIST_END(head); \ -} - -#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ - (elm)->field.sle_next = (slistelm)->field.sle_next; \ - (slistelm)->field.sle_next = (elm); \ -} while (0) - -#define SLIST_INSERT_HEAD(head, elm, field) do { \ - (elm)->field.sle_next = (head)->slh_first; \ - (head)->slh_first = (elm); \ -} while (0) - -#define SLIST_REMOVE_NEXT(head, elm, field) do { \ - (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \ -} while (0) - -#define SLIST_REMOVE_HEAD(head, field) do { \ - (head)->slh_first = (head)->slh_first->field.sle_next; \ -} while (0) - -#define SLIST_REMOVE(head, elm, type, field) do { \ - if ((head)->slh_first == (elm)) { \ - SLIST_REMOVE_HEAD((head), field); \ - } else { \ - struct type *curelm = (head)->slh_first; \ - \ - while (curelm->field.sle_next != (elm)) \ - curelm = curelm->field.sle_next; \ - curelm->field.sle_next = \ - curelm->field.sle_next->field.sle_next; \ - _Q_INVALIDATE((elm)->field.sle_next); \ - } \ -} while (0) - -/* - * List definitions. - */ -#define LIST_HEAD(name, type) \ -struct name { \ - struct type *lh_first; /* first element */ \ -} - -#define LIST_HEAD_INITIALIZER(head) \ - { NULL } - -#define LIST_ENTRY(type) \ -struct { \ - struct type *le_next; /* next element */ \ - struct type **le_prev; /* address of previous next element */ \ -} - -/* - * List access methods - */ -#define LIST_FIRST(head) ((head)->lh_first) -#define LIST_END(head) NULL -#define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head)) -#define LIST_NEXT(elm, field) ((elm)->field.le_next) - -#define LIST_FOREACH(var, head, field) \ - for((var) = LIST_FIRST(head); \ - (var)!= LIST_END(head); \ - (var) = LIST_NEXT(var, field)) - -/* - * List functions. - */ -#define LIST_INIT(head) do { \ - LIST_FIRST(head) = LIST_END(head); \ -} while (0) - -#define LIST_INSERT_AFTER(listelm, elm, field) do { \ - if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ - (listelm)->field.le_next->field.le_prev = \ - &(elm)->field.le_next; \ - (listelm)->field.le_next = (elm); \ - (elm)->field.le_prev = &(listelm)->field.le_next; \ -} while (0) - -#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ - (elm)->field.le_prev = (listelm)->field.le_prev; \ - (elm)->field.le_next = (listelm); \ - *(listelm)->field.le_prev = (elm); \ - (listelm)->field.le_prev = &(elm)->field.le_next; \ -} while (0) - -#define LIST_INSERT_HEAD(head, elm, field) do { \ - if (((elm)->field.le_next = (head)->lh_first) != NULL) \ - (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ - (head)->lh_first = (elm); \ - (elm)->field.le_prev = &(head)->lh_first; \ -} while (0) - -#define LIST_REMOVE(elm, field) do { \ - if ((elm)->field.le_next != NULL) \ - (elm)->field.le_next->field.le_prev = \ - (elm)->field.le_prev; \ - *(elm)->field.le_prev = (elm)->field.le_next; \ - _Q_INVALIDATE((elm)->field.le_prev); \ - _Q_INVALIDATE((elm)->field.le_next); \ -} while (0) - -#define LIST_REPLACE(elm, elm2, field) do { \ - if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ - (elm2)->field.le_next->field.le_prev = \ - &(elm2)->field.le_next; \ - (elm2)->field.le_prev = (elm)->field.le_prev; \ - *(elm2)->field.le_prev = (elm2); \ - _Q_INVALIDATE((elm)->field.le_prev); \ - _Q_INVALIDATE((elm)->field.le_next); \ -} while (0) - -/* - * Simple queue definitions. - */ -#define SIMPLEQ_HEAD(name, type) \ -struct name { \ - struct type *sqh_first; /* first element */ \ - struct type **sqh_last; /* addr of last next element */ \ -} - -#define SIMPLEQ_HEAD_INITIALIZER(head) \ - { NULL, &(head).sqh_first } - -#define SIMPLEQ_ENTRY(type) \ -struct { \ - struct type *sqe_next; /* next element */ \ -} - -/* - * Simple queue access methods. - */ -#define SIMPLEQ_FIRST(head) ((head)->sqh_first) -#define SIMPLEQ_END(head) NULL -#define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head)) -#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) - -#define SIMPLEQ_FOREACH(var, head, field) \ - for((var) = SIMPLEQ_FIRST(head); \ - (var) != SIMPLEQ_END(head); \ - (var) = SIMPLEQ_NEXT(var, field)) - -/* - * Simple queue functions. - */ -#define SIMPLEQ_INIT(head) do { \ - (head)->sqh_first = NULL; \ - (head)->sqh_last = &(head)->sqh_first; \ -} while (0) - -#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ - if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ - (head)->sqh_last = &(elm)->field.sqe_next; \ - (head)->sqh_first = (elm); \ -} while (0) - -#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ - (elm)->field.sqe_next = NULL; \ - *(head)->sqh_last = (elm); \ - (head)->sqh_last = &(elm)->field.sqe_next; \ -} while (0) - -#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ - if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ - (head)->sqh_last = &(elm)->field.sqe_next; \ - (listelm)->field.sqe_next = (elm); \ -} while (0) - -#define SIMPLEQ_REMOVE_HEAD(head, field) do { \ - if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ - (head)->sqh_last = &(head)->sqh_first; \ -} while (0) - -/* - * Tail queue definitions. - */ -#define TAILQ_HEAD(name, type) \ -struct name { \ - struct type *tqh_first; /* first element */ \ - struct type **tqh_last; /* addr of last next element */ \ -} - -#define TAILQ_HEAD_INITIALIZER(head) \ - { NULL, &(head).tqh_first } - -#define TAILQ_ENTRY(type) \ -struct { \ - struct type *tqe_next; /* next element */ \ - struct type **tqe_prev; /* address of previous next element */ \ -} - -/* - * tail queue access methods - */ -#define TAILQ_FIRST(head) ((head)->tqh_first) -#define TAILQ_END(head) NULL -#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) -#define TAILQ_LAST(head, headname) \ - (*(((struct headname *)((head)->tqh_last))->tqh_last)) -/* XXX */ -#define TAILQ_PREV(elm, headname, field) \ - (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) -#define TAILQ_EMPTY(head) \ - (TAILQ_FIRST(head) == TAILQ_END(head)) - -#define TAILQ_FOREACH(var, head, field) \ - for((var) = TAILQ_FIRST(head); \ - (var) != TAILQ_END(head); \ - (var) = TAILQ_NEXT(var, field)) - -#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ - for((var) = TAILQ_LAST(head, headname); \ - (var) != TAILQ_END(head); \ - (var) = TAILQ_PREV(var, headname, field)) - -/* - * Tail queue functions. - */ -#define TAILQ_INIT(head) do { \ - (head)->tqh_first = NULL; \ - (head)->tqh_last = &(head)->tqh_first; \ -} while (0) - -#define TAILQ_INSERT_HEAD(head, elm, field) do { \ - if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ - (head)->tqh_first->field.tqe_prev = \ - &(elm)->field.tqe_next; \ - else \ - (head)->tqh_last = &(elm)->field.tqe_next; \ - (head)->tqh_first = (elm); \ - (elm)->field.tqe_prev = &(head)->tqh_first; \ -} while (0) - -#define TAILQ_INSERT_TAIL(head, elm, field) do { \ - (elm)->field.tqe_next = NULL; \ - (elm)->field.tqe_prev = (head)->tqh_last; \ - *(head)->tqh_last = (elm); \ - (head)->tqh_last = &(elm)->field.tqe_next; \ -} while (0) - -#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ - if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ - (elm)->field.tqe_next->field.tqe_prev = \ - &(elm)->field.tqe_next; \ - else \ - (head)->tqh_last = &(elm)->field.tqe_next; \ - (listelm)->field.tqe_next = (elm); \ - (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ -} while (0) - -#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ - (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ - (elm)->field.tqe_next = (listelm); \ - *(listelm)->field.tqe_prev = (elm); \ - (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ -} while (0) - -#define TAILQ_REMOVE(head, elm, field) do { \ - if (((elm)->field.tqe_next) != NULL) \ - (elm)->field.tqe_next->field.tqe_prev = \ - (elm)->field.tqe_prev; \ - else \ - (head)->tqh_last = (elm)->field.tqe_prev; \ - *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ - _Q_INVALIDATE((elm)->field.tqe_prev); \ - _Q_INVALIDATE((elm)->field.tqe_next); \ -} while (0) - -#define TAILQ_REPLACE(head, elm, elm2, field) do { \ - if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \ - (elm2)->field.tqe_next->field.tqe_prev = \ - &(elm2)->field.tqe_next; \ - else \ - (head)->tqh_last = &(elm2)->field.tqe_next; \ - (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ - *(elm2)->field.tqe_prev = (elm2); \ - _Q_INVALIDATE((elm)->field.tqe_prev); \ - _Q_INVALIDATE((elm)->field.tqe_next); \ -} while (0) - -/* - * Circular queue definitions. - */ -#define CIRCLEQ_HEAD(name, type) \ -struct name { \ - struct type *cqh_first; /* first element */ \ - struct type *cqh_last; /* last element */ \ -} - -#define CIRCLEQ_HEAD_INITIALIZER(head) \ - { CIRCLEQ_END(&head), CIRCLEQ_END(&head) } - -#define CIRCLEQ_ENTRY(type) \ -struct { \ - struct type *cqe_next; /* next element */ \ - struct type *cqe_prev; /* previous element */ \ -} - -/* - * Circular queue access methods - */ -#define CIRCLEQ_FIRST(head) ((head)->cqh_first) -#define CIRCLEQ_LAST(head) ((head)->cqh_last) -#define CIRCLEQ_END(head) ((void *)(head)) -#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next) -#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev) -#define CIRCLEQ_EMPTY(head) \ - (CIRCLEQ_FIRST(head) == CIRCLEQ_END(head)) - -#define CIRCLEQ_FOREACH(var, head, field) \ - for((var) = CIRCLEQ_FIRST(head); \ - (var) != CIRCLEQ_END(head); \ - (var) = CIRCLEQ_NEXT(var, field)) - -#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ - for((var) = CIRCLEQ_LAST(head); \ - (var) != CIRCLEQ_END(head); \ - (var) = CIRCLEQ_PREV(var, field)) - -/* - * Circular queue functions. - */ -#define CIRCLEQ_INIT(head) do { \ - (head)->cqh_first = CIRCLEQ_END(head); \ - (head)->cqh_last = CIRCLEQ_END(head); \ -} while (0) - -#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ - (elm)->field.cqe_next = (listelm)->field.cqe_next; \ - (elm)->field.cqe_prev = (listelm); \ - if ((listelm)->field.cqe_next == CIRCLEQ_END(head)) \ - (head)->cqh_last = (elm); \ - else \ - (listelm)->field.cqe_next->field.cqe_prev = (elm); \ - (listelm)->field.cqe_next = (elm); \ -} while (0) - -#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ - (elm)->field.cqe_next = (listelm); \ - (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ - if ((listelm)->field.cqe_prev == CIRCLEQ_END(head)) \ - (head)->cqh_first = (elm); \ - else \ - (listelm)->field.cqe_prev->field.cqe_next = (elm); \ - (listelm)->field.cqe_prev = (elm); \ -} while (0) - -#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ - (elm)->field.cqe_next = (head)->cqh_first; \ - (elm)->field.cqe_prev = CIRCLEQ_END(head); \ - if ((head)->cqh_last == CIRCLEQ_END(head)) \ - (head)->cqh_last = (elm); \ - else \ - (head)->cqh_first->field.cqe_prev = (elm); \ - (head)->cqh_first = (elm); \ -} while (0) - -#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ - (elm)->field.cqe_next = CIRCLEQ_END(head); \ - (elm)->field.cqe_prev = (head)->cqh_last; \ - if ((head)->cqh_first == CIRCLEQ_END(head)) \ - (head)->cqh_first = (elm); \ - else \ - (head)->cqh_last->field.cqe_next = (elm); \ - (head)->cqh_last = (elm); \ -} while (0) - -#define CIRCLEQ_REMOVE(head, elm, field) do { \ - if ((elm)->field.cqe_next == CIRCLEQ_END(head)) \ - (head)->cqh_last = (elm)->field.cqe_prev; \ - else \ - (elm)->field.cqe_next->field.cqe_prev = \ - (elm)->field.cqe_prev; \ - if ((elm)->field.cqe_prev == CIRCLEQ_END(head)) \ - (head)->cqh_first = (elm)->field.cqe_next; \ - else \ - (elm)->field.cqe_prev->field.cqe_next = \ - (elm)->field.cqe_next; \ - _Q_INVALIDATE((elm)->field.cqe_prev); \ - _Q_INVALIDATE((elm)->field.cqe_next); \ -} while (0) - -#define CIRCLEQ_REPLACE(head, elm, elm2, field) do { \ - if (((elm2)->field.cqe_next = (elm)->field.cqe_next) == \ - CIRCLEQ_END(head)) \ - (head).cqh_last = (elm2); \ - else \ - (elm2)->field.cqe_next->field.cqe_prev = (elm2); \ - if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) == \ - CIRCLEQ_END(head)) \ - (head).cqh_first = (elm2); \ - else \ - (elm2)->field.cqe_prev->field.cqe_next = (elm2); \ - _Q_INVALIDATE((elm)->field.cqe_prev); \ - _Q_INVALIDATE((elm)->field.cqe_next); \ -} while (0) - -#endif /* !_SYS_QUEUE_H_ */ diff --git a/libs/miniupnpc/codelength.h b/libs/miniupnpc/codelength.h deleted file mode 100644 index f11e5e936..000000000 --- a/libs/miniupnpc/codelength.h +++ /dev/null @@ -1,24 +0,0 @@ -/* $Id: codelength.h,v 1.1 2008/10/06 22:04:06 nanard Exp $ */ -/* Project : miniupnp - * Author : Thomas BERNARD - * copyright (c) 2005-2008 Thomas Bernard - * This software is subjet to the conditions detailed in the - * provided LICENCE file. */ -#ifndef __CODELENGTH_H__ -#define __CODELENGTH_H__ - -/* Encode length by using 7bit per Byte : - * Most significant bit of each byte specifies that the - * following byte is part of the code */ -#define DECODELENGTH(n, p) n = 0; \ - do { n = (n << 7) | (*p & 0x7f); } \ - while(*(p++)&0x80); - -#define CODELENGTH(n, p) if(n>=268435456) *(p++) = (n >> 28) | 0x80; \ - if(n>=2097152) *(p++) = (n >> 21) | 0x80; \ - if(n>=16384) *(p++) = (n >> 14) | 0x80; \ - if(n>=128) *(p++) = (n >> 7) | 0x80; \ - *(p++) = n & 0x7f; - -#endif - diff --git a/libs/miniupnpc/connecthostport.h b/libs/miniupnpc/connecthostport.h deleted file mode 100644 index 57e24eb27..000000000 --- a/libs/miniupnpc/connecthostport.h +++ /dev/null @@ -1,17 +0,0 @@ -/* $Id: connecthostport.h,v 1.1 2010/04/04 23:21:03 nanard Exp $ */ -/* Project: miniupnp - * http://miniupnp.free.fr/ - * Author: Thomas Bernard - * Copyright (c) 2010 Thomas Bernard - * This software is subjects to the conditions detailed - * in the LICENCE file provided within this distribution */ -#ifndef __CONNECTHOSTPORT_H__ -#define __CONNECTHOSTPORT_H__ - -/* connecthostport() - * return a socket connected (TCP) to the host and port - * or -1 in case of error */ -int connecthostport(const char * host, unsigned short port); - -#endif - diff --git a/libs/miniupnpc/declspec.h b/libs/miniupnpc/declspec.h deleted file mode 100644 index b804247d2..000000000 --- a/libs/miniupnpc/declspec.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef __DECLSPEC_H__ -#define __DECLSPEC_H__ - -#if defined(WIN32) && !defined(STATICLIB) - #ifdef MINIUPNP_EXPORTS - #define LIBSPEC __declspec(dllexport) - #else - #define LIBSPEC __declspec(dllimport) - #endif -#else - #define LIBSPEC -#endif - -#endif - diff --git a/libs/miniupnpc/external-ip.sh b/libs/miniupnpc/external-ip.sh index 965d86b2a..3873bd7e1 100755 --- a/libs/miniupnpc/external-ip.sh +++ b/libs/miniupnpc/external-ip.sh @@ -1,4 +1,4 @@ #!/bin/sh -# $Id: external-ip.sh,v 1.1 2010/08/05 12:57:41 nanard Exp $ +# $Id: external-ip.sh,v 1.2 2017/11/02 15:33:09 nanard Exp $ # (c) 2010 Reuben Hawkins -upnpc -s | grep ExternalIPAddress | sed 's/[^0-9\.]//g' +upnpc -s | sed -n -e 's/^ExternalIPAddress = \([0-9.]*\)$/\1/p' diff --git a/libs/miniupnpc/igd_desc_parse.h b/libs/miniupnpc/include/igd_desc_parse.h similarity index 84% rename from libs/miniupnpc/igd_desc_parse.h rename to libs/miniupnpc/include/igd_desc_parse.h index bab1fd56f..0de546b69 100644 --- a/libs/miniupnpc/igd_desc_parse.h +++ b/libs/miniupnpc/include/igd_desc_parse.h @@ -1,13 +1,13 @@ -/* $Id: igd_desc_parse.h,v 1.10 2011/04/11 09:19:24 nanard Exp $ */ +/* $Id: igd_desc_parse.h,v 1.12 2014/11/17 17:19:13 nanard Exp $ */ /* Project : miniupnp * http://miniupnp.free.fr/ * Author : Thomas Bernard - * Copyright (c) 2005-2010 Thomas Bernard + * Copyright (c) 2005-2014 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. * */ -#ifndef __IGD_DESC_PARSE_H__ -#define __IGD_DESC_PARSE_H__ +#ifndef IGD_DESC_PARSE_H_INCLUDED +#define IGD_DESC_PARSE_H_INCLUDED /* Structure to store the result of the parsing of UPnP * descriptions of Internet Gateway Devices */ @@ -42,7 +42,8 @@ struct IGDdatas { void IGDstartelt(void *, const char *, int); void IGDendelt(void *, const char *, int); void IGDdata(void *, const char *, int); +#ifdef DEBUG void printIGD(struct IGDdatas *); +#endif /* DEBUG */ -#endif - +#endif /* IGD_DESC_PARSE_H_INCLUDED */ diff --git a/libs/miniupnpc/miniupnpc.h b/libs/miniupnpc/include/miniupnpc.h similarity index 52% rename from libs/miniupnpc/miniupnpc.h rename to libs/miniupnpc/include/miniupnpc.h index 50df01777..90537ed45 100644 --- a/libs/miniupnpc/miniupnpc.h +++ b/libs/miniupnpc/include/miniupnpc.h @@ -1,15 +1,17 @@ -/* $Id: miniupnpc.h,v 1.23 2011/04/11 08:21:46 nanard Exp $ */ -/* Project: miniupnp - * http://miniupnp.free.fr/ +/* $Id: miniupnpc.h,v 1.61 2022/10/21 21:15:02 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * Project: miniupnp + * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * Author: Thomas Bernard - * Copyright (c) 2005-2011 Thomas Bernard + * Copyright (c) 2005-2022 Thomas Bernard * This software is subjects to the conditions detailed * in the LICENCE file provided within this distribution */ -#ifndef __MINIUPNPC_H__ -#define __MINIUPNPC_H__ +#ifndef MINIUPNPC_H_INCLUDED +#define MINIUPNPC_H_INCLUDED -#include "declspec.h" +#include "miniupnpc_declspec.h" #include "igd_desc_parse.h" +#include "upnpdev.h" /* error codes : */ #define UPNPDISCOVER_SUCCESS (0) @@ -17,6 +19,16 @@ #define UPNPDISCOVER_SOCKET_ERROR (-101) #define UPNPDISCOVER_MEMORY_ERROR (-102) +/* versions : */ +#define MINIUPNPC_VERSION "2.2.5" +#define MINIUPNPC_API_VERSION 17 + +/* Source port: + Using "1" as an alias for 1900 for backwards compatibility + (presuming one would have used that for the "sameport" parameter) */ +#define UPNP_LOCAL_PORT_ANY 0 +#define UPNP_LOCAL_PORT_SAME 1 + #ifdef __cplusplus extern "C" { #endif @@ -29,13 +41,6 @@ simpleUPnPcommand(int, const char *, const char *, const char *, struct UPNParg *, int *); -struct UPNPDev { - struct UPNPDev * pNext; - char * descURL; - char * st; - char buffer[2]; -}; - /* upnpDiscover() * discover UPnP devices on the network. * The discovered devices are returned as a chained list. @@ -47,21 +52,43 @@ struct UPNPDev { * is NULL. * If multicastif is not NULL, it will be used instead of the default * multicast interface for sending SSDP discover packets. - * If sameport is not null, SSDP packets will be sent from the source port - * 1900 (same as destination port) otherwise system assign a source port. */ -LIBSPEC struct UPNPDev * + * If localport is set to UPNP_LOCAL_PORT_SAME(1) SSDP packets will be sent + * from the source port 1900 (same as destination port), if set to + * UPNP_LOCAL_PORT_ANY(0) system assign a source port, any other value will + * be attempted as the source port. + * "searchalltypes" parameter is useful when searching several types, + * if 0, the discovery will stop with the first type returning results. + * TTL should default to 2. */ +MINIUPNP_LIBSPEC struct UPNPDev * upnpDiscover(int delay, const char * multicastif, - const char * minissdpdsock, int sameport, - int ipv6, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, int * error); -/* freeUPNPDevlist() - * free list returned by upnpDiscover() */ -LIBSPEC void freeUPNPDevlist(struct UPNPDev * devlist); + +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscoverAll(int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error); + +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscoverDevice(const char * device, int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error); + +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscoverDevices(const char * const deviceTypes[], + int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error, + int searchalltypes); /* parserootdesc() : * parse root XML description of a UPnP device and fill the IGDdatas * structure. */ -LIBSPEC void parserootdesc(const char *, int, struct IGDdatas *); +MINIUPNP_LIBSPEC void parserootdesc(const char *, int, struct IGDdatas *); /* structure used to get fast access to urls * controlURL: controlURL of the WANIPConnection @@ -74,6 +101,7 @@ struct UPNPUrls { char * ipcondescURL; char * controlURL_CIF; char * controlURL_6FC; + char * rootdescURL; }; /* UPNP_GetValidIGD() : @@ -88,7 +116,7 @@ struct UPNPUrls { * passed as parameters are set. Donc forget to call FreeUPNPUrls(urls) to * free allocated memory. */ -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_GetValidIGD(struct UPNPDev * devlist, struct UPNPUrls * urls, struct IGDdatas * data, @@ -96,21 +124,25 @@ UPNP_GetValidIGD(struct UPNPDev * devlist, /* UPNP_GetIGDFromUrl() * Used when skipping the discovery process. + * When succeding, urls, data, and lanaddr arguments are set. * return value : * 0 - Not ok * 1 - OK */ -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_GetIGDFromUrl(const char * rootdescurl, struct UPNPUrls * urls, struct IGDdatas * data, char * lanaddr, int lanaddrlen); -LIBSPEC void GetUPNPUrls(struct UPNPUrls *, struct IGDdatas *, const char *); +MINIUPNP_LIBSPEC void +GetUPNPUrls(struct UPNPUrls *, struct IGDdatas *, + const char *, unsigned int); -LIBSPEC void FreeUPNPUrls(struct UPNPUrls *); +MINIUPNP_LIBSPEC void +FreeUPNPUrls(struct UPNPUrls *); /* return 0 or 1 */ -LIBSPEC int UPNPIGD_IsConnected(struct UPNPUrls *, struct IGDdatas *); +MINIUPNP_LIBSPEC int UPNPIGD_IsConnected(struct UPNPUrls *, struct IGDdatas *); #ifdef __cplusplus diff --git a/libs/miniupnpc/include/miniupnpc_declspec.h b/libs/miniupnpc/include/miniupnpc_declspec.h new file mode 100644 index 000000000..40adb922e --- /dev/null +++ b/libs/miniupnpc/include/miniupnpc_declspec.h @@ -0,0 +1,21 @@ +#ifndef MINIUPNPC_DECLSPEC_H_INCLUDED +#define MINIUPNPC_DECLSPEC_H_INCLUDED + +#if defined(_WIN32) && !defined(MINIUPNP_STATICLIB) + /* for windows dll */ + #ifdef MINIUPNP_EXPORTS + #define MINIUPNP_LIBSPEC __declspec(dllexport) + #else + #define MINIUPNP_LIBSPEC __declspec(dllimport) + #endif +#else + #if defined(__GNUC__) && __GNUC__ >= 4 + /* fix dynlib for OS X 10.9.2 and Apple LLVM version 5.0 */ + #define MINIUPNP_LIBSPEC __attribute__ ((visibility ("default"))) + #else + #define MINIUPNP_LIBSPEC + #endif +#endif + +#endif /* MINIUPNPC_DECLSPEC_H_INCLUDED */ + diff --git a/libs/miniupnpc/miniupnpctypes.h b/libs/miniupnpc/include/miniupnpctypes.h similarity index 56% rename from libs/miniupnpc/miniupnpctypes.h rename to libs/miniupnpc/include/miniupnpctypes.h index 86081c3cc..1ae978d70 100644 --- a/libs/miniupnpc/miniupnpctypes.h +++ b/libs/miniupnpc/include/miniupnpctypes.h @@ -1,13 +1,15 @@ -/* $Id: miniupnpctypes.h,v 1.1 2011/02/15 11:10:40 nanard Exp $ */ +/* $Id: miniupnpctypes.h,v 1.3 2021/08/21 09:50:00 nanard Exp $ */ /* Miniupnp project : http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org * Author : Thomas Bernard - * Copyright (c) 2011 Thomas Bernard + * Copyright (c) 2021 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided within this distribution */ -#ifndef __MINIUPNPCTYPES_H__ -#define __MINIUPNPCTYPES_H__ +#ifndef MINIUPNPCTYPES_H_INCLUDED +#define MINIUPNPCTYPES_H_INCLUDED -#if (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) +/* Use unsigned long long when available : + * strtoull is C99 */ +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L #define UNSIGNED_INTEGER unsigned long long #define STRTOUI strtoull #else diff --git a/libs/miniupnpc/include/miniwget.h b/libs/miniupnpc/include/miniwget.h new file mode 100644 index 000000000..92943c144 --- /dev/null +++ b/libs/miniupnpc/include/miniwget.h @@ -0,0 +1,27 @@ +/* $Id: miniwget.h,v 1.13 2018/04/06 10:53:15 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2005-2016 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#ifndef MINIWGET_H_INCLUDED +#define MINIWGET_H_INCLUDED + +#include "miniupnpc_declspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +MINIUPNP_LIBSPEC void * miniwget(const char *, int *, unsigned int, int *); + +MINIUPNP_LIBSPEC void * miniwget_getaddr(const char *, int *, char *, int, unsigned int, int *); + +int parseURL(const char *, char *, unsigned short *, char * *, unsigned int *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libs/miniupnpc/portlistingparse.h b/libs/miniupnpc/include/portlistingparse.h similarity index 76% rename from libs/miniupnpc/portlistingparse.h rename to libs/miniupnpc/include/portlistingparse.h index 18524786c..661ad1faa 100644 --- a/libs/miniupnpc/portlistingparse.h +++ b/libs/miniupnpc/include/portlistingparse.h @@ -1,22 +1,16 @@ -/* $Id: portlistingparse.h,v 1.4 2011/02/15 23:03:56 nanard Exp $ */ +/* $Id: portlistingparse.h,v 1.11 2015/07/21 13:16:55 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * (c) 2011 Thomas Bernard + * (c) 2011-2015 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ -#ifndef __PORTLISTINGPARSE_H__ -#define __PORTLISTINGPARSE_H__ +#ifndef PORTLISTINGPARSE_H_INCLUDED +#define PORTLISTINGPARSE_H_INCLUDED -#include "declspec.h" +#include "miniupnpc_declspec.h" /* for the definition of UNSIGNED_INTEGER */ #include "miniupnpctypes.h" -#if defined(NO_SYS_QUEUE_H) || defined(WIN32) || defined(__HAIKU__) -#include "bsdqueue.h" -#else -#include -#endif - #ifdef __cplusplus extern "C" { #endif @@ -37,11 +31,11 @@ typedef enum { PortMappingEltNone, PortMappingEntry, NewRemoteHost, NewExternalPort, NewProtocol, NewInternalPort, NewInternalClient, - NewEnabled, NewDescription, + NewEnabled, NewDescription, NewLeaseTime } portMappingElt; struct PortMapping { - LIST_ENTRY(PortMapping) entries; + struct PortMapping * l_next; /* list next element */ UNSIGNED_INTEGER leaseTime; unsigned short externalPort; unsigned short internalPort; @@ -53,15 +47,15 @@ struct PortMapping { }; struct PortMappingParserData { - LIST_HEAD(portmappinglisthead, PortMapping) head; + struct PortMapping * l_head; /* list head */ portMappingElt curelt; }; -LIBSPEC void +MINIUPNP_LIBSPEC void ParsePortListing(const char * buffer, int bufsize, struct PortMappingParserData * pdata); -LIBSPEC void +MINIUPNP_LIBSPEC void FreePortListing(struct PortMappingParserData * pdata); #ifdef __cplusplus diff --git a/libs/miniupnpc/upnpcommands.h b/libs/miniupnpc/include/upnpcommands.h similarity index 61% rename from libs/miniupnpc/upnpcommands.h rename to libs/miniupnpc/include/upnpcommands.h index 66d95e0ca..994104e55 100644 --- a/libs/miniupnpc/upnpcommands.h +++ b/libs/miniupnpc/include/upnpcommands.h @@ -1,15 +1,13 @@ -/* $Id: upnpcommands.h,v 1.23 2011/04/11 09:14:00 nanard Exp $ */ +/* $Id: upnpcommands.h,v 1.33 2019/02/10 12:29:25 nanard Exp $ */ /* Miniupnp project : http://miniupnp.free.fr/ * Author : Thomas Bernard - * Copyright (c) 2005-2011 Thomas Bernard + * Copyright (c) 2005-2018 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided within this distribution */ -#ifndef __UPNPCOMMANDS_H__ -#define __UPNPCOMMANDS_H__ +#ifndef UPNPCOMMANDS_H_INCLUDED +#define UPNPCOMMANDS_H_INCLUDED -#include "upnpreplyparse.h" -#include "portlistingparse.h" -#include "declspec.h" +#include "miniupnpc_declspec.h" #include "miniupnpctypes.h" /* MiniUPnPc return codes : */ @@ -17,24 +15,28 @@ #define UPNPCOMMAND_UNKNOWN_ERROR (-1) #define UPNPCOMMAND_INVALID_ARGS (-2) #define UPNPCOMMAND_HTTP_ERROR (-3) +#define UPNPCOMMAND_INVALID_RESPONSE (-4) +#define UPNPCOMMAND_MEM_ALLOC_ERROR (-5) #ifdef __cplusplus extern "C" { #endif -LIBSPEC UNSIGNED_INTEGER +struct PortMappingParserData; + +MINIUPNP_LIBSPEC UNSIGNED_INTEGER UPNP_GetTotalBytesSent(const char * controlURL, const char * servicetype); -LIBSPEC UNSIGNED_INTEGER +MINIUPNP_LIBSPEC UNSIGNED_INTEGER UPNP_GetTotalBytesReceived(const char * controlURL, const char * servicetype); -LIBSPEC UNSIGNED_INTEGER +MINIUPNP_LIBSPEC UNSIGNED_INTEGER UPNP_GetTotalPacketsSent(const char * controlURL, const char * servicetype); -LIBSPEC UNSIGNED_INTEGER +MINIUPNP_LIBSPEC UNSIGNED_INTEGER UPNP_GetTotalPacketsReceived(const char * controlURL, const char * servicetype); @@ -43,7 +45,7 @@ UPNP_GetTotalPacketsReceived(const char * controlURL, * Return values : * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR * or a UPnP Error code */ -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_GetStatusInfo(const char * controlURL, const char * servicetype, char * status, @@ -55,23 +57,23 @@ UPNP_GetStatusInfo(const char * controlURL, * Return Values : * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR * or a UPnP Error code */ -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_GetConnectionTypeInfo(const char * controlURL, const char * servicetype, char * connectionType); /* UPNP_GetExternalIPAddress() call the corresponding UPNP method. * if the third arg is not null the value is copied to it. - * at least 16 bytes must be available + * at least 16 bytes must be available * * Return values : * 0 : SUCCESS * NON ZERO : ERROR Either an UPnP error code or an unknown error. - * + * * possible UPnP Errors : * 402 Invalid Args - See UPnP Device Architecture section on Control. * 501 Action Failed - See UPnP Device Architecture section on Control. */ -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_GetExternalIPAddress(const char * controlURL, const char * servicetype, char * extIpAdd); @@ -82,7 +84,7 @@ UPNP_GetExternalIPAddress(const char * controlURL, * return values : * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR * or a UPnP Error Code. */ -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_GetLinkLayerMaxBitRates(const char* controlURL, const char* servicetype, unsigned int * bitrateDown, @@ -95,33 +97,75 @@ UPNP_GetLinkLayerMaxBitRates(const char* controlURL, * Return values : * 0 : SUCCESS * NON ZERO : ERROR. Either an UPnP error code or an unknown error. - * + * * List of possible UPnP errors for AddPortMapping : * errorCode errorDescription (short) - Description (long) * 402 Invalid Args - See UPnP Device Architecture section on Control. * 501 Action Failed - See UPnP Device Architecture section on Control. + * 606 Action not authorized - The action requested REQUIRES authorization and + * the sender was not authorized. * 715 WildCardNotPermittedInSrcIP - The source IP address cannot be * wild-carded * 716 WildCardNotPermittedInExtPort - The external port cannot be wild-carded * 718 ConflictInMappingEntry - The port mapping entry specified conflicts * with a mapping assigned previously to another client * 724 SamePortValuesRequired - Internal and External port values - * must be the same + * must be the same * 725 OnlyPermanentLeasesSupported - The NAT implementation only supports * permanent lease times on port mappings * 726 RemoteHostOnlySupportsWildcard - RemoteHost must be a wildcard * and cannot be a specific IP address or DNS name * 727 ExternalPortOnlySupportsWildcard - ExternalPort must be a wildcard and - * cannot be a specific port value */ -LIBSPEC int + * cannot be a specific port value + * 728 NoPortMapsAvailable - There are not enough free ports available to + * complete port mapping. + * 729 ConflictWithOtherMechanisms - Attempted port mapping is not allowed + * due to conflict with other mechanisms. + * 732 WildCardNotPermittedInIntPort - The internal port cannot be wild-carded + */ +MINIUPNP_LIBSPEC int UPNP_AddPortMapping(const char * controlURL, const char * servicetype, - const char * extPort, - const char * inPort, - const char * inClient, - const char * desc, - const char * proto, - const char * remoteHost, - const char * leaseDuration); + const char * extPort, + const char * inPort, + const char * inClient, + const char * desc, + const char * proto, + const char * remoteHost, + const char * leaseDuration); + +/* UPNP_AddAnyPortMapping() + * if desc is NULL, it will be defaulted to "libminiupnpc" + * remoteHost is usually NULL because IGD don't support it. + * + * Return values : + * 0 : SUCCESS + * NON ZERO : ERROR. Either an UPnP error code or an unknown error. + * + * List of possible UPnP errors for AddPortMapping : + * errorCode errorDescription (short) - Description (long) + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 501 Action Failed - See UPnP Device Architecture section on Control. + * 606 Action not authorized - The action requested REQUIRES authorization and + * the sender was not authorized. + * 715 WildCardNotPermittedInSrcIP - The source IP address cannot be + * wild-carded + * 716 WildCardNotPermittedInExtPort - The external port cannot be wild-carded + * 728 NoPortMapsAvailable - There are not enough free ports available to + * complete port mapping. + * 729 ConflictWithOtherMechanisms - Attempted port mapping is not allowed + * due to conflict with other mechanisms. + * 732 WildCardNotPermittedInIntPort - The internal port cannot be wild-carded + */ +MINIUPNP_LIBSPEC int +UPNP_AddAnyPortMapping(const char * controlURL, const char * servicetype, + const char * extPort, + const char * inPort, + const char * inClient, + const char * desc, + const char * proto, + const char * remoteHost, + const char * leaseDuration, + char * reservedPort); /* UPNP_DeletePortMapping() * Use same argument values as what was used for AddPortMapping(). @@ -132,24 +176,46 @@ UPNP_AddPortMapping(const char * controlURL, const char * servicetype, * * List of possible UPnP errors for DeletePortMapping : * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 606 Action not authorized - The action requested REQUIRES authorization + * and the sender was not authorized. * 714 NoSuchEntryInArray - The specified value does not exist in the array */ -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_DeletePortMapping(const char * controlURL, const char * servicetype, - const char * extPort, const char * proto, - const char * remoteHost); + const char * extPort, const char * proto, + const char * remoteHost); + +/* UPNP_DeletePortRangeMapping() + * Use same argument values as what was used for AddPortMapping(). + * remoteHost is usually NULL because IGD don't support it. + * Return Values : + * 0 : SUCCESS + * NON ZERO : error. Either an UPnP error code or an undefined error. + * + * List of possible UPnP errors for DeletePortMapping : + * 606 Action not authorized - The action requested REQUIRES authorization + * and the sender was not authorized. + * 730 PortMappingNotFound - This error message is returned if no port + * mapping is found in the specified range. + * 733 InconsistentParameters - NewStartPort and NewEndPort values are not consistent. */ +MINIUPNP_LIBSPEC int +UPNP_DeletePortMappingRange(const char * controlURL, const char * servicetype, + const char * extPortStart, const char * extPortEnd, + const char * proto, + const char * manage); /* UPNP_GetPortMappingNumberOfEntries() * not supported by all routers */ -LIBSPEC int -UPNP_GetPortMappingNumberOfEntries(const char* controlURL, - const char* servicetype, - unsigned int * num); +MINIUPNP_LIBSPEC int +UPNP_GetPortMappingNumberOfEntries(const char * controlURL, + const char * servicetype, + unsigned int * numEntries); /* UPNP_GetSpecificPortMappingEntry() * retrieves an existing port mapping * params : * in extPort * in proto + * in remoteHost * out intClient (16 bytes) * out intPort (6 bytes) * out desc (80 bytes) @@ -158,12 +224,21 @@ UPNP_GetPortMappingNumberOfEntries(const char* controlURL, * * return value : * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR - * or a UPnP Error Code. */ -LIBSPEC int + * or a UPnP Error Code. + * + * List of possible UPnP errors for _GetSpecificPortMappingEntry : + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 501 Action Failed - See UPnP Device Architecture section on Control. + * 606 Action not authorized - The action requested REQUIRES authorization + * and the sender was not authorized. + * 714 NoSuchEntryInArray - The specified value does not exist in the array. + */ +MINIUPNP_LIBSPEC int UPNP_GetSpecificPortMappingEntry(const char * controlURL, const char * servicetype, const char * extPort, const char * proto, + const char * remoteHost, char * intClient, char * intPort, char * desc, @@ -188,9 +263,11 @@ UPNP_GetSpecificPortMappingEntry(const char * controlURL, * * Possible UPNP Error codes : * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 606 Action not authorized - The action requested REQUIRES authorization + * and the sender was not authorized. * 713 SpecifiedArrayIndexInvalid - The specified array index is out of bounds */ -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_GetGenericPortMappingEntry(const char * controlURL, const char * servicetype, const char * index, @@ -212,7 +289,7 @@ UPNP_GetGenericPortMappingEntry(const char * controlURL, * 733 InconsistantParameters - NewStartPort and NewEndPort values are not * consistent. */ -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_GetListOfPortMappings(const char * controlURL, const char * servicetype, const char * startPort, @@ -221,14 +298,14 @@ UPNP_GetListOfPortMappings(const char * controlURL, const char * numberOfPorts, struct PortMappingParserData * data); -/* IGD:2, functions for service WANIPv6FirewallControl:1 */ -LIBSPEC int +/* IGD:2, functions for service WANIPv6FirewallControl:1 */ +MINIUPNP_LIBSPEC int UPNP_GetFirewallStatus(const char * controlURL, const char * servicetype, - int * firewallEnabled, + int * firewallEnabled, int * inboundPinholeAllowed); -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_GetOutboundPinholeTimeout(const char * controlURL, const char * servicetype, const char * remoteHost, const char * remotePort, @@ -237,7 +314,7 @@ UPNP_GetOutboundPinholeTimeout(const char * controlURL, const char * servicetype const char * proto, int * opTimeout); -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_AddPinhole(const char * controlURL, const char * servicetype, const char * remoteHost, const char * remotePort, @@ -247,19 +324,19 @@ UPNP_AddPinhole(const char * controlURL, const char * servicetype, const char * leaseTime, char * uniqueID); -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_UpdatePinhole(const char * controlURL, const char * servicetype, const char * uniqueID, const char * leaseTime); -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_DeletePinhole(const char * controlURL, const char * servicetype, const char * uniqueID); -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_CheckPinholeWorking(const char * controlURL, const char * servicetype, const char * uniqueID, int * isWorking); -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_GetPinholePackets(const char * controlURL, const char * servicetype, const char * uniqueID, int * packets); diff --git a/libs/miniupnpc/include/upnpdev.h b/libs/miniupnpc/include/upnpdev.h new file mode 100644 index 000000000..171d495be --- /dev/null +++ b/libs/miniupnpc/include/upnpdev.h @@ -0,0 +1,44 @@ +/* $Id: upnpdev.h,v 1.4 2021/08/21 09:45:01 nanard Exp $ */ +/* Project : miniupnp + * Web : http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ + * Author : Thomas BERNARD + * copyright (c) 2005-2021 Thomas Bernard + * This software is subjet to the conditions detailed in the + * provided LICENSE file. */ +#ifndef UPNPDEV_H_INCLUDED +#define UPNPDEV_H_INCLUDED + +#include "miniupnpc_declspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct UPNPDev { + struct UPNPDev * pNext; + char * descURL; + char * st; + char * usn; + unsigned int scope_id; +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + /* C99 flexible array member */ + char buffer[]; +#elif defined(__GNUC__) + char buffer[0]; +#else + /* Fallback to a hack */ + char buffer[1]; +#endif +}; + +/* freeUPNPDevlist() + * free list returned by upnpDiscover() */ +MINIUPNP_LIBSPEC void freeUPNPDevlist(struct UPNPDev * devlist); + + +#ifdef __cplusplus +} +#endif + + +#endif /* UPNPDEV_H_INCLUDED */ diff --git a/libs/miniupnpc/upnperrors.h b/libs/miniupnpc/include/upnperrors.h similarity index 52% rename from libs/miniupnpc/upnperrors.h rename to libs/miniupnpc/include/upnperrors.h index 2c544c97c..3115aee5b 100644 --- a/libs/miniupnpc/upnperrors.h +++ b/libs/miniupnpc/include/upnperrors.h @@ -1,23 +1,23 @@ -/* $Id: upnperrors.h,v 1.2 2008/07/02 23:31:15 nanard Exp $ */ -/* (c) 2007 Thomas Bernard +/* $Id: upnperrors.h,v 1.6 2015/07/21 13:16:55 nanard Exp $ */ +/* (c) 2007-2015 Thomas Bernard * All rights reserved. * MiniUPnP Project. * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * This software is subjet to the conditions detailed in the * provided LICENCE file. */ -#ifndef __UPNPERRORS_H__ -#define __UPNPERRORS_H__ +#ifndef UPNPERRORS_H_INCLUDED +#define UPNPERRORS_H_INCLUDED -#include "declspec.h" +#include "miniupnpc_declspec.h" #ifdef __cplusplus extern "C" { #endif /* strupnperror() - * Return a string description of the UPnP error code + * Return a string description of the UPnP error code * or NULL for undefinded errors */ -LIBSPEC const char * strupnperror(int err); +MINIUPNP_LIBSPEC const char * strupnperror(int err); #ifdef __cplusplus } diff --git a/libs/miniupnpc/upnpreplyparse.h b/libs/miniupnpc/include/upnpreplyparse.h similarity index 71% rename from libs/miniupnpc/upnpreplyparse.h rename to libs/miniupnpc/include/upnpreplyparse.h index 267ea8783..6badd15b2 100644 --- a/libs/miniupnpc/upnpreplyparse.h +++ b/libs/miniupnpc/include/upnpreplyparse.h @@ -1,34 +1,31 @@ -/* $Id: upnpreplyparse.h,v 1.11 2011/02/07 16:17:06 nanard Exp $ */ +/* $Id: upnpreplyparse.h,v 1.19 2014/10/27 16:33:19 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * (c) 2006-2011 Thomas Bernard + * (c) 2006-2013 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ -#ifndef __UPNPREPLYPARSE_H__ -#define __UPNPREPLYPARSE_H__ - -#if defined(NO_SYS_QUEUE_H) || defined(WIN32) || defined(__HAIKU__) -#include "bsdqueue.h" -#else -#include -#endif +#ifndef UPNPREPLYPARSE_H_INCLUDED +#define UPNPREPLYPARSE_H_INCLUDED #ifdef __cplusplus extern "C" { #endif struct NameValue { - LIST_ENTRY(NameValue) entries; - char name[64]; - char value[64]; + struct NameValue * l_next; + char name[64]; + char value[128]; }; struct NameValueParserData { - LIST_HEAD(listhead, NameValue) head; - char curelt[64]; + struct NameValue * l_head; + char curelt[64]; char * portListing; int portListingLength; + int topelt; + const char * cdata; + int cdatalen; }; /* ParseNameValue() */ @@ -45,10 +42,12 @@ char * GetValueFromNameValueList(struct NameValueParserData * pdata, const char * Name); +#if 0 /* GetValueFromNameValueListIgnoreNS() */ char * GetValueFromNameValueListIgnoreNS(struct NameValueParserData * pdata, const char * Name); +#endif /* DisplayNameValueList() */ #ifdef DEBUG diff --git a/libs/miniupnpc/java/JavaBridgeTest.java b/libs/miniupnpc/java/JavaBridgeTest.java index 62bba345c..c658c5990 100644 --- a/libs/miniupnpc/java/JavaBridgeTest.java +++ b/libs/miniupnpc/java/JavaBridgeTest.java @@ -1,4 +1,6 @@ import java.nio.ByteBuffer; +import java.nio.IntBuffer; + import fr.free.miniupnp.*; /** @@ -27,7 +29,7 @@ public class JavaBridgeTest { return; } - devlist = miniupnpc.upnpDiscover(UPNP_DELAY, (String) null, (String) null, 0, null); + devlist = miniupnpc.upnpDiscover(UPNP_DELAY, (String) null, (String) null, 0, 0, (byte)2, IntBuffer.allocate(1)); if (devlist != null) { System.out.println("List of UPNP devices found on the network :"); for (UPNPDev device = devlist; device != null; device = device.pNext) { @@ -70,12 +72,12 @@ public class JavaBridgeTest { System.out.println("AddPortMapping() failed with code " + ret); ret = miniupnpc.UPNP_GetSpecificPortMappingEntry( urls.controlURL.getString(0), new String(data.first.servicetype), - args[0], args[1], intClient, intPort, + args[0], args[1], null, intClient, intPort, desc, enabled, leaseDuration); if (ret != MiniupnpcLibrary.UPNPCOMMAND_SUCCESS) System.out.println("GetSpecificPortMappingEntry() failed with code " + ret); System.out.println("InternalIP:Port = " + - new String(intClient.array()) + ":" + new String(intPort.array()) + + new String(intClient.array()) + ":" + new String(intPort.array()) + " (" + new String(desc.array()) + ")"); ret = miniupnpc.UPNP_DeletePortMapping( urls.controlURL.getString(0), diff --git a/libs/miniupnpc/java/testjava.bat b/libs/miniupnpc/java/testjava.bat new file mode 100644 index 000000000..b836da149 --- /dev/null +++ b/libs/miniupnpc/java/testjava.bat @@ -0,0 +1,8 @@ +@echo off +set JAVA=java +set JAVAC=javac +REM notice the semicolon for Windows. Write once, run ... oh nevermind +set CP=miniupnpc_win32.jar;. + +%JAVAC% -cp "%CP%" JavaBridgeTest.java || exit 1 +%JAVA% -cp "%CP%" JavaBridgeTest 12345 UDP || exit 1 diff --git a/libs/miniupnpc/java/testjava.sh b/libs/miniupnpc/java/testjava.sh index c997baf9e..9880523a1 100755 --- a/libs/miniupnpc/java/testjava.sh +++ b/libs/miniupnpc/java/testjava.sh @@ -1,8 +1,8 @@ -#! /bin/sh +#!/bin/bash JAVA=java JAVAC=javac +CP=$(for i in *.jar; do echo -n $i:; done). -$JAVAC -cp miniupnpc_Linux.jar JavaBridgeTest.java -$JAVA -cp miniupnpc_Linux.jar:. JavaBridgeTest 12345 UDP - +$JAVAC -cp $CP JavaBridgeTest.java || exit 1 +$JAVA -cp $CP JavaBridgeTest 12345 UDP || exit 1 diff --git a/libs/miniupnpc/man3/miniupnpc.3 b/libs/miniupnpc/man3/miniupnpc.3 index 0e35aaac5..e5c3ec88c 100644 --- a/libs/miniupnpc/man3/miniupnpc.3 +++ b/libs/miniupnpc/man3/miniupnpc.3 @@ -1,5 +1,4 @@ -\" $Id: miniupnpc.3,v 1.3 2011/07/25 18:02:11 nanard Exp $ -.TH miniupnpc 3 +.TH MINIUPNPC 3 .SH NAME miniupnpc \- UPnP client library .SH SYNOPSIS @@ -26,25 +25,28 @@ through the miniupnpc API. The name of the C functions are matching the UPnP methods names. ie: GetGenericPortMappingEntry is UPNP_GetGenericPortMappingEntry. .SH "API FUNCTIONS" -.IP "struct UPNPDev * upnpDiscover(int delay, const char * multicastif, const char * minissdpdsock, int sameport, int ipv6, int * error);" +.IP "struct UPNPDev * upnpDiscover(int delay, const char * multicastif, const char * minissdpdsock, int localport, int ipv6, int * error);" execute the discovery process. delay (in millisecond) is the maximum time for waiting any device response. If available, device list will be obtained from MiniSSDPd. Default path for minissdpd socket will be used if minissdpdsock argument is NULL. If multicastif is not NULL, it will be used instead of the default multicast interface for sending SSDP discover packets. -If sameport is not null, SSDP packets will be sent from the source port 1900 (same as destination port) otherwise system assign a source port. +If localport is set to UPNP_LOCAL_PORT_SAME(1) SSDP packets will be sent +from the source port 1900 (same as destination port), if set to +UPNP_LOCAL_PORT_ANY(0) system assign a source port, any other value will +be attempted as the source port. If ipv6 is not 0, IPv6 is used instead of IPv4 for the discovery process. .IP "void freeUPNPDevlist(struct UPNPDev * devlist);" free the list returned by upnpDiscover(). .IP "int UPNP_GetValidIGD(struct UPNPDev * devlist, struct UPNPUrls * urls, struct IGDdatas * data, char * lanaddr, int lanaddrlen);" browse the list of device returned by upnpDiscover(), find a live UPnP internet gateway device and fill structures passed as arguments -with data used for UPNP methods invokation. +with data used for UPNP methods invocation. .IP "int UPNP_GetIGDFromUrl(const char * rootdescurl, struct UPNPUrls * urls, struct IGDdatas * data, char * lanaddr, int lanaddrlen);" -permit to bypass the upnpDiscover() call if the xml root description +permit one to bypass the upnpDiscover() call if the xml root description URL of the UPnP IGD is known. Fill structures passed as arguments -with data used for UPNP methods invokation. +with data used for UPNP methods invocation. .IP "void GetUPNPUrls(struct UPNPUrls *, struct IGDdatas *, const char *);" .IP "void FreeUPNPUrls(struct UPNPUrls *);" diff --git a/libs/miniupnpc/mingw32/libminiupnpc.a b/libs/miniupnpc/mingw32/libminiupnpc.a index fb2065753fa28f20bcdd5ee63634c9e1f10a37e5..063e379bdec80b0e574996b7bb9084ba23394f6d 100644 GIT binary patch literal 63762 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�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

){h_FhfXMY}t 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 gm<;eKkb294);($l4u`s&Q$S4qn1| zR7ObgX?xnAp&$qfSv)>hLDecWHKuNuCK&c?aQ|{VGSw)X+`$}=s2OF8F9F!w{R9WV z$CJYua&J?g?#N`#KXDs3HOrkDkC-02L7dYZf@A~pGXi|@c$ynmS$ zjqr5rJyJ)Xe~9ss-bBF%G{ThDSZr$H=Q|(|b4uDlG)L-`hr{07S+2+&XALd80#Bh} zD_lNyH}~s*6v{*cG%laCZD&qFRrpi;cXJ+suNNoKW-WSKVpnK!)gfr3}Wm4n?nGR@A<`YiX3L!Bxhv7jtp5p zgVAEL^zYka*TK8FJ&)}*Yk@hb-q3_%4X%Bl!tZRdXRda4^P{H)Gj;rvu5sk8?W9~G zZI)N7*$b|2`0f&7|6ea7TuFmGTWgZ4MK~QWRe3KML7$vqQ_!(rBUcd1FouSF0|0VBL-oWQIw}lQo&fK^EM}8W@icJ$05>z;nrA38z0H_49P+w3hL`=h2P3Ta znQh_vV;>lIb6hMp&vov*ZHx-nFWmcQ(F@6(20moPl_Qo0SOd78B-;;GVbu_mzfTzM zIC)EQ0K5tGV4iWsfr&$wM{qQ=Q;C=7FN^q`8B}a@C_^H>wVeFd3okPaxF38qb2slF zonayK@T62|tPHg;$`&3D%d+sXc|f%8=4X?;=_sF1k<)7!6U}E_x>mO7%+fs804b*x z`eX>-r6#S%VZ2zF#y&O}Ju>~h;2jD)3l+7N{Il(Hylhv%o?RUG4tzfHjSe#|m`!l+ zYu=`%ymmPps176BWm_DT$+Dy<40lYLs}53-OOu75E8U7Ww!PnOosyEaTPIy)S+h=- zYOu~aQCnNA)3)D*=86Rio2(Oz>c6eiH&`d$0@}1rypz(nURbAHXtF(RS|>V`ZPsbm z&*jyv6MamVYGs|IUNk|>AGw=1%@gW*>oZOJ6v-nk25J*-qJ|)BS8arK!GJJP+#>sC z6NQkk#=ho?w{;tZ+$foVhLJ*zYK{M9U zCmHcJE2Vo*!CP;w@sPKi=2ptm5wV8VO49cmWu>A`4zJEynbFw@D-|%ZWITR@mGyD| zP^Z6qiNIY(6s5RZ9ddsk3X6CL)Cw*(A5B@@4Pr;g#A8~hPqBj9+=rI~eS_;pUE`Aa zZuL+d0rOk|U66aXsvjWsxoIQA$9zWab@G$I3x~@uj<|e~6KMA}bl!|_!!3#tI$NxM zL(*;9-$qyloFp^ZlKcrQf6(9}vR9B@@%(u~56_!?p1m*5$#a{+r4qO}np*MjIta`i zWt)NocJj&AA%qHIG(Gsir>~&~!P0hW*sz8gHhm2>(h(h9qV#P8OZ22xElK0xlHj0C zy|YVKBR{0HH_fMdlkj8ZLkAfs8G_f0jdopys~zM$$ewG-8RvcYNkI4~vG;w-eD(M= zr>oB1WX||20WDbuj_2Zeb_9IYUz72sN!W5cJA8t>OCMS~ z&FKg98VTM&aye+75r}M5kGcNAV@KU@?f~Do*<}pmT^p1ISuBDSYOwP*rqY`Pa+smk z_nX{us&`N|YQ)${5WINwPGz+D)dmrwL);ZM^j^7M{$Xd0=Da+{3N=%n zPpEv5c5~g>?|;spY1?9408*1+9>*nQlqI;dO7ymfQ7rG%)p86ygmDIs-KYPYKXOO@ z6OU~8V~QDy%+MVtrSb9*=Vh@1^j36@{V9ZF%}7tLS4>H_FW5&_Ht7kQc=Kug++7Ji z#}X}T(Dm!%+xm0&B5c5j(UN0muI+h2JB!xXJF`P6K5PgFft_BQ*fnI57wk1W?#}G? z>iM&*PYQ_5Znfm)XeYIr+HT+3X&f=rwG&~n=-BZ?FRo|!0umQ%jiH1*QR3(kj75Kh zojTE+ya_nu%HV#ZOS>}&BH><=X&gR;>mbUu81dXNQulzueGmm$?DN>64A*5ZKV}TV znW%nTc!2L&4K0k`hOM7I94=p0sTQr~MMgb|CA1A=)q?&sJyrs&?A|H$10wz%Gl5?K z4bJM6h+aB9xe#>wl^DUZ(H zY`nUfe#F7ebYMe<3k^jCD`^N{hyYe{6~?O+{O0%J5xEsZFKL1Ah=nmjOEWIciIH(} zG+OvFi;F9q9xX*&oDm!PWqK=*i_?enCgyZM;=aJ+Vs^$kM zpua6JF;uy4=ITi3I@`lP;||(td++~Vw$rT#+hKWmqN-qNIEFlpVgn4!a1CR1o1G$MbsFlCo_Fx-zE23}B&cFq*NAPBYlU{kwcq#}k!! zo<+WonWOl;lfv!1z+>Z_9!#x0!oAS|)#}FmBx-bB-HuKRJ9Xp|Z(txnV5Ul})v{VgC;F23|jdUa#0Y{*B_Oy6x zb3E}li?btpgX{-xgs=IeZna!4ekmnB=>?kjgF}ZRshUC@D$XPzb3HStkOl7=EqGm# z0CBV~{-+NEUxeD(feK_E-M$^or?bx~E{7R| zQw^^ASx)b(L9glN_NPcavORR0Z*FmZv?)K2G zHf%j!L3kgH2Uymp6)vB0j=McYmHo+*Ykt!OJV#Uk&v9&VhSSUO5Q7*Gf6l_@fW%7P z4sbc?VxV+}JRo0Sd6=}K1yzU9Ie3?|`_z+#BYa_U9~RG^X{mkRTaB?FKlklVYmr5{ zFZZ;q$@P1NDph%2s?>XS-$(WNDT5$lF&Q&Md0C|I=G6KH8#BmgIsIjVAa;7gF4>sb zZ5p^oAU#ItgdG7Arz^+R;VCeQ;T*P6#T$)&?Z)grZSFf_V4l4*n4;##!Q-9lQGAUj zklEu~RD??#;~mWD#l)?1(lEeuLAhUUGlUDnWyJ`njUq_J=7^#(hL}nQVXzU*W{Cn$ zvq^`gf+~S)DF<93@Ec4X;v0|2NYa;BhHV0x)@lr0r4pY0ltq@^{7q$5Fx(GX4 zzO#((-cM>G#CpxQmeE*~mepDF9_!NTbUFDk4pXr>Mo0vidA=@}R%*xiFbm5dvyU`F z=F&=_VQ(1*4nJz>&N1z61Y<*Xc2r}lJPOAxV|dJ$kdi2@3FvUK&*?Q-!OqTl|x5D5z>mJ!ytQ*6>3nm zMv%S30J)}4(oYpv^u z<`ygz154&Hj`3UGBBmMJ#E}L}&$0_OBzdhlhSf)X9I(QZQO&Tm#z6xnv>*k!iEBm3 zRqr$w949djEYjT;sz4#|kB@o130Lw0XP0LM2l?QGwsoMjHAGT=)v1Ztafi`hQPT<+ zHYvrA?F|>r5S?o@Tg|qgAKY$qwCn3qYOd1)rK5xWm`aB@J@=j+nt&xO ztk}Izt`I3LJl{tbu>lNa^sP@5p&=(?!zeL=Ok(r4m?Bf0xl_fNK_5XcuG8wpV#~WN zL(jqqty{ZOltNiNOvkH2*))_sDLt5q36F`Ri6iWD?XmCkBBK2~`NTDcS?{2?vf>F@ zr#s@ws85-MTnCBiDaDWt*vF}0V}9~tJN$H`y95%WoyR6zNjPp1rhg|zVHuv*vAdpu zCeGB2NDGnj8VriG-X3DreHp--kN`wQkc%IC2j^x`3^DZ>V>fzS2N(w_I_SFUFzYFT zwf05g<~`*QVdmLI#iq+-6c~d=(c$J6#(FL_%!RCVZEIE<)-V}NC6LJ=D~XEpB|^I$ z;FLI>HHYm^kzE56QAqrV!}c8>GGJb7`!?!h%_;P?e_3!R8I9a3y2)@I@y`kGu-Cks zLg-wg1V_eXNF3)A|2QVEenUnO<1ucwFed19aYAaap9vbl{=m}dh=_A1BF=AX?JHACZ@5TPb1;!IQ|m;O1f z6&Y57vE=PGzxLa;zrkqgzsRUQD=I=`nC!`M+W-g?p*l1$Dg*`O-~ds=YEVShs5P^v zdV7q2bR-^}_Ro9B)*6~^XT%vpr70!Muhq2bzKvdM3A`)-3zB#p+1XR|TV}vd8_mAAY*HM6I{W zgY&B@({PI$1g%r3rMH<6Ww~vj9eTE3xYe}MzRX+m31Kd?$8Xs{Yeh$}8g^T(zGP(B zTW+Q=-oC|IP1}eF7hsFUnbz5Wi|q1ZI@p;dD7(_PHB#B9;n@l#^By%0<@=iu}t zjq5GOnO6vIHOF{t=ON}~=V*N3u3VRIs}tkxcq)Ro6X9gq^~VehYfFMEgQ5$Y|vDj{O7Vx4?-adM%R z6e6|3#|)LMIkCBV*?W&H{w+p)T@qOjb(olFG8}RT5P|cSPD+Txvoz5LSCh`NIQ_Sa zu?p$bbr!?3Rg6i;BEq`G$Z0Of1QAiztrTT7*g}*Qa0nys&RJQ(ghUvPt;AWihdA;+ zAkYdRWRZ0c;;Mi`9(gZKsE|q4l?$!vC{#J`s16f`iQQCoH^i$k%JAhku8bcCPqV6m zv`kVya20qM5zeZvxuLbNy8NxeSX(d;Ryhsogrg5^#!YQ30NJDub(jz`E9)4kD%-6B6ngEJeC@BrrBz0j;A7@}@ zOqx)kRE&Izg_V623savFaohq63ZdkqBC7LjF^*WVF-0bwi3~`t_P&XUzh9)4x6dFf z?JA1QI|Y_U5NV3QZZRHO_b9N80qK}SxQC^{ESZ#ubLC}u8RI?Uq+!hJFrjTJrlsK~{_^LV) zS|N=HaW()#6e-Bb4+lyY*XPGomfM!aIoC$7n&$=)B|LgsKE^poMhR&?!}Oudt-2So zAw|YDjfZuBF!78W`iQMUpjWtnviH8<`)P1tyC8`6xYSuWyEQKV345d^4-unzd`i!> zI?154U4yVq2A1!xwIJcNF@$Ma3~Y?H^^h?&Gt1S9wwC%*NOIs?Nto6(x)IGG&xks%gA1{y;3Er(lj39S zDh_+~lNMVMgfuWJ$~w4UdKHXx6fO?`6T6kt4-ZMU4YY@g+y9M=Y@j1pP8H*<=*Fpy z*yL8kCfi^_R4GDf(1`-!r4GaJMTbsh2@@=?B9;Ex((ox>*_^gp3|}VE=)~|rdWrNb zqFLpa1SAvJO);%4g1d=BBnU@;{oGw2i-#fAwrk<6R&B#oLdR3&9Htb}x)93lgn>*R z`}L7l`>Mbsif)Qp*$W9#&O)YmfhDAYkZ+TPjU9LoIf{+L!ymmDeO2=6M!X6vsX=Hk zbW^a(U^)DIwq=`l$9R?VP!_WdMuUJIR&uaj>ZQxNjB&IcqOl}{gS-FzvU*msDzewS z=mA4bL2SKOCpyl5v-^<98psd|LnLPV?42YOoEPd3F~gx+ML(=nm*J1qw{2X1Z7e>I z!O4@)LvZq)icp>VM>x3bVQdRo>p*fNw#IwW;Z)RPYl;9iV`~uxte+y*;m1ZykC8z1 zKefaA*mw7`${0jEf5#8=S@OeGwI^g*J2a;o(2>|ikHUA{d% zII)YRUnff2@aCX?VcH*DB$JPaIE?gikgYL=rp0&0FwFdGLcaj0z6?T19LP#{L_+ITFG;Fl!O1OLP!Z{`oJ9;jh3xKdvj5oPt2%2zh zaYDOH8f;y9z(+>KKR*BfFM~dEw&wwMmAwhY7=|pPEVj17#VBL|Ntogdr>Wr~b`d%8 z=_Kp61u7;KblN2P;_@Us@2+L@t=a6w`G${E8Kg>3A&?-(dvl_B9^NLq$hXUwQ^19I zQqCO_DTpq>W1MB(I?)AGa2%bgD?5E!z$9*0^IQXn0Vt;;Emc8B+dd!e%!SUhEw|eD z`GR+~zMOfH6~|6PW7+}3FfwZ7D#KT#+_*azH3rg#S=W-|P)n!j7OjtYf9>S^?~U-P z;7IJkNXk<4M7A49OD9kw~l^Dvt7CSgZjHF6k z>@wcBY8k}#{R&v$4m-?q=$w2S0AYYrc#j{T)jaH-^v|no!EVI%tesso0K)9D@H5(b z=g0Z$VQ>SJvN>XMUA=sCic^el`zI`8?W9CLWBPszNX&NX1THIg5DbF(H&Pc2vom!y>nB2B zhG{s~Enva!3ZKi1K}X~;@>M4%Z%*7xm{c=Hj!S57MpHXrXmN!N!z@syq{oq(m($gN zsWG{CesFl&Ly68HtIp#Zz;;BkC~!`097_33G21-&z1rNHKd0l(h_oQtA4MhR&we9L zb(9M&2xD1Ok?D))NK3Ql6|fym#?pKKeE#IoMJ;Rrjf5V9#_iRYld<2wYJo>zoAH&w zXo>noSZ`KuaVdk>c!_MH!nA*zAvlVcyT zAX^*@fAdw98rM6AXn9K#eOYS9A(%GB3f+zDYK2-F`PE+j?J+J3GZre;^5~3B!32d` z9^Z`~?`GL0VvMwasjKn?9%RJxa%gx&#@Bc`UF4BTMPU%OX=zj_rlp{PoC2%^y#&2e z&UGL?1MHQc?>XGSN<-jBjnp%Sz&-6slI17jS87^i;3gi^b&eTFS#+Z6F$z+q^^377hli;e}l!mSuEZVQ6!;w`|_Zy1aY)@|cBt(-2zT zLG!(k>^6g*_037hn8L|MftT3=zD;sSs?X&`wGQjLWUMPqx9~+7Cs5 ztza0g$z7~^Vg6vZ_BZmb{Id~p+eV*IjuwF6Zwj^o(alEB{CS?21sas!@I1HMlt*?X%sSJm2KXZs@{EdlcLyt6D)Ia? zB;f#_i`W485CF1(&vU!2mt~YJ^iJ1Qy{e-{v1^u*AGfn-pS~IzRDl27s zODiJk&PJwmsXhEN#!rGJ!|mlG4ix3@x|v*#4*{wY&j<_K0bIIc`?ypu$HK=^WMFQyqHEwO%Wfg? zt)1na*M3Imdy3(@G9mci(_x?@W|WC_&H~iF?hCsjq|iBH_XUJ zc0QwJ8MkM`&oeaq$iMS^wm4?i45nRpy2KHUpw(GFTO5s+lV@ChlR%97n1#_a8|j`U z9#z-0G+Ug^J}({!kD&qfDp~O{J@$`lMtZ~~_IBz%BlOBxY*T!}0v9Y}>5eEA@O01y z5!RN+$(`MdsFxfKYubgqMUL&)a+I{Rb5vVx1tY(Wf5s<2+w%wTF9vXg{kK7R$-e{< z)`FpW@ei;!CrEJQatl6^cKNoM^NIbhk8``h3l-bxF}P?=A*%*@3f|!C`#4`bn=%yJ zFOWU-5lfAa^P|aZ)_)wMLrm2g?K{T}x!X5P452vt z8+qTtBm8B*mazpO+ycg1g(ytO&0A~-#$tv!pR4ZuaukU|_@qrpzmBZJrt`~i+u!gP z{p*a%+K@sI8#g#j+>xO~9MSd)y^=hP16O+{Se263BNwS^$?KWh6%~`$GdCv)5bzWF zcFZS?Sz?`&wS;UCX%xwXxw*I=TEcbaIt?9rjQbUeB0a(3i>lDHCWbH`4sos@cRwvr zCIIpSN*c^qV(+6FHJN8h5X9`lHMBkULN!ReyVb;C#f7>&w(^Fr-LI}Dvn(@+UsNRB zT2tVL4T`3tVP(!G>P${9r>t6J3KG@ai{=-*!V9w8QBC*oe|1@dy&21FEGEvb{_6$i zI*qqDPgSj1{0%?R2vpLd#h%A`D)>dHOm{N!ih)F4TRMjPXwj?IMLw?Mgd>p8E7;ka zE_l4T9J6Xpy4r#BIL}#e9!KlhXdLS*!zE5fc~gle_WGd<^6>Rwj7pe`{9quVSRJc^!libtec?b}8&sn= zUP9!(tvHkaGI_$3o~v_QBm}G~i0WcGXPmAQB8)AIR*2$Fyc-A5g+e7jy%$|AMEbor zUUdNt>Koy_`m(>V+$-=*b z^T5WizG&PLKeZkZq zLh%JtQ(2VzRcUJcx+#3E-P9ywk~Jq&Q<)QA$`qzX=QG<(O;U(r7*{5x9Kw}BsSn@G zmK?R2nq*HJrY4R}x0{+MXwznDa&uDRnHdvUs5-8hC0S=`JiCs{zSh*R++<}$WR4of zfyMSJsI{gBK2Zx)qc_Gm{)VZkv?Lj$hLP~sd4{< z{B1KeZcW&l71;|@6E4X`-PD9P9Gj}!R!n<6Sokr#64p%(UpQ?Vr_^yQWbg)7s0UZb z^TS@|<;~R)7aWI&Z6_Uev>rAO4)%LzKQa+BpYHJ#5xo~&57R%EZw$5o6zk(g-)=>D z=d3wGJiCC^=sK{lqUo^1Hea}jGdHm~wc`!lH&{p76l1m!_0H`Nmyl;!nImLrpSYv! znX7FGBi+*RGOdy<38^S3wqtDW=6x)jT+C%y+;P#m`5n&}`|#bzY~Uw>bOq*ioOjr^ z^A3&H2neS$4hDxCT|9mki_&&2#6&tB2{m2NeKHhkbu+*AL{t z82|CO0LM*mY~bAlIXI?*cwB;8ZK1JU$X>w1xkWejynF#hkqzVV%e;62wmY8J=;95r z(bS-WLn0a*NW->9TN6WzsgI8*i|2;|5sRwhkRGnloz99Di>i+gUOsuaEJ?%bEKg5I zpR*fr!Fn&Wd|PZ`@-Z|3yQ!}RpO-#RROs&YJ!(AP&L%~hmkqglJs!x!mi0$LT+ zZ63ZUSr74H)>UqWksx=>h6xhWDH<3d_VbDb73@!R#MoBeObsjzMvQMJyJg>Hjax(s-t4t~m*Jw; z?1GOuRwu*XiS`}+u_N&0{sLLQcFkb z47Ox_HIy*%L8tfj8U*}<%twD$mMWRVVxfEP`xk^&gD`4A?Es~cv~y;kkM{m1c2giw+D>S_-n5Hk@c{{tOo`kibk!$cG4BJ28M4Bhm6o1U0=SBMG6xa za4t5x+de`Ml9CoE!=paX`YQnzXj6Ua?= zeV=i*e9OK-gVSE8`*D6>CLSb|vKG%gsIWs*J@o*L#OX3{k&Hxpm3tt%6ojmrs!o;h zQYIicuA(i*500{J6iEmWN8K%| z9fCJs01TUu5Us3|Y|&A(W>gb~C+$ggqyf(@X>)(0{J3b3vCc~e25k^= z6mW+-%|6Zvd)f{hV;sO6HyGE5%e_avpa{3`aB~WAsk>i`*s9HYu)}x1B5md1Q=BG= z8RYKQ_e==#B$yrEJ&@0To7*VbYEC`Op}P*nhn-OFa`{?@LT&>Hi_Q%!rxVD6&NkSX zEV->@^;n|V-42{*e5dmr^UsEteb%s#L;XA4j>4SDsl8f+a4xdH+}rT`hhg98g@YhW z{T4b0lpbPviif&`SkJH`!Lv{XJ!I$~Pmy1n@Nizlc|#(E?Am&3$Pj1J#0bK{X{ZCn zvV@1%ZcZ)_j{7L7L8|?J)1jfvBZBa9aJ~yg@-~gwH0k-nPI~Ap@TXhpDY#&Bbn^=$ zCnSeYVZLhMA*dLLxD+IeKv_k)38lt?qXNSoR{Jb*1&|sc}cDqW)fcm9`fdmwKbM$ zF|=#@3VYo*Ut*b7GGG~vQIlnmh=oZzZ;-~(>g>Rf+gml3u@MKt8Kpy) zb(U#av>SUAB%;o;1S1{WO?wmu4I@}+Py&~h5q!hXvR_{4VDzr*{YXuuB`Wi^1gr-essvZ(N)n^MbtiisDqe zjG_5UP=C{kU53KiUkXB5D9c|8Rodl2F}>9G8OELb4J-?Cfao$JL%S^QgcG=RJmYBC zZVC?V@(h?hnZ1xY)C{+aEKw1LO|N+2gvIYlhBG!}C^7)!oM33gzQD)`%5xtdA7m1W zQyIG{EV9ecM|xrK_8bYu;8hz6+z$1yawYzqbF!368yp!_!CcwZXA>q^rl zsnHfp<|^FYH>h++YJde90LzYFMXMOkeBS(YOEk+6?Q)H6-!Q=ZaEl;gsZ7X?lJ!xw z#txR(nn1eoIMt~k5FMRdcC1emP}voi@nw+DR_uI(1Y|ZV9vRI+f2xLJfC$($AQQ2W zR(3W*9mOhhn~W?DPXs{N0Oo`{vj}=o&yEUk(T+?|;Jp?qYy>YR^G@C8UHgLAv1|8) zDHA6?DPAzKr%Gyx#u$UBW~(m|tjcK-TzKKTta=Mxo73rqM{Ppf-4!1;afSr1w}OQ3 z(+M}K47do?$LGWV?g`X3B?2PUBUQgO*xH^%0EapNxZD|pZ!G-9h#i_k8mdyE+^vRV z%OO+5{q#P4Yg1P`w?>?SW<8}(MS|51FCi`Zsz73hq%11~E@#2`vN*BB?1kDy86cq^ zOmK4KJk_DS!_WypZ+!oyE>VMARf4y#X{wL17PVR28G%6l{ ze>;8r{y+ZX``PsN`;X&s*|zl`-T&BTYx*zm@%!oHc=mEP`TlkK__CTT|Knlf`1B;E zfL)kiFnJe;%1FiTci>V?=fgg3QL6w=e|Ep~;j@eRKFt#FRzIM`1&^v|?%#z8w{AhV zbsGjB{CszD@nZ$&+r2=;WsD!d#4x~7t_{H1UIgK7wnw|k5&r@o>za3r1j;$QO`?D^ zLE&GQ6Mg`vHh6{80buqEEOA?nAHc2s4SmBWb^!V)#19}BZ}g7)tY~(8h!QYHxCr6? zj&U9c8X?)#-gqZIWO{`D-LyblF}wedjh|2rD6!w?b+jkBhG5B7UcbT&&tCR|#H(o? zZ<>JJu|wCnqk%?=S5rD3WCA9r*NnTW`uC|FfUsp}7w7#Us(WX?%kFm!-IE=R#Tz>~ zod*xkaiL#;i+x@I5AdOooW9%O#X4RlYyF2xGAbVM2nhT*Y^WM1YkLJor3Rr+eYOS? z7}g*eJgs5rb2b3bMF|i+NS)ck6(UIoS0B!gDz^FYF^syPl83~?JA22WbaTVK4?uvv zRd<>vzM?zBW~@k6t;LkNefE80GI2F_Z})QESCZ=2^V+EYh|C{Kb@uxB`v zhvhNd-0vddK@6(y4Mm^lVKQy$-v)poLDp-McSxdo-bel`!j9%Cn-(yVw%TjMbcz#5 z2RMU-^?Z>Jy}wa*<0#2Cp*wP|_>KcWJcd$ORYC$Kzr`Gr!S#{fr3+-i=v8+Qr*u(D z;dx?3dl>gaU?(G=zfm&Ad8l~?3<8r|0NerE{Z9Umv9iu&3u6ETD)O(uWG14wGMNqE z#AMl5Mm(vg?t8YJkcm=qynVlcniRkTmlbL_j_`M&CZ2fYa`!tA zMi!_AgiI3RRL(VmI@b=c6Y?Iug&P64piEz0?cJX}!2>n!ShWL=@Sh&<4p8IB?(e{e zsWGkAm+_4;PV$Y}%@{U^!&V^KCst51sVWy5#qJQ$&ecE&(n#$+{4{;MdwP}C;m*6b zIK5L%nd!`D8-zM4;WqnpL^ZxWXwmXzgF@nz)3eEFp5qFGKwe&)4Gu1QD208N^AbT+ z;JVd0dY+}z2}H-ljfWPJ&5ya$V)maWObHW*eKJC&?s@;1@q35%94J2Jg~0xt(=5`U zASs7tVBeu-R0G@_2=3MuDfL|8O!}IgF{}~lL_Zh|4Z6e#4OJ3I$*u#_fW^UF@ylK? zh7CTj6iCS}j@eC=0?99vHeQuiA^T88=>*UYBDv2JQw~usdKSJKz?+u(+76--yr}>v z78e92uGg1WxM$?u!N77y(7=@t@(Dh@dwRZFJi+U+xy&&ATt0JD)AZ)jOtYVhaeS*% zP5F#yd#{{(eGA%-Yw%P>RVLiF96jwV$IuD(;H;xc0c9izKvrRsi!@VZQf1`+G(fTB zLNHvwp>D$r+lDVQck)=h!w8<4m*BdVyn)y3A@Fpv=abb6XD|#D+ULYms*M~8#bku33X91R_POSptSG2L=dVWQShC6zOy z7+jT(dxzI2bo47Xkam>Jo!$)};B}=jXo$x{bGlN!0WeQPAY(Il?-bOCm5x2#f$_P5 zVWXlz#!>gUI1mO_+^Egb6+|dbT0t8u1DGwqVE~jn={LbN%%tT{cHVronREl^I#)=p zU)WA3J0Z zL8BWW{HaaqskBa0h%L+@&~_|C{unbd9Ke?()yWCjTZN}@CTD!73O`&wLbHKXQ0O!T zg(4{$MN&l~>qT%M-UzDW7@}sB#H}C;Koy`Vm=&X%>5K)09IZ`8R_N~V&?${>lP3{c z*<=XCzsDfTFaDOV%NX&=)NN>OR>`Tm_m7Pbjs$vePu0QNYKYigXnl+{{So;R52(P* zxe1Yla3hxEYV?5t=0rf-%^S%l^t8^Z5$fYAVnv&*!2~Xs%n(e>1dJnzi-2AO90Qdb z6YP%1wcxBBoY}4yq{X^V&Sc0Bmbi6EJ{;YiP98r#qXw8+Q8&O-W*(f4mUbhAJ$D;C zwbX;6!8!ecH`vSZa`akBAz36RxPf6rw>BBX62XWK!Z$&|q4RRCW*BR=(|x$}cm@Sz zw&T9H9915@qrV&vM$?qAcWdO>FP+EKCgl(kOXLcvHHZQuM zYTkki=@~I0BVG0ouJFRW9sP5#O}NkrRj1*vEdg>+&jn&$SUUW)^FPW~lhMP*k zLlKP;ph(1;5l}I_IRaeCQ~0+S0b3pdwn2F1!4?j~dqCrEHRR9=Zm9<$@mv5nXrk($86@9FRmZQwMGr9yH`5wxofVy6hB9l zqH7HGD9}G1vj%QNEN#+aO^KW>K3*8(c5Sxt(h3sO32SWlh+)MTteaY)W7IG{?a+)a z`^rfX?fktMO;$eRUt2z`OZm0(3Dx%NN}&2t^rI4C0txL`)sF;R$Vbqq<>CVGq|kVW z!EedO#}A7CT%01Y5xIh9>N?DGg$w_+gXayiy#bkp{CgsB?(l z!CYWhWFsVW)PpK(N4sOe=57}!RBZLB(Pv}tK2?*bEX?ZR>Gen(?R@GsX7LQ(RYlae zXkf`oa1ARNG>$$s8_6uRF30D*qGMJ9Lo&=NT6KR#olOKjQejpw0A>;%{LcPUR-u!o(?VpGS&QC%sn_vj zWa0}ku6E|n7mE~8NnOsLWxn~uJDlg0wmaH5x4;z5R{bMT3@$HV5A)VYM`Fmhr8&l; z1#$gt*7K=)PwyMgv2S8=svIJ9Vdl0CLTq5#lx@BE(0hL_4hP&-t+Sm>)mm(v_Ca`oBf#G!3 z^T=(zHO6a-@OB7?mHXkv&nPi;dU$Z8gq;qPyiAE1!?oF|!ys;C|M)u|bP5^3yU{B& zf;`mFOj+TA`Qr7lxlP?%S31AwM6qr$Ch5FLp7~az&{J;_w@`OK4<*Y$@&)pdFHX16f0ws24jE zHJFoD;E*VzxH&-RHO!W{&e8ivaGhSOt7x6;Z25Yw8}xrW*At&Yp>>KM_KxX*CD#Ku z@&P*lrk{L$)w|?@ha;Q?*FD*IpMh=!fe=J|m@F0BkK6(V94E_P<-p>pn}k}*mh1Tb zM!kO@%?y#-7{rH&1&WxVMCOmZ4^|tK2tbptYuh%^Y9Eq8`e`(6Ner7FPiCXfdBT{B zdwMxpAZsdbkhG_~iefw|T0G*z!s-c2krDG2^AJJCyZ*^L#N37~x2Z)(hjH(3$#SD6 zs^;1i76TUD#GVm`Ww5mTiZF?>!pJ$UM=B5zhK2g80Is1eU%bOG_CJm`-Q6~{ zLmMN}lb@@hoj}6&s(t~`CNR)ucvE*41hbjfi2_z_YBPQJwEXRAF&f*6ynHU>Dax|J zQJxN`&$G!-OV&v@c$tcOR{VjrM9iFL6{$EZ8?%qINf|=UbUzzHoNa~M1`%dZ^$`SH z(wC${ONjF5|9u29a{&ahwoE}_5>FT(mOy}CQL!K(B52-WfZXR>!h%37&iiMmi+h~i zJOT0<#9p_xlV@LO`4XQG+iVvs08Br{|x)BhExq@m9E2_j<23;xW~8#80p0E&b{) zhpMsYS>@MJuPELv`(~{s>zmP|5?vS^CXBJ6=+a6Zp=EZ@rm{N_`NsOg2XW^3WmRk5 zg%a>{eAhC5%62%5-j4m8h6r6JBF(zvdDd&e!i=YEYkQ$ZH^SE0<^!%#ZMAj(oVr0{ z3EiNvti+!SQ6=oF#zK%k_=s%R4fOp(^xe>nT_qDXb%O$K(+#4!tNvPa1I-=WQ)i^K z=?2=jIv906FVuw|ra$&eI=)}B7A&T~v~OZBKsVr=_76^JL}P}xwD)hYqI9P`6Lx?? zwYJu(d?_{-k7Y#=q>51}4jt-aMhYURiD?G@MHJ=_f!S1YB7HgC$;1L8{FTvjh<97zN6?bXS0 zbYFHKDwDV6{c9%~s8#P)+yCfXOZcg$RjA-#j~_C3*v~FxlRJp8sj5|?Fr-xnu48dv zq2nhOn(Z;Gq{9jZ30#I3Cnu-5YmNwSdA#dy;F5J9T*9XFPy)VSo-uQW^Q`ABtBHaN zt66@MMeWP9DEDPw-%X!~$m}}Gb8Fo4SZ3*R)PKBxim@njBac1%u%Wl*1x=6|!-6LA z+nYd=-Igkv2;9B=CbXMucgHLoNZgxcReYNxB^O zKG{idM~r(dSeQZ}nEb1594l@QMSsL9eMQaMW!+Mh7XL80Ydl4aROoQ~1?bds=WO^a zM!4GEFI=Z1;)`T^_|7ooLuYE9I_KOh6)`DQ`nu2z(Q{vC5RJr6?TU(RaW}^n{9A-X z{QznP;4;vV0CVnBd-S6zg8gJ~N2`XMwZ?YbMF`9j_1h8-N#hp|mBk*Vvnb9sI(yR5 z5OF4*4bhBVZt-avPaAH)&G{2~`!L5$I`N&hsx^jT%Go!#v?W&oh8UEue}06Gj-xBY zQ*H{~wf&v#&hq0@`Ei&Ein4GWRE5JFLd}H+?cr(%TDvt}sw@!w$hr3J0Owa>8UI!I&#{d;~2;bz6 zoT4q%B{)qKR~(33B0t;3b~)~V+rl<#tw$ICW%mCaNRG+UH0@RVAF z0pn&3!;58e96#7f#}7yu^``+DngdAb%emd4dVa0>!cHV0r+1F{P~Vf2dGgy-#U7sf zKIA5D>`cl6JDDEa0a71Lrsk=CCnXL7Z1Erf6};XaGUjH8OV5uCw19>!(cZ*a@8Alz z)zlAJZ1ErfHi-W5Llz3pzyfk+bQ{6L7ZI6JgUkh6@+>S_4KSsFr&tqGX0;(~@Yktv zs5msRl@1M%Kni=^3n6p7K$#z-qD$dxa<@bo_g)9pM*&?b7dymzHGT9w1LbpO@f{FiFribwNbP-}5EU3u6D z_8%bq(113Gm>{_GAwoSBuMJq+5?is& zkio>%zy$xVpoq!uCr8#6Gd34bX#}f z8t@KRs58X_$0$qQ+_D1zojs&LEMLaYE-Q=hn>xoV?n)eUyj!fhXms7Zi&hQk*D5hh zqj~vpj1n2T6&g+ucPWZGx>iOP`as)nEGqDI4nhu5bH-XR;Qti3oY52t!eFl{2rw@glmJ30m=Et7M9_jk0UI&XEhch9 zfp7^J?mDx2DVhTrOkMdo%?niH<2*`GUQNo;&NcP)o?~{K%@mAMyJZ5 zL3;=aj1EqR!-K=jPhmK-%xVdI>~LPBLAplse#DpgV?1=l9$!Aa#uH6k0DJp1eu<|s zYXPu$?3)7PPfXd0?NB$<>gXQ*}u@U1;c^xw&4w zz`iK-Ln9Ec1AP@jY7UKH49*8R0YO~@GBhG$Y)FHc!;nFDP~a`)*ouLTd|;He{SKRfuS35TVU4&+p)H(YY6ZH2n-wawI zWE{FI?Px=-3zl#iE10G3LK*-jLx=t0X|Kjl$f-O|6H2%oTX3e0opn;-KYbPH_6-3UvPJo1)}D%P7^ry#zm;<}%ikmdMjHcds-*kJTv zvZ;+a?Yc2`UnKD&xW_r;?$tlQ&1KHsE^%$KjX!y#Cp~zp#ddvK8GmkX?s~$=2ukPx z={yqx1t^_p?)|Hkk9%4oVTw8T{$)19Oqgq%ami)ExTGl>*cIW+%HE?&el#a7BVfcY zbI4FJMFDZ#ci2B4T;u}Di_=LsFOY%O1a3~Qv8gi} zMs*5o{%?nox6oyfPgJ^nD!{cS>axfO(k1gWV&Gu|?hha{U1#t{m#frpa!E#9kI0ph z)V*x1YN~)hH>=v}>zW@Au}zcMxf%4O zZN@qpA}xl#2lv(hg%glsfnAFlu7M7LGaPt!RnV@F#^`2RGa`bG-PJj()P(HCkgdGojh^ z+~Cr=*8mG%GP^js@Y3ASB8JVV9-CMk)Bfr(d(|GiWr)l_LdX`tFptdJHLpzvYlTKc zxHdg6pba7vIz_kyTbxgf?nL}17ctl?#Huf^0fU*z>FU5J!i#PJ2(~Z=WrP_h%w`L0 zFoBt>dD?s+BlYUAl5m+=9}vMEhKc-ZUUfK9*8~>l>rcHPz5FkT1G1aHU!SlL}B9vz`%sh zWVUhL3KG0X2dX*$S`I&$SF_F`@%)M0e|;f*Wa3 zZt&m#X%+S#XDbxgKRp_LKwU@JeYmbg#zK2|dU834r^YvX zdeXayhcS;{s8s~06&0eyoLKFPp%J6dMB3` z*MmA;TyBvduBnVNuARR8roQRR2fPW4{Y{{pziHn)#l-}ty~bBf4tN)M`@2B=Z@nAe zwNZn(l;z>u(2>|dR2?GF2}K4?7|n?uAEjP24xfr)NEa=H zaejJrlixGB$C@1f-vq%`I32wk>>=O+j=yYvhxC^m(hQ3`arVZ>)rTv5*=@s+a^w!| zBp%(ITSfW5U`d`HUG`3Rx8-IRef!&HaQtOx8DCEC@wFQipzIH?k9&XV_@5W&s7=Yg zx9so!)kWto-T1lnmu>rb`SxgYcW37>_T`(?Bbd5fYj}H!qRq#rA9xGIU-sZ%@oW;K02M~DySQxILos`1EAtvlzX55O#+CcqMg`{ZdZXqKdD;$@=(m+I@>!sptL^STFa3%lBZQUHuhH%c0HY$KKN@9E zftX!Qm>1z`^uDZ|Ert(RgN~4Jk+o@oc|Q3#Vx4v**Q;~@Kc(2Z zm+@#cdXyPAKZvNS&)A=0UcP@-p=PMf^(AhDIr;#Ts_B76_Gp{9&2C#P^>$4$TFN+J z&OGT4*D`-3^QyxqP3E!fk6|8rZeyM_i>36)Ja?Tl=0>~bL>^qTIiG;=T#>lU&}wBJ zZL6NO(=nqc?#ci|GLsR!$pK8=EQsdeVwf51O57D;+sN3lsS5Y$Ud0_IW-_@LH1usD z!B00JWIB1q$_b$9e8d*VWLXUyt^^Th`KKN@;K23SVehiid|#eIcyJ=H?y0xRXliBZ zU=`)kdLC8!BPstqv_7L;YROCl%H6LnOq55f%q0&_nal|mQ;NG!Sf#tsumy3gEer}5 zVwJhki?SwuBVeQ1m&vd(5J^JI-}Fs!;B23)H)k|ylzf5NnHQ@w;_JVwxDH=~xqfaz< zuPB0ghwP}v%PE>!0#qGn!tw5HQzG9-)(&{q8d%9(dJ9t1W2@4uJ^JIc?F8!6HW+C{ zjlAd2i*%wRZH2K70b3N|i2k$@=KZm$!aQZCH{<}T_X98B<3E3@syt4fUw?tJ+?@A@ zmpugZF8yk0?1%U~3N;x&RgBP%V9vKtjpONRfr=)1Qz*uD^TLMN!fp#m^)z|%RjM#s zTu$as&y%vv{ZV)pPjlo7SI1?g0J8=3=hvxwDm7=LpzZnp;a(3|ZzR&-=9F72Crh;2 zTXb^@O> z^r+a948wxGJWTj89Hm3>FdHZtX$*5YYV^=>%*YgF2)tL_XHU9q@Zu12%M(3*uuajdr2jq4!85i)1} z3vjX%UiXe@(PeLe4++p(w4z1Etw&2-VDQxVWL|XZXAFJDRtd-Gsi_2xd~MuZ_RcPb zy#}kW;AN?)FY?N*70&3hF61aHiWSBTvl*^Oz`A@e;=K=A3cHf*Z#2K`Xwo$%nl^^l z0ci|-C|A%8TEt&ucdVl&F}Rf$wyvjzt?OtB(P*3k1E_GFmH^UFWtiCHLTJvr)ZCOV z4H_9F#>~6U;+m&vj=&lhw#;L4)R9Msc;9G@tNf51JGI;XxObAqYbqXTPi2)}&nK!s z$?YCMTY}XNGNuMg+=qd&m1ZTMg>hbGi7}TR=TmS-rYQjPE2sb<7fdVp-~i#Kgr7pl z!Q1nb-VnFR(&Zl<4Bs)j!d|2)p4q?$&<2Y$Q^6WCy>;2c6nex@ z`4{yyhN<~g2BG~u;F^D}{+_t4C}H70fk_h5|W2p(k$4DmGQs=kB*(IOA~vClOlKwQZJy}aQ@{0hV7Q_)mk;4dG5u; zWV=442k8AUSVx}0>jGAFcS|r*7QYTcz{NZ#IO#igV`>wj0Hi&PVHsM4Y?%lI9n+X? zgs%q^Cp_>3>|pO2Hd{T)g7Nfc)66-H#d^>Sk9-)81pf0P1`H_Y;w_rf(gG)Zmi)kpXzLOjuw=d4xM)GkA}sw@(*LiKtsOD z_7SkZ@gDoD=N1_- z*d7D3J9}6Tqy8#VEAuFTDFsMw`Yvzq`5K#_C8-!99S( zcGFmF5SpY7uEwz&O-Rl;XfP@X{EKnUys$Cq_BEXuwfDammOC}($*VoWJ8V^ij(TOWMT`Yzr5kTclnB8;L!uVYF#p;ChL&-G3?PE zxAj-Sdv6%jHg>pPv$j&5%b>z&z+Sj4yUw|`*0&tKHW;B1HjT*DcXohrJsY!OZNe!F+PHN2h;;UwU?d$E- z1f0#`7~{SQ@fX^t35}DCEPR056@CX9AyCJGgV-0UH`q*x$aK4?DkMx~9x4FxW|~?= zR)?3RQsBT}{1UMep$8CJi2ZgE{vE_hQuvk)9!3>f^)rYKcv%I7BS0+e!vDJNU4BUB z84l{({z-@ws_-`qGnSIeYy}ufiuCm5@@c6_Eqoz&`e%znRH|EkW-Oo>t?v9VEIzuk z9e3N!$IBkfPP5n$)umJ2UG0rR=;h1v;_toM8 zs`fY9xGJS)#vkp3M~S`!C@ZZ^p2Ieu9peI(9ph09+jr}(I=luWfpi%ObWpk=TXHUb(98?Ajy%4IPmULx={gi#p( z5MdIQM|9vAntA>LipB;WoS|r$sc7@6zZM?C8E$M%$$=IJ0jPwmAP*kE=Edb9m9JX7 z8n}t+iuAzA!UPzvCi{pg0halys;0n>k)GAw7pFZLA6j~w(}1PeS$TU!$40*8FZu^E zH@gK+Z1U46dZ7(rNggS8tq4=dQtJl7xZ&F6--@s}Xh&N3{lY!X5DqxaL!Jbv7vMC$ zJ3zu?@8(8}0!}{NsSfWNV&;)L6xW7u4s6Yce<1_Y5LR*Fky-U6C^?x7TNxA&4*-Tk zNf0z5m}cO?)z&*@N7OFQYsX;|!}c<1pnBq*ytxer#6lo-pc^~iL(IhkzgU$IK(2+} z=n034ANO$jw?7ynRY>-T;UsPm98Xr`<#ZwI;-)8fqy<~?vaySaVPqRR$uz13P7L$2 zE_$I2DQT1y18+epX;h^(*~g>}j8z#@#;_L315VzKm!T$7c!8^$gCp`MTZAxB#YKo3 zaEzjdb?i+TzER^is>>$JK|GO2}QeQ0u|uSoz5%!M8xiB14_Wngr3xFRn=h zF6psKX6X+2LPSvE51HNi0-Q$UCToqD(!n{e^_%%m-ub50{Q!Mlz_0AR1zLk|)2o2! zz0l`uvWwouM$G1!&252`8Qk>y57BS=M&YtV-{Kzpi8O6x1~#C?JmI2d% zs_C10j2MvWhqvoyz=kD*Q~D=`ZSU?Th}j+=_xzf<4h{-)1Z-J2zjJ%AK(-Z<0Cg0b zIXf`aC`go51HmJtLhj!mzO1Z38UDt}5LA~r;MHvQ3MPHlmEmWO-}kS05)PlRv9`rX z=&D=m5PL%1{d=SePnOpU7+3EldcB`t@bQKy|DYd?Q_?&2^N2a)DQ)p@k$>;JHXt_S{c zJ3L73g9&NU4hdz-v`#oJyl^f28tE)Q-Zo_O=Z(qv45N}avb{g;Uk%;uhxT)%)V%%# zL_Q*JOFc)`W3%ym!+VJyY^=5Q;BVU~K}lJ?4Hx6x)nDUXC};EQSAX?4&5m}pe}&ss z@Fp~y<1b!!y{dm(K;DTf0k8%Yn4<6lQ`FmY94U-zF!{{P2L1&t_XY$av5jAY5-Ey1 z8#r^GLC@fze@QPC&q&hV!|J!IU%B|~(t4P~hexQ5G))h~6OFfx)gCmqUQh*4{d$WH zB?!Z(S_i?$DNp=wh#I$!fW?KkaO)dX&&#;l=d&CEgl@FD#8sp(*arKIQ*Z(7?iKDj zjni2$n-Qz>J1_-YxtH8u!I7frGfcIu)?h2`oBw5nvk`d1+-)bg{N!4OvFn!%sD^A-KLphgFnYLN#TBGM@&jU|zYY*YwaZB%AH)WnW|vvs z1kt&Z%_iSvv|;a3m2+K}t7yk#uZJgKkIbclvXp^haj$9T(jIyY4wWlY?IU}QUUVn9GgLutP%(N{^yiFVoZN;F%HpGJS_pFa7qBG`?+?HSs5)kU`_TjM#3J5rcfa z3QrpjMvrIj0ck7@fGg@3U|d$&VdM{x zb+g#GQm7=ht^U+QL4}ig-&;M8Zc#<}p{fw*G~b^;CHavSzcR?jWN@b%T7@N5K7MV) z#psF>&e`C)=U|p$z_)Y^!-h(&1$-I{qSPUslb!Z-G zHf@A*1Rqwv;TB0~1!fB|0GilIkKCR=BvZt!&2;dGPk9F=oK$n)P2If4IC_Z3u4`j= z9cZj+#A&Q+Hg6Y;>dt=p5{#Z1@Kk0jG14U>L(SE!NhH|pt+Lfj$TV9`FqcQ~^0E*J zjr3+ry&PsnMcbTY_?3c^32aeQQ={rzhLx3*08xRS4c>QeFEs^#D_s8H zp0JFri33k?+HwE(&= z4iJeM2ZLj>ZHcS_+FVCKBL}hlz7;=7#Mly!r`q~RCO~a^x687Fwc?;0i|r)BinNf( zAt)M)4;x~>Fa#5@9vmFCw3{}e9io8&bU+Z<0%@bsAPXY7%yx;f5Rr>)`dd2Txt>PW z7{w`Z%%7glLfMQA_qs&0}}37x(uon+CBuL!G0T%%BZH zpJDHuk}|MzO+|m!E@j~2)j0*gj8hq;w1H{Tun4UAa)qnzf4aClw*59Zsn>J&DVJB+6Eo|M2>)ILxJx zCmA)&2dk9k+no3Yy%q4;U5KhDW1_E&um~iR>uq8%S1ZO~0%50^mk1ax-h-G6s2o!< z*2=Q(FvIt6VhW>=-ECsJjnFIh4)q;zgbN$01A445jW+Ssi>L~y!Ze`7Q*K&-rziuI z7j_hD>Cmq>H}gWW$sM{cR{5^Fb_=!J1H$<@f6an1^4D+Y3(F+dgozA@v_3-9_2Zan z#(AUr?y#x#3wk^XfoR4vjtg<)5sU*<%-PuKzS)03f2oW=zq98^;kNra#U?B28d+iD?31QM<3bKPd!@pf=eoy?`eQHvjN{7@NT=V>4`X3!gW)J<`%(wDgY& zHpXabaE*5XB1M+R4xyMCUIQ$B25@1FxZ%h=V%y@tybg`#XPI!0Md&*!VpD_9S>vu8N> zc6oVmNjgwJ7i75iis#2Yp2~p1vL+66G|`xU==V;KsYl*o>_ZV=(}rC-u<3~1*_kTu zI3(3veVm;kaRk>Rc>`IZhWVVeiEm&Z=w0p72}MAlT*RAmg=;I&46S(Co2Rr1Ygvuv zi<EON69`PQiYN{+DTxG2gDX|9j-aq#t*^X>jOPz#WqdjPBqB@NC;(ih!LwD!|SuYpY z+KNRZC~fZsK7xFPcpT5T}$BE!te8hXqCu|AKIown4!uYfeFW3 zDYQ)s~zvJE-V2uX+L1~+u+gX94+L)IWFXIUfgn9()1R)h^R<_ZH1Yh z6BtM1J{hUpKy{nP5jAiyr%ZgU74zSCrm9Au7jgQPO;iO~C;VXC~6=SCMYVXbO$fG|@ z#d01=1DHR~k;wZqGaf~&6v^*P5SR}{=TX2(j{s*T|K5yBW%ZdBzS``{*bL#Vhw(6D z_ShILZqXjEM)b{B$eD(J7e0*72#*K9ezZBEZ1fr7@oK}19_uYOn~1TNQElgwC=a?sW;sd?#2NR5EF`?sYh$&J~h&(&wbH)Ue?YIEm zhK8brtBgwl1Xc7;f>OsG&NtlMf<~^!4T?))gCf}hRuK^E-A(Q2kSjJuth)?1E_}U1 zP@)dOeSWm`jyJj9B_!4=_+A@;#JE|lczNatWs;8G9b8@>@Lq1x&eg8{;QmLR-Lu(I zs*>qk<2~EwfJ?^E87}h9yW}!8x&(6dR!m?t$9xeJ98m|SGB04$)~J7=7y3G!j2|AK zW=|iLHb}3MwRqrfzJEeNkIW54rWqffqeN%+RL-g9k*Y-N*SMzz!7hVX36pX}L~@P@ zAapjZgbmaMpSUO3P+hKsO9sn@*yMWnk91Sp7)(#Kg~8j{7icm#yw^4c|4sij8 z;UQ9nMx5}FdErBDMqp$!MMgG({>HhfM-4P24p3R!8bH`>0x&>Y1R*wU+h~W!CXsXn zpXN#TY~ z#(_)(+z&FoW^JKNKuJ8!8e>voBl$V`6{Wc<2OoP@P&5l;PG--eJh9a%;>ImfPUcm@ zc*J#j>?mCOi02?t%A)kjNQkHjkD zYBCD56kFk>!xcADB6?>vSjWuOn*IScuHPc08Hj{F^57T|SuCFx+?lwW{+c5~GlYa$ z@B+oigJ_{ao*hq-Qs_}8N5wyg!0CX&`s4ol_+S_j4=Rx{EkI#OSfbD^3BeP7llkH) z#|{dx9y0|WzoRA2 z<*r9F&K3E;341r>j6Iola;tDqZwv#qlN?Keoi^Y?hJedB6!zUNYxx6%4{aO@$Dzp~ z`3?;Z(R;8+mY5JT0>ewrAb)MVL*Ab9{&5!y~~Tk|4d5| z7Ith~f;gCzmN6Vd#IGo!FM;5eY=NqahatsL*NACSF3}T!H z!kmE;89rw0P7bnPXV>}51kr?yfx%*tIY(k>@OF+rGA`HTh_}u2foplpXCdkszL-g3+wi~O>?}%CY z6c_DO6S_~*;0hl{2Wj6W-=RsFhqz4v$6_)6r-Ud*Z#*$+T*d^`w#!)O9ddP}y94{e zCM)C^UsrBDa-1HpFX5ZN#vmwRv9jK_gEO(}L@Mm+w+tGlI)~ob3TR#x&s~7wX!(&D zl-4&a1O_7V#IqYbG{yVH-xbSzjxU^Z@RqBue0OKh7Q*ejJI?gR?he^*d3uNy;oEyG zwrBd{>fmFIaVJMP4aK54e8m)Zq5_#`h(TLGCKb($69F_7I}AM`=QZ6^5b{ZENfsnp z!V&w|QoIi99ptLC>57L)5A>Hc>UWan@eCeTyx-Yu`v&C_GbQU-kZunpMg9~e(-M$M zW+|3jwlEWgH_|07lX>HLPt!6gfy|q$hACm^jr^d_oA84Skbx9wS?WmYO2}m1kSa2~ zyf~GG>o9_deTH=wPWGWb0GVTLh9l>u8#$OWwS{K-h#Q~1%$}#%n_z}yn)YPv+U$)j z?a-d&YqkPM`e>ub#Y3cd-=QZw$K{5|Q|ff}2SyjtpwCdme{MI8V2z^0q#3Sh%pYpa z;Auhrw&y3^!N$bvX2Lb{s`u?_@qWIn#AWGS#7w#M`~!9*Q5(m9olTHD%6j$0nlV>k zJ-&LLR9o!!q4Dj}%j)@QuGMyqK{%0MZ|xgDFc8JBxlScXyWT_umm-f@Gju@^p?E2I z$zY{f_3q@?S?#FgO?Zc4#TemTbvplK%0-)E%GG8(c#;>P`EH?IJeHoci^1rBK)Xyg ze{b!=t)MOH#c=rVs9u{p(DtT&g|)G0x@k3I^zLhgI=FsCP`#sr0~`U0oY1MQ0f(8> z*@0x;1Q+=p9Xmn56U41Cg3{yi;p&5rM+RQH$y!w=P9wd=!dc5c>~^c~Bs+II|1^GH z@(xWKwX$%FQ9L#lRgtS~0;nhxzvNGl#B3sHfXN-^4(a%VfD{ z0~1$|dqY$^v-<1Md`q2MtF~+A-G+3Hub3U z8y=ftS@EIn`+5luT$s#GjF1$xLc!`Y->ThMUSo9|XkuFcPhs?Zyz7#7BG?He(l zrucTp(-T`%D5FlF3roRZ^WWe|jBTEDSZ%Um?>Dm~JZ-SV=E~CHs8bk2j@gnfHY|F+ z$dh;@5-2-5L4jezBNwGZvhH)tsV2`11T50bSzIMHGrnO&MSs$y>7Sw!7w9zFy`ZR*)1A zGe|{8U$1jW<-g)#4(Ww0Uf<`CDouok8RV|lBzir9E+vK8MXoZ*IQ#$Ed$;bkj^$cd zKjnK&GJM7mi3=ff?r3O zd9X#SuD;c+tE#KJ7rzgG_XHA7j}EQRy!<>9( z+(!KU7$u09z!As)5EqAc&p=6H8;K+{Bu&3_8{nc|7@CV40*=RYn)x9WV)8iKRKEHJ^v=x0VhaGwIw>4 zw8h+Hc{wu!5CfvDvcE%@&`^^7m}bvdtDfbl$kD*q^!gN>1W^ph++cvGPb%|d&T6XQGh z?rn8wh$tCd>sVb@bk~kjXh>g_R{YahN=XOJ&y($e}^UIMz zPHjsxnh9nD;Ioqn-WK;0sQZ>sYtGTL*Ej;~NgVs!=lCbyOpEQ@v+*Q$bN6@T)&fLk zMJFo8_d4c}ozJe_re%~b>Q`E^o2oHd`#30576?C`2$Tv}9n z@6m+i_WBwf>%jwJM`ubw&vo{>gTTA9*(bX#FmA%4%!$?BZ*}z?tWn-b<-B%%{a(kk zKzoZsJG-r`?~DlnnT)QHjZl)#SJ`)GJA5ClZg{(gJbqSj&QsAZxJOc_Y*kI#?jGG| zSC(M7&VBcIhU!DDMElSSUMX|S^cCnptekc<2^UaST$%XQ?VPG4@vGZ8?>~OLLrX?T zEa|=g!cGtR*m~aKPK1D9CL4__9n)caUxiRM#xSHO{iU*zNN4UmHyiq>UJQ1#W*7`t zGt6hUEOQQkOmp;8#x#tCZ*X1O2c8`>DghV9GdSR!T@53Z1U}M+NI-^GVi-T+*?g7I zO@(4}h9PQv6haMez|pWRxwcm5Qrlsa+UEo?qNZ#lPzX4b0ySl^YjLnfG{cClP{Ye> z7*;qM&tHvjZ`k-~elff}^H^4gf!z&5sf+vjw+mh}tw4$)3bScc)3g`{wrTOG@5N52 z5N`&0H9x{*e!H3tgfMLCqn_YJG!5AULLH_dgR(mY{i)h`CN0B1!pZ3Eql$dz%U*GD zwP2;A`{hUM&!=(3o2;-Kr0^}L)<8~b zGC0R#x8nDU)8U(f*)4F(+{jjL$MW*{pd0IKm8Uw#rgKUJltvK2v!+GwbX=MdP@{Jf zB*@LUsic9xB6kBi5`;TEG3}x-9gTh}Cd6>+2I&_&=2MMbN={?XTG7}UU^T{K{;-X` z2@=!*>-*RVtg$ztgBZ-bjj`Jcrse1<$4sN$F?`*Y9~vo4$8=2(h!jeCh6lxy>JnG_ zT=QOiwjr6w5BAx%00pfW#}usy?(=QJM{;h5$+|n2i*Fcv$gv(6 zhvCQ&X?^SoC`ibo)joEB``Ga+JML#;n5y75hB$Xmz=nyp8NSYKVys3dLOsK$hI;fS z=Gp}aRuCYFr75tAX1uFTkc^?v5UV%SoVGKy36kz!1O70d-7rR$IYkmMUuRPq32o^@ z?@!C2If6npFvVB8rWGtKN=8^6J*eRF*IFYa*21b<0E!U=BQp~F?a28mBmWu53BHc( z-VU>So5)FE!8;7`3OTIMf}B(%IEVSW!8xN@As>tce@Q-=vLA1QT1EGIb3yXR6Na(z@^UmR*GPsDch76}^WEdt zBAN)^hrZ=T>`R>B@Fj&AYx%VDJ~ZrzMRpjAw28IA1l1T_($okos)J_B7etmm~7uVzUcv`K69gwx=(+K)7WF`7EVS+-8EBQ1k3N^ulMm${Hm`2DX z!%yEj4ceFO(+F;la8-xls@gOXOi+UX7)^uFE<K4S+YN;ne6_2J0@Xvc^<(YE*qX>biRN3i)oz ziG+-{z&JI!3>O(S47xsz)TC2`;4zI$WWZcHJ+yz?Wl(9iIAQ#&JJ`2ukgj}KLwp+BiUajKJ_pii@z3$ z`UlV%Ovm_>aVHO^+cI23O#%$=WSB|Z>1Wx&(YxvdcQO)`bLa8sbTo6#;>(#GZ~s%G zS2mcU2XRg$^mTa@y_#wpWUvk+P(Ig^b=oQg?=W8@D!a#sI2*ZLTfDT5Aq1W^CREV zCfM}NF8Ff}foXM*3$Mc4P2117g=Jf98~bKp_S}H&9Hq+?xfqv|5>_K?va5Gqc0q;k zF`g*lBfOB>+H4437U3J7kI!1xKxRYQiw&)4{qJ4_iwp1_*YAI^H`&rf3iQq$-{RNC z%pAADvAh<*ICd9c@NBwCpvx*k@2)z*wWt$ZTLX<{kul3Z1;c;JviN6UI2OQ2BJE;| ztr$*#!N9!pKn%<(tr$*qf`@r?fa?XraKnRQhUj1DLDf4IckyNeTf5MEHur3B4j0VD z%M3Ji*2t#btP;G%8v*i}XKu|*eUr6zS?6E!q-;)Ea~f;ptag#zn$4*R7vyB>s?Vt; zXR}IBlUb+voPHUfA5VTM!YW)C7tQt&Tq4;288L`_5HAGaqM5zW#`G z4!T~sYivj;`*%D9Z84Ax53Qg+dnAT(qEZ{s| zi8a*-oi!#ZGn8ucLaW)lns7m4X7FiJtdWtpStaPp$eAy-T*tz|>P zeK~&p4=>jB<*9*<;EM=mb09mgkwAkpQbgM-?Z8H=6}*v!wQV$++MgLcJOgoui`t^f zGeT#LiOP{k*kho&;`b(8kT`|5twuJfW|g2X(+hKrJx%*y!Rr>>-WejT?G2HGWsl`^ zg`TdKVnbZsjV2r6?jB>;)nr=$gM=yEZB??_w$up%X5sGIID(li-2JW7C5!&C=>|(h z0KAt1;EnM(UV^wO0B%)Ur<)oN>Sh6OYrfis3+jG30R9hBH}6KXhyZmj1*jY2aloK1 zQ0wZ7RkG=}G#=DNor0Mw(4g*@1Jt$Yek*nJZhRJ@pHl$2Hr=39C#ahO$h|6nysJ)7 zmjPr3)F!&kSlRl$ILd7vd9yeIzP)sUTYh*8Bq(4?jR&_`tSd;po5&zEN3mIo1yXf^ z30rjtm*1=(%>)9VIdE1xUCkWDShU#A9uwEfB!ZfUWqH(f1u0DuixaQbbklje5$?U2Sm`vdZt6Cyr zSKUA&gAo6wbG|VS=cRonTE#gxFN<(MFJ0ScQ0X{afCa7qW=?pO@f{G(OdUSosx?1l z{s&0lkG{Im*?=)Uc=gQ&0~yR_B1Dxa8SOb^b^HK-D@>q;j=B0Iv_Did#?>>M^2AI z%Py;xExL5nbR_<*AkI0sGcCGw9nb>Zk5nN-TbH#xef8Uq!)0BViJIyTI7eEa9-7V9 zYG}SYsyK>R#YVuztif9Sl>Eb^7>4W}aE?Qq2aXZ2hDNZXO2Qb#fM~ZcPMx{?;pTL) zj%Wls;GEO?$;Yu88o`b#fi|k!G=khQbUM(#q5AKpbWUrq4KmQmYsEwf*2zZERw*{Ui zIi6*m507T!$@%>CXn2e}$!GtBgIHkl&0zbuSZ(+w+uUA@W5m}F{4K6|VekA}Z$s|m zWmF3Y_w(28?5jUD`mF2%W;|{fC2rXhjJ09_R%%oo_(QJ-H6I=iFYv}YQq+dCD?4t7 zyALtW{`1An0xzmQ&KEZ~kIM~G+t4mZm*17Ct1@w2({o%#fnokO|G4;%hezADz*mfu zU-dZM^Z1H`%!9*de5)H-@&2iWM!k`{Rn-V?9^l)nk9M1}g^-ytpbJ=UA3r|MAD7pD zLp#1>rv#Y<_Wk#dcgm+AyuaOaXV1J^mHG0K162kBx6M(HRpyJgAdYA9u_kWJh@#EY zH6A`mS$-aU0g7fp? z*=Wv(2$IZ0jc+;~B)IDVP2=?ElmKHT49TuJ4dI}~h(~J$m_Edyy4Typ(q)qG6+`|u zb|Z9A0Dhogf`5o?-tAyjcr9oy$9sIm$VSF1D!}nTN@F@UZ?4a#U}#b70C6qDb=Qys zBj8zvokAm0)pS?Tcs1G>BZTS*@?uH9N=aey|ivu=G9htstV>~bi=I2K*g+`0s^}A3hAbFKxY@@>{@cD7MO=}=N z+u-o|e#2vL8W{&h&}s;u6fV>)$qWu7z9Vr9+`bi?|ewonT36FNU!|KCX>d^L1v$xSw6L0=TAU zh*HD@WSV-e%NV7^Q}0;NeDs@;?(djh4a%J|f zxSyl?PVq)VCf)egLaeDNDtml{p!^H}g|~eMLA8>pK$e{PnL6dZgv@7U`h>h2KJR4^AeWW`21DHR2}GA z0Dytu7~vJ@oBO5O!~Op3m&MPF52GqxTAxkmwxJ9?ZU#p(9odJjXL)&NI_UL}1VQ(? z=6!ysHhuLuG(N614brhO>m3N6lb-XuTWZo~w^vVe8&MVXXm$x|7?Egb+eJTz#wXFP z^=XvB+W?ZT@kxm$D6tSeLkP-&WWxikM&vR12*`K91oblCS$ZSf?2`S>XR zsEix+>Mg5V<9c)p>|c;=ibAK1B0(G+pa_I_jF{gwKL6YNgwMFLh-(P{1@?VeH&9G5 zF5;|U9s9HtZ>7%Nseq+M0AQ%b2^lgnY?saZ>}f1!%?Edn zZ^vRYMr&%VvrlOQ%sK5QtE1u7>@^q$@4<~{Zwk($IT)NpL=y7fO9^d4;rQX>!fsv_sHUWk1+->!vDj=rJv=fCRqzy3G#=lJDjwR% zvNT%;_xRxq3 znSC6s^Maqjv9+4hJbQH}d6`^}|J|mUv?*^Gw~0b?n3QqD+*EL^B$tyJkD6#*b^hVL zd8YD0qvCqL?PmF(rc-%jrKa^DG{9Y~svHG|boc8S+Iho=dH+`6|az@#t;RK_Cqos3-g z1u(ML45D-Nc+!(@CS@Ob3#99st&yz(FgdUi^$*WzY?R;WRWihvHv}Bv%3sc#?WvRL zoAcT5SJ%iVV~gGq&(t)}r=M5b#qY0Za8q}}sCL6g1*u1CHdKcCY_@B?n^>@UUq2eX z8+*&BsMO@h!VG!%E{dLvZNmn`D21RM6t zP|<&`uNdjZ8X>9#1`W%mLl`R(Es8R;*3c)^ijlxw(v!*MFGF18aB_8Sn=BMNK+(tG zzu+y3kgK}m@zfrMuS1)n05o{P+GgrxNFLcW)te1pnrqCaO|tPX$-cLb+cnpV?e+WF z;_dx1c_@hi@=~LY;+t&8uIMB}0{^5ebc>e9L!qK|1db9Kuo^aSQYgC7`SAs=MV*O1 zX~u2_AO_Xz!}Y`cbcyio%3dxsjcQ~GW8NsdL(Qgk4cWGriJ72%4L0;_4a~937^w}H z@HX7(=n|R*s}Od-PKHM=PG%1GyM+YU2rFKuU5v20>0$8tVT%+{MC^3!^TUEfB0?4Q zbF^U92?XRsWc-g567Dh}YX)XN?}aAB@$z39R1Q&`0*G0JSX+?bi#{ZU%AnNh0J-%t$us}*Mv802Ib`y&)1oMjZ#-RG=xotX$gm>{C+md zYI6q@g%doVW`=BsHcGg$-)VH4_sjeH#hT+g8J~_O7xRx^!=^2dOfa24DNJYkP6=5{vK%%%=iDv!U*M1+?iLJ~IF{Yz9JCd%XsY-K@!MeDY?D#7XvVegzN<{@e`s7U4Hx$E?W$ zA#SfYmV}X=NOT+Q{TLg?h8(0Sh3&QZ`|J*zqfA1^F)dq*___f0d1~VpLzsH8@ng@Y z6~e4#K!E;`K!MhfV4e+1K!y#TGx(o_1+0zfh*iyyZhkTeZUEaLi>3t+x44CYclN;yOVKmlQCL~r;p9@b8Gwk zc(vI=>}@dF_3!uV@$u;$cQQF{>_NJjeZM}sM_?(n>~{A156jO{fV~5nblT{=`Skih zHyKjL;IoXJU}JbYU|6?n-t)$MC5ZTUJ%)p=yavw2$+l8}KuG^Oz1RUn%auG#%T!bZ z^t=x2>}=83KZ}YyoFS>ssK}4IsAy$!r|ML+A|)C9$y8*^--n7WW_D{k352btCKUt7 z)jbV%qavX}MXh&uLE`Gjp5|ja5Zg`Xc`0J1=0BZ0t#tgF%MuJL7}IONx=w;3e`sx2 zRO^cfc9tH404{S2l@f9Gxcw)yQ(-+vo@7;qJPfEW@Qfb# z7Qr+yM%J)2XlG)k zVgS*9yBb}-xx}XF$%LK@Cc_04FJ&~%tGj+zCKq=#Atpe}A-*AAe|;0bYc9^08k8`4>7s)5VACt+z1UziPm*1fCVJbk^&M?AAqbcKHgRsBuKVt>OqHb zlSDytY9j!b3cvwF{t>RJv%Et%5*S05Mg?faS^}~&XCwyP`|sr3e7ajf$KEnLe1Xu- zoEErzO!5gN2W&DJOz+-dSMTW&rV_q^DtRouW0_I3w7?nN!_&JhbS*+KcZ+nhy5us2 zb|tLRWtYp}?lu-q)mLA=XAj%O{makWWdx7BRO|O^ys1-d;(3wQZ}92l?SH^RqmU=X z;P#lub_8Hn7FH_vfu^5vo7YF)c#Q%~94Ai9EW>~f@xI!vm(X5=!eF=plV!olBlkJX zkV64q(eVP-*n9qbh5H<{a#J*i>WxaMoS;7#R~47<(D>7F2r1Tx1 zPl7qg8vv%MdhTxZcn{21v+=VR`7y1>)#?mlmdp3eu{3fkvvqF=Ok93>Jb z^os$!xSfFFfI3KO!CAnGF-6v+d^h0RN#8X=Pn#N-*H`<0#qVWXfG9bCVSPEsrICNaDcsb!qd|WA=wsgYmMLZfZXY->8 zVjR5JsseP?Ob09nJe^E&Ra6D$2%w7+VBg7bV7mFu{W}^C^P}U}lgUppOl%T!9fGxO z>kND`A1thYSW8DoC9ut$by#*%mKke!*~w*DW~lJ8$;o+HXll_5FR;CPF+^;ZAfyZO z-8De#lf}4b^y;E2HV*LGsVJMC#XO?*OK&+V_xYoF+^oXsB5`MOI=Qr97U}^pOzWO* zAD^zbx!y4yOV@hxX%#n%(y)?dwP+Go83`&2q*EW?y~S<{Hmy_uN6e)a@ua5>_H^$b z7|FuKrDQ1P1ai@>BBk`cJwn2Y7OJ*5GeSh;3|LT+dd(Ir=+SdMX<>OC^k zsR&GCGiv6F^zd1M1)~9Dn>Hj1Oth2-@j+;qD~7fjKFtL~0jT}`@c9>G(fn^cS6X1v zJZsE7SlK+042x0jyiA29ECMibRHIvMR|1l&tG5SL!r_T}R ziM}7nU5puZr4D2w0G7!_@+kK8&JzX%2a@AWG@dB4d|!NoF4zn{9U~=i`IsAV;7&{V z^1A_RMauf}+tX`hSeU^1sAoA76|Y;$`r)(Gzws?Yp*9H%!PLrp%>HR2^8zvwUH+{m+;H`g*Ixibsh@K3Xu|miNreon)fZnzLN^EhRg17< zXXo8}xa!84`Hm-^#N`V5@DW>|QJ!)3>TK~l%TuYjgl-kC^tFiEB)bp|r`=RG=*u!2 z^zV5@C5EUtP?8__uA`E&V$Ya^%&}vGJ zD=)MfC%YW|zFn@W_$1eHXbhBEH_&?kNGSFUuu_6i#b4GgOlJi5{b}tT0^HLs*5NbW ze!@K{YI0YD%1P(m0GER{0N0Zq7#E~i&KG~M#9zvCl!{D|Hdrnb?>~p-*x(;#IjbbI z;E%E#C)mz%R-&Ewzm(?e;-LTNR z84i_(<-)*o245SNcH_9GoeeAF;%Zoq5FSh5>ZyyyVpvh=D=<$N7`6xn0lk;(vxhyxuyEev9dx?p+7jV+NoT>Kj^Xw8Vu4+uuhd zyqql_-?jja*wYbUs~fpVlejfORFQ&D#~rFK}vW zo61=~w*OP@*3bm*=VB7T`v#OPH$t4nV-PBg5Yh4JE7!n1_q%xw8 zlIjAiL@l`}xd7|C1h{w$KnLtfnU;;bVkQoGUDgz=5_d;dvxnldyCExxdHEeTCD1B@ zIahwynopQ|@`c1haFBFb*p4-70=ZeDUHj7iqTE0VTY!)CbsK zK?bx*GSc=~uT|I?e6jjgyB)-XtVJVc+F25v6QQVIm5LP56pRK}S^~?Pn8+5*dZ90v zdhVL61}I~oP!L=!|CfP$Yt{JHawz}18OZFcFEfyV{Fw~oO2%%kC2N>z6AToEZrbPx z9*4?8z}oBJMYU99++T~(k?lr!v0Ev~w2~SlToqt)pa?%@v3f=vz0?_5?{}jsW)Ei3 z1c&kkQM*8mV!Klob#K|io9i{;;&h}GXxobuJ*%YClA8CHuwHxpeq2*e(&Mh8$4TwlsEpQPTYcA)RH z&xZsdT(J0X&yBv}@$tV+UfLPy`JS1nFyU#$Tr9E>m}%O7dw9z`4-~nVVDM_VqCSlG z105Y@GrStES#0FQb)$wy1H+BjEpSFt!qrA;s`S7TTB=dfYX8Pjny4N8U>we7YNPaG zV}}DzvM{jPC>@T68aQc4vt?Q_<7QgJClT6Rr(HBgQrrw>ZdO^tFv#shHi~uf8Q&Ia zg>gJ`(+vzM;M1MZj!mk9#yB0LJUp3?GvO3b-`W^r@{UB*Y>qQv9Fs`A^KqJZ>}V1D z44OId+2Bm=XmtxtG)&4)$WH4M$KuiT+2Z>B-KyHeWRaHsb#d~a-?iwkj~}j|KI&;g zZ4`wU#qHB=T*=7_9z|R8x{)VO72;>*C8e(llfm4A#R6;O;Sm>ZgeZB?_u^q=$ye#K zVi=aOMe9sn9t|g*)U|JB*;G%2d+=iV^cEL&Dz_SW3`~2o4J|lrhqx2G5_gHKGaL7m za4UGd)Y;6po*>--hictD;l#8q`cb?ELu$*w-inef2Tu9u$uLT|IuD$2qLb<<4$F<^f>3Hwq<|>cPX;b@kwz#&z}Ju@*G(xb-|%Q!*Zy zzFKYX?u+j_UeQ!19$Tp*L&?o)Oa0Gw`or`p7akR{zxTTVAV3SH1Ggy_J8=xeyiG+rGymC|#QG~tFNZMfn09k{Wg zR@}IZ@SQ2cO`UH-*cb;ifDkrJu#OvVOHn`Ixz0BocPSVS#Br4Wd3>|x36_$f+@CmJ z@;wFw1Xx;m`C*ftJs@@Ep}H!NYk+(8u)Hjfjw_mHn97GqXHIff1d2@d6cRmP1tX!6IsXKro(Zl7#9_1qwI;k}D_f8l zN6wXgvNiCqYz^K<$JfW$BI@BNN-Ko33t#Fs)kaV}6x0gq{KEIEYvXf@y%7eSGIy<1_Wr3X9{H-W(BlF}c(YpJ_SY$oFqF*e8C9$y^G$CjZkYtFH<$LF1iI94mhmH-@Y zdwj0h5NDvp*a9{J)r74PHdgdK#GB1tFII0Y==B9Ew{uTViwa?^7vzhj6Aumfq%63- z114~u1-F|VTQwPIx9E1+WMJNc+i*qFNE7PU^{KHWO!#bS{MW30Qp?M5uXbL$zmCz4r69On83F4IVJOB!R{wlu}uJTRq`+FF$~zgsp| zoree=US03zr#FO)mYHig3DQBL0pi=e3k+U z6@lwW$O&9SLPg~N0uq8BB0#%`R&)8Ss2lQ|g0u!&AR!uyIPIPVYAYm!S+%E7?f%WD zGOQ@^<;j;~heAvMU>usxcO!zsiw?xP5wl}Y`E@Mpq zXsYPlw@jxZ=Qm9!AvC1b;yA?!e7->)h?8qqf)>+>#v+=t2Uy4{_kQj2%xqcTQ%t8{ zr1FS55hKn;(WSJIX%T0QyQvE&N+y=4s$|`28rWBt3b9B}eP|97(&*X6prHajJHUf@ ze9^Wx+n}FfKi@LjihiYbZka7X+h>~y5mdE^P&xbr6NF81p*=!_um#-$JfcH;79J{K zk{Y%c(V&aS=_BN+Aeu<5ow~BbNj!az)7?cCpLwCqsMEuiw&OlUjmq;YStIr*Pw}9T zt~kaX1e@|5$|5q%U5&7zr_0h63V+7t(XzD?f#&)KY3!2J?qA~&{y?<3c`wkHcT3#s zYV4)pr6cUX{?Sd7q&l_3qGZ%-#O%_ZtS2T8L3`$}UYx})u3!F9lg+fUML=)QB6@Y! zl58@Q*{r}6*E8rEw^mJtY+87)Y%*liVth?*rF6A(YbK&4x3Zv(TPs1WZ>h1Qja%)z zZ|oJ^Y6Z#hi#*Ey58TIS<5t3dfm^M(jayl;gIlfW+xPYU|HiFJ5r%HHh-{s;oLfV; zrnu~$0<-Pl5uM$$@N5Co;hhgyh&(>~rTtW6+M0}Y3 z$4Wcq;I;N6mbr&ol_RC|bqWeIPf~odZ-LGI=T%?^+sAgu|Kx6Y@0q#yq{IF7V2xB$ zmB6b-MF}e0oWManK@W{K^ql+H|A&<#~Z*xBPUy_uU){-0s9QATKLndRie48o|BdJTCpAo2TeDf{k9Fx z!im>Z?P{^WhtRvoa0XSIU$Mw15Q%WOTtE%nw07J`0qjTc7AJYy(GqY7;99$kvN}eT z(C&6mhLNb^!ncnqc!qj%c~@x-nEEF>qxeE_eNT|}tV0Y0o0MW-E;S=!%mf_

U%hgYYwX=o6}S}h-^ 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Πw8+4sUwDLqe3O1NW_-oC=-+cOMg~b2&w1d>rX8UEnEaM z%Iiu*A@6b$EV*nE9Cg>e_0q0VEKEI>)6?nTz5CWmzBN&)R4mcl%Eb@eq!b{)S&gP6 zj2x9&a%2@#3{nXj1h;wzF~>}_IzVdJh}aTS)=E*Di4YO@5^UV)HmlQ_Iy_{;F&&6- zoarBC(ADkFoxRN?yzSnwKl{7z_u(i|4+gNpyNTPfgpEDfM0l}nf3I)%*9R|l>@PJ4 zR_w2x_0Nc3jGdOgS;t+Fnr95zo4vt?YQ~HmfO|j%7k#@61Y}*F4d+J1k)i zS^bByK(JvP6Om5Sim{H%vWayqQ~Fij&mM?@Vx|i^1hFofdF;A19T);_knGr{THoQ& zdY=jC16#mkPG7wbVjA;yb9jD&fLLfU3mZNaD|SLm<+M6$FszF3fMZpx00`8iyIYke zf~I}5oN6`<0^gsb`*QN|T=294yrriqJ=5eXh zS+1QTAUf6G+)j9nKJAL&n)X>w46x4-M%ZKs zeX-ntT?+^@ICPpbA$qav5~Q=z`6k`i8JBHe8UVenh3(0Ts%%}J1J?f+GlL_xErCx*yVG+y}1MXTETg3vF zPq!A6Q@R<@Pb_H+PKGGJIxVoibTtdP!vN_Rgam(fzCyB`<0Gf!9w?VYH2sY85W6GZsNxQR^~=K$xt z2t$OA6Wh4LTs)*Yc&3;eB$GvFp<9Fl&u|Tq`8zOH=MD^5ef9tXiSd901^1i96%4)E z1KPKvGlt2Zt;2hvklZ;@NUVuwIGk!WaEuK!N;8^YNjb|0)(%BXcB1B@&oGWs%_ z=4z*zdwV#ZB36t9(Jbxu*%dQ*erx!eF%71N#$VFPa zp8?lEw{P0i^I$cN$PD_YYUwt`)@l(ehPA*CKo4bzBh9h{Ls;}0n*|`yyRp8#jUBc- z#tz((8E$MGZ>)3{}!aDcwY6~m&msQi1KTUBVxOCJ;6qY*lnB&+w+TBQWF762pnRa}q ze{gt&?%sS}{@g!0=!K-ZChcPQ2Gr0LzJ=Z2fZ`=Z61{7!=Iv-V!ln{PEZyku-r0L4 zRsOi2G}P2%mIW5=FFpp|J2G!$-2X zGH`F+ZiwtwwsV_mJjHxImDxns5j{R?2r~nRYD7N}Z9ofJlph5Jgt5+*a|q}tx@nu~ zL>WyUC&`cpHc1eYGCsYSJhmBt8EJFmR2ri27Aw~_P%Rag&4&!zJ-twQF0h;%l=B0Q z#cOD^L^gzRa3#@3*?BHYRJN{|oXdHhY(Z^Ga6zOIJj^6u6cddkO>={W!Qe%ejD!O{ zV1A_R3GSsuatsdW?}FWSq37Rxo+-wAb@>AfKOlE7!`ZNjJ*`lkc8XpOCBE+NA~&Hu zH{o!Su$uPewOWa0pVDrBfh~Df^H<{ z*f!ds&d4qpRL~n_ya}2aH!0)A&_IMRTtkMLc6Hy6s}l?~;3}B}AJiF4ISh#qc?h!5 zcr2J?270`OpeqfTp=IW&+x?B+?f{W!u*E!a{{Xhr#x^X*Zjx%j3RsIGWQb`1u4CdR z#T4dKOo3$TedZ7i^>4v4}{PIEX<@LjL zsqZFcOw2pz?Qg@}l&r>0tV}U0u%6`M6}7xA1k(+Aw-_dZqt@JT4FxUuqRvfCC6~s4 zaMEOS;Gz=}3t=EJd9ZhMfLWpLNG54r%`7e?z zF&%Wib+C7L5XlnrN@R&y{hb|*55$9aR{g zk58y_Q`rqGE`nA|O(?+`EgjmDt{g!V!<6qIf*-9H)CMXeo^q*9e*|$(p^XO9ima`m zcJk@~ghWM4|TP{`r(z*Mc0P-3<;sjfu7s~2HrUP_CxGioo7(#m7@k0oe{pzB6?{HxwNSTGm)pt;Ak-~n2Ey98R8R$d!S%HvUQ$?%L6k}8K7m{_ z;?6Ka4nu)FTtB2L5*m7i47OGop&#->R6YYx!~8>~1psAvHdb{CgJnT#(bXEJ23^7yR?PxA>Aadt>0fa9 z0#~t#glL@h3RnWreu)3M;?714#NEUVdW3!KC5qA2t2nodUY1gfu34oRpHfl`y9>C% zuxoEcDMm2&!@+_K4bfBUphh*rv23C1*todHgX3f3Dz= zQZ)#X1jl;7lhsouW&qX?G&b*+FTQ6H#~nR{KBN+k=^eD5E0&hx7?8QhTfejzyoP%T z8}#jYW0|xQ7M0P`zo$*oG3L;QZAd%X>h#(v9qBVO@RVC@rT`qaW&-WwrXws?uJR7h zi4^tVLYggQ34Z1o}jD2%F<^ zZ-OU#b;NW(XcKfbIJjN<5iupqEkveY!Ahj#wW~j{=+1C}cJF*=_~DMAYV%qhw z5jfA$Cq3p8cNU0Ln)lX7I&!;%8r|Btn5rYYVYei*P�{h8m!DR~ODsk>or|Bb}o4 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 diff --git a/libs/miniupnpc/mingw64/libminiupnpc.a b/libs/miniupnpc/mingw64/libminiupnpc.a index 4275196d37a8c9574d5c7f66e9938b8855dd7d87..afa9533fbd80786547c65c919872efa4b08e51ad 100644 GIT binary patch literal 72024 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 VXzK{an#g9R^EzUk>)M@){{zT;Xb1oR literal 135706 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�b-r+1mct*02*iV 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/ -#include -#include -#include -#include -#include -#if defined(WIN32) || defined(__amigaos__) || defined(__amigaos4__) -#ifdef WIN32 -#include -#include -#include -#include -#include -#endif -#if defined(__amigaos__) || defined(__amigaos4__) -#include -#endif -#if defined(__amigaos__) -#define uint16_t unsigned short -#endif -/* Hack */ -#define UNIX_PATH_LEN 108 -struct sockaddr_un { - uint16_t sun_family; - char sun_path[UNIX_PATH_LEN]; -}; -#else -#include -#include -#endif - -#include "minissdpc.h" -#include "miniupnpc.h" - -#include "codelength.h" - -struct UPNPDev * -getDevicesFromMiniSSDPD(const char * devtype, const char * socketpath) -{ - struct UPNPDev * tmp; - struct UPNPDev * devlist = NULL; - unsigned char buffer[2048]; - ssize_t n; - unsigned char * p; - unsigned char * url; - unsigned int i; - unsigned int urlsize, stsize, usnsize, l; - int s; - struct sockaddr_un addr; - - s = socket(AF_UNIX, SOCK_STREAM, 0); - if(s < 0) - { - /*syslog(LOG_ERR, "socket(unix): %m");*/ - perror("socket(unix)"); - return NULL; - } - addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, socketpath, sizeof(addr.sun_path)); - /* TODO : check if we need to handle the EINTR */ - if(connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) - { - /*syslog(LOG_WARNING, "connect(\"%s\"): %m", socketpath);*/ - close(s); - return NULL; - } - stsize = strlen(devtype); - buffer[0] = 1; /* request type 1 : request devices/services by type */ - p = buffer + 1; - l = stsize; CODELENGTH(l, p); - if(p + stsize > buffer + sizeof(buffer)) - { - /* devtype is too long ! */ - close(s); - return NULL; - } - memcpy(p, devtype, stsize); - p += stsize; - if(write(s, buffer, p - buffer) < 0) - { - /*syslog(LOG_ERR, "write(): %m");*/ - perror("minissdpc.c: write()"); - close(s); - return NULL; - } - n = read(s, buffer, sizeof(buffer)); - if(n<=0) - { - perror("minissdpc.c: read()"); - close(s); - return NULL; - } - p = buffer + 1; - for(i = 0; i < buffer[0]; i++) - { - if(p+2>=buffer+sizeof(buffer)) - break; - DECODELENGTH(urlsize, p); - if(p+urlsize+2>=buffer+sizeof(buffer)) - break; - url = p; - p += urlsize; - DECODELENGTH(stsize, p); - if(p+stsize+2>=buffer+sizeof(buffer)) - break; - tmp = (struct UPNPDev *)malloc(sizeof(struct UPNPDev)+urlsize+stsize); - tmp->pNext = devlist; - tmp->descURL = tmp->buffer; - tmp->st = tmp->buffer + 1 + urlsize; - memcpy(tmp->buffer, url, urlsize); - tmp->buffer[urlsize] = '\0'; - memcpy(tmp->buffer + urlsize + 1, p, stsize); - p += stsize; - tmp->buffer[urlsize+1+stsize] = '\0'; - devlist = tmp; - /* added for compatibility with recent versions of MiniSSDPd - * >= 2007/12/19 */ - DECODELENGTH(usnsize, p); - p += usnsize; - if(p>buffer + sizeof(buffer)) - break; - } - close(s); - return devlist; -} - diff --git a/libs/miniupnpc/minissdpc.h b/libs/miniupnpc/minissdpc.h deleted file mode 100644 index 25e91ce31..000000000 --- a/libs/miniupnpc/minissdpc.h +++ /dev/null @@ -1,15 +0,0 @@ -/* $Id: minissdpc.h,v 1.1 2007/08/31 15:15:33 nanard Exp $ */ -/* Project: miniupnp - * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * Author: Thomas Bernard - * Copyright (c) 2005-2007 Thomas Bernard - * This software is subjects to the conditions detailed - * in the LICENCE file provided within this distribution */ -#ifndef __MINISSDPC_H__ -#define __MINISSDPC_H__ - -struct UPNPDev * -getDevicesFromMiniSSDPD(const char * devtype, const char * socketpath); - -#endif - diff --git a/libs/miniupnpc/miniupnpc-config.cmake b/libs/miniupnpc/miniupnpc-config.cmake new file mode 100644 index 000000000..116876a5d --- /dev/null +++ b/libs/miniupnpc/miniupnpc-config.cmake @@ -0,0 +1,6 @@ +if(NOT EXISTS "${CMAKE_CURRENT_LIST_DIR}/libminiupnpc-shared.cmake" OR MINIUPNPC_USE_STATIC_LIBS) + include("${CMAKE_CURRENT_LIST_DIR}/miniupnpc-private.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/libminiupnpc-static.cmake") +else() + include("${CMAKE_CURRENT_LIST_DIR}/libminiupnpc-shared.cmake") +endif() diff --git a/libs/miniupnpc/miniupnpc.c b/libs/miniupnpc/miniupnpc.c deleted file mode 100644 index 0a3aae33d..000000000 --- a/libs/miniupnpc/miniupnpc.c +++ /dev/null @@ -1,943 +0,0 @@ -/* $Id: miniupnpc.c,v 1.95 2011/05/15 21:42:26 nanard Exp $ */ -/* Project : miniupnp - * Author : Thomas BERNARD - * copyright (c) 2005-2011 Thomas Bernard - * This software is subjet to the conditions detailed in the - * provided LICENSE file. */ -#define __EXTENSIONS__ 1 -#if !defined(MACOSX) && !defined(__sun) -#if !defined(_XOPEN_SOURCE) && !defined(__OpenBSD__) && !defined(__NetBSD__) -#ifndef __cplusplus -#define _XOPEN_SOURCE 600 -#endif -#endif -#ifndef __BSD_VISIBLE -#define __BSD_VISIBLE 1 -#endif -#endif - -#include -#include -#include -#ifdef WIN32 -/* Win32 Specific includes and defines */ -#include -#include -#include -#include -#define snprintf _snprintf -#ifndef strncasecmp -#if defined(_MSC_VER) && (_MSC_VER >= 1400) -#define strncasecmp _memicmp -#else /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ -#define strncasecmp memicmp -#endif /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ -#endif /* #ifndef strncasecmp */ -#define MAXHOSTNAMELEN 64 -#else /* #ifdef WIN32 */ -/* Standard POSIX includes */ -#include -#if defined(__amigaos__) && !defined(__amigaos4__) -/* Amiga OS 3 specific stuff */ -#define socklen_t int -#else -#include -#endif -#include -#include -#include -#include -#include -#include -#include -#if !defined(__amigaos__) && !defined(__amigaos4__) -#include -#endif -#include -#include -#define closesocket close -#endif /* #else WIN32 */ -#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT -#include -#endif -#if defined(__amigaos__) || defined(__amigaos4__) -/* Amiga OS specific stuff */ -#define TIMEVAL struct timeval -#endif - -#include "miniupnpc.h" -#include "minissdpc.h" -#include "miniwget.h" -#include "minisoap.h" -#include "minixml.h" -#include "upnpcommands.h" -#include "connecthostport.h" -#include "receivedata.h" - -#ifdef WIN32 -#define PRINT_SOCKET_ERROR(x) printf("Socket error: %s, %d\n", x, WSAGetLastError()); -#else -#define PRINT_SOCKET_ERROR(x) perror(x) -#endif - -#define SOAPPREFIX "s" -#define SERVICEPREFIX "u" -#define SERVICEPREFIX2 'u' - -/* root description parsing */ -LIBSPEC void parserootdesc(const char * buffer, int bufsize, struct IGDdatas * data) -{ - struct xmlparser parser; - /* xmlparser object */ - parser.xmlstart = buffer; - parser.xmlsize = bufsize; - parser.data = data; - parser.starteltfunc = IGDstartelt; - parser.endeltfunc = IGDendelt; - parser.datafunc = IGDdata; - parser.attfunc = 0; - parsexml(&parser); -#ifdef DEBUG - printIGD(data); -#endif -} - -/* simpleUPnPcommand2 : - * not so simple ! - * return values : - * pointer - OK - * NULL - error */ -char * simpleUPnPcommand2(int s, const char * url, const char * service, - const char * action, struct UPNParg * args, - int * bufsize, const char * httpversion) -{ - char hostname[MAXHOSTNAMELEN+1]; - unsigned short port = 0; - char * path; - char soapact[128]; - char soapbody[2048]; - char * buf; - int n; - - *bufsize = 0; - snprintf(soapact, sizeof(soapact), "%s#%s", service, action); - if(args==NULL) - { - /*soapbodylen = */snprintf(soapbody, sizeof(soapbody), - "\r\n" - "<" SOAPPREFIX ":Envelope " - "xmlns:" SOAPPREFIX "=\"http://schemas.xmlsoap.org/soap/envelope/\" " - SOAPPREFIX ":encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" - "<" SOAPPREFIX ":Body>" - "<" SERVICEPREFIX ":%s xmlns:" SERVICEPREFIX "=\"%s\">" - "" - "" - "\r\n", action, service, action); - } - else - { - char * p; - const char * pe, * pv; - int soapbodylen; - soapbodylen = snprintf(soapbody, sizeof(soapbody), - "\r\n" - "<" SOAPPREFIX ":Envelope " - "xmlns:" SOAPPREFIX "=\"http://schemas.xmlsoap.org/soap/envelope/\" " - SOAPPREFIX ":encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" - "<" SOAPPREFIX ":Body>" - "<" SERVICEPREFIX ":%s xmlns:" SERVICEPREFIX "=\"%s\">", - action, service); - p = soapbody + soapbodylen; - while(args->elt) - { - /* check that we are never overflowing the string... */ - if(soapbody + sizeof(soapbody) <= p + 100) - { - /* we keep a margin of at least 100 bytes */ - return NULL; - } - *(p++) = '<'; - pe = args->elt; - while(*pe) - *(p++) = *(pe++); - *(p++) = '>'; - if((pv = args->val)) - { - while(*pv) - *(p++) = *(pv++); - } - *(p++) = '<'; - *(p++) = '/'; - pe = args->elt; - while(*pe) - *(p++) = *(pe++); - *(p++) = '>'; - args++; - } - *(p++) = '<'; - *(p++) = '/'; - *(p++) = SERVICEPREFIX2; - *(p++) = ':'; - pe = action; - while(*pe) - *(p++) = *(pe++); - strncpy(p, ">\r\n", - soapbody + sizeof(soapbody) - p); - } - if(!parseURL(url, hostname, &port, &path)) return NULL; - if(s<0) - { - s = connecthostport(hostname, port); - if(s < 0) - { - return NULL; - } - } - - n = soapPostSubmit(s, path, hostname, port, soapact, soapbody, httpversion); - if(n<=0) { -#ifdef DEBUG - printf("Error sending SOAP request\n"); -#endif - closesocket(s); - return NULL; - } - - buf = getHTTPResponse(s, bufsize); -#ifdef DEBUG - if(*bufsize > 0 && buf) - { - printf("SOAP Response :\n%.*s\n", *bufsize, buf); - } -#endif - closesocket(s); - return buf; -} - -/* simpleUPnPcommand : - * not so simple ! - * return values : - * pointer - OK - * NULL - error */ -char * simpleUPnPcommand(int s, const char * url, const char * service, - const char * action, struct UPNParg * args, - int * bufsize) -{ - char * buf; - - buf = simpleUPnPcommand2(s, url, service, action, args, bufsize, "1.1"); -/* - buf = simpleUPnPcommand2(s, url, service, action, args, bufsize, "1.0"); - if (!buf || *bufsize == 0) - { -#if DEBUG - printf("Error or no result from SOAP request; retrying with HTTP/1.1\n"); -#endif - buf = simpleUPnPcommand2(s, url, service, action, args, bufsize, "1.1"); - } -*/ - return buf; -} - -/* parseMSEARCHReply() - * the last 4 arguments are filled during the parsing : - * - location/locationsize : "location:" field of the SSDP reply packet - * - st/stsize : "st:" field of the SSDP reply packet. - * The strings are NOT null terminated */ -static void -parseMSEARCHReply(const char * reply, int size, - const char * * location, int * locationsize, - const char * * st, int * stsize) -{ - int a, b, i; - i = 0; - a = i; /* start of the line */ - b = 0; /* end of the "header" (position of the colon) */ - while(isin_addr, sizeof (struct in_addr)); - break; - - case AF_INET6: - memcpy (dst, &sin6->sin6_addr, sizeof (struct in6_addr)); - break; - } - return 1; - } - - return 0; - } -#endif - -/* upnpDiscover() : - * return a chained list of all devices found or NULL if - * no devices was found. - * It is up to the caller to free the chained list - * delay is in millisecond (poll) */ -LIBSPEC struct UPNPDev * -upnpDiscover(int delay, const char * multicastif, - const char * minissdpdsock, int sameport, - int ipv6, - int * error) -{ - struct UPNPDev * tmp; - struct UPNPDev * devlist = 0; - int opt = 1; - static const char MSearchMsgFmt[] = - "M-SEARCH * HTTP/1.1\r\n" - "HOST: %s:" XSTR(PORT) "\r\n" - "ST: %s\r\n" - "MAN: \"ssdp:discover\"\r\n" - "MX: %u\r\n" - "\r\n"; - static const char * const deviceList[] = { -#if 0 - "urn:schemas-upnp-org:device:InternetGatewayDevice:2", - "urn:schemas-upnp-org:service:WANIPConnection:2", -#endif - "urn:schemas-upnp-org:device:InternetGatewayDevice:1", - "urn:schemas-upnp-org:service:WANIPConnection:1", - "urn:schemas-upnp-org:service:WANPPPConnection:1", - "upnp:rootdevice", - 0 - }; - int deviceIndex = 0; - char bufr[1536]; /* reception and emission buffer */ - int sudp; - int n; - struct sockaddr_storage sockudp_r; - unsigned int mx; -#ifdef NO_GETADDRINFO - struct sockaddr_storage sockudp_w; -#else - int rv; - struct addrinfo hints, *servinfo, *p; -#endif -#ifdef WIN32 - MIB_IPFORWARDROW ip_forward; -#endif - int linklocal = 1; - - if(error) - *error = UPNPDISCOVER_UNKNOWN_ERROR; -#if !defined(WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) - /* first try to get infos from minissdpd ! */ - if(!minissdpdsock) - minissdpdsock = "/var/run/minissdpd.sock"; - while(!devlist && deviceList[deviceIndex]) { - devlist = getDevicesFromMiniSSDPD(deviceList[deviceIndex], - minissdpdsock); - /* We return what we have found if it was not only a rootdevice */ - if(devlist && !strstr(deviceList[deviceIndex], "rootdevice")) { - if(error) - *error = UPNPDISCOVER_SUCCESS; - return devlist; - } - deviceIndex++; - } - deviceIndex = 0; -#endif - /* fallback to direct discovery */ -#ifdef WIN32 - sudp = socket(ipv6 ? PF_INET6 : PF_INET, SOCK_DGRAM, IPPROTO_UDP); -#else - sudp = socket(ipv6 ? PF_INET6 : PF_INET, SOCK_DGRAM, 0); -#endif - if(sudp < 0) - { - if(error) - *error = UPNPDISCOVER_SOCKET_ERROR; - PRINT_SOCKET_ERROR("socket"); - return NULL; - } - /* reception */ - memset(&sockudp_r, 0, sizeof(struct sockaddr_storage)); - if(ipv6) { - struct sockaddr_in6 * p = (struct sockaddr_in6 *)&sockudp_r; - p->sin6_family = AF_INET6; - if(sameport) - p->sin6_port = htons(PORT); - p->sin6_addr = in6addr_any; /* in6addr_any is not available with MinGW32 3.4.2 */ - } else { - struct sockaddr_in * p = (struct sockaddr_in *)&sockudp_r; - p->sin_family = AF_INET; - if(sameport) - p->sin_port = htons(PORT); - p->sin_addr.s_addr = INADDR_ANY; - } -#ifdef WIN32 -/* This code could help us to use the right Network interface for - * SSDP multicast traffic */ -/* Get IP associated with the index given in the ip_forward struct - * in order to give this ip to setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF) */ - if(!ipv6 - && (GetBestRoute(inet_addr("223.255.255.255"), 0, &ip_forward) == NO_ERROR)) { - DWORD dwRetVal = 0; - PMIB_IPADDRTABLE pIPAddrTable; - DWORD dwSize = 0; -#ifdef DEBUG - IN_ADDR IPAddr; -#endif - int i; -#ifdef DEBUG - printf("ifIndex=%lu nextHop=%lx \n", ip_forward.dwForwardIfIndex, ip_forward.dwForwardNextHop); -#endif - pIPAddrTable = (MIB_IPADDRTABLE *) malloc(sizeof (MIB_IPADDRTABLE)); - if (GetIpAddrTable(pIPAddrTable, &dwSize, 0) == ERROR_INSUFFICIENT_BUFFER) { - free(pIPAddrTable); - pIPAddrTable = (MIB_IPADDRTABLE *) malloc(dwSize); - } - if(pIPAddrTable) { - dwRetVal = GetIpAddrTable( pIPAddrTable, &dwSize, 0 ); -#ifdef DEBUG - printf("\tNum Entries: %ld\n", pIPAddrTable->dwNumEntries); -#endif - for (i=0; i < (int) pIPAddrTable->dwNumEntries; i++) { -#ifdef DEBUG - printf("\n\tInterface Index[%d]:\t%ld\n", i, pIPAddrTable->table[i].dwIndex); - IPAddr.S_un.S_addr = (u_long) pIPAddrTable->table[i].dwAddr; - printf("\tIP Address[%d]: \t%s\n", i, inet_ntoa(IPAddr) ); - IPAddr.S_un.S_addr = (u_long) pIPAddrTable->table[i].dwMask; - printf("\tSubnet Mask[%d]: \t%s\n", i, inet_ntoa(IPAddr) ); - IPAddr.S_un.S_addr = (u_long) pIPAddrTable->table[i].dwBCastAddr; - printf("\tBroadCast[%d]: \t%s (%ld)\n", i, inet_ntoa(IPAddr), pIPAddrTable->table[i].dwBCastAddr); - printf("\tReassembly size[%d]:\t%ld\n", i, pIPAddrTable->table[i].dwReasmSize); - printf("\tType and State[%d]:", i); - printf("\n"); -#endif - if (pIPAddrTable->table[i].dwIndex == ip_forward.dwForwardIfIndex) { - /* Set the address of this interface to be used */ - struct in_addr mc_if; - memset(&mc_if, 0, sizeof(mc_if)); - mc_if.s_addr = pIPAddrTable->table[i].dwAddr; - if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&mc_if, sizeof(mc_if)) < 0) { - PRINT_SOCKET_ERROR("setsockopt"); - } - ((struct sockaddr_in *)&sockudp_r)->sin_addr.s_addr = pIPAddrTable->table[i].dwAddr; -#ifndef DEBUG - break; -#endif - } - } - free(pIPAddrTable); - pIPAddrTable = NULL; - } - } -#endif - -#ifdef WIN32 - if (setsockopt(sudp, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof (opt)) < 0) -#else - if (setsockopt(sudp, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt)) < 0) -#endif - { - if(error) - *error = UPNPDISCOVER_SOCKET_ERROR; - PRINT_SOCKET_ERROR("setsockopt"); - return NULL; - } - - if(multicastif) - { - if(ipv6) { -#if !defined(WIN32) - /* according to MSDN, if_nametoindex() is supported since - * MS Windows Vista and MS Windows Server 2008. - * http://msdn.microsoft.com/en-us/library/bb408409%28v=vs.85%29.aspx */ - unsigned int ifindex = if_nametoindex(multicastif); /* eth0, etc. */ - if(setsockopt(sudp, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifindex, sizeof(&ifindex)) < 0) - { - PRINT_SOCKET_ERROR("setsockopt"); - } -#else -#ifdef DEBUG - printf("Setting of multicast interface not supported in IPv6 under Windows.\n"); -#endif -#endif - } else { - struct in_addr mc_if; - mc_if.s_addr = inet_addr(multicastif); /* ex: 192.168.x.x */ - ((struct sockaddr_in *)&sockudp_r)->sin_addr.s_addr = mc_if.s_addr; - if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&mc_if, sizeof(mc_if)) < 0) - { - PRINT_SOCKET_ERROR("setsockopt"); - } - } - } - - /* Avant d'envoyer le paquet on bind pour recevoir la reponse */ - if (bind(sudp, (const struct sockaddr *)&sockudp_r, - ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) != 0) - { - if(error) - *error = UPNPDISCOVER_SOCKET_ERROR; - PRINT_SOCKET_ERROR("bind"); - closesocket(sudp); - return NULL; - } - - if(error) - *error = UPNPDISCOVER_SUCCESS; - /* Calculating maximum response time in seconds */ - mx = ((unsigned int)delay) / 1000u; - /* receiving SSDP response packet */ - for(n = 0; deviceList[deviceIndex]; deviceIndex++) - { - if(n == 0) - { - /* sending the SSDP M-SEARCH packet */ - n = snprintf(bufr, sizeof(bufr), - MSearchMsgFmt, - ipv6 ? - (linklocal ? "[" UPNP_MCAST_LL_ADDR "]" : "[" UPNP_MCAST_SL_ADDR "]") - : UPNP_MCAST_ADDR, - deviceList[deviceIndex], mx); -#ifdef DEBUG - printf("Sending %s", bufr); -#endif -#ifdef NO_GETADDRINFO - /* the following code is not using getaddrinfo */ - /* emission */ - memset(&sockudp_w, 0, sizeof(struct sockaddr_storage)); - if(ipv6) { - struct sockaddr_in6 * p = (struct sockaddr_in6 *)&sockudp_w; - p->sin6_family = AF_INET6; - p->sin6_port = htons(PORT); - inet_pton(AF_INET6, - linklocal ? UPNP_MCAST_LL_ADDR : UPNP_MCAST_SL_ADDR, - &(p->sin6_addr)); - } else { - struct sockaddr_in * p = (struct sockaddr_in *)&sockudp_w; - p->sin_family = AF_INET; - p->sin_port = htons(PORT); - p->sin_addr.s_addr = inet_addr(UPNP_MCAST_ADDR); - } - n = sendto(sudp, bufr, n, 0, - (const void *)&sockudp_w, - ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)); - if (n < 0) { - if(error) - *error = UPNPDISCOVER_SOCKET_ERROR; - PRINT_SOCKET_ERROR("sendto"); - break; - } -#else /* #ifdef NO_GETADDRINFO */ - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; // AF_INET6 or AF_INET - hints.ai_socktype = SOCK_DGRAM; - /*hints.ai_flags = */ - if ((rv = getaddrinfo(ipv6 - ? (linklocal ? UPNP_MCAST_LL_ADDR : UPNP_MCAST_SL_ADDR) - : UPNP_MCAST_ADDR, - XSTR(PORT), &hints, &servinfo)) != 0) { - if(error) - *error = UPNPDISCOVER_SOCKET_ERROR; -#ifdef WIN32 - fprintf(stderr, "getaddrinfo() failed: %d\n", rv); -#else - fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); -#endif - break; - } - for(p = servinfo; p; p = p->ai_next) { - n = sendto(sudp, bufr, n, 0, p->ai_addr, p->ai_addrlen); - if (n < 0) { - PRINT_SOCKET_ERROR("sendto"); - continue; - } - } - freeaddrinfo(servinfo); - if(n < 0) { - if(error) - *error = UPNPDISCOVER_SOCKET_ERROR; - break; - } -#endif /* #ifdef NO_GETADDRINFO */ - } - /* Waiting for SSDP REPLY packet to M-SEARCH */ - n = receivedata(sudp, bufr, sizeof(bufr), delay); - if (n < 0) { - /* error */ - if(error) - *error = UPNPDISCOVER_SOCKET_ERROR; - break; - } else if (n == 0) { - /* no data or Time Out */ - if (devlist) { - /* no more device type to look for... */ - if(error) - *error = UPNPDISCOVER_SUCCESS; - break; - } - if(ipv6) { - if(linklocal) { - linklocal = 0; - --deviceIndex; - } else { - linklocal = 1; - } - } - } else { - const char * descURL=NULL; - int urlsize=0; - const char * st=NULL; - int stsize=0; - /*printf("%d byte(s) :\n%s\n", n, bufr);*/ /* affichage du message */ - parseMSEARCHReply(bufr, n, &descURL, &urlsize, &st, &stsize); - if(st&&descURL) - { -#ifdef DEBUG - printf("M-SEARCH Reply:\nST: %.*s\nLocation: %.*s\n", - stsize, st, urlsize, descURL); -#endif - for(tmp=devlist; tmp; tmp = tmp->pNext) { - if(memcmp(tmp->descURL, descURL, urlsize) == 0 && - tmp->descURL[urlsize] == '\0' && - memcmp(tmp->st, st, stsize) == 0 && - tmp->st[stsize] == '\0') - break; - } - /* at the exit of the loop above, tmp is null if - * no duplicate device was found */ - if(tmp) - continue; - tmp = (struct UPNPDev *)malloc(sizeof(struct UPNPDev)+urlsize+stsize); - if(!tmp) { - /* memory allocation error */ - if(error) - *error = UPNPDISCOVER_MEMORY_ERROR; - break; - } - tmp->pNext = devlist; - tmp->descURL = tmp->buffer; - tmp->st = tmp->buffer + 1 + urlsize; - memcpy(tmp->buffer, descURL, urlsize); - tmp->buffer[urlsize] = '\0'; - memcpy(tmp->buffer + urlsize + 1, st, stsize); - tmp->buffer[urlsize+1+stsize] = '\0'; - devlist = tmp; - } - } - } - closesocket(sudp); - return devlist; -} - -/* freeUPNPDevlist() should be used to - * free the chained list returned by upnpDiscover() */ -LIBSPEC void freeUPNPDevlist(struct UPNPDev * devlist) -{ - struct UPNPDev * next; - while(devlist) - { - next = devlist->pNext; - free(devlist); - devlist = next; - } -} - -static void -url_cpy_or_cat(char * dst, const char * src, int n) -{ - if( (src[0] == 'h') - &&(src[1] == 't') - &&(src[2] == 't') - &&(src[3] == 'p') - &&(src[4] == ':') - &&(src[5] == '/') - &&(src[6] == '/')) - { - strncpy(dst, src, n); - } - else - { - int l = strlen(dst); - if(src[0] != '/') - dst[l++] = '/'; - if(l<=n) - strncpy(dst + l, src, n - l); - } -} - -/* Prepare the Urls for usage... - */ -LIBSPEC void GetUPNPUrls(struct UPNPUrls * urls, struct IGDdatas * data, - const char * descURL) -{ - char * p; - int n1, n2, n3, n4; - n1 = strlen(data->urlbase); - if(n1==0) - n1 = strlen(descURL); - n1 += 2; /* 1 byte more for Null terminator, 1 byte for '/' if needed */ - n2 = n1; n3 = n1; n4 = n1; - n1 += strlen(data->first.scpdurl); - n2 += strlen(data->first.controlurl); - n3 += strlen(data->CIF.controlurl); - n4 += strlen(data->IPv6FC.controlurl); - - urls->ipcondescURL = (char *)malloc(n1); - urls->controlURL = (char *)malloc(n2); - urls->controlURL_CIF = (char *)malloc(n3); - urls->controlURL_6FC = (char *)malloc(n4); - /* maintenant on chope la desc du WANIPConnection */ - if(data->urlbase[0] != '\0') - strncpy(urls->ipcondescURL, data->urlbase, n1); - else - strncpy(urls->ipcondescURL, descURL, n1); - p = strchr(urls->ipcondescURL+7, '/'); - if(p) p[0] = '\0'; - strncpy(urls->controlURL, urls->ipcondescURL, n2); - strncpy(urls->controlURL_CIF, urls->ipcondescURL, n3); - strncpy(urls->controlURL_6FC, urls->ipcondescURL, n4); - - url_cpy_or_cat(urls->ipcondescURL, data->first.scpdurl, n1); - - url_cpy_or_cat(urls->controlURL, data->first.controlurl, n2); - - url_cpy_or_cat(urls->controlURL_CIF, data->CIF.controlurl, n3); - - url_cpy_or_cat(urls->controlURL_6FC, data->IPv6FC.controlurl, n4); - -#ifdef DEBUG - printf("urls->ipcondescURL='%s' %u n1=%d\n", urls->ipcondescURL, - (unsigned)strlen(urls->ipcondescURL), n1); - printf("urls->controlURL='%s' %u n2=%d\n", urls->controlURL, - (unsigned)strlen(urls->controlURL), n2); - printf("urls->controlURL_CIF='%s' %u n3=%d\n", urls->controlURL_CIF, - (unsigned)strlen(urls->controlURL_CIF), n3); - printf("urls->controlURL_6FC='%s' %u n4=%d\n", urls->controlURL_6FC, - (unsigned)strlen(urls->controlURL_6FC), n4); -#endif -} - -LIBSPEC void -FreeUPNPUrls(struct UPNPUrls * urls) -{ - if(!urls) - return; - free(urls->controlURL); - urls->controlURL = 0; - free(urls->ipcondescURL); - urls->ipcondescURL = 0; - free(urls->controlURL_CIF); - urls->controlURL_CIF = 0; - free(urls->controlURL_6FC); - urls->controlURL_6FC = 0; -} - -int -UPNPIGD_IsConnected(struct UPNPUrls * urls, struct IGDdatas * data) -{ - char status[64]; - unsigned int uptime; - status[0] = '\0'; - UPNP_GetStatusInfo(urls->controlURL, data->first.servicetype, - status, &uptime, NULL); - if(0 == strcmp("Connected", status)) - { - return 1; - } - else - return 0; -} - - -/* UPNP_GetValidIGD() : - * return values : - * 0 = NO IGD found - * 1 = A valid connected IGD has been found - * 2 = A valid IGD has been found but it reported as - * not connected - * 3 = an UPnP device has been found but was not recognized as an IGD - * - * In any non zero return case, the urls and data structures - * passed as parameters are set. Donc forget to call FreeUPNPUrls(urls) to - * free allocated memory. - */ -LIBSPEC int -UPNP_GetValidIGD(struct UPNPDev * devlist, - struct UPNPUrls * urls, - struct IGDdatas * data, - char * lanaddr, int lanaddrlen) -{ - char * descXML; - int descXMLsize = 0; - struct UPNPDev * dev; - int ndev = 0; - int state; /* state 1 : IGD connected. State 2 : IGD. State 3 : anything */ - if(!devlist) - { -#ifdef DEBUG - printf("Empty devlist\n"); -#endif - return 0; - } - for(state = 1; state <= 3; state++) - { - for(dev = devlist; dev; dev = dev->pNext) - { - /* we should choose an internet gateway device. - * with st == urn:schemas-upnp-org:device:InternetGatewayDevice:1 */ - descXML = miniwget_getaddr(dev->descURL, &descXMLsize, - lanaddr, lanaddrlen); - if(descXML) - { - ndev++; - memset(data, 0, sizeof(struct IGDdatas)); - memset(urls, 0, sizeof(struct UPNPUrls)); - parserootdesc(descXML, descXMLsize, data); - free(descXML); - descXML = NULL; - if(0==strcmp(data->CIF.servicetype, - "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1") - || state >= 3 ) - { - GetUPNPUrls(urls, data, dev->descURL); - -#ifdef DEBUG - printf("UPNPIGD_IsConnected(%s) = %d\n", - urls->controlURL, - UPNPIGD_IsConnected(urls, data)); -#endif - if((state >= 2) || UPNPIGD_IsConnected(urls, data)) - return state; - FreeUPNPUrls(urls); - if(data->second.servicetype[0] != '\0') { -#ifdef DEBUG - printf("We tried %s, now we try %s !\n", - data->first.servicetype, data->second.servicetype); -#endif - /* swaping WANPPPConnection and WANIPConnection ! */ - memcpy(&data->tmp, &data->first, sizeof(struct IGDdatas_service)); - memcpy(&data->first, &data->second, sizeof(struct IGDdatas_service)); - memcpy(&data->second, &data->tmp, sizeof(struct IGDdatas_service)); - GetUPNPUrls(urls, data, dev->descURL); -#ifdef DEBUG - printf("UPNPIGD_IsConnected(%s) = %d\n", - urls->controlURL, - UPNPIGD_IsConnected(urls, data)); -#endif - if((state >= 2) || UPNPIGD_IsConnected(urls, data)) - return state; - FreeUPNPUrls(urls); - } - } - memset(data, 0, sizeof(struct IGDdatas)); - } -#ifdef DEBUG - else - { - printf("error getting XML description %s\n", dev->descURL); - } -#endif - } - } - return 0; -} - -/* UPNP_GetIGDFromUrl() - * Used when skipping the discovery process. - * return value : - * 0 - Not ok - * 1 - OK */ -int -UPNP_GetIGDFromUrl(const char * rootdescurl, - struct UPNPUrls * urls, - struct IGDdatas * data, - char * lanaddr, int lanaddrlen) -{ - char * descXML; - int descXMLsize = 0; - descXML = miniwget_getaddr(rootdescurl, &descXMLsize, - lanaddr, lanaddrlen); - if(descXML) { - memset(data, 0, sizeof(struct IGDdatas)); - memset(urls, 0, sizeof(struct UPNPUrls)); - parserootdesc(descXML, descXMLsize, data); - free(descXML); - descXML = NULL; - GetUPNPUrls(urls, data, rootdescurl); - return 1; - } else { - return 0; - } -} - diff --git a/libs/miniupnpc/miniupnpc.def b/libs/miniupnpc/miniupnpc.def index 10b9f5800..baa841f20 100644 --- a/libs/miniupnpc/miniupnpc.def +++ b/libs/miniupnpc/miniupnpc.def @@ -1,9 +1,13 @@ LIBRARY ; miniupnpc library + miniupnpc EXPORTS ; miniupnpc upnpDiscover + upnpDiscoverDevice + upnpDiscoverDevices + upnpDiscoverAll freeUPNPDevlist parserootdesc UPNP_GetValidIGD @@ -23,7 +27,9 @@ EXPORTS UPNP_GetExternalIPAddress UPNP_GetLinkLayerMaxBitRates UPNP_AddPortMapping + UPNP_AddAnyPortMapping UPNP_DeletePortMapping + UPNP_DeletePortMappingRange UPNP_GetPortMappingNumberOfEntries UPNP_GetSpecificPortMappingEntry UPNP_GetGenericPortMappingEntry diff --git a/libs/miniupnpc/miniupnpc.pc.in b/libs/miniupnpc/miniupnpc.pc.in new file mode 100644 index 000000000..3d33cbdb0 --- /dev/null +++ b/libs/miniupnpc/miniupnpc.pc.in @@ -0,0 +1,18 @@ +# this template is filled-in by CMake `configure_file(... @ONLY)` +# the `@....@` are filled in by CMake configure_file(), +# from variables set in your CMakeLists.txt or by CMake itself +# +# Good tutoral for understanding .pc files: +# https://people.freedesktop.org/~dbn/pkg-config-guide.html + +prefix="@CMAKE_INSTALL_PREFIX@" +exec_prefix="${prefix}" +libdir="${prefix}/lib" +includedir="${prefix}/include" + +Name: @PROJECT_NAME@ +Description: @PROJECT_DESCRIPTION@ +URL: @PROJECT_HOMEPAGE_URL@ +Version: @PROJECT_VERSION@ +Libs: -L"${libdir}" -lminiupnpc +Cflags: -I"${includedir}" diff --git a/libs/miniupnpc/miniupnpc.rc b/libs/miniupnpc/miniupnpc.rc new file mode 100644 index 000000000..92954afda --- /dev/null +++ b/libs/miniupnpc/miniupnpc.rc @@ -0,0 +1,36 @@ +#include +#include "rc_version.h" + +VS_VERSION_INFO VERSIONINFO + FILEVERSION LIBMINIUPNPC_MAJOR_VERSION,LIBMINIUPNPC_MINOR_VERSION,LIBMINIUPNPC_MICRO_VERSION,0 + PRODUCTVERSION LIBMINIUPNPC_MAJOR_VERSION,LIBMINIUPNPC_MINOR_VERSION,LIBMINIUPNPC_MICRO_VERSION,0 + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0 +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE VFT2_UNKNOWN // not used +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "04090000" /* Lang = US English, Charset = ASCII */ + BEGIN + VALUE "FileDescription", "MiniUPnPc library\0" + VALUE "FileVersion", LIBMINIUPNPC_DOTTED_VERSION "\0" + VALUE "InternalName", INTERNAL_NAME + VALUE "LegalCopyright", "Copyright (C) Thomas Bernard\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", INTERNAL_NAME + VALUE "ProductName", "MiniUPnPc\0" + VALUE "ProductVersion", LIBMINIUPNPC_DOTTED_VERSION "\0" + VALUE "Comments", "For more information visit https://miniupnp.tuxfamil.org/\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 0 /* US English, ASCII */ + END +END diff --git a/libs/miniupnpc/miniupnpcstrings.h.cmake b/libs/miniupnpc/miniupnpcstrings.h.cmake index a3726350c..78c8fe9c5 100644 --- a/libs/miniupnpc/miniupnpcstrings.h.cmake +++ b/libs/miniupnpc/miniupnpcstrings.h.cmake @@ -1,7 +1,15 @@ -#ifndef __MINIUPNPCSTRINGS_H__ -#define __MINIUPNPCSTRINGS_H__ +#ifndef MINIUPNPCSTRINGS_H_INCLUDED +#define MINIUPNPCSTRINGS_H_INCLUDED #define OS_STRING "${CMAKE_SYSTEM_NAME}" #define MINIUPNPC_VERSION_STRING "${MINIUPNPC_VERSION}" +#if 0 +/* according to "UPnP Device Architecture 1.0" */ +#define UPNP_VERSION_STRING "UPnP/1.0" +#else +/* according to "UPnP Device Architecture 1.1" */ +#define UPNP_VERSION_STRING "UPnP/1.1" +#endif + #endif diff --git a/libs/miniupnpc/miniupnpcstrings.h.in b/libs/miniupnpc/miniupnpcstrings.h.in index 201c9a862..68bf4293d 100644 --- a/libs/miniupnpc/miniupnpcstrings.h.in +++ b/libs/miniupnpc/miniupnpcstrings.h.in @@ -1,15 +1,23 @@ -/* $Id: miniupnpcstrings.h.in,v 1.4 2011/01/04 11:41:53 nanard Exp $ */ +/* $Id: miniupnpcstrings.h.in,v 1.6 2014/11/04 22:31:55 nanard Exp $ */ /* Project: miniupnp * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * Author: Thomas Bernard - * Copyright (c) 2005-2011 Thomas Bernard + * Copyright (c) 2005-2014 Thomas Bernard * This software is subjects to the conditions detailed * in the LICENCE file provided within this distribution */ -#ifndef __MINIUPNPCSTRINGS_H__ -#define __MINIUPNPCSTRINGS_H__ +#ifndef MINIUPNPCSTRINGS_H_INCLUDED +#define MINIUPNPCSTRINGS_H_INCLUDED #define OS_STRING "OS/version" #define MINIUPNPC_VERSION_STRING "version" +#if 0 +/* according to "UPnP Device Architecture 1.0" */ +#define UPNP_VERSION_STRING "UPnP/1.0" +#else +/* according to "UPnP Device Architecture 1.1" */ +#define UPNP_VERSION_STRING "UPnP/1.1" +#endif + #endif diff --git a/libs/miniupnpc/miniwget.h b/libs/miniupnpc/miniwget.h deleted file mode 100644 index 8314b4000..000000000 --- a/libs/miniupnpc/miniwget.h +++ /dev/null @@ -1,30 +0,0 @@ -/* $Id: miniwget.h,v 1.6 2010/12/09 16:11:33 nanard Exp $ */ -/* Project : miniupnp - * Author : Thomas Bernard - * Copyright (c) 2005 Thomas Bernard - * This software is subject to the conditions detailed in the - * LICENCE file provided in this distribution. - * */ -#ifndef __MINIWGET_H__ -#define __MINIWGET_H__ - -#include "declspec.h" - -#ifdef __cplusplus -extern "C" { -#endif - -LIBSPEC void * getHTTPResponse(int s, int * size); - -LIBSPEC void * miniwget(const char *, int *); - -LIBSPEC void * miniwget_getaddr(const char *, int *, char *, int); - -int parseURL(const char *, char *, unsigned short *, char * *); - -#ifdef __cplusplus -} -#endif - -#endif - diff --git a/libs/miniupnpc/msvc/genminiupnpcstrings.vbs b/libs/miniupnpc/msvc/genminiupnpcstrings.vbs new file mode 100644 index 000000000..415b2ee6b --- /dev/null +++ b/libs/miniupnpc/msvc/genminiupnpcstrings.vbs @@ -0,0 +1,112 @@ +' VBScript to generate miniupnpcstrings.h +' Copyright 2018 Thomas Bernard +'Set WshShell = CreateObject("WScript.Shell") +Set FSO = CreateObject("Scripting.FileSystemObject") +versionfile = "..\version" +infile = "..\miniupnpcstrings.h.in" +outfile = "..\miniupnpcstrings.h" +outfilerc = "..\rc_version.h" + +On Error Resume Next + +'Wscript.Echo revision + +Err.Clear +Set f = FSO.OpenTextFile(versionfile, 1, False) ' 1 = Read +If Err.Number = 0 Then + version = f.ReadLine + f.Close +Else + ' Exit error + WScript.Quit 1 +End If + +os_version = "0.0.0" +strComputer = "." +Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2") +Set colOperatingSystems = objWMIService.ExecQuery ("Select * from Win32_OperatingSystem") +For Each objOperatingSystem in colOperatingSystems + 'Wscript.Echo objOperatingSystem.Caption & " -- " + os_version = objOperatingSystem.Version +Next + +'Wscript.Echo os_version + +Dim array +needWrite = True + +' First Check if the file already contains the right versions +Err.Clear +Set f_in = FSO.OpenTextFile(outfile, 1, False) +If Err.Number = 0 Then + old_version = "" + old_os_version = "" + Do Until f_in.AtEndOfStream + line = f_in.ReadLine + If Len(line) > 0 Then + array = Split(line, " ") + If UBound(array) >= 2 And array(0) = "#define" Then + If array(1) = "OS_STRING" Then + old_os_version = Replace(array(2), Chr(34), "") + ElseIf array(1) = "MINIUPNPC_VERSION_STRING" Then + old_version = Replace(array(2), Chr(34), "") + End if + End if + End If + Loop + f_in.Close + If old_version = version And old_os_version = "MSWindows/" & os_version Then + needWrite = False + Else + needWrite = True + End If +End If + +If Not needWrite Then + ' check files dates + Set fIn1 = FSO.GetFile(versionfile) + Set fIn2 = FSO.GetFile(infile) + Set fOut = FSO.GetFile(outfile) + If DateDiff("s", fIn1.DateLastModified, fOut.DateLastModified) < 0 Then + needWrite = True + End If + If DateDiff("s", fIn2.DateLastModified, fOut.DateLastModified) < 0 Then + needWrite = True + End If +End If + +If Not needWrite Then + ' nothing to do + WScript.Quit 0 +End if + +' generate the file +Err.Clear +Set f_in = FSO.OpenTextFile(infile, 1, False) +If Err.Number = 0 Then + Set f_out = FSO.OpenTextFile(outfile, 2, True) ' 2 = Write + Do Until f_in.AtEndOfStream + line = f_in.ReadLine + If Len(line) > 0 Then + array = Split(line, " ") + If UBound(array) >= 2 And array(0) = "#define" Then + If array(1) = "OS_STRING" Then + line = "#define OS_STRING " & Chr(34) & "MSWindows/" & os_version & Chr(34) + ElseIf array(1) = "MINIUPNPC_VERSION_STRING" Then + line = "#define MINIUPNPC_VERSION_STRING " & Chr(34) & version & Chr(34) + End if + End if + End If + f_out.WriteLine line + Loop + f_in.Close + f_out.Close +End If + +Set f_out = FSO.OpenTextFile(outfilerc, 2, True) ' 2 = Write +f_out.WriteLine "#define LIBMINIUPNPC_DOTTED_VERSION " & Chr(34) & version & Chr(34) +ver = Split(version, ".") +f_out.WriteLine "#define LIBMINIUPNPC_MAJOR_VERSION " & ver(0) +f_out.WriteLine "#define LIBMINIUPNPC_MINOR_VERSION " & ver(1) +f_out.WriteLine "#define LIBMINIUPNPC_MICRO_VERSION " & ver(2) +f_out.Close diff --git a/libs/miniupnpc/msvc/miniupnpc.vcproj b/libs/miniupnpc/msvc/miniupnpc.vcproj index cce2de576..90646c53f 100644 --- a/libs/miniupnpc/msvc/miniupnpc.vcproj +++ b/libs/miniupnpc/msvc/miniupnpc.vcproj @@ -41,7 +41,7 @@ + + @@ -184,6 +188,10 @@ RelativePath="..\upnpcommands.c" > + + @@ -198,10 +206,6 @@ Filter="h;hpp;hxx;hm;inl;inc;xsd" UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" > - - @@ -218,6 +222,10 @@ RelativePath="..\minisoap.h" > + + @@ -250,6 +258,10 @@ RelativePath="..\upnpcommands.h" > + + diff --git a/libs/miniupnpc/msvc/miniupnpc.vcxproj b/libs/miniupnpc/msvc/miniupnpc.vcxproj new file mode 100644 index 000000000..24c24ea08 --- /dev/null +++ b/libs/miniupnpc/msvc/miniupnpc.vcxproj @@ -0,0 +1,215 @@ + + + + + Debug Dll + Win32 + + + Debug + Win32 + + + Release Dll + Win32 + + + Release + Win32 + + + + {D28CE435-CB33-4BAE-8A52-C6EF915956F5} + miniupnpc + Win32Proj + + + + StaticLibrary + v140 + Unicode + true + + + DynamicLibrary + v140 + Unicode + true + + + StaticLibrary + v140 + Unicode + + + DynamicLibrary + v140 + Unicode + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>14.0.25123.0 + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + + + + Disabled + _CRT_SECURE_NO_WARNINGS;MINIUPNP_STATICLIB;DEBUG;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + Level3 + EditAndContinue + ..;..\include;%(AdditionalIncludeDirectories) + + + genminiupnpcstrings.vbs + + + INTERNAL_NAME="\"miniupnpc.dll\0\"";%(PreprocessorDefinitions) + + + + + Disabled + _CRT_SECURE_NO_WARNINGS;MINIUPNP_EXPORTS;DEBUG;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + EditAndContinue + ..;..\include;%(AdditionalIncludeDirectories) + + + genminiupnpcstrings.vbs + + + ws2_32.lib;IPHlpApi.Lib;%(AdditionalDependencies) + + + INTERNAL_NAME="\"miniupnpc.dll\0\"";%(PreprocessorDefinitions) + + + + + MaxSpeed + true + _CRT_SECURE_NO_WARNINGS;MINIUPNP_STATICLIB;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + Level3 + ProgramDatabase + ..;..\include;%(AdditionalIncludeDirectories) + + + genminiupnpcstrings.vbs + + + INTERNAL_NAME="\"miniupnpc.dll\0\"";%(PreprocessorDefinitions) + + + + + MaxSpeed + true + _CRT_SECURE_NO_WARNINGS;MINIUPNP_EXPORTS;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + ..;..\include;%(AdditionalIncludeDirectories) + + + genminiupnpcstrings.vbs + + + ws2_32.lib;IPHlpApi.Lib;%(AdditionalDependencies) + + + INTERNAL_NAME="\"miniupnpc.dll\0\"";%(PreprocessorDefinitions) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libs/miniupnpc/msvc/miniupnpc.vcxproj.filters b/libs/miniupnpc/msvc/miniupnpc.vcxproj.filters new file mode 100644 index 000000000..245bc4c24 --- /dev/null +++ b/libs/miniupnpc/msvc/miniupnpc.vcxproj.filters @@ -0,0 +1,133 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + {508da401-2f8e-4fdb-9a43-95baa95a91e7} + + + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + + + Fichiers d%27en-tête + + + Fichiers d%27en-tête + + + Fichiers d%27en-tête + + + Fichiers d%27en-tête + + + Fichiers d%27en-tête + + + Fichiers d%27en-tête + + + Fichiers d%27en-tête + + + Fichiers d%27en-tête + + + Fichiers d%27en-tête + + + Fichiers d%27en-tête + + + Fichiers d%27en-tête + + + Fichiers d%27en-tête + + + Fichiers d%27en-tête + + + Fichiers d%27en-tête + + + Fichiers d%27en-tête + + + Fichiers d%27en-tête + + + Fichiers d%27en-tête + + + Fichiers d%27en-tête + + + Fichiers d%27en-tête + + + + + scripts + + + + + Fichiers de ressources + + + \ No newline at end of file diff --git a/libs/miniupnpc/msvc/miniupnpc_vs2010.sln b/libs/miniupnpc/msvc/miniupnpc_vs2010.sln new file mode 100644 index 000000000..2fdfe91ab --- /dev/null +++ b/libs/miniupnpc/msvc/miniupnpc_vs2010.sln @@ -0,0 +1,36 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual C++ Express 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "miniupnpc_vs2010", "miniupnpc_vs2010.vcxproj", "{D28CE435-CB33-4BAE-8A52-C6EF915956F5}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "upnpc-static_vs2010", "upnpc-static_vs2010.vcxproj", "{469E1CF6-08A2-4B7B-A2AA-5BDB089857C1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug Dll|Win32 = Debug Dll|Win32 + Debug|Win32 = Debug|Win32 + Release Dll|Win32 = Release Dll|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D28CE435-CB33-4BAE-8A52-C6EF915956F5}.Debug Dll|Win32.ActiveCfg = Debug Dll|Win32 + {D28CE435-CB33-4BAE-8A52-C6EF915956F5}.Debug Dll|Win32.Build.0 = Debug Dll|Win32 + {D28CE435-CB33-4BAE-8A52-C6EF915956F5}.Debug|Win32.ActiveCfg = Debug|Win32 + {D28CE435-CB33-4BAE-8A52-C6EF915956F5}.Debug|Win32.Build.0 = Debug|Win32 + {D28CE435-CB33-4BAE-8A52-C6EF915956F5}.Release Dll|Win32.ActiveCfg = Release Dll|Win32 + {D28CE435-CB33-4BAE-8A52-C6EF915956F5}.Release Dll|Win32.Build.0 = Release Dll|Win32 + {D28CE435-CB33-4BAE-8A52-C6EF915956F5}.Release|Win32.ActiveCfg = Release|Win32 + {D28CE435-CB33-4BAE-8A52-C6EF915956F5}.Release|Win32.Build.0 = Release|Win32 + {469E1CF6-08A2-4B7B-A2AA-5BDB089857C1}.Debug Dll|Win32.ActiveCfg = Debug Dll|Win32 + {469E1CF6-08A2-4B7B-A2AA-5BDB089857C1}.Debug Dll|Win32.Build.0 = Debug Dll|Win32 + {469E1CF6-08A2-4B7B-A2AA-5BDB089857C1}.Debug|Win32.ActiveCfg = Debug|Win32 + {469E1CF6-08A2-4B7B-A2AA-5BDB089857C1}.Debug|Win32.Build.0 = Debug|Win32 + {469E1CF6-08A2-4B7B-A2AA-5BDB089857C1}.Release Dll|Win32.ActiveCfg = Release Dll|Win32 + {469E1CF6-08A2-4B7B-A2AA-5BDB089857C1}.Release Dll|Win32.Build.0 = Release Dll|Win32 + {469E1CF6-08A2-4B7B-A2AA-5BDB089857C1}.Release|Win32.ActiveCfg = Release|Win32 + {469E1CF6-08A2-4B7B-A2AA-5BDB089857C1}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/libs/miniupnpc/msvc/miniupnpc_vs2010.vcxproj b/libs/miniupnpc/msvc/miniupnpc_vs2010.vcxproj new file mode 100644 index 000000000..e5433fb36 --- /dev/null +++ b/libs/miniupnpc/msvc/miniupnpc_vs2010.vcxproj @@ -0,0 +1,209 @@ + + + + + Debug Dll + Win32 + + + Debug + Win32 + + + Release Dll + Win32 + + + Release + Win32 + + + + {D28CE435-CB33-4BAE-8A52-C6EF915956F5} + miniupnpc + Win32Proj + + + + StaticLibrary + Unicode + true + + + DynamicLibrary + Unicode + true + + + StaticLibrary + Unicode + + + DynamicLibrary + Unicode + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)$(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(Configuration)\ + miniupnpc + miniupnpc + miniupnpc + miniupnpc + + + + Disabled + _CRT_SECURE_NO_WARNINGS;MINIUPNP_STATICLIB;DEBUG;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + EditAndContinue + ..;..\include;%(AdditionalIncludeDirectories) + + + genminiupnpcstrings.vbs + + + INTERNAL_NAME="\"miniupnpc.dll\0\"";%(PreprocessorDefinitions) + + + + + Disabled + _CRT_SECURE_NO_WARNINGS;MINIUPNP_EXPORTS;DEBUG;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + EditAndContinue + ..;..\include;%(AdditionalIncludeDirectories) + + + genminiupnpcstrings.vbs + + + ws2_32.lib;IPHlpApi.Lib;%(AdditionalDependencies) + + + INTERNAL_NAME="\"miniupnpc.dll\0\"";%(PreprocessorDefinitions) + + + + + MaxSpeed + true + _CRT_SECURE_NO_WARNINGS;MINIUPNP_STATICLIB;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + ..;..\include;%(AdditionalIncludeDirectories) + + + genminiupnpcstrings.vbs + + + INTERNAL_NAME="\"miniupnpc.dll\0\"";%(PreprocessorDefinitions) + + + + + MaxSpeed + true + _CRT_SECURE_NO_WARNINGS;MINIUPNP_EXPORTS;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + ..;..\include;%(AdditionalIncludeDirectories) + + + genminiupnpcstrings.vbs + + + ws2_32.lib;IPHlpApi.Lib;%(AdditionalDependencies) + + + INTERNAL_NAME="\"miniupnpc.dll\0\"";%(PreprocessorDefinitions) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libs/miniupnpc/msvc/miniupnpc_vs2010.vcxproj.filters b/libs/miniupnpc/msvc/miniupnpc_vs2010.vcxproj.filters new file mode 100644 index 000000000..deb8688d4 --- /dev/null +++ b/libs/miniupnpc/msvc/miniupnpc_vs2010.vcxproj.filters @@ -0,0 +1,133 @@ + + + + + sources + + + sources + + + sources + + + sources + + + sources + + + sources + + + sources + + + sources + + + sources + + + sources + + + sources + + + sources + + + sources + + + sources + + + + + headers + + + headers + + + headers + + + headers + + + headers + + + headers + + + headers + + + headers + + + headers + + + headers + + + headers + + + headers + + + headers + + + headers + + + headers + + + headers + + + headers + + + headers + + + headers + + + Fichiers d%27en-tête + + + Fichiers d%27en-tête + + + + + {f2cbd46b-f63f-412e-80e5-b7c9048a1add} + + + {2b3996de-1bc4-418b-8a83-a5f34fdf0df5} + + + {45dbc39d-c3ca-4a75-adf0-76070e20415a} + + + + + scripts + + + + + Fichiers de ressources + + + diff --git a/libs/miniupnpc/msvc/miniupnpc_vs2015.sln b/libs/miniupnpc/msvc/miniupnpc_vs2015.sln new file mode 100644 index 000000000..76318f69c --- /dev/null +++ b/libs/miniupnpc/msvc/miniupnpc_vs2015.sln @@ -0,0 +1,38 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25123.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "miniupnpc", "miniupnpc.vcxproj", "{D28CE435-CB33-4BAE-8A52-C6EF915956F5}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "upnpc-static", "upnpc-static.vcxproj", "{469E1CF6-08A2-4B7B-A2AA-5BDB089857C1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug Dll|Win32 = Debug Dll|Win32 + Debug|Win32 = Debug|Win32 + Release Dll|Win32 = Release Dll|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D28CE435-CB33-4BAE-8A52-C6EF915956F5}.Debug Dll|Win32.ActiveCfg = Debug Dll|Win32 + {D28CE435-CB33-4BAE-8A52-C6EF915956F5}.Debug Dll|Win32.Build.0 = Debug Dll|Win32 + {D28CE435-CB33-4BAE-8A52-C6EF915956F5}.Debug|Win32.ActiveCfg = Debug|Win32 + {D28CE435-CB33-4BAE-8A52-C6EF915956F5}.Debug|Win32.Build.0 = Debug|Win32 + {D28CE435-CB33-4BAE-8A52-C6EF915956F5}.Release Dll|Win32.ActiveCfg = Release Dll|Win32 + {D28CE435-CB33-4BAE-8A52-C6EF915956F5}.Release Dll|Win32.Build.0 = Release Dll|Win32 + {D28CE435-CB33-4BAE-8A52-C6EF915956F5}.Release|Win32.ActiveCfg = Release|Win32 + {D28CE435-CB33-4BAE-8A52-C6EF915956F5}.Release|Win32.Build.0 = Release|Win32 + {469E1CF6-08A2-4B7B-A2AA-5BDB089857C1}.Debug Dll|Win32.ActiveCfg = Debug Dll|Win32 + {469E1CF6-08A2-4B7B-A2AA-5BDB089857C1}.Debug Dll|Win32.Build.0 = Debug Dll|Win32 + {469E1CF6-08A2-4B7B-A2AA-5BDB089857C1}.Debug|Win32.ActiveCfg = Debug|Win32 + {469E1CF6-08A2-4B7B-A2AA-5BDB089857C1}.Debug|Win32.Build.0 = Debug|Win32 + {469E1CF6-08A2-4B7B-A2AA-5BDB089857C1}.Release Dll|Win32.ActiveCfg = Release Dll|Win32 + {469E1CF6-08A2-4B7B-A2AA-5BDB089857C1}.Release Dll|Win32.Build.0 = Release Dll|Win32 + {469E1CF6-08A2-4B7B-A2AA-5BDB089857C1}.Release|Win32.ActiveCfg = Release|Win32 + {469E1CF6-08A2-4B7B-A2AA-5BDB089857C1}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/libs/miniupnpc/msvc/upnpc-static.vcproj b/libs/miniupnpc/msvc/upnpc-static.vcproj index 37fbbca27..f2246f57b 100644 --- a/libs/miniupnpc/msvc/upnpc-static.vcproj +++ b/libs/miniupnpc/msvc/upnpc-static.vcproj @@ -41,7 +41,7 @@ + + + + Debug Dll + Win32 + + + Debug + Win32 + + + Release Dll + Win32 + + + Release + Win32 + + + + {469E1CF6-08A2-4B7B-A2AA-5BDB089857C1} + upnpcstatic + Win32Proj + + + + Application + v140 + Unicode + true + + + Application + v140 + Unicode + true + + + Application + v140 + Unicode + + + Application + v140 + Unicode + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>14.0.25123.0 + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + true + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + true + upnpc-shared + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + upnpc-shared + + + + Disabled + _DEBUG;_CONSOLE;MINIUPNP_STATICLIB;DEBUG;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + Level3 + EditAndContinue + ..;..\include + + + ws2_32.lib;$(Configuration)\miniupnpc.lib;IPHlpApi.Lib;%(AdditionalDependencies) + true + Console + MachineX86 + + + + + Disabled + _DEBUG;_CONSOLE;MINIUPNP_STATICLIB;DEBUG;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + EditAndContinue + ..;..\include + + + ws2_32.lib;$(Configuration)\miniupnpc.lib;$(AdditionalDependencies) + true + Console + MachineX86 + + + + + MaxSpeed + true + NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;MINIUPNP_STATICLIB;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + Level3 + ProgramDatabase + ..;..\include + + + ws2_32.lib;$(Configuration)\miniupnpc.lib;IPHlpApi.Lib;%(AdditionalDependencies) + true + Console + true + true + MachineX86 + + + + + MaxSpeed + true + NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + ..;..\include + + + ws2_32.lib;$(Configuration)\miniupnpc.lib;IPHlpApi.Lib;%(AdditionalDependencies) + true + Console + true + true + MachineX86 + + + + + + + + {d28ce435-cb33-4bae-8a52-c6ef915956f5} + false + + + + + + \ No newline at end of file diff --git a/libs/miniupnpc/msvc/upnpc-static.vcxproj.filters b/libs/miniupnpc/msvc/upnpc-static.vcxproj.filters new file mode 100644 index 000000000..a67f6d317 --- /dev/null +++ b/libs/miniupnpc/msvc/upnpc-static.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Fichiers sources + + + \ No newline at end of file diff --git a/libs/miniupnpc/msvc/upnpc-static_vs2010.vcxproj b/libs/miniupnpc/msvc/upnpc-static_vs2010.vcxproj new file mode 100644 index 000000000..d97fb5887 --- /dev/null +++ b/libs/miniupnpc/msvc/upnpc-static_vs2010.vcxproj @@ -0,0 +1,176 @@ + + + + + Debug Dll + Win32 + + + Debug + Win32 + + + Release Dll + Win32 + + + Release + Win32 + + + + {469E1CF6-08A2-4B7B-A2AA-5BDB089857C1} + upnpcstatic + Win32Proj + + + + Application + Unicode + true + + + Application + Unicode + true + + + Application + Unicode + + + Application + Unicode + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)$(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(Configuration)\ + true + true + $(SolutionDir)$(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(Configuration)\ + false + false + upnpc-static + upnpc-static + upnpc-static + upnpc-static + + + + Disabled + _DEBUG;_CONSOLE;MINIUPNP_STATICLIB;DEBUG;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + EditAndContinue + ..;..\include + + + ws2_32.lib;IPHlpApi.Lib;$(Configuration)\miniupnpc.lib;%(AdditionalDependencies) + true + Console + MachineX86 + + + + + Disabled + _DEBUG;_CONSOLE;DEBUG;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + EditAndContinue + ..;..\include + + + ws2_32.lib;$(Configuration)\miniupnpc.lib;%(AdditionalDependencies) + true + Console + MachineX86 + + + + + MaxSpeed + true + NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;MINIUPNP_STATICLIB;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + ..;..\include + + + ws2_32.lib;IPHlpApi.Lib;$(Configuration)\miniupnpc.lib;%(AdditionalDependencies) + true + Console + true + true + MachineX86 + + + + + MaxSpeed + true + NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + ..;..\include + + + ws2_32.lib;$(Configuration)\miniupnpc.lib;%(AdditionalDependencies) + true + Console + true + true + MachineX86 + + + + + + + + {d28ce435-cb33-4bae-8a52-c6ef915956f5} + false + + + + + + \ No newline at end of file diff --git a/libs/miniupnpc/pymoduletest.py b/libs/miniupnpc/pymoduletest.py index d35a3b092..0bda1bf2e 100644 --- a/libs/miniupnpc/pymoduletest.py +++ b/libs/miniupnpc/pymoduletest.py @@ -1,35 +1,70 @@ -#! /usr/bin/python +#! /usr/bin/env python +# vim: tabstop=2 shiftwidth=2 expandtab # MiniUPnP project # Author : Thomas Bernard +# Python 3 # This Sample code is public domain. -# website : http://miniupnp.tuxfamily.org/ +# website : https://miniupnp.tuxfamily.org/ # import the python miniupnpc module import miniupnpc import sys -# create the object -u = miniupnpc.UPnP() -print 'inital(default) values :' -print ' discoverdelay', u.discoverdelay -print ' lanaddr', u.lanaddr -print ' multicastif', u.multicastif -print ' minissdpdsocket', u.minissdpdsocket -u.discoverdelay = 200; +try: + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('-m', '--multicastif') + parser.add_argument('-p', '--minissdpdsocket') + parser.add_argument('-d', '--discoverdelay', type=int, default=200) + parser.add_argument('-z', '--localport', type=int, default=0) + # create the object + u = miniupnpc.UPnP(**vars(parser.parse_args())) +except: + print('argparse not available') + i = 1 + multicastif = None + minissdpdsocket = None + discoverdelay = 200 + localport = 0 + while i < len(sys.argv): + print(sys.argv[i]) + if sys.argv[i] == '-m' or sys.argv[i] == '--multicastif': + multicastif = sys.argv[i+1] + elif sys.argv[i] == '-p' or sys.argv[i] == '--minissdpdsocket': + minissdpdsocket = sys.argv[i+1] + elif sys.argv[i] == '-d' or sys.argv[i] == '--discoverdelay': + discoverdelay = int(sys.argv[i+1]) + elif sys.argv[i] == '-z' or sys.argv[i] == '--localport': + localport = int(sys.argv[i+1]) + else: + raise Exception('invalid argument %s' % sys.argv[i]) + i += 2 + # create the object + u = miniupnpc.UPnP(multicastif, minissdpdsocket, discoverdelay, localport) + +print('inital(default) values :') +print(' discoverdelay', u.discoverdelay) +print(' lanaddr', u.lanaddr) +print(' multicastif', u.multicastif) +print(' minissdpdsocket', u.minissdpdsocket) #u.minissdpdsocket = '../minissdpd/minissdpd.sock' -# discovery process, it usualy takes several seconds (2 seconds or more) -print 'Discovering... delay=%ums' % u.discoverdelay -print u.discover(), 'device(s) detected' +# discovery process, it usually takes several seconds (2 seconds or more) +print('Discovering... delay=%ums' % u.discoverdelay) +print('%d device(s) detected' % u.discover()) # select an igd try: u.selectigd() -except Exception, e: - print 'Exception :', e +except Exception as e: + print('Exception :', e) sys.exit(1) +# it is also possible to pass the root description URL to u.selectigd() : +# u.selectigd('http://192.168.1.254:5678/desc/root') # display information about the IGD and the internet connection -print 'local ip address :', u.lanaddr -print 'external ip address :', u.externalipaddress() -print u.statusinfo(), u.connectiontype() +print('local ip address :', u.lanaddr) +print('external ip address :', u.externalipaddress()) +print( u.statusinfo(), u.connectiontype()) +print('total bytes : sent', u.totalbytesent(), 'received', u.totalbytereceived()) +print('total packets : sent', u.totalpacketsent(), 'received', u.totalpacketreceived()) #print u.addportmapping(64000, 'TCP', # '192.168.1.166', 63000, 'port mapping test', '') @@ -43,10 +78,14 @@ while True: p = u.getgenericportmapping(i) if p==None: break - print i, p + print(i, p) (port, proto, (ihost,iport), desc, c, d, e) = p #print port, desc i = i + 1 -print u.getspecificportmapping(port, proto) +print(u.getspecificportmapping(port, proto)) +try: + print(u.getportmappingnumberofentries()) +except Exception as e: + print('GetPortMappingNumberOfEntries() is not supported :', e) diff --git a/libs/miniupnpc/receivedata.c b/libs/miniupnpc/receivedata.c deleted file mode 100644 index a1eadfc46..000000000 --- a/libs/miniupnpc/receivedata.c +++ /dev/null @@ -1,81 +0,0 @@ -/* $Id: receivedata.c,v 1.1 2011/04/11 08:21:47 nanard Exp $ */ -/* Project : miniupnp - * Author : Thomas Bernard - * Copyright (c) 2011 Thomas Bernard - * This software is subject to the conditions detailed in the - * LICENCE file provided in this distribution. */ - -#include -#ifdef WIN32 -#include -#include -#else -#include -#if defined(__amigaos__) && !defined(__amigaos4__) -#define socklen_t int -#else /* #if defined(__amigaos__) && !defined(__amigaos4__) */ -#include -#endif /* #else defined(__amigaos__) && !defined(__amigaos4__) */ -#include -#if !defined(__amigaos__) && !defined(__amigaos4__) -#include -#endif -#include -#define MINIUPNPC_IGNORE_EINTR -#endif - -#ifdef WIN32 -#define PRINT_SOCKET_ERROR(x) printf("Socket error: %s, %d\n", x, WSAGetLastError()); -#else -#define PRINT_SOCKET_ERROR(x) perror(x) -#endif - -#include "receivedata.h" - -int -receivedata(int socket, char * data, int length, int timeout) -{ - int n; -#if !defined(WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) - /* using poll */ - struct pollfd fds[1]; /* for the poll */ -#ifdef MINIUPNPC_IGNORE_EINTR - do { -#endif - fds[0].fd = socket; - fds[0].events = POLLIN; - n = poll(fds, 1, timeout); -#ifdef MINIUPNPC_IGNORE_EINTR - } while(n < 0 && errno == EINTR); -#endif - if(n < 0) { - PRINT_SOCKET_ERROR("poll"); - return -1; - } else if(n == 0) { - /* timeout */ - return 0; - } -#else /* !defined(WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) */ - /* using select under WIN32 and amigaos */ - fd_set socketSet; - TIMEVAL timeval; - FD_ZERO(&socketSet); - FD_SET(socket, &socketSet); - timeval.tv_sec = timeout / 1000; - timeval.tv_usec = (timeout % 1000) * 1000; - n = select(FD_SETSIZE, &socketSet, NULL, NULL, &timeval); - if(n < 0) { - PRINT_SOCKET_ERROR("select"); - return -1; - } else if(n == 0) { - return 0; - } -#endif - n = recv(socket, data, length, 0); - if(n<0) { - PRINT_SOCKET_ERROR("recv"); - } - return n; -} - - diff --git a/libs/miniupnpc/receivedata.h b/libs/miniupnpc/receivedata.h deleted file mode 100644 index 7a551b9ac..000000000 --- a/libs/miniupnpc/receivedata.h +++ /dev/null @@ -1,17 +0,0 @@ -/* $Id: receivedata.h,v 1.1 2011/04/11 08:21:47 nanard Exp $ */ -/* Project: miniupnp - * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * Author: Thomas Bernard - * Copyright (c) 2011 Thomas Bernard - * This software is subjects to the conditions detailed - * in the LICENCE file provided within this distribution */ -#ifndef __RECEIVEDATA_H__ -#define __RECEIVEDATA_H__ - -/* Reads data from the specified socket. - * Returns the number of bytes read if successful, zero if no bytes were - * read or if we timed out. Returns negative if there was an error. */ -int receivedata(int socket, char * data, int length, int timeout); - -#endif - diff --git a/libs/miniupnpc/setup.py b/libs/miniupnpc/setup.py index ca31f9615..d54f924b5 100644 --- a/libs/miniupnpc/setup.py +++ b/libs/miniupnpc/setup.py @@ -1,15 +1,35 @@ -#! /usr/bin/python -# $Id: setup.py,v 1.6 2011/01/04 09:46:08 nanard Exp $ -# the MiniUPnP Project (c) 2007-2011 Thomas Bernard -# http://miniupnp.tuxfamily.org/ or http://miniupnp.free.fr/ +#! /usr/bin/env python +# vim: tabstop=8 shiftwidth=8 expandtab +# $Id: setup.py,v 1.15 2021/09/28 21:10:11 nanard Exp $ +# the MiniUPnP Project (c) 2007-2021 Thomas Bernard +# https://miniupnp.tuxfamily.org/ or http://miniupnp.free.fr/ # # python script to build the miniupnpc module under unix # -# replace libminiupnpc.a by libminiupnpc.so for shared library usage -from distutils.core import setup, Extension -setup(name="miniupnpc", version="1.5", - ext_modules=[ - Extension(name="miniupnpc", sources=["miniupnpcmodule.c"], - extra_objects=["libminiupnpc.a"]) - ]) +# Uses MAKE environment variable (defaulting to 'make') + +from setuptools import setup, Extension +from setuptools.command import build_ext +import subprocess +import os + +EXT = ['build/libminiupnpc.a'] + +class make_then_build_ext(build_ext.build_ext): + def run(self): + subprocess.check_call([os.environ.get('MAKE', 'make')] + EXT) + build_ext.build_ext.run(self) + +setup(name="miniupnpc", + version=open('VERSION').read().strip(), + author='Thomas BERNARD', + author_email='miniupnp@free.fr', + license=open('LICENSE').read(), + url='http://miniupnp.free.fr/', + description='miniUPnP client', + cmdclass={'build_ext': make_then_build_ext}, + ext_modules=[ + Extension(name="miniupnpc", sources=["src/miniupnpcmodule.c"], + include_dirs=['include'], extra_objects=EXT) + ]) diff --git a/libs/miniupnpc/setupmingw32.py b/libs/miniupnpc/setupmingw32.py index d0539e450..26e85f037 100644 --- a/libs/miniupnpc/setupmingw32.py +++ b/libs/miniupnpc/setupmingw32.py @@ -1,15 +1,35 @@ -#! /usr/bin/python -# $Id: setupmingw32.py,v 1.5 2011/05/15 21:18:43 nanard Exp $ -# the MiniUPnP Project (c) 2007-2011 Thomas Bernard -# http://miniupnp.tuxfamily.org/ or http://miniupnp.free.fr/ +#! /usr/bin/env python +# vim: tabstop=8 shiftwidth=8 expandtab +# $Id: setupmingw32.py,v 1.14 2021/09/28 21:10:11 nanard Exp $ +# the MiniUPnP Project (c) 2007-2021 Thomas Bernard +# https://miniupnp.tuxfamily.org/ or http://miniupnp.free.fr/ # # python script to build the miniupnpc module under windows (using mingw32) # -from distutils.core import setup, Extension -setup(name="miniupnpc", version="1.5", - ext_modules=[ - Extension(name="miniupnpc", sources=["miniupnpcmodule.c"], - libraries=["ws2_32", "iphlpapi"], - extra_objects=["libminiupnpc.a"]) - ]) +import sys + +if (sys.version_info.major * 10 + sys.version_info.minor) >= 35: + compat_lib = ["legacy_stdio_definitions"] +else: + compat_lib = [] + +try: + from setuptools import setup, Extension +except ImportError: + from distutils.core import setup, Extension +from distutils import sysconfig +sysconfig.get_config_vars()["OPT"] = '' +sysconfig.get_config_vars()["CFLAGS"] = '' +setup(name="miniupnpc", + version=open('VERSION').read().strip(), + author='Thomas BERNARD', + author_email='miniupnp@free.fr', + license=open('LICENSE').read(), + url='http://miniupnp.free.fr/', + description='miniUPnP client', + ext_modules=[ + Extension(name="miniupnpc", sources=["src/miniupnpcmodule.c"], + libraries=["ws2_32", "iphlpapi"] + compat_lib, + include_dirs=['include'], extra_objects=["miniupnpc.lib"]) + ]) diff --git a/libs/miniupnpc/src/addr_is_reserved.c b/libs/miniupnpc/src/addr_is_reserved.c new file mode 100644 index 000000000..bd721c17c --- /dev/null +++ b/libs/miniupnpc/src/addr_is_reserved.c @@ -0,0 +1,79 @@ +/* $Id: addr_is_reserved.c,v 1.5 2021/05/10 20:53:02 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * Project : miniupnp + * Web : http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ + * Author : Thomas BERNARD + * copyright (c) 2005-2021 Thomas Bernard + * This software is subjet to the conditions detailed in the + * provided LICENSE file. */ +#ifdef _WIN32 +/* Win32 Specific includes and defines */ +#include +#include +#if !defined(_MSC_VER) +#include +#else /* !defined(_MSC_VER) */ +typedef unsigned long uint32_t; +#endif /* !defined(_MSC_VER) */ +#else /* _WIN32 */ +#include +#include +#include +#include +#endif /* _WIN32 */ + +/* List of IP address blocks which are private / reserved and therefore not suitable for public external IP addresses */ +#define IP(a, b, c, d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) +#define MSK(m) (32-(m)) +static const struct { uint32_t address; uint32_t rmask; } reserved[] = { + { IP( 0, 0, 0, 0), MSK( 8) }, /* RFC1122 "This host on this network" */ + { IP( 10, 0, 0, 0), MSK( 8) }, /* RFC1918 Private-Use */ + { IP(100, 64, 0, 0), MSK(10) }, /* RFC6598 Shared Address Space */ + { IP(127, 0, 0, 0), MSK( 8) }, /* RFC1122 Loopback */ + { IP(169, 254, 0, 0), MSK(16) }, /* RFC3927 Link-Local */ + { IP(172, 16, 0, 0), MSK(12) }, /* RFC1918 Private-Use */ + { IP(192, 0, 0, 0), MSK(24) }, /* RFC6890 IETF Protocol Assignments */ + { IP(192, 0, 2, 0), MSK(24) }, /* RFC5737 Documentation (TEST-NET-1) */ + { IP(192, 31, 196, 0), MSK(24) }, /* RFC7535 AS112-v4 */ + { IP(192, 52, 193, 0), MSK(24) }, /* RFC7450 AMT */ + { IP(192, 88, 99, 0), MSK(24) }, /* RFC7526 6to4 Relay Anycast */ + { IP(192, 168, 0, 0), MSK(16) }, /* RFC1918 Private-Use */ + { IP(192, 175, 48, 0), MSK(24) }, /* RFC7534 Direct Delegation AS112 Service */ + { IP(198, 18, 0, 0), MSK(15) }, /* RFC2544 Benchmarking */ + { IP(198, 51, 100, 0), MSK(24) }, /* RFC5737 Documentation (TEST-NET-2) */ + { IP(203, 0, 113, 0), MSK(24) }, /* RFC5737 Documentation (TEST-NET-3) */ + { IP(224, 0, 0, 0), MSK( 4) }, /* RFC1112 Multicast */ + { IP(240, 0, 0, 0), MSK( 4) }, /* RFC1112 Reserved for Future Use + RFC919 Limited Broadcast */ +}; +#undef IP +#undef MSK + +/** + * @return 1 or 0 + */ +int addr_is_reserved(const char * addr_str) +{ + uint32_t addr_n, address; + size_t i; + +#if defined(_WIN32) && _WIN32_WINNT < 0x0600 // _WIN32_WINNT_VISTA + addr_n = inet_addr(addr_str); + if (addr_n == INADDR_NONE) + return 1; +#else + /* was : addr_n = inet_addr(addr_str); */ + if (inet_pton(AF_INET, addr_str, &addr_n) <= 0) { + /* error */ + return 1; + } +#endif + + address = ntohl(addr_n); + + for (i = 0; i < sizeof(reserved)/sizeof(reserved[0]); ++i) { + if ((address >> reserved[i].rmask) == (reserved[i].address >> reserved[i].rmask)) + return 1; + } + + return 0; +} diff --git a/libs/miniupnpc/src/addr_is_reserved.h b/libs/miniupnpc/src/addr_is_reserved.h new file mode 100644 index 000000000..3286bc4c8 --- /dev/null +++ b/libs/miniupnpc/src/addr_is_reserved.h @@ -0,0 +1,14 @@ +/* $Id: addr_is_reserved.h,v 1.1 2020/09/28 21:11:19 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * Project: miniupnp + * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ + * Author: Thomas Bernard + * Copyright (c) 2005-2020 Thomas Bernard + * This software is subjects to the conditions detailed + * in the LICENCE file provided within this distribution */ +#ifndef ADDR_IS_RESERVED_H_INCLUDED +#define ADDR_IS_RESERVED_H_INCLUDED + +int addr_is_reserved(const char * addr_str); + +#endif /* ADDR_IS_RESERVED_H_INCLUDED */ diff --git a/libs/miniupnpc/src/codelength.h b/libs/miniupnpc/src/codelength.h new file mode 100644 index 000000000..f5f8e30f9 --- /dev/null +++ b/libs/miniupnpc/src/codelength.h @@ -0,0 +1,54 @@ +/* $Id: codelength.h,v 1.5 2015/07/09 12:40:18 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas BERNARD + * copyright (c) 2005-2015 Thomas Bernard + * This software is subjet to the conditions detailed in the + * provided LICENCE file. */ +#ifndef CODELENGTH_H_INCLUDED +#define CODELENGTH_H_INCLUDED + +/* Encode length by using 7bit per Byte : + * Most significant bit of each byte specifies that the + * following byte is part of the code */ + +/* n : unsigned + * p : unsigned char * + */ +#define DECODELENGTH(n, p) n = 0; \ + do { n = (n << 7) | (*p & 0x7f); } \ + while((*(p++)&0x80) && (n<(1<<25))); + +/* n : unsigned + * READ : function/macro to read one byte (unsigned char) + */ +#define DECODELENGTH_READ(n, READ) \ + n = 0; \ + do { \ + unsigned char c; \ + READ(c); \ + n = (n << 7) | (c & 0x07f); \ + if(!(c&0x80)) break; \ + } while(n<(1<<25)); + +/* n : unsigned + * p : unsigned char * + * p_limit : unsigned char * + */ +#define DECODELENGTH_CHECKLIMIT(n, p, p_limit) \ + n = 0; \ + do { \ + if((p) >= (p_limit)) break; \ + n = (n << 7) | (*(p) & 0x7f); \ + } while((*((p)++)&0x80) && (n<(1<<25))); + + +/* n : unsigned + * p : unsigned char * + */ +#define CODELENGTH(n, p) if(n>=268435456) *(p++) = (n >> 28) | 0x80; \ + if(n>=2097152) *(p++) = (n >> 21) | 0x80; \ + if(n>=16384) *(p++) = (n >> 14) | 0x80; \ + if(n>=128) *(p++) = (n >> 7) | 0x80; \ + *(p++) = n & 0x7f; + +#endif /* CODELENGTH_H_INCLUDED */ diff --git a/libs/miniupnpc/connecthostport.c b/libs/miniupnpc/src/connecthostport.c similarity index 62% rename from libs/miniupnpc/connecthostport.c rename to libs/miniupnpc/src/connecthostport.c index 76e8e374b..79f832b8d 100644 --- a/libs/miniupnpc/connecthostport.c +++ b/libs/miniupnpc/src/connecthostport.c @@ -1,7 +1,8 @@ -/* $Id: connecthostport.c,v 1.5 2011/04/09 08:49:50 nanard Exp $ */ -/* Project : miniupnp +/* $Id: connecthostport.c,v 1.24 2020/11/09 19:26:53 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * Project : miniupnp * Author : Thomas Bernard - * Copyright (c) 2010-2011 Thomas Bernard + * Copyright (c) 2010-2020 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. */ @@ -13,35 +14,32 @@ #include #include -#ifdef WIN32 +#ifdef _WIN32 #include #include #include #define MAXHOSTNAMELEN 64 -#define snprintf _snprintf +#include "win32_snprintf.h" #define herror #define socklen_t int -#else /* #ifdef WIN32 */ +#else /* #ifdef _WIN32 */ #include +#include +#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT +#include +#endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ #include +#include #include #define closesocket close #include +#include /* defining MINIUPNPC_IGNORE_EINTR enable the ignore of interruptions * during the connect() call */ #define MINIUPNPC_IGNORE_EINTR -#ifndef USE_GETHOSTBYNAME -#include #include -#endif /* #ifndef USE_GETHOSTBYNAME */ -#endif /* #else WIN32 */ - -/* definition of PRINT_SOCKET_ERROR */ -#ifdef WIN32 -#define PRINT_SOCKET_ERROR(x) printf("Socket error: %s, %d\n", x, WSAGetLastError()); -#else -#define PRINT_SOCKET_ERROR(x) perror(x) -#endif +#include +#endif /* #else _WIN32 */ #if defined(__amigaos__) || defined(__amigaos4__) #define herror(A) printf("%s\n", A) @@ -49,12 +47,18 @@ #include "connecthostport.h" +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 64 +#endif + /* connecthostport() * return a socket connected (TCP) to the host and port * or -1 in case of error */ -int connecthostport(const char * host, unsigned short port) +SOCKET connecthostport(const char * host, unsigned short port, + unsigned int scope_id) { - int s, n; + SOCKET s; + int n; #ifdef USE_GETHOSTBYNAME struct sockaddr_in dest; struct hostent *hp; @@ -67,21 +71,21 @@ int connecthostport(const char * host, unsigned short port) #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT struct timeval timeout; #endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ - + #ifdef USE_GETHOSTBYNAME hp = gethostbyname(host); if(hp == NULL) { herror(host); - return -1; + return INVALID_SOCKET; } memcpy(&dest.sin_addr, hp->h_addr, sizeof(dest.sin_addr)); memset(dest.sin_zero, 0, sizeof(dest.sin_zero)); s = socket(PF_INET, SOCK_STREAM, 0); - if(s < 0) + if(ISINVALID(s)) { PRINT_SOCKET_ERROR("socket"); - return -1; + return INVALID_SOCKET; } #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT /* setting a 3 seconds timeout for the connect() call */ @@ -89,35 +93,52 @@ int connecthostport(const char * host, unsigned short port) timeout.tv_usec = 0; if(setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(struct timeval)) < 0) { - PRINT_SOCKET_ERROR("setsockopt"); + PRINT_SOCKET_ERROR("setsockopt SO_RCVTIMEO"); } timeout.tv_sec = 3; timeout.tv_usec = 0; if(setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(struct timeval)) < 0) { - PRINT_SOCKET_ERROR("setsockopt"); + PRINT_SOCKET_ERROR("setsockopt SO_SNDTIMEO"); } #endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ dest.sin_family = AF_INET; dest.sin_port = htons(port); n = connect(s, (struct sockaddr *)&dest, sizeof(struct sockaddr_in)); #ifdef MINIUPNPC_IGNORE_EINTR - while(n < 0 && errno == EINTR) + /* EINTR The system call was interrupted by a signal that was caught + * EINPROGRESS The socket is nonblocking and the connection cannot + * be completed immediately. */ + while(n < 0 && (errno == EINTR || errno == EINPROGRESS)) { socklen_t len; fd_set wset; int err; FD_ZERO(&wset); FD_SET(s, &wset); - if((n = select(s + 1, NULL, &wset, NULL, NULL)) == -1 && errno == EINTR) +#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT + timeout.tv_sec = 3; + timeout.tv_usec = 0; + n = select(s + 1, NULL, &wset, NULL, &timeout); +#else + n = select(s + 1, NULL, &wset, NULL, NULL); +#endif + if(n == -1 && errno == EINTR) continue; +#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT + if(n == 0) { + errno = ETIMEDOUT; + n = -1; + break; + } +#endif /*len = 0;*/ /*n = getpeername(s, NULL, &len);*/ len = sizeof(err); if(getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len) < 0) { PRINT_SOCKET_ERROR("getsockopt"); closesocket(s); - return -1; + return INVALID_SOCKET; } if(err != 0) { errno = err; @@ -129,7 +150,7 @@ int connecthostport(const char * host, unsigned short port) { PRINT_SOCKET_ERROR("connect"); closesocket(s); - return -1; + return INVALID_SOCKET; } #else /* #ifdef USE_GETHOSTBYNAME */ /* use getaddrinfo() instead of gethostbyname() */ @@ -145,10 +166,12 @@ int connecthostport(const char * host, unsigned short port) if(host[0] == '[') { /* literal ip v6 address */ - int i; - for(i = 0; host[i+1] && (host[i+1] != ']') && i < MAXHOSTNAMELEN; i++) + int i, j; + for(i = 0, j = 1; host[j] && (host[j] != ']') && i < MAXHOSTNAMELEN; i++, j++) { - tmp_host[i] = host[i+1]; + tmp_host[i] = host[j]; + if(0 == strncmp(host+j, "%25", 3)) /* %25 is just url encoding for '%' */ + j+=2; /* skip "25" */ } tmp_host[i] = '\0'; } @@ -160,19 +183,29 @@ int connecthostport(const char * host, unsigned short port) n = getaddrinfo(tmp_host, port_str, &hints, &ai); if(n != 0) { -#ifdef WIN32 +#ifdef _WIN32 fprintf(stderr, "getaddrinfo() error : %d\n", n); #else fprintf(stderr, "getaddrinfo() error : %s\n", gai_strerror(n)); #endif - return -1; + return INVALID_SOCKET; } - s = -1; + s = INVALID_SOCKET; for(p = ai; p; p = p->ai_next) { + if(!ISINVALID(s)) + closesocket(s); +#ifdef DEBUG + printf("ai_family=%d ai_socktype=%d ai_protocol=%d (PF_INET=%d, PF_INET6=%d)\n", + p->ai_family, p->ai_socktype, p->ai_protocol, PF_INET, PF_INET6); +#endif s = socket(p->ai_family, p->ai_socktype, p->ai_protocol); - if(s < 0) + if(ISINVALID(s)) continue; + if(p->ai_addr->sa_family == AF_INET6 && scope_id > 0) { + struct sockaddr_in6 * addr6 = (struct sockaddr_in6 *)p->ai_addr; + addr6->sin6_scope_id = scope_id; + } #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT /* setting a 3 seconds timeout for the connect() call */ timeout.tv_sec = 3; @@ -188,17 +221,34 @@ int connecthostport(const char * host, unsigned short port) PRINT_SOCKET_ERROR("setsockopt"); } #endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ - n = connect(s, p->ai_addr, p->ai_addrlen); + n = connect(s, p->ai_addr, MSC_CAST_INT p->ai_addrlen); #ifdef MINIUPNPC_IGNORE_EINTR - while(n < 0 && errno == EINTR) + /* EINTR The system call was interrupted by a signal that was caught + * EINPROGRESS The socket is nonblocking and the connection cannot + * be completed immediately. */ + while(n < 0 && (errno == EINTR || errno == EINPROGRESS)) { socklen_t len; fd_set wset; int err; FD_ZERO(&wset); FD_SET(s, &wset); - if((n = select(s + 1, NULL, &wset, NULL, NULL)) == -1 && errno == EINTR) +#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT + timeout.tv_sec = 3; + timeout.tv_usec = 0; + n = select(s + 1, NULL, &wset, NULL, &timeout); +#else + n = select(s + 1, NULL, &wset, NULL, NULL); +#endif + if(n == -1 && errno == EINTR) continue; +#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT + if(n == 0) { + errno = ETIMEDOUT; + n = -1; + break; + } +#endif /*len = 0;*/ /*n = getpeername(s, NULL, &len);*/ len = sizeof(err); @@ -206,7 +256,7 @@ int connecthostport(const char * host, unsigned short port) PRINT_SOCKET_ERROR("getsockopt"); closesocket(s); freeaddrinfo(ai); - return -1; + return INVALID_SOCKET; } if(err != 0) { errno = err; @@ -214,28 +264,21 @@ int connecthostport(const char * host, unsigned short port) } } #endif /* #ifdef MINIUPNPC_IGNORE_EINTR */ - if(n < 0) - { - closesocket(s); - continue; - } - else - { + if(n >= 0) /* connect() was successful */ break; - } } freeaddrinfo(ai); - if(s < 0) + if(ISINVALID(s)) { PRINT_SOCKET_ERROR("socket"); - return -1; + return INVALID_SOCKET; } if(n < 0) { PRINT_SOCKET_ERROR("connect"); - return -1; + closesocket(s); + return INVALID_SOCKET; } #endif /* #ifdef USE_GETHOSTBYNAME */ return s; } - diff --git a/libs/miniupnpc/src/connecthostport.h b/libs/miniupnpc/src/connecthostport.h new file mode 100644 index 000000000..b1ce627ce --- /dev/null +++ b/libs/miniupnpc/src/connecthostport.h @@ -0,0 +1,20 @@ +/* $Id: connecthostport.h,v 1.4 2018/04/06 10:53:13 nanard Exp $ */ +/* Project: miniupnp + * http://miniupnp.free.fr/ + * Author: Thomas Bernard + * Copyright (c) 2010-2018 Thomas Bernard + * This software is subjects to the conditions detailed + * in the LICENCE file provided within this distribution */ +#ifndef CONNECTHOSTPORT_H_INCLUDED +#define CONNECTHOSTPORT_H_INCLUDED + +#include "miniupnpc_socketdef.h" + +/* connecthostport() + * return a socket connected (TCP) to the host and port + * or INVALID_SOCKET in case of error */ +SOCKET connecthostport(const char * host, unsigned short port, + unsigned int scope_id); + +#endif + diff --git a/libs/miniupnpc/igd_desc_parse.c b/libs/miniupnpc/src/igd_desc_parse.c similarity index 83% rename from libs/miniupnpc/igd_desc_parse.c rename to libs/miniupnpc/src/igd_desc_parse.c index 6c3e65677..d2999ad01 100644 --- a/libs/miniupnpc/igd_desc_parse.c +++ b/libs/miniupnpc/src/igd_desc_parse.c @@ -1,8 +1,8 @@ -/* $Id: igd_desc_parse.c,v 1.14 2011/04/11 09:19:24 nanard Exp $ */ +/* $Id: igd_desc_parse.c,v 1.17 2015/09/15 13:30:04 nanard Exp $ */ /* Project : miniupnp * http://miniupnp.free.fr/ * Author : Thomas Bernard - * Copyright (c) 2005-2010 Thomas Bernard + * Copyright (c) 2005-2015 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. */ @@ -15,7 +15,9 @@ void IGDstartelt(void * d, const char * name, int l) { struct IGDdatas * datas = (struct IGDdatas *)d; - memcpy( datas->cureltname, name, l); + if(l >= MINIUPNPC_URL_MAXSIZE) + l = MINIUPNPC_URL_MAXSIZE-1; + memcpy(datas->cureltname, name, l); datas->cureltname[l] = '\0'; datas->level++; if( (l==7) && !memcmp(name, "service", l) ) { @@ -26,6 +28,8 @@ void IGDstartelt(void * d, const char * name, int l) } } +#define COMPARE(str, cstr) (0==memcmp(str, cstr, sizeof(cstr) - 1)) + /* End element handler : * update nesting level counter and update parser state if * service element is parsed */ @@ -36,23 +40,16 @@ void IGDendelt(void * d, const char * name, int l) /*printf("endelt %2d %.*s\n", datas->level, l, name);*/ if( (l==7) && !memcmp(name, "service", l) ) { - /* - if( datas->state < 1 - && !strcmp(datas->servicetype, - // "urn:schemas-upnp-org:service:WANIPConnection:1") ) - "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1")) - datas->state ++; - */ - if(0==strcmp(datas->tmp.servicetype, - "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1")) { + if(COMPARE(datas->tmp.servicetype, + "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:")) { memcpy(&datas->CIF, &datas->tmp, sizeof(struct IGDdatas_service)); - } else if(0==strcmp(datas->tmp.servicetype, - "urn:schemas-upnp-org:service:WANIPv6FirewallControl:1")) { + } else if(COMPARE(datas->tmp.servicetype, + "urn:schemas-upnp-org:service:WANIPv6FirewallControl:")) { memcpy(&datas->IPv6FC, &datas->tmp, sizeof(struct IGDdatas_service)); - } else if(0==strcmp(datas->tmp.servicetype, - "urn:schemas-upnp-org:service:WANIPConnection:1") - || 0==strcmp(datas->tmp.servicetype, - "urn:schemas-upnp-org:service:WANPPPConnection:1") ) { + } else if(COMPARE(datas->tmp.servicetype, + "urn:schemas-upnp-org:service:WANIPConnection:") + || COMPARE(datas->tmp.servicetype, + "urn:schemas-upnp-org:service:WANPPPConnection:") ) { if(datas->first.servicetype[0] == '\0') { memcpy(&datas->first, &datas->tmp, sizeof(struct IGDdatas_service)); } else { @@ -93,6 +90,7 @@ void IGDdata(void * d, const char * data, int l) } } +#ifdef DEBUG void printIGD(struct IGDdatas * d) { printf("urlbase = '%s'\n", d->urlbase); @@ -121,5 +119,5 @@ void printIGD(struct IGDdatas * d) printf(" eventSubURL = '%s'\n", d->IPv6FC.eventsuburl); printf(" SCPDURL = '%s'\n", d->IPv6FC.scpdurl); } - +#endif /* DEBUG */ diff --git a/libs/miniupnpc/src/listdevices.c b/libs/miniupnpc/src/listdevices.c new file mode 100644 index 000000000..dabc056e9 --- /dev/null +++ b/libs/miniupnpc/src/listdevices.c @@ -0,0 +1,197 @@ +/* $Id: listdevices.c,v 1.8 2018/05/03 08:16:44 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2013-2015 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. */ + +#include +#include +#include +#ifdef _WIN32 +#include +#endif /* _WIN32 */ +#include "miniupnpc.h" + +struct upnp_dev_list { + struct upnp_dev_list * next; + char * descURL; + struct UPNPDev * * array; + size_t count; + size_t allocated_count; +}; + +#define ADD_DEVICE_COUNT_STEP 16 + +void add_device(struct upnp_dev_list * * list_head, struct UPNPDev * dev) +{ + struct upnp_dev_list * elt; + size_t i; + + if(dev == NULL) + return; + for(elt = *list_head; elt != NULL; elt = elt->next) { + if(strcmp(elt->descURL, dev->descURL) == 0) { + for(i = 0; i < elt->count; i++) { + if (strcmp(elt->array[i]->st, dev->st) == 0 && strcmp(elt->array[i]->usn, dev->usn) == 0) { + return; /* already found */ + } + } + if(elt->count >= elt->allocated_count) { + struct UPNPDev * * tmp; + elt->allocated_count += ADD_DEVICE_COUNT_STEP; + tmp = realloc(elt->array, elt->allocated_count * sizeof(struct UPNPDev *)); + if(tmp == NULL) { + fprintf(stderr, "Failed to realloc(%p, %lu)\n", elt->array, (unsigned long)(elt->allocated_count * sizeof(struct UPNPDev *))); + return; + } + elt->array = tmp; + } + elt->array[elt->count++] = dev; + return; + } + } + elt = malloc(sizeof(struct upnp_dev_list)); + if(elt == NULL) { + fprintf(stderr, "Failed to malloc(%lu)\n", (unsigned long)sizeof(struct upnp_dev_list)); + return; + } + elt->next = *list_head; + elt->descURL = strdup(dev->descURL); + if(elt->descURL == NULL) { + fprintf(stderr, "Failed to strdup(%s)\n", dev->descURL); + free(elt); + return; + } + elt->allocated_count = ADD_DEVICE_COUNT_STEP; + elt->array = malloc(ADD_DEVICE_COUNT_STEP * sizeof(struct UPNPDev *)); + if(elt->array == NULL) { + fprintf(stderr, "Failed to malloc(%lu)\n", (unsigned long)(ADD_DEVICE_COUNT_STEP * sizeof(struct UPNPDev *))); + free(elt->descURL); + free(elt); + return; + } + elt->array[0] = dev; + elt->count = 1; + *list_head = elt; +} + +void free_device(struct upnp_dev_list * elt) +{ + free(elt->descURL); + free(elt->array); + free(elt); +} + +int main(int argc, char * * argv) +{ + const char * searched_device = NULL; + const char * * searched_devices = NULL; + const char * multicastif = 0; + const char * minissdpdpath = 0; + int ipv6 = 0; + unsigned char ttl = 2; + int error = 0; + struct UPNPDev * devlist = 0; + struct UPNPDev * dev; + struct upnp_dev_list * sorted_list = NULL; + struct upnp_dev_list * dev_array; + int i; + +#ifdef _WIN32 + WSADATA wsaData; + int nResult = WSAStartup(MAKEWORD(2,2), &wsaData); + if(nResult != NO_ERROR) + { + fprintf(stderr, "WSAStartup() failed.\n"); + return -1; + } +#endif + + for(i = 1; i < argc; i++) { + if(strcmp(argv[i], "-6") == 0) + ipv6 = 1; + else if(strcmp(argv[i], "-d") == 0) { + if(++i >= argc) { + fprintf(stderr, "%s option needs one argument\n", "-d"); + return 1; + } + searched_device = argv[i]; + } else if(strcmp(argv[i], "-t") == 0) { + if(++i >= argc) { + fprintf(stderr, "%s option needs one argument\n", "-t"); + return 1; + } + ttl = (unsigned char)atoi(argv[i]); + } else if(strcmp(argv[i], "-l") == 0) { + if(++i >= argc) { + fprintf(stderr, "-l option needs at least one argument\n"); + return 1; + } + searched_devices = (const char * *)(argv + i); + break; + } else if(strcmp(argv[i], "-m") == 0) { + if(++i >= argc) { + fprintf(stderr, "-m option needs one argument\n"); + return 1; + } + multicastif = argv[i]; + } else { + printf("usage : %s [options] [-l ...]\n", argv[0]); + printf("options :\n"); + printf(" -6 : use IPv6\n"); + printf(" -m address/ifname : network interface to use for multicast\n"); + printf(" -d : search only for this type of device\n"); + printf(" -l ... : search only for theses types of device\n"); + printf(" -t ttl : set multicast TTL. Default value is 2.\n"); + printf(" -h : this help\n"); + return 1; + } + } + + if(searched_device) { + printf("searching UPnP device type %s\n", searched_device); + devlist = upnpDiscoverDevice(searched_device, + 2000, multicastif, minissdpdpath, + 0/*localport*/, ipv6, ttl, &error); + } else if(searched_devices) { + printf("searching UPnP device types :\n"); + for(i = 0; searched_devices[i]; i++) + printf("\t%s\n", searched_devices[i]); + devlist = upnpDiscoverDevices(searched_devices, + 2000, multicastif, minissdpdpath, + 0/*localport*/, ipv6, ttl, &error, 1); + } else { + printf("searching all UPnP devices\n"); + devlist = upnpDiscoverAll(2000, multicastif, minissdpdpath, + 0/*localport*/, ipv6, ttl, &error); + } + if(devlist) { + for(dev = devlist, i = 1; dev != NULL; dev = dev->pNext, i++) { + printf("%3d: %-48s\n", i, dev->st); + printf(" %s\n", dev->descURL); + printf(" %s\n", dev->usn); + add_device(&sorted_list, dev); + } + putchar('\n'); + for (dev_array = sorted_list; dev_array != NULL ; dev_array = dev_array->next) { + printf("%s :\n", dev_array->descURL); + for(i = 0; (unsigned)i < dev_array->count; i++) { + printf("%2d: %s\n", i+1, dev_array->array[i]->st); + printf(" %s\n", dev_array->array[i]->usn); + } + putchar('\n'); + } + freeUPNPDevlist(devlist); + while(sorted_list != NULL) { + dev_array = sorted_list; + sorted_list = sorted_list->next; + free_device(dev_array); + } + } else { + printf("no device found.\n"); + } + + return 0; +} + diff --git a/libs/miniupnpc/minihttptestserver.c b/libs/miniupnpc/src/minihttptestserver.c similarity index 56% rename from libs/miniupnpc/minihttptestserver.c rename to libs/miniupnpc/src/minihttptestserver.c index a1dddf0ea..57235841e 100644 --- a/libs/miniupnpc/minihttptestserver.c +++ b/libs/miniupnpc/src/minihttptestserver.c @@ -1,7 +1,7 @@ -/* $Id: minihttptestserver.c,v 1.6 2011/05/09 08:53:15 nanard Exp $ */ +/* $Id: minihttptestserver.c,v 1.25 2020/05/29 21:14:22 nanard Exp $ */ /* Project : miniUPnP * Author : Thomas Bernard - * Copyright (c) 2011 Thomas Bernard + * Copyright (c) 2011-2018 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. * */ @@ -16,36 +16,43 @@ #include #include #include +#include + +#ifndef INADDR_LOOPBACK +#define INADDR_LOOPBACK 0x7f000001 +#endif #define CRAP_LENGTH (2048) -volatile int quit = 0; -volatile int child_to_wait_for = 0; +static int server(unsigned short port, const char * expected_file_name, int ipv6); + +volatile sig_atomic_t quit = 0; +volatile sig_atomic_t child_to_wait_for = 0; /** * signal handler for SIGCHLD (child status has changed) */ void handle_signal_chld(int sig) { - printf("handle_signal_chld(%d)\n", sig); + (void)sig; + /* printf("handle_signal_chld(%d)\n", sig); */ ++child_to_wait_for; } /** * signal handler for SIGINT (CRTL C) */ -#if 0 void handle_signal_int(int sig) { - printf("handle_signal_int(%d)\n", sig); + (void)sig; + /* printf("handle_signal_int(%d)\n", sig); */ quit = 1; } -#endif /** * build a text/plain content of the specified length */ -void build_content(char * p, int n) +void build_content(char * p, size_t n) { char line_buffer[80]; int k; @@ -74,10 +81,10 @@ void build_content(char * p, int n) /** * build crappy content */ -void build_crap(char * p, int n) +void build_crap(char * p, size_t n) { static const char crap[] = "_CRAP_\r\n"; - int i; + size_t i; while(n > 0) { i = sizeof(crap) - 1; @@ -93,15 +100,19 @@ void build_crap(char * p, int n) * build chunked response. * return a malloc'ed buffer */ -char * build_chunked_response(int content_length, int * response_len) { +char * build_chunked_response(size_t content_length, size_t * response_len) +{ char * response_buffer; char * content_buffer; - int buffer_length; - int i, n; + size_t buffer_length; + size_t i; + unsigned int n; /* allocate to have some margin */ buffer_length = 256 + content_length + (content_length >> 4); response_buffer = malloc(buffer_length); + if(response_buffer == NULL) + return NULL; *response_len = snprintf(response_buffer, buffer_length, "HTTP/1.1 200 OK\r\n" "Content-Type: text/plain\r\n" @@ -110,6 +121,10 @@ char * build_chunked_response(int content_length, int * response_len) { /* build the content */ content_buffer = malloc(content_length); + if(content_buffer == NULL) { + free(response_buffer); + return NULL; + } build_content(content_buffer, content_length); /* chunk it */ @@ -129,16 +144,126 @@ char * build_chunked_response(int content_length, int * response_len) { response_buffer[(*response_len)++] = '\r'; response_buffer[(*response_len)++] = '\n'; } - memcpy(response_buffer + *response_len, "0\r\n", 3); - *response_len += 3; + /* the last chunk : "0\r\n" a empty body and then + * the final "\r\n" */ + memcpy(response_buffer + *response_len, "0\r\n\r\n", 5); + *response_len += 5; free(content_buffer); - printf("resp_length=%d buffer_length=%d content_length=%d\n", + printf("resp_length=%lu buffer_length=%lu content_length=%lu\n", *response_len, buffer_length, content_length); return response_buffer; } -enum modes { MODE_INVALID, MODE_CHUNKED, MODE_ADDCRAP, MODE_NORMAL }; +/* favicon.ico generator */ +#ifdef OLD_HEADER +#define FAVICON_LENGTH (6 + 16 + 12 + 8 + 32 * 4) +#else +#define FAVICON_LENGTH (6 + 16 + 40 + 8 + 32 * 4) +#endif +void build_favicon_content(unsigned char * p, size_t n) +{ + int i; + if(n < FAVICON_LENGTH) + return; + /* header : 6 bytes */ + *p++ = 0; + *p++ = 0; + *p++ = 1; /* type : ICO */ + *p++ = 0; + *p++ = 1; /* number of images in file */ + *p++ = 0; + /* image directory (1 entry) : 16 bytes */ + *p++ = 16; /* width */ + *p++ = 16; /* height */ + *p++ = 2; /* number of colors in the palette. 0 = no palette */ + *p++ = 0; /* reserved */ + *p++ = 1; /* color planes */ + *p++ = 0; /* " */ + *p++ = 1; /* bpp */ + *p++ = 0; /* " */ +#ifdef OLD_HEADER + *p++ = 12 + 8 + 32 * 4; /* bmp size */ +#else + *p++ = 40 + 8 + 32 * 4; /* bmp size */ +#endif + *p++ = 0; /* " */ + *p++ = 0; /* " */ + *p++ = 0; /* " */ + *p++ = 6 + 16; /* bmp offset */ + *p++ = 0; /* " */ + *p++ = 0; /* " */ + *p++ = 0; /* " */ + /* BMP */ +#ifdef OLD_HEADER + /* BITMAPCOREHEADER */ + *p++ = 12; /* size of this header */ + *p++ = 0; /* " */ + *p++ = 0; /* " */ + *p++ = 0; /* " */ + *p++ = 16; /* width */ + *p++ = 0; /* " */ + *p++ = 16 * 2; /* height x 2 ! */ + *p++ = 0; /* " */ + *p++ = 1; /* color planes */ + *p++ = 0; /* " */ + *p++ = 1; /* bpp */ + *p++ = 0; /* " */ +#else + /* BITMAPINFOHEADER */ + *p++ = 40; /* size of this header */ + *p++ = 0; /* " */ + *p++ = 0; /* " */ + *p++ = 0; /* " */ + *p++ = 16; /* width */ + *p++ = 0; /* " */ + *p++ = 0; /* " */ + *p++ = 0; /* " */ + *p++ = 16 * 2; /* height x 2 ! */ + *p++ = 0; /* " */ + *p++ = 0; /* " */ + *p++ = 0; /* " */ + *p++ = 1; /* color planes */ + *p++ = 0; /* " */ + *p++ = 1; /* bpp */ + *p++ = 0; /* " */ + /* compression method, image size, ppm x, ppm y */ + /* colors in the palette ? */ + /* important colors */ + for(i = 4 * 6; i > 0; --i) + *p++ = 0; +#endif + /* palette */ + *p++ = 0; /* b */ + *p++ = 0; /* g */ + *p++ = 0; /* r */ + *p++ = 0; /* reserved */ + *p++ = 255; /* b */ + *p++ = 255; /* g */ + *p++ = 255; /* r */ + *p++ = 0; /* reserved */ + /* pixel data */ + for(i = 16; i > 0; --i) { + if(i & 1) { + *p++ = 0125; + *p++ = 0125; + } else { + *p++ = 0252; + *p++ = 0252; + } + *p++ = 0; + *p++ = 0; + } + /* Opacity MASK */ + for(i = 16 * 4; i > 0; --i) { + *p++ = 0; + } +} + +enum modes { + MODE_INVALID, MODE_CHUNKED, MODE_ADDCRAP, MODE_NORMAL, MODE_FAVICON, MODE_MALFORMED +}; + const struct { const enum modes mode; const char * text; @@ -146,28 +271,33 @@ const struct { {MODE_CHUNKED, "chunked"}, {MODE_ADDCRAP, "addcrap"}, {MODE_NORMAL, "normal"}, + {MODE_FAVICON, "favicon.ico"}, + {MODE_MALFORMED, "malformed"}, {MODE_INVALID, NULL} }; /** * write the response with random behaviour ! */ -void send_response(int c, const char * buffer, int len) +void send_response(int c, const char * buffer, size_t len) { - int n; + ssize_t n; while(len > 0) { n = (rand() % 99) + 1; - if(n > len) + if((size_t)n > len) n = len; n = write(c, buffer, n); if(n < 0) { - perror("write"); - return; + if(errno != EINTR) { + perror("write"); + return; + } + /* if errno == EINTR, try again */ } else { len -= n; buffer += n; + usleep(10000); /* 10ms */ } - usleep(10000); /* 10ms */ } } @@ -177,17 +307,18 @@ void send_response(int c, const char * buffer, int len) void handle_http_connection(int c) { char request_buffer[2048]; - int request_len = 0; + size_t request_len = 0; int headers_found = 0; - int n, i; + ssize_t n, m; + size_t i; char request_method[16]; char request_uri[256]; char http_version[16]; char * p; char * response_buffer; - int response_len; + size_t response_len; enum modes mode; - int content_length = 16*1024; + size_t content_length = 16*1024; /* read the request */ while(request_len < sizeof(request_buffer) && !headers_found) { @@ -195,6 +326,8 @@ void handle_http_connection(int c) request_buffer + request_len, sizeof(request_buffer) - request_len); if(n < 0) { + if(errno == EINTR) + continue; perror("read"); return; } else if(n==0) { @@ -213,9 +346,10 @@ void handle_http_connection(int c) } if(!headers_found) { /* error */ + printf("no HTTP header found in the request\n"); return; } - printf("headers :\n%.*s", request_len, request_buffer); + printf("headers :\n%.*s", (int)request_len, request_buffer); /* the request have been received, now parse the request line */ p = request_buffer; for(i = 0; i < sizeof(request_method) - 1; i++) { @@ -227,7 +361,7 @@ void handle_http_connection(int c) request_method[i] = '\0'; while(*p == ' ') p++; - for(i = 0; i < sizeof(request_uri) - 1; i++) { + for(i = 0; i < (int)sizeof(request_uri) - 1; i++) { if(*p == ' ' || *p == '\r') break; request_uri[i] = *p; @@ -236,7 +370,7 @@ void handle_http_connection(int c) request_uri[i] = '\0'; while(*p == ' ') p++; - for(i = 0; i < sizeof(http_version) - 1; i++) { + for(i = 0; i < (int)sizeof(http_version) - 1; i++) { if(*p == ' ' || *p == '\r') break; http_version[i] = *p; @@ -249,10 +383,24 @@ void handle_http_connection(int c) if(0 != strcmp(request_method, "GET")) { const char response405[] = "HTTP/1.1 405 Method Not Allowed\r\n" "Allow: GET\r\n\r\n"; + const char * pc; /* 405 Method Not Allowed */ /* The response MUST include an Allow header containing a list * of valid methods for the requested resource. */ - write(c, response405, sizeof(response405) - 1); + n = sizeof(response405) - 1; + pc = response405; + while(n > 0) { + m = write(c, pc, n); + if(m<0) { + if(errno != EINTR) { + perror("write"); + return; + } + } else { + n -= m; + pc += m; + } + } return; } @@ -266,33 +414,81 @@ void handle_http_connection(int c) } switch(mode) { + case MODE_MALFORMED: + response_len = 2048; + response_buffer = malloc(response_len); + if(!response_buffer) + break; + n = snprintf(response_buffer, response_len, + "HTTP/1.1 \r\n" + "\r\n" + /*"0000\r\n"*/); + for (i = n; i < response_len; i++) { + response_buffer[i] = ' '; + } + response_len = n; + break; case MODE_CHUNKED: response_buffer = build_chunked_response(content_length, &response_len); break; case MODE_ADDCRAP: response_len = content_length+256; response_buffer = malloc(response_len); + if(!response_buffer) + break; n = snprintf(response_buffer, response_len, "HTTP/1.1 200 OK\r\n" "Server: minihttptestserver\r\n" "Content-Type: text/plain\r\n" - "Content-Length: %d\r\n" + "Content-Length: %lu\r\n" "\r\n", content_length); response_len = content_length+n+CRAP_LENGTH; - response_buffer = realloc(response_buffer, response_len); + p = realloc(response_buffer, response_len); + if(p == NULL) { + /* error 500 */ + free(response_buffer); + response_buffer = NULL; + break; + } + response_buffer = p; build_content(response_buffer + n, content_length); build_crap(response_buffer + n + content_length, CRAP_LENGTH); break; + case MODE_FAVICON: + content_length = FAVICON_LENGTH; + response_len = content_length + 256; + response_buffer = malloc(response_len); + if(!response_buffer) + break; + n = snprintf(response_buffer, response_len, + "HTTP/1.1 200 OK\r\n" + "Server: minihttptestserver\r\n" + "Content-Type: image/vnd.microsoft.icon\r\n" + "Content-Length: %lu\r\n" + "\r\n", content_length); + /* image/x-icon */ + build_favicon_content((unsigned char *)(response_buffer + n), content_length); + response_len = content_length + n; + break; default: response_len = content_length+256; response_buffer = malloc(response_len); + if(!response_buffer) + break; n = snprintf(response_buffer, response_len, "HTTP/1.1 200 OK\r\n" "Server: minihttptestserver\r\n" "Content-Type: text/plain\r\n" "\r\n"); response_len = content_length+n; - response_buffer = realloc(response_buffer, response_len); + p = realloc(response_buffer, response_len); + if(p == NULL) { + /* Error 500 */ + free(response_buffer); + response_buffer = NULL; + break; + } + response_buffer = p; build_content(response_buffer + n, response_len - n); } @@ -308,15 +504,8 @@ void handle_http_connection(int c) */ int main(int argc, char * * argv) { int ipv6 = 0; - int s, c, i; + int r, i; unsigned short port = 0; - struct sockaddr_storage server_addr; - socklen_t server_addrlen; - struct sockaddr_storage client_addr; - socklen_t client_addrlen; - pid_t pid; - int child = 0; - int status; const char * expected_file_name = NULL; for(i = 1; i < argc; i++) { @@ -339,15 +528,46 @@ int main(int argc, char * * argv) { fprintf(stderr, "unknown command line switch '%s'\n", argv[i]); } } else { - fprintf(stderr, "unkown command line argument '%s'\n", argv[i]); + fprintf(stderr, "unknown command line argument '%s'\n", argv[i]); } } srand(time(NULL)); - signal(SIGCHLD, handle_signal_chld); -#if 0 - signal(SIGINT, handle_signal_int); -#endif + + r = server(port, expected_file_name, ipv6); + if(r != 0) { + printf("*** ERROR ***\n"); + } + return r; +} + +static int server(unsigned short port, const char * expected_file_name, int ipv6) +{ + int s, c; + int i; + struct sockaddr_storage server_addr; + socklen_t server_addrlen; + struct sockaddr_storage client_addr; + socklen_t client_addrlen; + pid_t pid; + int child = 0; + int status; + struct sigaction sa; + + memset(&sa, 0, sizeof(struct sigaction)); + + /*signal(SIGCHLD, handle_signal_chld);*/ + sa.sa_handler = handle_signal_chld; + if(sigaction(SIGCHLD, &sa, NULL) < 0) { + perror("sigaction"); + return 1; + } + /*signal(SIGINT, handle_signal_int);*/ + sa.sa_handler = handle_signal_int; + if(sigaction(SIGINT, &sa, NULL) < 0) { + perror("sigaction"); + return 1; + } s = socket(ipv6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0); if(s < 0) { @@ -360,12 +580,12 @@ int main(int argc, char * * argv) { struct sockaddr_in6 * addr = (struct sockaddr_in6 *)&server_addr; addr->sin6_family = AF_INET6; addr->sin6_port = htons(port); - addr->sin6_addr = in6addr_any; + addr->sin6_addr = in6addr_loopback; } else { struct sockaddr_in * addr = (struct sockaddr_in *)&server_addr; addr->sin_family = AF_INET; addr->sin_port = htons(port); - addr->sin_addr.s_addr = htonl(INADDR_ANY); + addr->sin_addr.s_addr = htonl(INADDR_LOOPBACK); } if(bind(s, (struct sockaddr *)&server_addr, ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) < 0) { @@ -399,10 +619,19 @@ int main(int argc, char * * argv) { if(f) { char * buffer; buffer = malloc(16*1024); - build_content(buffer, 16*1024); - fwrite(buffer, 1, 16*1024, f); - free(buffer); + if(buffer == NULL) { + fprintf(stderr, "memory allocation error\n"); + } else { + build_content(buffer, 16*1024); + i = fwrite(buffer, 1, 16*1024, f); + if(i != 16*1024) { + fprintf(stderr, "error writing to file %s : %dbytes written (out of %d)\n", expected_file_name, i, 16*1024); + } + free(buffer); + } fclose(f); + } else { + fprintf(stderr, "error opening file %s for writing\n", expected_file_name); } } @@ -413,16 +642,16 @@ int main(int argc, char * * argv) { if(pid < 0) { perror("wait"); } else { - printf("child(%d) terminated with status %d\n", pid, status); + printf("child(%d) terminated with status %d\n", (int)pid, status); } --child_to_wait_for; } - /* TODO : add a select() call in order to handle the case - * when a signal is caught */ client_addrlen = sizeof(struct sockaddr_storage); c = accept(s, (struct sockaddr *)&client_addr, &client_addrlen); if(c < 0) { + if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + continue; perror("accept"); return 1; } @@ -450,7 +679,7 @@ int main(int argc, char * * argv) { if(pid < 0) { perror("wait"); } else { - printf("child(%d) terminated with status %d\n", pid, status); + printf("child(%d) terminated with status %d\n", (int)pid, status); } --child_to_wait_for; } diff --git a/libs/miniupnpc/minisoap.c b/libs/miniupnpc/src/minisoap.c similarity index 81% rename from libs/miniupnpc/minisoap.c rename to libs/miniupnpc/src/minisoap.c index 8889bf040..78606672d 100644 --- a/libs/miniupnpc/minisoap.c +++ b/libs/miniupnpc/src/minisoap.c @@ -1,7 +1,8 @@ -/* $Id: minisoap.c,v 1.21 2011/03/22 19:15:35 nanard Exp $ */ -/* Project : miniupnp +/* $Id: minisoap.c,v 1.30 2020/11/09 19:27:42 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * Project : miniupnp * Author : Thomas Bernard - * Copyright (c) 2005-2009 Thomas Bernard + * Copyright (c) 2005-2020 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. * @@ -9,10 +10,10 @@ */ #include #include -#ifdef WIN32 +#ifdef _WIN32 #include #include -#define snprintf _snprintf +#include "win32_snprintf.h" #else #include #include @@ -24,16 +25,10 @@ /* only for malloc */ #include -#ifdef WIN32 -#define PRINT_SOCKET_ERROR(x) printf("Socket error: %s, %d\n", x, WSAGetLastError()); -#else -#define PRINT_SOCKET_ERROR(x) perror(x) -#endif - /* httpWrite sends the headers and the body to the socket * and returns the number of bytes sent */ static int -httpWrite(int fd, const char * body, int bodysize, +httpWrite(SOCKET fd, const char * body, int bodysize, const char * headers, int headerssize) { int n = 0; @@ -43,10 +38,10 @@ httpWrite(int fd, const char * body, int bodysize, /* Note : my old linksys router only took into account * soap request that are sent into only one packet */ char * p; - /* TODO: AVOID MALLOC */ + /* TODO: AVOID MALLOC, we could use writev() for that */ p = malloc(headerssize+bodysize); if(!p) - return 0; + return -1; memcpy(p, headers, headerssize); memcpy(p+headerssize, body, bodysize); /*n = write(fd, p, headerssize+bodysize);*/ @@ -55,9 +50,9 @@ httpWrite(int fd, const char * body, int bodysize, PRINT_SOCKET_ERROR("send"); } /* disable send on the socket */ - /* draytek routers dont seems to like that... */ + /* draytek routers don't seem to like that... */ #if 0 -#ifdef WIN32 +#ifdef _WIN32 if(shutdown(fd, SD_SEND)<0) { #else if(shutdown(fd, SHUT_WR)<0) { /*SD_SEND*/ @@ -70,7 +65,7 @@ httpWrite(int fd, const char * body, int bodysize, } /* self explanatory */ -int soapPostSubmit(int fd, +int soapPostSubmit(SOCKET fd, const char * url, const char * host, unsigned short port, @@ -78,11 +73,10 @@ int soapPostSubmit(int fd, const char * body, const char * httpversion) { - int bodysize; char headerbuf[512]; int headerssize; char portstr[8]; - bodysize = (int)strlen(body); + int bodysize = (int)strlen(body); /* We are not using keep-alive HTTP connections. * HTTP/1.1 needs the header Connection: close to do that. * This is the default with HTTP/1.0 @@ -96,7 +90,7 @@ int soapPostSubmit(int fd, headerssize = snprintf(headerbuf, sizeof(headerbuf), "POST %s HTTP/%s\r\n" "Host: %s%s\r\n" - "User-Agent: " OS_STRING ", UPnP/1.0, MiniUPnPc/" MINIUPNPC_VERSION_STRING "\r\n" + "User-Agent: " OS_STRING ", " UPNP_VERSION_STRING ", MiniUPnPc/" MINIUPNPC_VERSION_STRING "\r\n" "Content-Length: %d\r\n" "Content-Type: text/xml\r\n" "SOAPAction: \"%s\"\r\n" @@ -105,6 +99,8 @@ int soapPostSubmit(int fd, "Pragma: no-cache\r\n" "\r\n", url, httpversion, host, portstr, bodysize, action); + if ((unsigned int)headerssize >= sizeof(headerbuf)) + return -1; #ifdef DEBUG /*printf("SOAP request : headersize=%d bodysize=%d\n", headerssize, bodysize); diff --git a/libs/miniupnpc/minisoap.h b/libs/miniupnpc/src/minisoap.h similarity index 51% rename from libs/miniupnpc/minisoap.h rename to libs/miniupnpc/src/minisoap.h index 696725f62..f28684205 100644 --- a/libs/miniupnpc/minisoap.h +++ b/libs/miniupnpc/src/minisoap.h @@ -1,14 +1,16 @@ -/* $Id: minisoap.h,v 1.4 2010/04/12 20:39:41 nanard Exp $ */ +/* $Id: minisoap.h,v 1.6 2018/04/06 10:53:13 nanard Exp $ */ /* Project : miniupnp * Author : Thomas Bernard - * Copyright (c) 2005 Thomas Bernard + * Copyright (c) 2005-2018 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. */ -#ifndef __MINISOAP_H__ -#define __MINISOAP_H__ +#ifndef MINISOAP_H_INCLUDED +#define MINISOAP_H_INCLUDED + +#include "miniupnpc_socketdef.h" /*int httpWrite(int, const char *, int, const char *);*/ -int soapPostSubmit(int, const char *, const char *, unsigned short, +int soapPostSubmit(SOCKET, const char *, const char *, unsigned short, const char *, const char *, const char *); #endif diff --git a/libs/miniupnpc/src/minissdpc.c b/libs/miniupnpc/src/minissdpc.c new file mode 100644 index 000000000..edebb1600 --- /dev/null +++ b/libs/miniupnpc/src/minissdpc.c @@ -0,0 +1,1019 @@ +/* $Id: minissdpc.c,v 1.49 2021/05/13 11:00:36 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * Project : miniupnp + * Web : http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ + * Author : Thomas BERNARD + * copyright (c) 2005-2021 Thomas Bernard + * This software is subjet to the conditions detailed in the + * provided LICENCE file. */ +#include +#include +#include +#include +#include +#if defined (__NetBSD__) +#include +#endif +#if defined(_WIN32) || defined(__amigaos__) || defined(__amigaos4__) +#ifdef _WIN32 +#include +#include +#include +#include +#include "win32_snprintf.h" +#if !defined(_MSC_VER) +#include +#else /* !defined(_MSC_VER) */ +typedef unsigned short uint16_t; +#endif /* !defined(_MSC_VER) */ +#ifndef strncasecmp +#if defined(_MSC_VER) && (_MSC_VER >= 1400) +#define strncasecmp _memicmp +#else /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ +#define strncasecmp memicmp +#endif /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ +#endif /* #ifndef strncasecmp */ +#if defined(WINAPI_FAMILY) && defined(WINAPI_FAMILY_PARTITION) +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP +#define in6addr_any in6addr_any_init +static const IN6_ADDR in6addr_any_init = {0}; +#endif +#endif +#endif /* _WIN32 */ +#if defined(__amigaos__) || defined(__amigaos4__) +#include +#endif /* defined(__amigaos__) || defined(__amigaos4__) */ +#if defined(__amigaos__) +#define uint16_t unsigned short +#endif /* defined(__amigaos__) */ +/* Hack */ +#define UNIX_PATH_LEN 108 +struct sockaddr_un { + uint16_t sun_family; + char sun_path[UNIX_PATH_LEN]; +}; +#else /* defined(_WIN32) || defined(__amigaos__) || defined(__amigaos4__) */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define closesocket close +#endif + +#include "miniupnpc_socketdef.h" + +#if !defined(__DragonFly__) && !defined(__OpenBSD__) && !defined(__NetBSD__) && !defined(__APPLE__) && !defined(_WIN32) && !defined(__CYGWIN__) && !defined(__sun) && !defined(__GNU__) && !defined(__FreeBSD_kernel__) && !defined(__HAIKU__) +#define HAS_IP_MREQN +#endif + +#ifndef _WIN32 +#include +#if defined(__sun) || defined(__HAIKU__) +#include +#endif +#endif + +#if defined(HAS_IP_MREQN) && defined(NEED_STRUCT_IP_MREQN) +/* Several versions of glibc don't define this structure, + * define it here and compile with CFLAGS NEED_STRUCT_IP_MREQN */ +struct ip_mreqn +{ + struct in_addr imr_multiaddr; /* IP multicast address of group */ + struct in_addr imr_address; /* local IP address of interface */ + int imr_ifindex; /* Interface index */ +}; +#endif + +#if defined(__amigaos__) || defined(__amigaos4__) +/* Amiga OS specific stuff */ +#define TIMEVAL struct timeval +#endif + +#include "minissdpc.h" +#include "miniupnpc.h" +#include "receivedata.h" + +#if !(defined(_WIN32) || defined(__amigaos__) || defined(__amigaos4__)) + +#include "codelength.h" + +struct UPNPDev * +getDevicesFromMiniSSDPD(const char * devtype, const char * socketpath, int * error) +{ + struct UPNPDev * devlist = NULL; + int s; + int res; + + s = connectToMiniSSDPD(socketpath); + if (s < 0) { + if (error) + *error = s; + return NULL; + } + res = requestDevicesFromMiniSSDPD(s, devtype); + if (res < 0) { + if (error) + *error = res; + } else { + devlist = receiveDevicesFromMiniSSDPD(s, error); + } + disconnectFromMiniSSDPD(s); + return devlist; +} + +/* macros used to read from unix socket */ +#define READ_BYTE_BUFFER(c) \ + if((int)bufferindex >= n) { \ + n = read(s, buffer, sizeof(buffer)); \ + if(n<=0) break; \ + bufferindex = 0; \ + } \ + c = buffer[bufferindex++]; + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif /* MIN */ + +#define READ_COPY_BUFFER(dst, len) \ + for(l = len, p = (unsigned char *)dst; l > 0; ) { \ + unsigned int lcopy; \ + if((int)bufferindex >= n) { \ + n = read(s, buffer, sizeof(buffer)); \ + if(n<=0) break; \ + bufferindex = 0; \ + } \ + lcopy = MIN(l, (n - bufferindex)); \ + memcpy(p, buffer + bufferindex, lcopy); \ + l -= lcopy; \ + p += lcopy; \ + bufferindex += lcopy; \ + } + +#define READ_DISCARD_BUFFER(len) \ + for(l = len; l > 0; ) { \ + unsigned int lcopy; \ + if(bufferindex >= n) { \ + n = read(s, buffer, sizeof(buffer)); \ + if(n<=0) break; \ + bufferindex = 0; \ + } \ + lcopy = MIN(l, (n - bufferindex)); \ + l -= lcopy; \ + bufferindex += lcopy; \ + } + +int +connectToMiniSSDPD(const char * socketpath) +{ + int s; + struct sockaddr_un addr; +#if defined(MINIUPNPC_SET_SOCKET_TIMEOUT) && !defined(__sun) + struct timeval timeout; +#endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ + + s = socket(AF_UNIX, SOCK_STREAM, 0); + if(s < 0) + { + /*syslog(LOG_ERR, "socket(unix): %m");*/ + perror("socket(unix)"); + return MINISSDPC_SOCKET_ERROR; + } +#if defined(MINIUPNPC_SET_SOCKET_TIMEOUT) && !defined(__sun) + /* setting a 3 seconds timeout */ + /* not supported for AF_UNIX sockets under Solaris */ + timeout.tv_sec = 3; + timeout.tv_usec = 0; + if(setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(struct timeval)) < 0) + { + perror("setsockopt SO_RCVTIMEO unix"); + } + timeout.tv_sec = 3; + timeout.tv_usec = 0; + if(setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(struct timeval)) < 0) + { + perror("setsockopt SO_SNDTIMEO unix"); + } +#endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ + if(!socketpath) + socketpath = "/var/run/minissdpd.sock"; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, socketpath, sizeof(addr.sun_path)); + /* TODO : check if we need to handle the EINTR */ + if(connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) + { + /*syslog(LOG_WARNING, "connect(\"%s\"): %m", socketpath);*/ + close(s); + return MINISSDPC_SOCKET_ERROR; + } + return s; +} + +int +disconnectFromMiniSSDPD(int s) +{ + if (close(s) < 0) + return MINISSDPC_SOCKET_ERROR; + return MINISSDPC_SUCCESS; +} + +int +requestDevicesFromMiniSSDPD(int s, const char * devtype) +{ + unsigned char buffer[256]; + unsigned char * p; + unsigned int stsize, l; + + stsize = strlen(devtype); + if(stsize == 8 && 0 == memcmp(devtype, "ssdp:all", 8)) + { + buffer[0] = 3; /* request type 3 : everything */ + } + else + { + buffer[0] = 1; /* request type 1 : request devices/services by type */ + } + p = buffer + 1; + l = stsize; CODELENGTH(l, p); + if(p + stsize > buffer + sizeof(buffer)) + { + /* devtype is too long ! */ +#ifdef DEBUG + fprintf(stderr, "devtype is too long ! stsize=%u sizeof(buffer)=%u\n", + stsize, (unsigned)sizeof(buffer)); +#endif /* DEBUG */ + return MINISSDPC_INVALID_INPUT; + } + memcpy(p, devtype, stsize); + p += stsize; + if(write(s, buffer, p - buffer) < 0) + { + /*syslog(LOG_ERR, "write(): %m");*/ + perror("minissdpc.c: write()"); + return MINISSDPC_SOCKET_ERROR; + } + return MINISSDPC_SUCCESS; +} + +struct UPNPDev * +receiveDevicesFromMiniSSDPD(int s, int * error) +{ + struct UPNPDev * tmp; + struct UPNPDev * devlist = NULL; + unsigned char buffer[256]; + ssize_t n; + unsigned char * p; + unsigned char * url; + unsigned char * st; + unsigned int bufferindex; + unsigned int i, ndev; + unsigned int urlsize, stsize, usnsize, l; + + n = read(s, buffer, sizeof(buffer)); + if(n<=0) + { + perror("minissdpc.c: read()"); + if (error) + *error = MINISSDPC_SOCKET_ERROR; + return NULL; + } + ndev = buffer[0]; + bufferindex = 1; + for(i = 0; i < ndev; i++) + { + DECODELENGTH_READ(urlsize, READ_BYTE_BUFFER); + if(n<=0) { + if (error) + *error = MINISSDPC_INVALID_SERVER_REPLY; + return devlist; + } +#ifdef DEBUG + printf(" urlsize=%u", urlsize); +#endif /* DEBUG */ + url = malloc(urlsize); + if(url == NULL) { + if (error) + *error = MINISSDPC_MEMORY_ERROR; + return devlist; + } + READ_COPY_BUFFER(url, urlsize); + if(n<=0) { + if (error) + *error = MINISSDPC_INVALID_SERVER_REPLY; + goto free_url_and_return; + } + DECODELENGTH_READ(stsize, READ_BYTE_BUFFER); + if(n<=0) { + if (error) + *error = MINISSDPC_INVALID_SERVER_REPLY; + goto free_url_and_return; + } +#ifdef DEBUG + printf(" stsize=%u", stsize); +#endif /* DEBUG */ + st = malloc(stsize); + if (st == NULL) { + if (error) + *error = MINISSDPC_MEMORY_ERROR; + goto free_url_and_return; + } + READ_COPY_BUFFER(st, stsize); + if(n<=0) { + if (error) + *error = MINISSDPC_INVALID_SERVER_REPLY; + goto free_url_and_st_and_return; + } + DECODELENGTH_READ(usnsize, READ_BYTE_BUFFER); + if(n<=0) { + if (error) + *error = MINISSDPC_INVALID_SERVER_REPLY; + goto free_url_and_st_and_return; + } +#ifdef DEBUG + printf(" usnsize=%u\n", usnsize); +#endif /* DEBUG */ + tmp = (struct UPNPDev *)malloc(sizeof(struct UPNPDev)+urlsize+stsize+usnsize); + if(tmp == NULL) { + if (error) + *error = MINISSDPC_MEMORY_ERROR; + goto free_url_and_st_and_return; + } + tmp->pNext = devlist; + tmp->descURL = tmp->buffer; + tmp->st = tmp->buffer + 1 + urlsize; + memcpy(tmp->buffer, url, urlsize); + tmp->buffer[urlsize] = '\0'; + memcpy(tmp->st, st, stsize); + tmp->buffer[urlsize+1+stsize] = '\0'; + free(url); + free(st); + url = NULL; + st = NULL; + tmp->usn = tmp->buffer + 1 + urlsize + 1 + stsize; + READ_COPY_BUFFER(tmp->usn, usnsize); + if(n<=0) { + if (error) + *error = MINISSDPC_INVALID_SERVER_REPLY; + goto free_tmp_and_return; + } + tmp->buffer[urlsize+1+stsize+1+usnsize] = '\0'; + tmp->scope_id = 0; /* default value. scope_id is not available with MiniSSDPd */ + devlist = tmp; + } + if (error) + *error = MINISSDPC_SUCCESS; + return devlist; + +free_url_and_st_and_return: + free(st); +free_url_and_return: + free(url); + return devlist; + +free_tmp_and_return: + free(tmp); + return devlist; +} + +#endif /* !(defined(_WIN32) || defined(__amigaos__) || defined(__amigaos4__)) */ + +/* parseMSEARCHReply() + * the last 4 arguments are filled during the parsing : + * - location/locationsize : "location:" field of the SSDP reply packet + * - st/stsize : "st:" field of the SSDP reply packet. + * - usn/usnsize : "usn:" filed of the SSDP reply packet + * The strings are NOT null terminated */ +static void +parseMSEARCHReply(const char * reply, int size, + const char * * location, int * locationsize, + const char * * st, int * stsize, + const char * * usn, int * usnsize) +{ + int a, b, i; + i = 0; + a = i; /* start of the line */ + b = 0; /* end of the "header" (position of the colon) */ + while(i= 0x0600 // _WIN32_WINNT_VISTA + ULONGLONG ts = GetTickCount64(); +#else + DWORD ts = GetTickCount(); +#endif + tv->tv_sec = (long)(ts / 1000); + tv->tv_usec = (ts % 1000) * 1000; + return 0; /* success */ +#elif defined(CLOCK_MONOTONIC_FAST) || defined(CLOCK_MONOTONIC) +#if defined(__APPLE__) +#if defined(__clang__) + if (__builtin_available(macOS 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *)) { +#else /* !defined(__clang__) */ + if (clock_gettime != NULL) { +#endif /* defined(__clang__) */ +#endif /* defined(__APPLE__) */ + struct timespec ts; + int ret_code = clock_gettime(UPNP_CLOCKID, &ts); + if (ret_code == 0) + { + tv->tv_sec = ts.tv_sec; + tv->tv_usec = ts.tv_nsec / 1000; + } + return ret_code; +#if defined(__APPLE__) + } + else + { + /* fall-back for earlier Apple platforms */ + return gettimeofday(tv, NULL); + } +#endif /* defined(__APPLE__) */ +#else + return gettimeofday(tv, NULL); +#endif +} +/* port upnp discover : SSDP protocol */ +#define SSDP_PORT 1900 +#define XSTR(s) STR(s) +#define STR(s) #s +#define UPNP_MCAST_ADDR "239.255.255.250" +/* for IPv6 */ +#define UPNP_MCAST_LL_ADDR "FF02::C" /* link-local */ +#define UPNP_MCAST_SL_ADDR "FF05::C" /* site-local */ + +/* direct discovery if minissdpd responses are not sufficient */ +/* ssdpDiscoverDevices() : + * return a chained list of all devices found or NULL if + * no devices was found. + * It is up to the caller to free the chained list + * delay is in millisecond (poll). + * UDA v1.1 says : + * The TTL for the IP packet SHOULD default to 2 and + * SHOULD be configurable. */ +struct UPNPDev * +ssdpDiscoverDevices(const char * const deviceTypes[], + int delay, const char * multicastif, + int localport, + int ipv6, unsigned char ttl, + int * error, + int searchalltypes) +{ + struct UPNPDev * tmp; + struct UPNPDev * devlist = NULL; + unsigned int scope_id = 0; + int opt = 1; + static const char MSearchMsgFmt[] = + "M-SEARCH * HTTP/1.1\r\n" + "HOST: %s:" XSTR(SSDP_PORT) "\r\n" + "ST: %s\r\n" + "MAN: \"ssdp:discover\"\r\n" + "MX: %u\r\n" + "\r\n"; + int deviceIndex; + char bufr[1536]; /* reception and emission buffer */ + SOCKET sudp; + int n; + struct sockaddr_storage sockudp_r; + unsigned int mx; +#ifdef NO_GETADDRINFO + struct sockaddr_storage sockudp_w; +#else + int rv; + struct addrinfo hints, *servinfo; +#endif +#ifdef _WIN32 + unsigned long _ttl = (unsigned long)ttl; +#endif + int linklocal = 1; + int sentok; + + if(error) + *error = MINISSDPC_UNKNOWN_ERROR; + + if(localport==UPNP_LOCAL_PORT_SAME) + localport = SSDP_PORT; + +#ifdef _WIN32 + sudp = socket(ipv6 ? PF_INET6 : PF_INET, SOCK_DGRAM, IPPROTO_UDP); +#else + sudp = socket(ipv6 ? PF_INET6 : PF_INET, SOCK_DGRAM, 0); +#endif + if(ISINVALID(sudp)) + { + if(error) + *error = MINISSDPC_SOCKET_ERROR; + PRINT_SOCKET_ERROR("socket"); + return NULL; + } + /* reception */ + memset(&sockudp_r, 0, sizeof(struct sockaddr_storage)); + if(ipv6) { + struct sockaddr_in6 * p = (struct sockaddr_in6 *)&sockudp_r; + p->sin6_family = AF_INET6; + if(localport > 0 && localport < 65536) + p->sin6_port = htons((unsigned short)localport); + p->sin6_addr = in6addr_any; /* in6addr_any is not available with MinGW32 3.4.2 */ + } else { + struct sockaddr_in * p = (struct sockaddr_in *)&sockudp_r; + p->sin_family = AF_INET; + if(localport > 0 && localport < 65536) + p->sin_port = htons((unsigned short)localport); + p->sin_addr.s_addr = INADDR_ANY; + } +#ifdef _WIN32 +/* This code could help us to use the right Network interface for + * SSDP multicast traffic */ +/* Get IP associated with the index given in the ip_forward struct + * in order to give this ip to setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF) */ + if(!ipv6) { + DWORD ifbestidx; +#if _WIN32_WINNT >= 0x0600 // _WIN32_WINNT_VISTA + // While we don't need IPv6 support, the IPv4 only funciton is not available in UWP apps. + SOCKADDR_IN destAddr; + memset(&destAddr, 0, sizeof(destAddr)); + destAddr.sin_family = AF_INET; + destAddr.sin_addr.s_addr = inet_addr("223.255.255.255"); + destAddr.sin_port = 0; + if (GetBestInterfaceEx((struct sockaddr *)&destAddr, &ifbestidx) == NO_ERROR) { +#else + if (GetBestInterface(inet_addr("223.255.255.255"), &ifbestidx) == NO_ERROR) { +#endif + DWORD dwRetVal = NO_ERROR; + PIP_ADAPTER_ADDRESSES pAddresses = NULL; + ULONG outBufLen = 15360; + int Iterations; + PIP_ADAPTER_ADDRESSES pCurrAddresses = NULL; + PIP_ADAPTER_UNICAST_ADDRESS pUnicast = NULL; + + for (Iterations = 0; Iterations < 3; Iterations++) { + pAddresses = (IP_ADAPTER_ADDRESSES *) HeapAlloc(GetProcessHeap(), 0, outBufLen); + if (pAddresses == NULL) { + break; + } + + dwRetVal = GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, NULL, pAddresses, &outBufLen); + + if (dwRetVal != ERROR_BUFFER_OVERFLOW) { + break; + } + HeapFree(GetProcessHeap(), 0, pAddresses); + pAddresses = NULL; + } + + if (dwRetVal == NO_ERROR) { + pCurrAddresses = pAddresses; + while (pCurrAddresses) { +#ifdef DEBUG + int i; + PIP_ADAPTER_MULTICAST_ADDRESS pMulticast = NULL; + PIP_ADAPTER_ANYCAST_ADDRESS pAnycast = NULL; + + printf("\tIfIndex (IPv4 interface): %u\n", pCurrAddresses->IfIndex); + printf("\tAdapter name: %s\n", pCurrAddresses->AdapterName); + pUnicast = pCurrAddresses->FirstUnicastAddress; + if (pUnicast != NULL) { + for (i = 0; pUnicast != NULL; i++) { + printf("\tIP Address[%d]: \t%s\n", i, inet_ntoa(((PSOCKADDR_IN)pUnicast->Address.lpSockaddr)->sin_addr) ); + pUnicast = pUnicast->Next; + } + printf("\tNumber of Unicast Addresses: %d\n", i); + } + pAnycast = pCurrAddresses->FirstAnycastAddress; + if (pAnycast) { + for (i = 0; pAnycast != NULL; i++) { + printf("\tAnycast Address[%d]: \t%s\n", i, inet_ntoa(((PSOCKADDR_IN)pAnycast->Address.lpSockaddr)->sin_addr) ); + pAnycast = pAnycast->Next; + } + printf("\tNumber of Anycast Addresses: %d\n", i); + } + pMulticast = pCurrAddresses->FirstMulticastAddress; + if (pMulticast) { + for (i = 0; pMulticast != NULL; i++) { + printf("\tMulticast Address[%d]: \t%s\n", i, inet_ntoa(((PSOCKADDR_IN)pMulticast->Address.lpSockaddr)->sin_addr) ); + pMulticast = pMulticast->Next; + } + } + printf("\n"); +#endif + pUnicast = pCurrAddresses->FirstUnicastAddress; + if (pCurrAddresses->IfIndex == ifbestidx && pUnicast != NULL) { + SOCKADDR_IN *ipv4 = (SOCKADDR_IN *)(pUnicast->Address.lpSockaddr); + /* Set the address of this interface to be used */ + struct in_addr mc_if; + memset(&mc_if, 0, sizeof(mc_if)); + mc_if.s_addr = ipv4->sin_addr.s_addr; + if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&mc_if, sizeof(mc_if)) < 0) { + PRINT_SOCKET_ERROR("setsockopt"); + } + ((struct sockaddr_in *)&sockudp_r)->sin_addr.s_addr = ipv4->sin_addr.s_addr; +#ifndef DEBUG + break; +#endif + } + pCurrAddresses = pCurrAddresses->Next; + } + } + if (pAddresses != NULL) { + HeapFree(GetProcessHeap(), 0, pAddresses); + pAddresses = NULL; + } + } + } +#endif /* _WIN32 */ + +#ifdef _WIN32 + if (setsockopt(sudp, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof (opt)) < 0) +#else + if (setsockopt(sudp, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt)) < 0) +#endif + { + if(error) + *error = MINISSDPC_SOCKET_ERROR; + PRINT_SOCKET_ERROR("setsockopt(SO_REUSEADDR,...)"); + goto error; + } + + if(ipv6) { +#ifdef _WIN32 + DWORD mcastHops = ttl; + if(setsockopt(sudp, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (const char *)&mcastHops, sizeof(mcastHops)) < 0) +#else /* _WIN32 */ + int mcastHops = ttl; + if(setsockopt(sudp, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &mcastHops, sizeof(mcastHops)) < 0) +#endif /* _WIN32 */ + { + PRINT_SOCKET_ERROR("setsockopt(IPV6_MULTICAST_HOPS,...)"); + } + } else { +#ifdef _WIN32 + if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_TTL, (const char *)&_ttl, sizeof(_ttl)) < 0) +#else /* _WIN32 */ + if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) +#endif /* _WIN32 */ + { + /* not a fatal error */ + PRINT_SOCKET_ERROR("setsockopt(IP_MULTICAST_TTL,...)"); + } + } + + if(multicastif && multicastif[0] != '\0') + { + if(ipv6) { +#if !defined(_WIN32) + /* according to MSDN, if_nametoindex() is supported since + * MS Windows Vista and MS Windows Server 2008. + * http://msdn.microsoft.com/en-us/library/bb408409%28v=vs.85%29.aspx */ + unsigned int ifindex = if_nametoindex(multicastif); /* eth0, etc. */ + if(ifindex == 0) + { + if(error) + *error = MINISSDPC_INVALID_INPUT; + fprintf(stderr, "Invalid multicast interface name %s\n", multicastif); + goto error; + } + if(setsockopt(sudp, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifindex, sizeof(ifindex)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt IPV6_MULTICAST_IF"); + } +#else +#ifdef DEBUG + printf("Setting of multicast interface not supported in IPv6 under Windows.\n"); +#endif +#endif + } else { + struct in_addr mc_if; +#if defined(_WIN32) +#if _WIN32_WINNT >= 0x0600 // _WIN32_WINNT_VISTA + InetPtonA(AF_INET, multicastif, &mc_if); +#else + mc_if.s_addr = inet_addr(multicastif); /* old Windows SDK do not support InetPtoA() */ +#endif +#else + /* was : mc_if.s_addr = inet_addr(multicastif); */ /* ex: 192.168.x.x */ + if (inet_pton(AF_INET, multicastif, &mc_if.s_addr) <= 0) { + mc_if.s_addr = INADDR_NONE; + } +#endif + if(mc_if.s_addr != INADDR_NONE) + { + ((struct sockaddr_in *)&sockudp_r)->sin_addr.s_addr = mc_if.s_addr; + if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&mc_if, sizeof(mc_if)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt IP_MULTICAST_IF"); + } + } else { + /* was not an ip address, try with an interface name */ +#ifndef _WIN32 +#ifdef HAS_IP_MREQN + struct ip_mreqn reqn; /* only defined with -D_BSD_SOURCE or -D_GNU_SOURCE */ +#endif + struct ifreq ifr; + int ifrlen = sizeof(ifr); + strncpy(ifr.ifr_name, multicastif, IFNAMSIZ); + ifr.ifr_name[IFNAMSIZ-1] = '\0'; + if(ioctl(sudp, SIOCGIFADDR, &ifr, &ifrlen) < 0) + { + PRINT_SOCKET_ERROR("ioctl(...SIOCGIFADDR...)"); + goto error; + } + mc_if.s_addr = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr; +#ifdef HAS_IP_MREQN + memset(&reqn, 0, sizeof(struct ip_mreqn)); + reqn.imr_address.s_addr = mc_if.s_addr; + reqn.imr_ifindex = if_nametoindex(multicastif); + if(reqn.imr_ifindex == 0) + { + if(error) + *error = MINISSDPC_INVALID_INPUT; + fprintf(stderr, "Invalid multicast ip address / interface name %s\n", multicastif); + goto error; + } + if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&reqn, sizeof(reqn)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt IP_MULTICAST_IF"); + } +#else + if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&mc_if, sizeof(mc_if)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt IP_MULTICAST_IF"); + } +#endif +#else /* _WIN32 */ +#ifdef DEBUG + printf("Setting of multicast interface not supported with interface name.\n"); +#endif +#endif /* #ifdef HAS_IP_MREQN / !defined(_WIN32) */ + } + } + } + + /* Before sending the packed, we first "bind" in order to be able + * to receive the response */ + if (bind(sudp, (const struct sockaddr *)&sockudp_r, + ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) != 0) + { + if(error) + *error = MINISSDPC_SOCKET_ERROR; + PRINT_SOCKET_ERROR("bind"); + closesocket(sudp); + return NULL; + } + + if(error) + *error = MINISSDPC_SUCCESS; + /* Calculating maximum response time in seconds */ + mx = ((unsigned int)delay) / 1000u; + if(mx == 0) { + mx = 1; + delay = 1000; + } + /* receiving SSDP response packet */ + for(deviceIndex = 0; deviceTypes[deviceIndex]; deviceIndex++) { + sentok = 0; + /* sending the SSDP M-SEARCH packet */ + n = snprintf(bufr, sizeof(bufr), + MSearchMsgFmt, + ipv6 ? + (linklocal ? "[" UPNP_MCAST_LL_ADDR "]" : "[" UPNP_MCAST_SL_ADDR "]") + : UPNP_MCAST_ADDR, + deviceTypes[deviceIndex], mx); + if ((unsigned int)n >= sizeof(bufr)) { + if(error) + *error = MINISSDPC_MEMORY_ERROR; + goto error; + } +#ifdef DEBUG + /*printf("Sending %s", bufr);*/ + printf("Sending M-SEARCH request to %s with ST: %s\n", + ipv6 ? + (linklocal ? "[" UPNP_MCAST_LL_ADDR "]" : "[" UPNP_MCAST_SL_ADDR "]") + : UPNP_MCAST_ADDR, + deviceTypes[deviceIndex]); +#endif +#ifdef NO_GETADDRINFO + /* the following code is not using getaddrinfo */ + /* emission */ + memset(&sockudp_w, 0, sizeof(struct sockaddr_storage)); + if(ipv6) { + struct sockaddr_in6 * p = (struct sockaddr_in6 *)&sockudp_w; + p->sin6_family = AF_INET6; + p->sin6_port = htons(SSDP_PORT); + inet_pton(AF_INET6, + linklocal ? UPNP_MCAST_LL_ADDR : UPNP_MCAST_SL_ADDR, + &(p->sin6_addr)); + } else { + struct sockaddr_in * p = (struct sockaddr_in *)&sockudp_w; + p->sin_family = AF_INET; + p->sin_port = htons(SSDP_PORT); + p->sin_addr.s_addr = inet_addr(UPNP_MCAST_ADDR); + } + n = sendto(sudp, bufr, n, 0, &sockudp_w, + ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)); + if (n < 0) { + if(error) + *error = MINISSDPC_SOCKET_ERROR; + PRINT_SOCKET_ERROR("sendto"); + } else { + sentok = 1; + } +#else /* #ifdef NO_GETADDRINFO */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; /* AF_INET6 or AF_INET */ + hints.ai_socktype = SOCK_DGRAM; + /*hints.ai_flags = */ + if ((rv = getaddrinfo(ipv6 + ? (linklocal ? UPNP_MCAST_LL_ADDR : UPNP_MCAST_SL_ADDR) + : UPNP_MCAST_ADDR, + XSTR(SSDP_PORT), &hints, &servinfo)) != 0) { + if(error) + *error = MINISSDPC_SOCKET_ERROR; +#ifdef _WIN32 + fprintf(stderr, "getaddrinfo() failed: %d\n", rv); +#else + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); +#endif + break; + } else { + struct addrinfo *p; + for(p = servinfo; p; p = p->ai_next) { + n = sendto(sudp, bufr, n, 0, p->ai_addr, MSC_CAST_INT p->ai_addrlen); + if (n < 0) { +#ifdef DEBUG + char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; + if (getnameinfo(p->ai_addr, (socklen_t)p->ai_addrlen, hbuf, sizeof(hbuf), sbuf, + sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV) == 0) { + fprintf(stderr, "host:%s port:%s\n", hbuf, sbuf); + } +#endif + PRINT_SOCKET_ERROR("sendto"); + continue; + } else { + sentok = 1; + } + } + freeaddrinfo(servinfo); + } + if(!sentok) { + if(error) + *error = MINISSDPC_SOCKET_ERROR; + } +#endif /* #ifdef NO_GETADDRINFO */ + /* Waiting for SSDP REPLY packet to M-SEARCH + * if searchalltypes is set, enter the loop only + * when the last deviceType is reached */ + if((sentok && !searchalltypes) || !deviceTypes[deviceIndex + 1]) { + struct timeval start = {0, 0}, current = {0, 0}; + upnp_gettimeofday(&start); + do { + n = receivedata(sudp, bufr, sizeof(bufr), delay, &scope_id); + if (n < 0) { + /* error */ + if(error) + *error = MINISSDPC_SOCKET_ERROR; + goto error; + } else if (n == 0) { + /* no data or Time Out */ +#ifdef DEBUG + printf("NODATA or TIMEOUT\n"); +#endif /* DEBUG */ + if (devlist && !searchalltypes) { + /* found some devices, stop now*/ + if(error) + *error = MINISSDPC_SUCCESS; + goto error; + } + } else { + const char * descURL=NULL; + int urlsize=0; + const char * st=NULL; + int stsize=0; + const char * usn=NULL; + int usnsize=0; + parseMSEARCHReply(bufr, n, &descURL, &urlsize, &st, &stsize, &usn, &usnsize); + if(st&&descURL) { +#ifdef DEBUG + printf("M-SEARCH Reply:\n ST: %.*s\n USN: %.*s\n Location: %.*s\n", + stsize, st, usnsize, (usn?usn:""), urlsize, descURL); +#endif /* DEBUG */ + for(tmp=devlist; tmp; tmp = tmp->pNext) { + if(strncmp(tmp->descURL, descURL, urlsize) == 0 && + tmp->descURL[urlsize] == '\0' && + strncmp(tmp->st, st, stsize) == 0 && + tmp->st[stsize] == '\0' && + (usnsize == 0 || strncmp(tmp->usn, usn, usnsize) == 0) && + tmp->usn[usnsize] == '\0') + break; + } + /* at the exit of the loop above, tmp is null if + * no duplicate device was found */ + if(tmp) + continue; + tmp = (struct UPNPDev *)malloc(sizeof(struct UPNPDev)+urlsize+stsize+usnsize+3); + if(!tmp) { + /* memory allocation error */ + if(error) + *error = MINISSDPC_MEMORY_ERROR; + goto error; + } + tmp->pNext = devlist; + tmp->descURL = tmp->buffer; + tmp->st = tmp->buffer + 1 + urlsize; + tmp->usn = tmp->st + 1 + stsize; + memcpy(tmp->buffer, descURL, urlsize); + tmp->buffer[urlsize] = '\0'; + memcpy(tmp->st, st, stsize); + tmp->buffer[urlsize+1+stsize] = '\0'; + if(usn != NULL) + memcpy(tmp->usn, usn, usnsize); + tmp->buffer[urlsize+1+stsize+1+usnsize] = '\0'; + tmp->scope_id = scope_id; + devlist = tmp; + } + if (upnp_gettimeofday(¤t) >= 0) { + /* exit the loop if delay is reached */ + long interval = (current.tv_sec - start.tv_sec) * 1000; + interval += (current.tv_usec - start.tv_usec) / 1000; + if (interval > (long)delay) + break; + } + } + } while(n > 0); + } + if(ipv6) { + /* switch linklocal flag */ + if(linklocal) { + linklocal = 0; + --deviceIndex; + } else { + linklocal = 1; + } + } + } +error: + closesocket(sudp); + return devlist; +} diff --git a/libs/miniupnpc/src/minissdpc.h b/libs/miniupnpc/src/minissdpc.h new file mode 100644 index 000000000..04cdbf82c --- /dev/null +++ b/libs/miniupnpc/src/minissdpc.h @@ -0,0 +1,58 @@ +/* $Id: minissdpc.h,v 1.8 2019/02/10 12:29:23 nanard Exp $ */ +/* Project: miniupnp + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * Author: Thomas Bernard + * Copyright (c) 2005-2015 Thomas Bernard + * This software is subjects to the conditions detailed + * in the LICENCE file provided within this distribution */ +#ifndef MINISSDPC_H_INCLUDED +#define MINISSDPC_H_INCLUDED + +#include "miniupnpc_declspec.h" +#include "upnpdev.h" + +/* error codes : */ +#define MINISSDPC_SUCCESS (0) +#define MINISSDPC_UNKNOWN_ERROR (-1) +#define MINISSDPC_SOCKET_ERROR (-101) +#define MINISSDPC_MEMORY_ERROR (-102) +#define MINISSDPC_INVALID_INPUT (-103) +#define MINISSDPC_INVALID_SERVER_REPLY (-104) + +#ifdef __cplusplus +extern "C" { +#endif + +#if !(defined(_WIN32) || defined(__amigaos__) || defined(__amigaos4__)) + +MINIUPNP_LIBSPEC struct UPNPDev * +getDevicesFromMiniSSDPD(const char * devtype, const char * socketpath, int * error); + +MINIUPNP_LIBSPEC int +connectToMiniSSDPD(const char * socketpath); + +MINIUPNP_LIBSPEC int +disconnectFromMiniSSDPD(int s); + +MINIUPNP_LIBSPEC int +requestDevicesFromMiniSSDPD(int s, const char * devtype); + +MINIUPNP_LIBSPEC struct UPNPDev * +receiveDevicesFromMiniSSDPD(int s, int * error); + +#endif /* !(defined(_WIN32) || defined(__amigaos__) || defined(__amigaos4__)) */ + +MINIUPNP_LIBSPEC struct UPNPDev * +ssdpDiscoverDevices(const char * const deviceTypes[], + int delay, const char * multicastif, + int localport, + int ipv6, unsigned char ttl, + int * error, + int searchalltypes); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/libs/miniupnpc/src/miniupnpc.c b/libs/miniupnpc/src/miniupnpc.c new file mode 100644 index 000000000..696af9323 --- /dev/null +++ b/libs/miniupnpc/src/miniupnpc.c @@ -0,0 +1,698 @@ +/* $Id: miniupnpc.c,v 1.159 2021/03/02 23:36:32 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * Project : miniupnp + * Web : http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ + * Author : Thomas BERNARD + * copyright (c) 2005-2021 Thomas Bernard + * This software is subjet to the conditions detailed in the + * provided LICENSE file. */ +#include +#include +#include +#ifdef _WIN32 +/* Win32 Specific includes and defines */ +#include +#include +#include +#include +#include "win32_snprintf.h" +#define strdup _strdup +#ifndef strncasecmp +#if defined(_MSC_VER) && (_MSC_VER >= 1400) +#define strncasecmp _memicmp +#else /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ +#define strncasecmp memicmp +#endif /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ +#endif /* #ifndef strncasecmp */ +#define MAXHOSTNAMELEN 64 +#else /* #ifdef _WIN32 */ +/* Standard POSIX includes */ +#include +#if defined(__amigaos__) && !defined(__amigaos4__) +/* Amiga OS 3 specific stuff */ +#define socklen_t int +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#if !defined(__amigaos__) && !defined(__amigaos4__) +#include +#endif +#include +#include +#define closesocket close +#endif /* #else _WIN32 */ +#ifdef __GNU__ +#define MAXHOSTNAMELEN 64 +#endif + + +#include "miniupnpc.h" +#include "minissdpc.h" +#include "miniwget.h" +#include "miniwget_private.h" +#include "minisoap.h" +#include "minixml.h" +#include "upnpcommands.h" +#include "connecthostport.h" +#include "addr_is_reserved.h" + +/* compare the beginning of a string with a constant string */ +#define COMPARE(str, cstr) (0==strncmp(str, cstr, sizeof(cstr) - 1)) + +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 64 +#endif + +#define SOAPPREFIX "s" +#define SERVICEPREFIX "u" +#define SERVICEPREFIX2 'u' + +/* root description parsing */ +MINIUPNP_LIBSPEC void parserootdesc(const char * buffer, int bufsize, struct IGDdatas * data) +{ + struct xmlparser parser; + /* xmlparser object */ + parser.xmlstart = buffer; + parser.xmlsize = bufsize; + parser.data = data; + parser.starteltfunc = IGDstartelt; + parser.endeltfunc = IGDendelt; + parser.datafunc = IGDdata; + parser.attfunc = 0; + parsexml(&parser); +#ifdef DEBUG + printIGD(data); +#endif +} + +/* simpleUPnPcommand2 : + * not so simple ! + * return values : + * pointer - OK + * NULL - error */ +static char * +simpleUPnPcommand2(SOCKET s, const char * url, const char * service, + const char * action, struct UPNParg * args, + int * bufsize, const char * httpversion) +{ + char hostname[MAXHOSTNAMELEN+1]; + unsigned short port = 0; + char * path; + char soapact[128]; + char soapbody[2048]; + int soapbodylen; + char * buf; + int n; + int status_code; + + *bufsize = 0; + snprintf(soapact, sizeof(soapact), "%s#%s", service, action); + if(args==NULL) + { + soapbodylen = snprintf(soapbody, sizeof(soapbody), + "\r\n" + "<" SOAPPREFIX ":Envelope " + "xmlns:" SOAPPREFIX "=\"http://schemas.xmlsoap.org/soap/envelope/\" " + SOAPPREFIX ":encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<" SOAPPREFIX ":Body>" + "<" SERVICEPREFIX ":%s xmlns:" SERVICEPREFIX "=\"%s\">" + "" + "" + "\r\n", action, service, action); + if ((unsigned int)soapbodylen >= sizeof(soapbody)) + return NULL; + } + else + { + char * p; + const char * pe, * pv; + const char * const pend = soapbody + sizeof(soapbody); + soapbodylen = snprintf(soapbody, sizeof(soapbody), + "\r\n" + "<" SOAPPREFIX ":Envelope " + "xmlns:" SOAPPREFIX "=\"http://schemas.xmlsoap.org/soap/envelope/\" " + SOAPPREFIX ":encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<" SOAPPREFIX ":Body>" + "<" SERVICEPREFIX ":%s xmlns:" SERVICEPREFIX "=\"%s\">", + action, service); + if ((unsigned int)soapbodylen >= sizeof(soapbody)) + return NULL; + p = soapbody + soapbodylen; + while(args->elt) + { + if(p >= pend) /* check for space to write next byte */ + return NULL; + *(p++) = '<'; + + pe = args->elt; + while(p < pend && *pe) + *(p++) = *(pe++); + + if(p >= pend) /* check for space to write next byte */ + return NULL; + *(p++) = '>'; + + if((pv = args->val)) + { + while(p < pend && *pv) + *(p++) = *(pv++); + } + + if((p+2) > pend) /* check for space to write next 2 bytes */ + return NULL; + *(p++) = '<'; + *(p++) = '/'; + + pe = args->elt; + while(p < pend && *pe) + *(p++) = *(pe++); + + if(p >= pend) /* check for space to write next byte */ + return NULL; + *(p++) = '>'; + + args++; + } + if((p+4) > pend) /* check for space to write next 4 bytes */ + return NULL; + *(p++) = '<'; + *(p++) = '/'; + *(p++) = SERVICEPREFIX2; + *(p++) = ':'; + + pe = action; + while(p < pend && *pe) + *(p++) = *(pe++); + + strncpy(p, ">\r\n", + pend - p); + if(soapbody[sizeof(soapbody)-1]) /* strncpy pads buffer with 0s, so if it doesn't end in 0, could not fit full string */ + return NULL; + } + if(!parseURL(url, hostname, &port, &path, NULL)) return NULL; + if(ISINVALID(s)) { + s = connecthostport(hostname, port, 0); + if(ISINVALID(s)) { + /* failed to connect */ + return NULL; + } + } + + n = soapPostSubmit(s, path, hostname, port, soapact, soapbody, httpversion); + if(n<=0) { +#ifdef DEBUG + printf("Error sending SOAP request\n"); +#endif + closesocket(s); + return NULL; + } + + buf = getHTTPResponse(s, bufsize, &status_code); +#ifdef DEBUG + if(*bufsize > 0 && buf) + { + printf("HTTP %d SOAP Response :\n%.*s\n", status_code, *bufsize, buf); + } + else + { + printf("HTTP %d, empty SOAP response. size=%d\n", status_code, *bufsize); + } +#endif + closesocket(s); + return buf; +} + +/* simpleUPnPcommand : + * not so simple ! + * return values : + * pointer - OK + * NULL - error */ +char * +simpleUPnPcommand(int s, const char * url, const char * service, + const char * action, struct UPNParg * args, + int * bufsize) +{ + char * buf; + +#if 1 + buf = simpleUPnPcommand2((SOCKET)s, url, service, action, args, bufsize, "1.1"); +#else + buf = simpleUPnPcommand2((SOCKET)s, url, service, action, args, bufsize, "1.0"); + if (!buf || *bufsize == 0) + { +#if DEBUG + printf("Error or no result from SOAP request; retrying with HTTP/1.1\n"); +#endif + buf = simpleUPnPcommand2((SOCKET)s, url, service, action, args, bufsize, "1.1"); + } +#endif + return buf; +} + +/* upnpDiscoverDevices() : + * return a chained list of all devices found or NULL if + * no devices was found. + * It is up to the caller to free the chained list + * delay is in millisecond (poll). + * UDA v1.1 says : + * The TTL for the IP packet SHOULD default to 2 and + * SHOULD be configurable. */ +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscoverDevices(const char * const deviceTypes[], + int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error, + int searchalltypes) +{ + struct UPNPDev * tmp; + struct UPNPDev * devlist = 0; +#if !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) + int deviceIndex; +#endif /* !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) */ + + if(error) + *error = UPNPDISCOVER_UNKNOWN_ERROR; +#if !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) + /* first try to get infos from minissdpd ! */ + if(!minissdpdsock) + minissdpdsock = "/var/run/minissdpd.sock"; + if(minissdpdsock[0] != '\0') { + for(deviceIndex = 0; deviceTypes[deviceIndex]; deviceIndex++) { + struct UPNPDev * minissdpd_devlist; + int only_rootdevice = 1; + minissdpd_devlist = getDevicesFromMiniSSDPD(deviceTypes[deviceIndex], + minissdpdsock, 0); + if(minissdpd_devlist) { +#ifdef DEBUG + printf("returned by MiniSSDPD: %s\t%s\n", + minissdpd_devlist->st, minissdpd_devlist->descURL); +#endif /* DEBUG */ + if(!strstr(minissdpd_devlist->st, "rootdevice")) + only_rootdevice = 0; + for(tmp = minissdpd_devlist; tmp->pNext != NULL; tmp = tmp->pNext) { +#ifdef DEBUG + printf("returned by MiniSSDPD: %s\t%s\n", + tmp->pNext->st, tmp->pNext->descURL); +#endif /* DEBUG */ + if(!strstr(tmp->st, "rootdevice")) + only_rootdevice = 0; + } + tmp->pNext = devlist; + devlist = minissdpd_devlist; + if(!searchalltypes && !only_rootdevice) + break; + } + } + } + for(tmp = devlist; tmp != NULL; tmp = tmp->pNext) { + /* We return what we have found if it was not only a rootdevice */ + if(!strstr(tmp->st, "rootdevice")) { + if(error) + *error = UPNPDISCOVER_SUCCESS; + return devlist; + } + } +#else /* !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) */ + (void)minissdpdsock; /* unused */ +#endif /* !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) */ + + /* direct discovery if minissdpd responses are not sufficient */ + { + struct UPNPDev * discovered_devlist; + discovered_devlist = ssdpDiscoverDevices(deviceTypes, delay, multicastif, localport, + ipv6, ttl, error, searchalltypes); + if(devlist == NULL) + devlist = discovered_devlist; + else { + for(tmp = devlist; tmp->pNext != NULL; tmp = tmp->pNext); + tmp->pNext = discovered_devlist; + } + } + return devlist; +} + +/* upnpDiscover() Discover IGD device */ +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscover(int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error) +{ + static const char * const deviceList[] = { +#if 0 + "urn:schemas-upnp-org:device:InternetGatewayDevice:2", + "urn:schemas-upnp-org:service:WANIPConnection:2", +#endif + "urn:schemas-upnp-org:device:InternetGatewayDevice:1", + "urn:schemas-upnp-org:service:WANIPConnection:1", + "urn:schemas-upnp-org:service:WANPPPConnection:1", + "upnp:rootdevice", + /*"ssdp:all",*/ + 0 + }; + return upnpDiscoverDevices(deviceList, + delay, multicastif, minissdpdsock, localport, + ipv6, ttl, error, 0); +} + +/* upnpDiscoverAll() Discover all UPnP devices */ +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscoverAll(int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error) +{ + static const char * const deviceList[] = { + /*"upnp:rootdevice",*/ + "ssdp:all", + 0 + }; + return upnpDiscoverDevices(deviceList, + delay, multicastif, minissdpdsock, localport, + ipv6, ttl, error, 0); +} + +/* upnpDiscoverDevice() Discover a specific device */ +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscoverDevice(const char * device, int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error) +{ + const char * const deviceList[] = { + device, + 0 + }; + return upnpDiscoverDevices(deviceList, + delay, multicastif, minissdpdsock, localport, + ipv6, ttl, error, 0); +} + +static char * +build_absolute_url(const char * baseurl, const char * descURL, + const char * url, unsigned int scope_id) +{ + size_t l, n; + char * s; + const char * base; + char * p; +#if defined(IF_NAMESIZE) && !defined(_WIN32) + char ifname[IF_NAMESIZE]; +#else /* defined(IF_NAMESIZE) && !defined(_WIN32) */ + char scope_str[8]; +#endif /* defined(IF_NAMESIZE) && !defined(_WIN32) */ + + if( (url[0] == 'h') + &&(url[1] == 't') + &&(url[2] == 't') + &&(url[3] == 'p') + &&(url[4] == ':') + &&(url[5] == '/') + &&(url[6] == '/')) + return strdup(url); + base = (baseurl[0] == '\0') ? descURL : baseurl; + n = strlen(base); + if(n > 7) { + p = strchr(base + 7, '/'); + if(p) + n = p - base; + } + l = n + strlen(url) + 1; + if(url[0] != '/') + l++; + if(scope_id != 0) { +#if defined(IF_NAMESIZE) && !defined(_WIN32) + if(if_indextoname(scope_id, ifname)) { + l += 3 + strlen(ifname); /* 3 == strlen(%25) */ + } +#else /* defined(IF_NAMESIZE) && !defined(_WIN32) */ + /* under windows, scope is numerical */ + l += 3 + snprintf(scope_str, sizeof(scope_str), "%u", scope_id); +#endif /* defined(IF_NAMESIZE) && !defined(_WIN32) */ + } + s = malloc(l); + if(s == NULL) return NULL; + memcpy(s, base, n); + if(scope_id != 0) { + s[n] = '\0'; + if(n > 13 && 0 == memcmp(s, "http://[fe80:", 13)) { + /* this is a linklocal IPv6 address */ + p = strchr(s, ']'); + if(p) { + /* insert %25 into URL */ +#if defined(IF_NAMESIZE) && !defined(_WIN32) + memmove(p + 3 + strlen(ifname), p, strlen(p) + 1); + memcpy(p, "%25", 3); + memcpy(p + 3, ifname, strlen(ifname)); + n += 3 + strlen(ifname); +#else /* defined(IF_NAMESIZE) && !defined(_WIN32) */ + memmove(p + 3 + strlen(scope_str), p, strlen(p) + 1); + memcpy(p, "%25", 3); + memcpy(p + 3, scope_str, strlen(scope_str)); + n += 3 + strlen(scope_str); +#endif /* defined(IF_NAMESIZE) && !defined(_WIN32) */ + } + } + } + if(url[0] != '/') + s[n++] = '/'; + memcpy(s + n, url, l - n); + return s; +} + +/* Prepare the Urls for usage... + */ +MINIUPNP_LIBSPEC void +GetUPNPUrls(struct UPNPUrls * urls, struct IGDdatas * data, + const char * descURL, unsigned int scope_id) +{ + /* strdup descURL */ + urls->rootdescURL = strdup(descURL); + + /* get description of WANIPConnection */ + urls->ipcondescURL = build_absolute_url(data->urlbase, descURL, + data->first.scpdurl, scope_id); + urls->controlURL = build_absolute_url(data->urlbase, descURL, + data->first.controlurl, scope_id); + urls->controlURL_CIF = build_absolute_url(data->urlbase, descURL, + data->CIF.controlurl, scope_id); + urls->controlURL_6FC = build_absolute_url(data->urlbase, descURL, + data->IPv6FC.controlurl, scope_id); + +#ifdef DEBUG + printf("urls->ipcondescURL='%s'\n", urls->ipcondescURL); + printf("urls->controlURL='%s'\n", urls->controlURL); + printf("urls->controlURL_CIF='%s'\n", urls->controlURL_CIF); + printf("urls->controlURL_6FC='%s'\n", urls->controlURL_6FC); +#endif +} + +MINIUPNP_LIBSPEC void +FreeUPNPUrls(struct UPNPUrls * urls) +{ + if(!urls) + return; + free(urls->controlURL); + urls->controlURL = 0; + free(urls->ipcondescURL); + urls->ipcondescURL = 0; + free(urls->controlURL_CIF); + urls->controlURL_CIF = 0; + free(urls->controlURL_6FC); + urls->controlURL_6FC = 0; + free(urls->rootdescURL); + urls->rootdescURL = 0; +} + +int +UPNPIGD_IsConnected(struct UPNPUrls * urls, struct IGDdatas * data) +{ + char status[64]; + unsigned int uptime; + status[0] = '\0'; + UPNP_GetStatusInfo(urls->controlURL, data->first.servicetype, + status, &uptime, NULL); + if(0 == strcmp("Connected", status)) + return 1; + else if(0 == strcmp("Up", status)) /* Also accept "Up" */ + return 1; + else + return 0; +} + + +/* UPNP_GetValidIGD() : + * return values : + * -1 = Internal error + * 0 = NO IGD found + * 1 = A valid connected IGD has been found + * 2 = A valid IGD has been found but it reported as + * not connected + * 3 = an UPnP device has been found but was not recognized as an IGD + * + * In any positive non zero return case, the urls and data structures + * passed as parameters are set. Don't forget to call FreeUPNPUrls(urls) to + * free allocated memory. + */ +MINIUPNP_LIBSPEC int +UPNP_GetValidIGD(struct UPNPDev * devlist, + struct UPNPUrls * urls, + struct IGDdatas * data, + char * lanaddr, int lanaddrlen) +{ + struct xml_desc { + char lanaddr[40]; + char * xml; + int size; + int is_igd; + } * desc = NULL; + struct UPNPDev * dev; + int ndev = 0; + int i; + int state = -1; /* state 1 : IGD connected. State 2 : IGD. State 3 : anything */ + char extIpAddr[16]; + int status_code = -1; + + if(!devlist) + { +#ifdef DEBUG + printf("Empty devlist\n"); +#endif + return 0; + } + /* counting total number of devices in the list */ + for(dev = devlist; dev; dev = dev->pNext) + ndev++; + /* ndev is always > 0 */ + desc = calloc(ndev, sizeof(struct xml_desc)); + if(!desc) + return -1; /* memory allocation error */ + /* Step 1 : downloading descriptions and testing type */ + for(dev = devlist, i = 0; dev; dev = dev->pNext, i++) + { + /* we should choose an internet gateway device. + * with st == urn:schemas-upnp-org:device:InternetGatewayDevice:1 */ + desc[i].xml = miniwget_getaddr(dev->descURL, &(desc[i].size), + desc[i].lanaddr, sizeof(desc[i].lanaddr), + dev->scope_id, &status_code); +#ifdef DEBUG + if(!desc[i].xml) + { + printf("error getting XML description %s\n", dev->descURL); + } +#endif + if(desc[i].xml) + { + memset(data, 0, sizeof(struct IGDdatas)); + memset(urls, 0, sizeof(struct UPNPUrls)); + parserootdesc(desc[i].xml, desc[i].size, data); + if(COMPARE(data->CIF.servicetype, + "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:")) + { + desc[i].is_igd = 1; + } + } + } + /* iterate the list to find a device depending on state */ + for(state = 1; state <= 3; state++) + { + for(dev = devlist, i = 0; dev; dev = dev->pNext, i++) + { + if(desc[i].xml) + { + memset(data, 0, sizeof(struct IGDdatas)); + memset(urls, 0, sizeof(struct UPNPUrls)); + parserootdesc(desc[i].xml, desc[i].size, data); + if(desc[i].is_igd || state >= 3 ) + { + int is_connected; + + GetUPNPUrls(urls, data, dev->descURL, dev->scope_id); + + /* in state 2 and 3 we don't test if device is connected ! */ + if(state >= 2) + goto free_and_return; + is_connected = UPNPIGD_IsConnected(urls, data); +#ifdef DEBUG + printf("UPNPIGD_IsConnected(%s) = %d\n", + urls->controlURL, is_connected); +#endif + /* checks that status is connected AND there is a external IP address assigned */ + if(is_connected && + (UPNP_GetExternalIPAddress(urls->controlURL, data->first.servicetype, extIpAddr) == 0)) { + if(!addr_is_reserved(extIpAddr)) + goto free_and_return; + } + FreeUPNPUrls(urls); + if(data->second.servicetype[0] != '\0') { +#ifdef DEBUG + printf("We tried %s, now we try %s !\n", + data->first.servicetype, data->second.servicetype); +#endif + /* swaping WANPPPConnection and WANIPConnection ! */ + memcpy(&data->tmp, &data->first, sizeof(struct IGDdatas_service)); + memcpy(&data->first, &data->second, sizeof(struct IGDdatas_service)); + memcpy(&data->second, &data->tmp, sizeof(struct IGDdatas_service)); + GetUPNPUrls(urls, data, dev->descURL, dev->scope_id); + is_connected = UPNPIGD_IsConnected(urls, data); +#ifdef DEBUG + printf("UPNPIGD_IsConnected(%s) = %d\n", + urls->controlURL, is_connected); +#endif + if(is_connected && + (UPNP_GetExternalIPAddress(urls->controlURL, data->first.servicetype, extIpAddr) == 0)) { + if(!addr_is_reserved(extIpAddr)) + goto free_and_return; + } + FreeUPNPUrls(urls); + } + } + memset(data, 0, sizeof(struct IGDdatas)); + } + } + } + state = 0; +free_and_return: + if (lanaddr != NULL && state >= 1 && state <= 3 && i < ndev) + strncpy(lanaddr, desc[i].lanaddr, lanaddrlen); + for(i = 0; i < ndev; i++) + free(desc[i].xml); + free(desc); + return state; +} + +/* UPNP_GetIGDFromUrl() + * Used when skipping the discovery process. + * return value : + * 0 - Not ok + * 1 - OK */ +int +UPNP_GetIGDFromUrl(const char * rootdescurl, + struct UPNPUrls * urls, + struct IGDdatas * data, + char * lanaddr, int lanaddrlen) +{ + char * descXML; + int descXMLsize = 0; + + descXML = miniwget_getaddr(rootdescurl, &descXMLsize, + lanaddr, lanaddrlen, 0, NULL); + if(descXML) { + memset(data, 0, sizeof(struct IGDdatas)); + memset(urls, 0, sizeof(struct UPNPUrls)); + parserootdesc(descXML, descXMLsize, data); + free(descXML); + GetUPNPUrls(urls, data, rootdescurl, 0); + return 1; + } else { + return 0; + } +} diff --git a/libs/miniupnpc/src/miniupnpc_socketdef.h b/libs/miniupnpc/src/miniupnpc_socketdef.h new file mode 100644 index 000000000..2d5cac5c2 --- /dev/null +++ b/libs/miniupnpc/src/miniupnpc_socketdef.h @@ -0,0 +1,44 @@ +/* $Id: miniupnpc_socketdef.h,v 1.4 2021/03/02 23:35:29 nanard Exp $ */ +/* Miniupnp project : http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ + * Author : Thomas Bernard + * Copyright (c) 2018 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided within this distribution */ +#ifndef MINIUPNPC_SOCKETDEF_H_INCLUDED +#define MINIUPNPC_SOCKETDEF_H_INCLUDED + +#ifdef _WIN32 + +#define ISINVALID(s) (INVALID_SOCKET==(s)) + +#else + +#ifndef SOCKET +#define SOCKET int +#endif +#ifndef SSIZE_T +#define SSIZE_T ssize_t +#endif +#ifndef INVALID_SOCKET +#define INVALID_SOCKET (-1) +#endif +#ifndef ISINVALID +#define ISINVALID(s) ((s)<0) +#endif + +#endif + +#ifdef _MSC_VER +#define MSC_CAST_INT (int) +#else +#define MSC_CAST_INT +#endif + +/* definition of PRINT_SOCKET_ERROR */ +#ifdef _WIN32 +#define PRINT_SOCKET_ERROR(x) fprintf(stderr, "Socket error: %s, %d\n", x, WSAGetLastError()); +#else +#define PRINT_SOCKET_ERROR(x) perror(x) +#endif + +#endif /* MINIUPNPC_SOCKETDEF_H_INCLUDED */ diff --git a/libs/miniupnpc/miniupnpcmodule.c b/libs/miniupnpc/src/miniupnpcmodule.c similarity index 61% rename from libs/miniupnpc/miniupnpcmodule.c rename to libs/miniupnpc/src/miniupnpcmodule.c index 08a61d22f..66a267073 100644 --- a/libs/miniupnpc/miniupnpcmodule.c +++ b/libs/miniupnpc/src/miniupnpcmodule.c @@ -1,17 +1,22 @@ -/* $Id: miniupnpcmodule.c,v 1.18 2011/04/10 11:21:23 nanard Exp $*/ -/* Project : miniupnp +/* $Id: miniupnpcmodule.c,v 1.38 2021/11/09 18:46:49 nanard Exp $*/ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * Project : miniupnp * Author : Thomas BERNARD - * website : http://miniupnp.tuxfamily.org/ - * copyright (c) 2007-2009 Thomas Bernard + * website : https://miniupnp.tuxfamily.org/ + * copyright (c) 2007-2021 Thomas Bernard * This software is subjet to the conditions detailed in the * provided LICENCE file. */ #include -#define STATICLIB -#include "structmember.h" +#define MINIUPNP_STATICLIB +#include #include "miniupnpc.h" #include "upnpcommands.h" #include "upnperrors.h" +#ifdef _WIN32 +#include +#endif + /* for compatibility with Python < 2.4 */ #ifndef Py_RETURN_NONE #define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None @@ -25,6 +30,16 @@ #define Py_RETURN_FALSE return Py_INCREF(Py_False), Py_False #endif +/* for compatibility with Python < 3.0 */ +#ifndef PyVarObject_HEAD_INIT +#define PyVarObject_HEAD_INIT(type, size) \ + PyObject_HEAD_INIT(type) size, +#endif + +#ifndef Py_TYPE +#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type) +#endif + typedef struct { PyObject_HEAD /* Type-specific fields go here. */ @@ -32,6 +47,7 @@ typedef struct { struct UPNPUrls urls; struct IGDdatas data; unsigned int discoverdelay; /* value passed to upnpDiscover() */ + unsigned int localport; /* value passed to upnpDiscover() */ char lanaddr[40]; /* our ip address on the LAN */ char * multicastif; char * minissdpdsocket; @@ -44,57 +60,115 @@ static PyMemberDef UPnP_members[] = { {"discoverdelay", T_UINT, offsetof(UPnPObject, discoverdelay), 0/*READWRITE*/, "value in ms used to wait for SSDP responses" }, - /* T_STRING is allways readonly :( */ + {"localport", T_UINT, offsetof(UPnPObject, localport), + 0/*READWRITE*/, + "If localport is set to UPNP_LOCAL_PORT_SAME(1) " + "SSDP packets will be sent from the source port " + "1900 (same as destination port), if set to " + "UPNP_LOCAL_PORT_ANY(0) system assign a source " + "port, any other value will be attempted as the " + "source port" + }, + /* T_STRING is always readonly :( */ {"multicastif", T_STRING, offsetof(UPnPObject, multicastif), 0, "IP of the network interface to be used for multicast operations" }, - {"minissdpdsocket", T_STRING, offsetof(UPnPObject, multicastif), + {"minissdpdsocket", T_STRING, offsetof(UPnPObject, minissdpdsocket), 0, "path of the MiniSSDPd unix socket" }, {NULL} }; + +static int UPnP_init(UPnPObject *self, PyObject *args, PyObject *kwds) +{ + char* multicastif = NULL; + char* minissdpdsocket = NULL; + static char *kwlist[] = { + "multicastif", "minissdpdsocket", "discoverdelay", + "localport", NULL + }; + + if(!PyArg_ParseTupleAndKeywords(args, kwds, "|zzII", kwlist, + &multicastif, + &minissdpdsocket, + &self->discoverdelay, + &self->localport)) + return -1; + + if(self->localport>1 && + (self->localport>65534||self->localport<1024)) { + PyErr_SetString(PyExc_Exception, "Invalid localport value"); + return -1; + } + if(multicastif) + self->multicastif = strdup(multicastif); + if(minissdpdsocket) + self->minissdpdsocket = strdup(minissdpdsocket); + + return 0; +} + static void UPnPObject_dealloc(UPnPObject *self) { freeUPNPDevlist(self->devlist); FreeUPNPUrls(&self->urls); - self->ob_type->tp_free((PyObject*)self); + free(self->multicastif); + free(self->minissdpdsocket); + Py_TYPE(self)->tp_free((PyObject*)self); } static PyObject * UPnP_discover(UPnPObject *self) { - struct UPNPDev * dev; - int i; + int error = 0; PyObject *res = NULL; + if(self->devlist) { freeUPNPDevlist(self->devlist); self->devlist = 0; - } + } Py_BEGIN_ALLOW_THREADS self->devlist = upnpDiscover((int)self->discoverdelay/*timeout in ms*/, - 0/* multicast if*/, - 0/*minissdpd socket*/, - 0/*sameport flag*/, + self->multicastif, + self->minissdpdsocket, + (int)self->localport, 0/*ip v6*/, - 0/*error */); + 2/* TTL */, + &error); Py_END_ALLOW_THREADS /* Py_RETURN_NONE ??? */ - for(dev = self->devlist, i = 0; dev; dev = dev->pNext) - i++; - res = Py_BuildValue("i", i); - return res; + if (self->devlist != NULL) { + struct UPNPDev * dev; + int i = 0; + + for(dev = self->devlist; dev; dev = dev->pNext) + i++; + res = Py_BuildValue("i", i); + return res; + } else { + PyErr_SetString(PyExc_Exception, strupnperror(error)); + return NULL; + } } static PyObject * -UPnP_selectigd(UPnPObject *self) +UPnP_selectigd(UPnPObject *self, PyObject *args) { + const char * rootDescUrl = NULL; int r; + if(!PyArg_ParseTuple(args, "|z", &rootDescUrl)) + return NULL; Py_BEGIN_ALLOW_THREADS - r = UPNP_GetValidIGD(self->devlist, &self->urls, &self->data, - self->lanaddr, sizeof(self->lanaddr)); + if (rootDescUrl == NULL) { + r = UPNP_GetValidIGD(self->devlist, &self->urls, &self->data, + self->lanaddr, sizeof(self->lanaddr)); + } else { + r = UPNP_GetIGDFromUrl(rootDescUrl, &self->urls, &self->data, + self->lanaddr, sizeof(self->lanaddr)); + } Py_END_ALLOW_THREADS if(r) { @@ -116,7 +190,11 @@ Py_BEGIN_ALLOW_THREADS i = UPNP_GetTotalBytesSent(self->urls.controlURL_CIF, self->data.CIF.servicetype); Py_END_ALLOW_THREADS +#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION > 3) return Py_BuildValue("I", i); +#else + return Py_BuildValue("i", (int)i); +#endif } static PyObject * @@ -127,7 +205,11 @@ Py_BEGIN_ALLOW_THREADS i = UPNP_GetTotalBytesReceived(self->urls.controlURL_CIF, self->data.CIF.servicetype); Py_END_ALLOW_THREADS +#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION > 3) return Py_BuildValue("I", i); +#else + return Py_BuildValue("i", (int)i); +#endif } static PyObject * @@ -138,7 +220,11 @@ Py_BEGIN_ALLOW_THREADS i = UPNP_GetTotalPacketsSent(self->urls.controlURL_CIF, self->data.CIF.servicetype); Py_END_ALLOW_THREADS +#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION > 3) return Py_BuildValue("I", i); +#else + return Py_BuildValue("i", (int)i); +#endif } static PyObject * @@ -149,7 +235,11 @@ Py_BEGIN_ALLOW_THREADS i = UPNP_GetTotalPacketsReceived(self->urls.controlURL_CIF, self->data.CIF.servicetype); Py_END_ALLOW_THREADS +#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION > 3) return Py_BuildValue("I", i); +#else + return Py_BuildValue("i", (int)i); +#endif } static PyObject * @@ -166,7 +256,11 @@ Py_BEGIN_ALLOW_THREADS status, &uptime, lastconnerror); Py_END_ALLOW_THREADS if(r==UPNPCOMMAND_SUCCESS) { +#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION > 3) return Py_BuildValue("(s,I,s)", status, uptime, lastconnerror); +#else + return Py_BuildValue("(s,i,s)", status, (int)uptime, lastconnerror); +#endif } else { /* TODO: have our own exception type ! */ PyErr_SetString(PyExc_Exception, strupnperror(r)); @@ -215,7 +309,7 @@ Py_END_ALLOW_THREADS } /* AddPortMapping(externalPort, protocol, internalHost, internalPort, desc, - * remoteHost) + * remoteHost, leaseDuration) * protocol is 'UDP' or 'TCP' */ static PyObject * UPnP_addportmapping(UPnPObject *self, PyObject *args) @@ -228,17 +322,24 @@ UPnP_addportmapping(UPnPObject *self, PyObject *args) const char * host; const char * desc; const char * remoteHost; - const char * leaseDuration = "0"; + unsigned int intLeaseDuration = 0; + char strLeaseDuration[12]; int r; - if (!PyArg_ParseTuple(args, "HssHss", &ePort, &proto, - &host, &iPort, &desc, &remoteHost)) +#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION > 3) + if (!PyArg_ParseTuple(args, "HssHzz|I", &ePort, &proto, + &host, &iPort, &desc, &remoteHost, &intLeaseDuration)) +#else + if (!PyArg_ParseTuple(args, "HssHzz|i", &ePort, &proto, + &host, &iPort, &desc, &remoteHost, (int *)&intLeaseDuration)) +#endif return NULL; Py_BEGIN_ALLOW_THREADS sprintf(extPort, "%hu", ePort); sprintf(inPort, "%hu", iPort); + sprintf(strLeaseDuration, "%u", intLeaseDuration); r = UPNP_AddPortMapping(self->urls.controlURL, self->data.first.servicetype, extPort, inPort, host, desc, proto, - remoteHost, leaseDuration); + remoteHost, strLeaseDuration); Py_END_ALLOW_THREADS if(r==UPNPCOMMAND_SUCCESS) { @@ -255,6 +356,42 @@ Py_END_ALLOW_THREADS } } +/* AddAnyPortMapping(externalPort, protocol, internalHost, internalPort, desc, + * remoteHost) + * protocol is 'UDP' or 'TCP' */ +static PyObject * +UPnP_addanyportmapping(UPnPObject *self, PyObject *args) +{ + char extPort[6]; + unsigned short ePort; + char inPort[6]; + unsigned short iPort; + char reservedPort[6]; + const char * proto; + const char * host; + const char * desc; + const char * remoteHost; + const char * leaseDuration = "0"; + int r; + if (!PyArg_ParseTuple(args, "HssHzz", &ePort, &proto, &host, &iPort, &desc, &remoteHost)) + return NULL; +Py_BEGIN_ALLOW_THREADS + sprintf(extPort, "%hu", ePort); + sprintf(inPort, "%hu", iPort); + r = UPNP_AddAnyPortMapping(self->urls.controlURL, self->data.first.servicetype, + extPort, inPort, host, desc, proto, + remoteHost, leaseDuration, reservedPort); +Py_END_ALLOW_THREADS + if(r==UPNPCOMMAND_SUCCESS) { + return Py_BuildValue("i", atoi(reservedPort)); + } else { + /* TODO: have our own exception type ! */ + PyErr_SetString(PyExc_Exception, strupnperror(r)); + return NULL; + } +} + + /* DeletePortMapping(extPort, proto, removeHost='') * proto = 'UDP', 'TCP' */ static PyObject * @@ -281,6 +418,37 @@ Py_END_ALLOW_THREADS } } +/* DeletePortMappingRange(extPort, proto, removeHost='') + * proto = 'UDP', 'TCP' */ +static PyObject * +UPnP_deleteportmappingrange(UPnPObject *self, PyObject *args) +{ + char extPortStart[6]; + unsigned short ePortStart; + char extPortEnd[6]; + unsigned short ePortEnd; + const char * proto; + unsigned char manage; + char manageStr[6]; + int r; + if(!PyArg_ParseTuple(args, "HHsb", &ePortStart, &ePortEnd, &proto, &manage)) + return NULL; +Py_BEGIN_ALLOW_THREADS + sprintf(extPortStart, "%hu", ePortStart); + sprintf(extPortEnd, "%hu", ePortEnd); + sprintf(manageStr, "%hu", (unsigned short)manage); + r = UPNP_DeletePortMappingRange(self->urls.controlURL, self->data.first.servicetype, + extPortStart, extPortEnd, proto, manageStr); +Py_END_ALLOW_THREADS + if(r==UPNPCOMMAND_SUCCESS) { + Py_RETURN_TRUE; + } else { + /* TODO: have our own exception type ! */ + PyErr_SetString(PyExc_Exception, strupnperror(r)); + return NULL; + } +} + static PyObject * UPnP_getportmappingnumberofentries(UPnPObject *self) { @@ -292,7 +460,11 @@ Py_BEGIN_ALLOW_THREADS &n); Py_END_ALLOW_THREADS if(r==UPNPCOMMAND_SUCCESS) { +#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION > 3) return Py_BuildValue("I", n); +#else + return Py_BuildValue("i", (int)n); +#endif } else { /* TODO: have our own exception type ! */ PyErr_SetString(PyExc_Exception, strupnperror(r)); @@ -300,7 +472,7 @@ Py_END_ALLOW_THREADS } } -/* GetSpecificPortMapping(ePort, proto) +/* GetSpecificPortMapping(ePort, proto, remoteHost='') * proto = 'UDP' or 'TCP' */ static PyObject * UPnP_getspecificportmapping(UPnPObject *self, PyObject *args) @@ -308,13 +480,14 @@ UPnP_getspecificportmapping(UPnPObject *self, PyObject *args) char extPort[6]; unsigned short ePort; const char * proto; + const char * remoteHost = ""; char intClient[40]; char intPort[6]; unsigned short iPort; char desc[80]; char enabled[4]; char leaseDuration[16]; - if(!PyArg_ParseTuple(args, "Hs", &ePort, &proto)) + if(!PyArg_ParseTuple(args, "Hs|z", &ePort, &proto, &remoteHost)) return NULL; extPort[0] = '\0'; intClient[0] = '\0'; intPort[0] = '\0'; desc[0] = '\0'; enabled[0] = '\0'; leaseDuration[0] = '\0'; @@ -322,7 +495,7 @@ Py_BEGIN_ALLOW_THREADS sprintf(extPort, "%hu", ePort); UPNP_GetSpecificPortMappingEntry(self->urls.controlURL, self->data.first.servicetype, - extPort, proto, + extPort, proto, remoteHost, intClient, intPort, desc, enabled, leaseDuration); Py_END_ALLOW_THREADS @@ -376,9 +549,15 @@ Py_END_ALLOW_THREADS ePort = (unsigned short)atoi(extPort); iPort = (unsigned short)atoi(intPort); dur = (unsigned int)strtoul(duration, 0, 0); +#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION > 3) return Py_BuildValue("(H,s,(s,H),s,s,s,I)", ePort, protocol, intClient, iPort, desc, enabled, rHost, dur); +#else + return Py_BuildValue("(i,s,(s,i),s,s,s,i)", + (int)ePort, protocol, intClient, (int)iPort, + desc, enabled, rHost, (int)dur); +#endif } else { @@ -391,7 +570,7 @@ static PyMethodDef UPnP_methods[] = { {"discover", (PyCFunction)UPnP_discover, METH_NOARGS, "discover UPnP IGD devices on the network" }, - {"selectigd", (PyCFunction)UPnP_selectigd, METH_NOARGS, + {"selectigd", (PyCFunction)UPnP_selectigd, METH_VARARGS, "select a valid UPnP IGD among discovered devices" }, {"totalbytesent", (PyCFunction)UPnP_totalbytesent, METH_NOARGS, @@ -418,9 +597,15 @@ static PyMethodDef UPnP_methods[] = { {"addportmapping", (PyCFunction)UPnP_addportmapping, METH_VARARGS, "add a port mapping" }, + {"addanyportmapping", (PyCFunction)UPnP_addanyportmapping, METH_VARARGS, + "add a port mapping, IGD to select alternative if necessary" + }, {"deleteportmapping", (PyCFunction)UPnP_deleteportmapping, METH_VARARGS, "delete a port mapping" }, + {"deleteportmappingrange", (PyCFunction)UPnP_deleteportmappingrange, METH_VARARGS, + "delete a range of port mappings" + }, {"getportmappingnumberofentries", (PyCFunction)UPnP_getportmappingnumberofentries, METH_NOARGS, "-- non standard --" }, @@ -434,8 +619,8 @@ static PyMethodDef UPnP_methods[] = { }; static PyTypeObject UPnPType = { - PyObject_HEAD_INIT(NULL) - 0, /*ob_size*/ + PyVarObject_HEAD_INIT(NULL, + 0) /*ob_size*/ "miniupnpc.UPnP", /*tp_name*/ sizeof(UPnPObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ @@ -470,13 +655,14 @@ static PyTypeObject UPnPType = { 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ - 0,/*(initproc)UPnP_init,*/ /* tp_init */ + (initproc)UPnP_init, /* tp_init */ 0, /* tp_alloc */ -#ifndef WIN32 +#ifndef _WIN32 PyType_GenericNew,/*UPnP_new,*/ /* tp_new */ #else - 0, + 0, /* tp_new */ #endif + 0, /* tp_free */ }; /* module methods */ @@ -484,24 +670,69 @@ static PyMethodDef miniupnpc_methods[] = { {NULL} /* Sentinel */ }; +#if PY_MAJOR_VERSION >= 3 +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "miniupnpc", /* m_name */ + "miniupnpc module.", /* m_doc */ + -1, /* m_size */ + miniupnpc_methods, /* m_methods */ + NULL, /* m_reload */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL, /* m_free */ +}; +#endif + #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ #define PyMODINIT_FUNC void #endif + PyMODINIT_FUNC -initminiupnpc(void) +#if PY_MAJOR_VERSION >= 3 +PyInit_miniupnpc(void) +#else +initminiupnpc(void) +#endif { PyObject* m; -#ifdef WIN32 +#ifdef _WIN32 + /* initialize Winsock. */ + WSADATA wsaData; + int nResult = WSAStartup(MAKEWORD(2,2), &wsaData); + if (nResult != 0) + { + /* error code could be WSASYSNOTREADY WSASYSNOTREADY + * WSASYSNOTREADY WSASYSNOTREADY WSASYSNOTREADY */ +#if PY_MAJOR_VERSION >= 3 + return 0; +#else + return; +#endif + } + UPnPType.tp_new = PyType_GenericNew; #endif if (PyType_Ready(&UPnPType) < 0) +#if PY_MAJOR_VERSION >= 3 + return 0; +#else return; +#endif +#if PY_MAJOR_VERSION >= 3 + m = PyModule_Create(&moduledef); +#else m = Py_InitModule3("miniupnpc", miniupnpc_methods, "miniupnpc module."); +#endif Py_INCREF(&UPnPType); PyModule_AddObject(m, "UPnP", (PyObject *)&UPnPType); + +#if PY_MAJOR_VERSION >= 3 + return m; +#endif } diff --git a/libs/miniupnpc/miniwget.c b/libs/miniupnpc/src/miniwget.c similarity index 50% rename from libs/miniupnpc/miniwget.c rename to libs/miniupnpc/src/miniwget.c index 7c9cb3ad5..19c683e64 100644 --- a/libs/miniupnpc/miniwget.c +++ b/libs/miniupnpc/src/miniwget.c @@ -1,21 +1,21 @@ -/* $Id: miniwget.c,v 1.52 2011/06/17 22:59:42 nanard Exp $ */ +/* $Id: miniwget.c,v 1.84 2020/11/09 19:41:18 nanard Exp $ */ /* Project : miniupnp + * Website : http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * Author : Thomas Bernard - * Copyright (c) 2005-2011 Thomas Bernard + * Copyright (c) 2005-2020 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. */ - + #include #include #include #include -#ifdef WIN32 +#ifdef _WIN32 #include #include #include #define MAXHOSTNAMELEN 64 -#define MIN(x,y) (((x)<(y))?(x):(y)) -#define snprintf _snprintf +#include "win32_snprintf.h" #define socklen_t int #ifndef strncasecmp #if defined(_MSC_VER) && (_MSC_VER >= 1400) @@ -24,7 +24,7 @@ #define strncasecmp memicmp #endif /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ #endif /* #ifndef strncasecmp */ -#else /* #ifdef WIN32 */ +#else /* #ifdef _WIN32 */ #include #include #if defined(__amigaos__) && !defined(__amigaos4__) @@ -33,22 +33,31 @@ #include #endif /* #else defined(__amigaos__) && !defined(__amigaos4__) */ #include +#include #include +#include #include #define closesocket close -/* defining MINIUPNPC_IGNORE_EINTR enable the ignore of interruptions - * during the connect() call */ -#define MINIUPNPC_IGNORE_EINTR -#endif /* #else WIN32 */ -#if defined(__sun) || defined(sun) +#include +#endif /* #else _WIN32 */ +#ifdef __GNU__ +#define MAXHOSTNAMELEN 64 +#endif /* __GNU__ */ + +#ifndef MIN #define MIN(x,y) (((x)<(y))?(x):(y)) -#endif +#endif /* MIN */ + #include "miniupnpcstrings.h" #include "miniwget.h" #include "connecthostport.h" #include "receivedata.h" +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 64 +#endif + /* * Read a HTTP response from a socket. * Process Content-Length and Transfer-encoding headers. @@ -56,7 +65,7 @@ * to the length parameter. */ void * -getHTTPResponse(int s, int * size) +getHTTPResponse(SOCKET s, int * size, int * status_code) { char buf[2048]; int n; @@ -67,20 +76,42 @@ getHTTPResponse(int s, int * size) unsigned int bytestocopy = 0; /* buffers : */ char * header_buf; - int header_buf_len = 2048; - int header_buf_used = 0; + unsigned int header_buf_len = 2048; + unsigned int header_buf_used = 0; char * content_buf; - int content_buf_len = 2048; - int content_buf_used = 0; + unsigned int content_buf_len = 2048; + unsigned int content_buf_used = 0; char chunksize_buf[32]; - int chunksize_buf_index; + unsigned int chunksize_buf_index; +#ifdef DEBUG + char * reason_phrase = NULL; + int reason_phrase_len = 0; +#endif + if(status_code) *status_code = -1; header_buf = malloc(header_buf_len); + if(header_buf == NULL) + { +#ifdef DEBUG + fprintf(stderr, "%s: Memory allocation error\n", "getHTTPResponse"); +#endif /* DEBUG */ + *size = -1; + return NULL; + } content_buf = malloc(content_buf_len); + if(content_buf == NULL) + { + free(header_buf); +#ifdef DEBUG + fprintf(stderr, "%s: Memory allocation error\n", "getHTTPResponse"); +#endif /* DEBUG */ + *size = -1; + return NULL; + } chunksize_buf[0] = '\0'; chunksize_buf_index = 0; - while((n = receivedata(s, buf, 2048, 5000)) > 0) + while((n = receivedata(s, buf, sizeof(buf), 5000, NULL)) > 0) { if(endofheaders == 0) { @@ -89,7 +120,15 @@ getHTTPResponse(int s, int * size) int colon=0; int valuestart=0; if(header_buf_used + n > header_buf_len) { - header_buf = realloc(header_buf, header_buf_used + n); + char * tmp = realloc(header_buf, header_buf_used + n); + if(tmp == NULL) { + /* memory allocation error */ + free(header_buf); + free(content_buf); + *size = -1; + return NULL; + } + header_buf = tmp; header_buf_len = header_buf_used + n; } memcpy(header_buf + header_buf_used, buf, n); @@ -97,14 +136,14 @@ getHTTPResponse(int s, int * size) /* search for CR LF CR LF (end of headers) * recognize also LF LF */ i = 0; - while(i < (header_buf_used-1) && (endofheaders == 0)) { + while(i < ((int)header_buf_used-1) && (endofheaders == 0)) { if(header_buf[i] == '\r') { i++; if(header_buf[i] == '\n') { i++; - if(i < header_buf_used && header_buf[i] == '\r') { + if(i < (int)header_buf_used && header_buf[i] == '\r') { i++; - if(i < header_buf_used && header_buf[i] == '\n') { + if(i < (int)header_buf_used && header_buf[i] == '\n') { endofheaders = i+1; } } @@ -121,7 +160,7 @@ getHTTPResponse(int s, int * size) continue; /* parse header lines */ for(i = 0; i < endofheaders - 1; i++) { - if(colon <= linestart && header_buf[i]==':') + if(linestart > 0 && colon <= linestart && header_buf[i]==':') { colon = i; while(i < (endofheaders-1) @@ -132,7 +171,34 @@ getHTTPResponse(int s, int * size) /* detecting end of line */ else if(header_buf[i]=='\r' || header_buf[i]=='\n') { - if(colon > linestart && valuestart > colon) + if(linestart == 0 && status_code) + { + /* Status line + * HTTP-Version SP Status-Code SP Reason-Phrase CRLF */ + int sp; + for(sp = 0; sp < i - 1; sp++) + if(header_buf[sp] == ' ') + { + if(*status_code < 0) + { + if (header_buf[sp+1] >= '1' && header_buf[sp+1] <= '9') + *status_code = atoi(header_buf + sp + 1); + } + else + { +#ifdef DEBUG + reason_phrase = header_buf + sp + 1; + reason_phrase_len = i - sp - 1; +#endif + break; + } + } +#ifdef DEBUG + printf("HTTP status code = %d, Reason phrase = %.*s\n", + *status_code, reason_phrase_len, reason_phrase); +#endif + } + else if(colon > linestart && valuestart > colon) { #ifdef DEBUG printf("header='%.*s', value='%.*s'\n", @@ -155,113 +221,130 @@ getHTTPResponse(int s, int * size) chunked = 1; } } - while(header_buf[i]=='\r' || header_buf[i] == '\n') + while((i < (int)header_buf_used) && (header_buf[i]=='\r' || header_buf[i] == '\n')) i++; linestart = i; colon = linestart; valuestart = 0; - } + } } /* copy the remaining of the received data back to buf */ n = header_buf_used - endofheaders; memcpy(buf, header_buf + endofheaders, n); /* if(headers) */ } - if(endofheaders) + /* if we get there, endofheaders != 0. + * In the other case, there was a continue above */ + /* content */ + if(chunked) { - /* content */ - if(chunked) + int i = 0; + while(i < n) { - int i = 0; - while(i < n) + if(chunksize == 0) { + /* reading chunk size */ + if(chunksize_buf_index == 0) { + /* skipping any leading CR LF */ + if(buf[i] == '\r') i++; + if(i= '0' + && chunksize_buf[j] <= '9') + chunksize = (chunksize << 4) + (chunksize_buf[j] - '0'); + else + chunksize = (chunksize << 4) + ((chunksize_buf[j] | 32) - 'a' + 10); + } + chunksize_buf[0] = '\0'; + chunksize_buf_index = 0; + i++; + } else { + /* not finished to get chunksize */ + continue; + } +#ifdef DEBUG + printf("chunksize = %u (%x)\n", chunksize, chunksize); +#endif if(chunksize == 0) { - /* reading chunk size */ - if(chunksize_buf_index == 0) { - /* skipping any leading CR LF */ - if(i= '0' - && chunksize_buf[j] <= '9') - chunksize = (chunksize << 4) + (chunksize_buf[j] - '0'); - else - chunksize = (chunksize << 4) + ((chunksize_buf[j] | 32) - 'a' + 10); - } - chunksize_buf[0] = '\0'; - chunksize_buf_index = 0; - i++; - } else { - /* not finished to get chunksize */ - continue; - } #ifdef DEBUG - printf("chunksize = %u (%x)\n", chunksize, chunksize); + printf("end of HTTP content - %d %d\n", i, n); + /*printf("'%.*s'\n", n-i, buf+i);*/ #endif - if(chunksize == 0) - { -#ifdef DEBUG - printf("end of HTTP content - %d %d\n", i, n); - /*printf("'%.*s'\n", n-i, buf+i);*/ -#endif - goto end_of_stream; - } + goto end_of_stream; } - bytestocopy = ((int)chunksize < n - i)?chunksize:(n - i); - if((int)(content_buf_used + bytestocopy) > content_buf_len) - { - if(content_length >= content_buf_used + (int)bytestocopy) { - content_buf_len = content_length; - } else { - content_buf_len = content_buf_used + (int)bytestocopy; - } - content_buf = (char *)realloc((void *)content_buf, - content_buf_len); - } - memcpy(content_buf + content_buf_used, buf + i, bytestocopy); - content_buf_used += bytestocopy; - i += bytestocopy; - chunksize -= bytestocopy; } - } - else - { - /* not chunked */ - if(content_length > 0 - && (content_buf_used + n) > content_length) { - /* skipping additional bytes */ - n = content_length - content_buf_used; - } - if(content_buf_used + n > content_buf_len) + /* it is guaranteed that (n >= i) */ + bytestocopy = (chunksize < (unsigned int)(n - i))?chunksize:(unsigned int)(n - i); + if((content_buf_used + bytestocopy) > content_buf_len) { - if(content_length >= content_buf_used + n) { + char * tmp; + if((content_length >= 0) && ((unsigned int)content_length >= (content_buf_used + bytestocopy))) { content_buf_len = content_length; } else { - content_buf_len = content_buf_used + n; + content_buf_len = content_buf_used + bytestocopy; } - content_buf = (char *)realloc((void *)content_buf, - content_buf_len); + tmp = realloc(content_buf, content_buf_len); + if(tmp == NULL) { + /* memory allocation error */ + free(content_buf); + free(header_buf); + *size = -1; + return NULL; + } + content_buf = tmp; } - memcpy(content_buf + content_buf_used, buf, n); - content_buf_used += n; + memcpy(content_buf + content_buf_used, buf + i, bytestocopy); + content_buf_used += bytestocopy; + i += bytestocopy; + chunksize -= bytestocopy; } } + else + { + /* not chunked */ + if(content_length > 0 + && (content_buf_used + n) > (unsigned int)content_length) { + /* skipping additional bytes */ + n = content_length - content_buf_used; + } + if(content_buf_used + n > content_buf_len) + { + char * tmp; + if(content_length >= 0 + && (unsigned int)content_length >= (content_buf_used + n)) { + content_buf_len = content_length; + } else { + content_buf_len = content_buf_used + n; + } + tmp = realloc(content_buf, content_buf_len); + if(tmp == NULL) { + /* memory allocation error */ + free(content_buf); + free(header_buf); + *size = -1; + return NULL; + } + content_buf = tmp; + } + memcpy(content_buf + content_buf_used, buf, n); + content_buf_used += n; + } /* use the Content-Length header value if available */ - if(content_length > 0 && content_buf_used >= content_length) + if(content_length > 0 && content_buf_used >= (unsigned int)content_length) { #ifdef DEBUG printf("End of HTTP content\n"); @@ -270,7 +353,7 @@ getHTTPResponse(int s, int * size) } } end_of_stream: - free(header_buf); header_buf = NULL; + free(header_buf); *size = content_buf_used; if(content_buf_used == 0) { @@ -284,21 +367,22 @@ end_of_stream: * do all the work. * Return NULL if something failed. */ static void * -miniwget3(const char * url, const char * host, +miniwget3(const char * host, unsigned short port, const char * path, int * size, char * addr_str, int addr_str_len, - const char * httpversion) + const char * httpversion, unsigned int scope_id, + int * status_code) { char buf[2048]; - int s; - int n = -1; + SOCKET s; + int n; int len; int sent; void * content; *size = 0; - s = connecthostport(host, port); - if(s < 0) + s = connecthostport(host, port, scope_id); + if(ISINVALID(s)) return NULL; /* get address for caller ! */ @@ -336,16 +420,14 @@ miniwget3(const char * url, const char * host, addr_str, addr_str_len); } #endif -#ifndef NO_GETADDRINFO /* getnameinfo return ip v6 address with the scope identifier * such as : 2a01:e35:8b2b:7330::%4281128194 */ n = getnameinfo((const struct sockaddr *)&saddr, saddrlen, addr_str, addr_str_len, NULL, 0, NI_NUMERICHOST | NI_NUMERICSERV); -#endif if(n != 0) { -#ifdef WIN32 +#ifdef _WIN32 fprintf(stderr, "getnameinfo() failed : %d\n", n); #else fprintf(stderr, "getnameinfo() failed : %s\n", gai_strerror(n)); @@ -362,10 +444,15 @@ miniwget3(const char * url, const char * host, "GET %s HTTP/%s\r\n" "Host: %s:%d\r\n" "Connection: Close\r\n" - "User-Agent: " OS_STRING ", UPnP/1.0, MiniUPnPc/" MINIUPNPC_VERSION_STRING "\r\n" + "User-Agent: " OS_STRING ", " UPNP_VERSION_STRING ", MiniUPnPc/" MINIUPNPC_VERSION_STRING "\r\n" "\r\n", path, httpversion, host, port); + if ((unsigned int)len >= sizeof(buf)) + { + closesocket(s); + return NULL; + } sent = 0; /* sending the HTTP request */ while(sent < len) @@ -382,7 +469,7 @@ miniwget3(const char * url, const char * host, sent += n; } } - content = getHTTPResponse(s, size); + content = getHTTPResponse(s, size, status_code); closesocket(s); return content; } @@ -390,24 +477,32 @@ miniwget3(const char * url, const char * host, /* miniwget2() : * Call miniwget3(); retry with HTTP/1.1 if 1.0 fails. */ static void * -miniwget2(const char * url, const char * host, - unsigned short port, const char * path, - int * size, char * addr_str, int addr_str_len) +miniwget2(const char * host, + unsigned short port, const char * path, + int * size, char * addr_str, int addr_str_len, + unsigned int scope_id, int * status_code) { char * respbuffer; - respbuffer = miniwget3(url, host, port, path, size, addr_str, addr_str_len, "1.1"); -/* - respbuffer = miniwget3(url, host, port, path, size, addr_str, addr_str_len, "1.0"); +#if 1 + respbuffer = miniwget3(host, port, path, size, + addr_str, addr_str_len, "1.1", + scope_id, status_code); +#else + respbuffer = miniwget3(host, port, path, size, + addr_str, addr_str_len, "1.0", + scope_id, status_code); if (*size == 0) { #ifdef DEBUG printf("Retrying with HTTP/1.1\n"); #endif free(respbuffer); - respbuffer = miniwget3(url, host, port, path, size, addr_str, addr_str_len, "1.1"); + respbuffer = miniwget3(host, port, path, size, + addr_str, addr_str_len, "1.1", + scope_id, status_code); } -*/ +#endif return respbuffer; } @@ -419,12 +514,15 @@ miniwget2(const char * url, const char * host, * url : source string not modified * hostname : hostname destination string (size of MAXHOSTNAMELEN+1) * port : port (destination) - * path : pointer to the path part of the URL + * path : pointer to the path part of the URL * * Return values : * 0 - Failure * 1 - Success */ -int parseURL(const char * url, char * hostname, unsigned short * port, char * * path) +int +parseURL(const char * url, + char * hostname, unsigned short * port, + char * * path, unsigned int * scope_id) { char * p1, *p2, *p3; if(!url) @@ -440,7 +538,43 @@ int parseURL(const char * url, char * hostname, unsigned short * port, char * * if(*p1 == '[') { /* IP v6 : http://[2a00:1450:8002::6a]/path/abc */ + char * scope; + scope = strchr(p1, '%'); p2 = strchr(p1, ']'); + if(p2 && scope && scope < p2 && scope_id) { + /* parse scope */ +#ifdef IF_NAMESIZE + char tmp[IF_NAMESIZE]; + int l; + scope++; + /* "%25" is just '%' in URL encoding */ + if(scope[0] == '2' && scope[1] == '5') + scope += 2; /* skip "25" */ + l = p2 - scope; + if(l >= IF_NAMESIZE) + l = IF_NAMESIZE - 1; + memcpy(tmp, scope, l); + tmp[l] = '\0'; + *scope_id = if_nametoindex(tmp); + if(*scope_id == 0) { + *scope_id = (unsigned int)strtoul(tmp, NULL, 10); + } +#else + /* under windows, scope is numerical */ + char tmp[8]; + size_t l; + scope++; + /* "%25" is just '%' in URL encoding */ + if(scope[0] == '2' && scope[1] == '5') + scope += 2; /* skip "25" */ + l = p2 - scope; + if(l >= sizeof(tmp)) + l = sizeof(tmp) - 1; + memcpy(tmp, scope, l); + tmp[l] = '\0'; + *scope_id = (unsigned int)strtoul(tmp, NULL, 10); +#endif + } p3 = strchr(p1, '/'); if(p2 && p3) { @@ -490,35 +624,41 @@ int parseURL(const char * url, char * hostname, unsigned short * port, char * * return 1; } -void * miniwget(const char * url, int * size) +void * +miniwget(const char * url, int * size, + unsigned int scope_id, int * status_code) { unsigned short port; char * path; /* protocol://host:port/chemin */ char hostname[MAXHOSTNAMELEN+1]; *size = 0; - if(!parseURL(url, hostname, &port, &path)) + if(!parseURL(url, hostname, &port, &path, &scope_id)) return NULL; #ifdef DEBUG - printf("parsed url : hostname='%s' port=%hu path='%s'\n", hostname, port, path); + printf("parsed url : hostname='%s' port=%hu path='%s' scope_id=%u\n", + hostname, port, path, scope_id); #endif - return miniwget2(url, hostname, port, path, size, 0, 0); + return miniwget2(hostname, port, path, size, 0, 0, scope_id, status_code); } -void * miniwget_getaddr(const char * url, int * size, char * addr, int addrlen) +void * +miniwget_getaddr(const char * url, int * size, + char * addr, int addrlen, unsigned int scope_id, + int * status_code) { unsigned short port; char * path; - /* protocol://host:port/chemin */ + /* protocol://host:port/path */ char hostname[MAXHOSTNAMELEN+1]; *size = 0; if(addr) addr[0] = '\0'; - if(!parseURL(url, hostname, &port, &path)) + if(!parseURL(url, hostname, &port, &path, &scope_id)) return NULL; #ifdef DEBUG - printf("parsed url : hostname='%s' port=%hu path='%s'\n", hostname, port, path); + printf("parsed url : hostname='%s' port=%hu path='%s' scope_id=%u\n", + hostname, port, path, scope_id); #endif - return miniwget2(url, hostname, port, path, size, addr, addrlen); + return miniwget2(hostname, port, path, size, addr, addrlen, scope_id, status_code); } - diff --git a/libs/miniupnpc/src/miniwget_private.h b/libs/miniupnpc/src/miniwget_private.h new file mode 100644 index 000000000..e4eaac808 --- /dev/null +++ b/libs/miniupnpc/src/miniwget_private.h @@ -0,0 +1,15 @@ +/* $Id: miniwget_private.h,v 1.1 2018/04/06 10:17:58 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2018 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#ifndef MINIWGET_INTERNAL_H_INCLUDED +#define MINIWGET_INTERNAL_H_INCLUDED + +#include "miniupnpc_socketdef.h" + +void * getHTTPResponse(SOCKET s, int * size, int * status_code); + +#endif diff --git a/libs/miniupnpc/minixml.c b/libs/miniupnpc/src/minixml.c similarity index 88% rename from libs/miniupnpc/minixml.c rename to libs/miniupnpc/src/minixml.c index 8b5594c88..935ec443e 100644 --- a/libs/miniupnpc/minixml.c +++ b/libs/miniupnpc/src/minixml.c @@ -1,10 +1,11 @@ -/* $Id: minixml.c,v 1.9 2011/02/07 13:44:57 nanard Exp $ */ -/* minixml.c : the minimum size a xml parser can be ! */ +/* $Id: minixml.c,v 1.12 2017/12/12 11:17:40 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * minixml.c : the minimum size a xml parser can be ! */ /* Project : miniupnp * webpage: http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * Author : Thomas Bernard -Copyright (c) 2005-2011, Thomas BERNARD +Copyright (c) 2005-2017, Thomas BERNARD All rights reserved. Redistribution and use in source and binary forms, with or without @@ -113,7 +114,20 @@ static void parseelt(struct xmlparser * p) const char * elementname; while(p->xml < (p->xmlend - 1)) { - if((p->xml)[0]=='<' && (p->xml)[1]!='?') + if((p->xml + 4) <= p->xmlend && (0 == memcmp(p->xml, "", 3) != 0); + p->xml += 3; + } + else if((p->xml)[0]=='<' && (p->xml)[1]!='?') { i = 0; elementname = ++p->xml; while( !IS_WHITE_SPACE(*p->xml) @@ -148,8 +162,9 @@ static void parseelt(struct xmlparser * p) if (p->xml >= p->xmlend) return; } - if(memcmp(p->xml, " */ + if((p->xmlend >= (p->xml + (9 + 3))) && (memcmp(p->xml, "xml += 9; data = p->xml; diff --git a/libs/miniupnpc/minixml.h b/libs/miniupnpc/src/minixml.h similarity index 83% rename from libs/miniupnpc/minixml.h rename to libs/miniupnpc/src/minixml.h index 857c70ee9..0a9fc08b7 100644 --- a/libs/miniupnpc/minixml.h +++ b/libs/miniupnpc/src/minixml.h @@ -1,4 +1,4 @@ -/* $Id: minixml.h,v 1.6 2006/11/30 11:47:21 nanard Exp $ */ +/* $Id: minixml.h,v 1.8 2019/02/10 12:29:25 nanard Exp $ */ /* minimal xml parser * * Project : miniupnp @@ -8,9 +8,9 @@ * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. * */ -#ifndef __MINIXML_H__ -#define __MINIXML_H__ -#define IS_WHITE_SPACE(c) ((c==' ') || (c=='\t') || (c=='\r') || (c=='\n')) +#ifndef MINIXML_H_INCLUDED +#define MINIXML_H_INCLUDED +#define IS_WHITE_SPACE(c) ((c)==' ' || (c)=='\t' || (c)=='\r' || (c)=='\n') /* if a callback function pointer is set to NULL, * the function is not called */ diff --git a/libs/miniupnpc/minixmlvalid.c b/libs/miniupnpc/src/minixmlvalid.c similarity index 94% rename from libs/miniupnpc/minixmlvalid.c rename to libs/miniupnpc/src/minixmlvalid.c index 766211bcf..dad148812 100644 --- a/libs/miniupnpc/minixmlvalid.c +++ b/libs/miniupnpc/src/minixmlvalid.c @@ -1,4 +1,4 @@ -/* $Id: minixmlvalid.c,v 1.4 2011/02/07 13:44:57 nanard Exp $ */ +/* $Id: minixmlvalid.c,v 1.7 2015/07/15 12:41:15 nanard Exp $ */ /* MiniUPnP Project * http://miniupnp.tuxfamily.org/ or http://miniupnp.free.fr/ * minixmlvalid.c : @@ -32,7 +32,7 @@ int evtlistcmp(struct eventlist * a, struct eventlist * b) if(a->n != b->n) { printf("event number not matching : %d != %d\n", a->n, b->n); - //return 1; + /*return 1;*/ } for(i=0; in; i++) { @@ -82,7 +82,7 @@ static const struct event evtref[] = {ELTEND, "elt2b", 5}, {ELTEND, "elt2a", 5}, {ELTEND, "xmlroot", 7} -}; +}; void startelt(void * data, const char * p, int l) { @@ -128,6 +128,11 @@ int testxmlparser(const char * xml, int size) struct xmlparser parser; evtlist.n = 0; evtlist.events = malloc(sizeof(struct event)*100); + if(evtlist.events == NULL) + { + fprintf(stderr, "Memory allocation error.\n"); + return -1; + } memset(&parser, 0, sizeof(parser)); parser.xmlstart = xml; parser.xmlsize = size; @@ -148,6 +153,8 @@ int testxmlparser(const char * xml, int size) int main(int argc, char * * argv) { int r; + (void)argc; (void)argv; + r = testxmlparser(xmldata, sizeof(xmldata)-1); if(r) printf("minixml validation test failed\n"); diff --git a/libs/miniupnpc/portlistingparse.c b/libs/miniupnpc/src/portlistingparse.c similarity index 81% rename from libs/miniupnpc/portlistingparse.c rename to libs/miniupnpc/src/portlistingparse.c index e09e80f39..54e5696ef 100644 --- a/libs/miniupnpc/portlistingparse.c +++ b/libs/miniupnpc/src/portlistingparse.c @@ -1,14 +1,22 @@ -/* $Id: portlistingparse.c,v 1.4 2011/03/18 11:02:17 nanard Exp $ */ +/* $Id: portlistingparse.c,v 1.11 2020/03/22 22:43:44 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * (c) 2011 Thomas Bernard + * (c) 2011-2020 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include +#ifdef DEBUG +#include +#endif /* DEBUG */ #include "portlistingparse.h" #include "minixml.h" +#if defined(__HAIKU__) +/* rename our private function because Haiku already defines a atoui() function */ +#define atoui atoui2 +#endif + /* list of the elements */ static const struct { const portMappingElt code; @@ -52,7 +60,7 @@ startelt(void * d, const char * name, int l) pdata->curelt = PortMappingEltNone; for(i = 0; elements[i].str; i++) { - if(memcmp(name, elements[i].str, l) == 0) + if(strlen(elements[i].str) == (size_t)l && memcmp(name, elements[i].str, l) == 0) { pdata->curelt = elements[i].code; break; @@ -62,7 +70,17 @@ startelt(void * d, const char * name, int l) { struct PortMapping * pm; pm = calloc(1, sizeof(struct PortMapping)); - LIST_INSERT_HEAD( &(pdata->head), pm, entries); + if(pm == NULL) + { + /* malloc error */ +#ifdef DEBUG + fprintf(stderr, "%s: error allocating memory", + "startelt"); +#endif /* DEBUG */ + return; + } + pm->l_next = pdata->l_head; /* insert in list */ + pdata->l_head = pm; } } @@ -71,6 +89,8 @@ static void endelt(void * d, const char * name, int l) { struct PortMappingParserData * pdata = (struct PortMappingParserData *)d; + (void)name; + (void)l; pdata->curelt = PortMappingEltNone; } @@ -80,7 +100,7 @@ data(void * d, const char * data, int l) { struct PortMapping * pm; struct PortMappingParserData * pdata = (struct PortMappingParserData *)d; - pm = pdata->head.lh_first; + pm = pdata->l_head; if(!pm) return; if(l > 63) @@ -132,7 +152,6 @@ ParsePortListing(const char * buffer, int bufsize, struct xmlparser parser; memset(pdata, 0, sizeof(struct PortMappingParserData)); - LIST_INIT(&(pdata->head)); /* init xmlparser */ parser.xmlstart = buffer; parser.xmlsize = bufsize; @@ -148,9 +167,10 @@ void FreePortListing(struct PortMappingParserData * pdata) { struct PortMapping * pm; - while((pm = pdata->head.lh_first) != NULL) + while((pm = pdata->l_head) != NULL) { - LIST_REMOVE(pm, entries); + /* remove from list */ + pdata->l_head = pm->l_next; free(pm); } } diff --git a/libs/miniupnpc/src/receivedata.c b/libs/miniupnpc/src/receivedata.c new file mode 100644 index 000000000..7f187f6e5 --- /dev/null +++ b/libs/miniupnpc/src/receivedata.c @@ -0,0 +1,105 @@ +/* $Id: receivedata.c,v 1.10 2021/03/02 23:33:07 nanard Exp $ */ +/* Project : miniupnp + * Website : http://miniupnp.free.fr/ + * Author : Thomas Bernard + * Copyright (c) 2011-2021 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. */ + +#include +#include +#ifdef _WIN32 +#include +#include +#else /* _WIN32 */ +#include +#if defined(__amigaos__) && !defined(__amigaos4__) +#define socklen_t int +#else /* #if defined(__amigaos__) && !defined(__amigaos4__) */ +#include +#endif /* #else defined(__amigaos__) && !defined(__amigaos4__) */ +#include +#include +#if !defined(__amigaos__) && !defined(__amigaos4__) +#include +#endif /* !defined(__amigaos__) && !defined(__amigaos4__) */ +#include +#define MINIUPNPC_IGNORE_EINTR +#endif /* _WIN32 */ + +#include "receivedata.h" + +int +receivedata(SOCKET socket, + char * data, int length, + int timeout, unsigned int * scope_id) +{ +#ifdef MINIUPNPC_GET_SRC_ADDR + struct sockaddr_storage src_addr; + socklen_t src_addr_len = sizeof(src_addr); +#endif /* MINIUPNPC_GET_SRC_ADDR */ + int n; +#if !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) + /* using poll */ + struct pollfd fds[1]; /* for the poll */ +#ifdef MINIUPNPC_IGNORE_EINTR + do { +#endif /* MINIUPNPC_IGNORE_EINTR */ + fds[0].fd = socket; + fds[0].events = POLLIN; + n = poll(fds, 1, timeout); +#ifdef MINIUPNPC_IGNORE_EINTR + } while(n < 0 && errno == EINTR); +#endif /* MINIUPNPC_IGNORE_EINTR */ + if(n < 0) { + PRINT_SOCKET_ERROR("poll"); + return -1; + } else if(n == 0) { + /* timeout */ + return 0; + } +#else /* !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) */ + /* using select under _WIN32 and amigaos */ + fd_set socketSet; + TIMEVAL timeval; + FD_ZERO(&socketSet); + FD_SET(socket, &socketSet); + timeval.tv_sec = timeout / 1000; + timeval.tv_usec = (timeout % 1000) * 1000; + n = select(FD_SETSIZE, &socketSet, NULL, NULL, &timeval); + if(n < 0) { + PRINT_SOCKET_ERROR("select"); + return -1; + } else if(n == 0) { + return 0; + } +#endif /* !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) */ +#ifdef MINIUPNPC_GET_SRC_ADDR + memset(&src_addr, 0, sizeof(src_addr)); + n = recvfrom(socket, data, length, 0, + (struct sockaddr *)&src_addr, &src_addr_len); +#else /* MINIUPNPC_GET_SRC_ADDR */ + n = recv(socket, data, length, 0); +#endif /* MINIUPNPC_GET_SRC_ADDR */ + if(n<0) { + PRINT_SOCKET_ERROR("recv"); + } +#ifdef MINIUPNPC_GET_SRC_ADDR + if (src_addr.ss_family == AF_INET6) { + const struct sockaddr_in6 * src_addr6 = (struct sockaddr_in6 *)&src_addr; +#ifdef DEBUG + printf("scope_id=%u\n", src_addr6->sin6_scope_id); +#endif /* DEBUG */ + if(scope_id) + *scope_id = src_addr6->sin6_scope_id; + } else { + if(scope_id) + *scope_id = 0; + } +#else /* MINIUPNPC_GET_SRC_ADDR */ + if(scope_id) + *scope_id = 0; +#endif /* MINIUPNPC_GET_SRC_ADDR */ + return n; +} + diff --git a/libs/miniupnpc/src/receivedata.h b/libs/miniupnpc/src/receivedata.h new file mode 100644 index 000000000..4dce3f3b4 --- /dev/null +++ b/libs/miniupnpc/src/receivedata.h @@ -0,0 +1,21 @@ +/* $Id: receivedata.h,v 1.5 2018/04/06 10:53:15 nanard Exp $ */ +/* Project: miniupnp + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * Author: Thomas Bernard + * Copyright (c) 2011-2018 Thomas Bernard + * This software is subjects to the conditions detailed + * in the LICENCE file provided within this distribution */ +#ifndef RECEIVEDATA_H_INCLUDED +#define RECEIVEDATA_H_INCLUDED + +#include "miniupnpc_socketdef.h" + +/* Reads data from the specified socket. + * Returns the number of bytes read if successful, zero if no bytes were + * read or if we timed out. Returns negative if there was an error. */ +int receivedata(SOCKET socket, + char * data, int length, + int timeout, unsigned int * scope_id); + +#endif + diff --git a/libs/miniupnpc/src/testaddr_is_reserved.c b/libs/miniupnpc/src/testaddr_is_reserved.c new file mode 100644 index 000000000..313f0daa4 --- /dev/null +++ b/libs/miniupnpc/src/testaddr_is_reserved.c @@ -0,0 +1,46 @@ +/* $Id: testaddr_is_reserved.c,v 1.1 2020/10/15 22:12:51 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * Project : miniupnp + * Web : http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ + * Author : Thomas BERNARD + * copyright (c) 2005-2020 Thomas Bernard + * This software is subjet to the conditions detailed in the + * provided LICENSE file. */ +#include +#include "addr_is_reserved.h" + +static const struct { + const char * str; + int expected_result; +} tests[] = { +{ "0.0.0.0", 1 }, +{ "8.8.8.8", 0 }, +{ "192.168.1.1", 1 }, +{ "10.250.42.12", 1 }, +{ "11.250.42.12", 0 }, +{ "172.31.1.1", 1 }, +{ "172.32.1.1", 0 }, +{ "169.254.42.42", 1 }, +{ "192.0.0.11", 1 }, +{ "198.0.0.11", 0 }, +{ "198.18.0.11", 1 }, +{ "100.64.1.1", 1 }, +{ "100.127.1.1", 1 }, +{ "100.128.1.1", 0 }, +{ NULL, 0 } +}; + +int main(int argc, char * * argv) { + int i, result; + (void)argc; (void)argv; + + for (i = 0; tests[i].str != NULL; i++) { + result = addr_is_reserved(tests[i].str); + printf("testing %s %d %d\n", tests[i].str, tests[i].expected_result, result); + if (result != tests[i].expected_result) { + fprintf(stderr, "*** FAILURE ***\n"); + return 1; /* Failure */ + } + } + return 0; /* success */ +} diff --git a/libs/miniupnpc/src/testigddescparse.c b/libs/miniupnpc/src/testigddescparse.c new file mode 100644 index 000000000..cdc765de4 --- /dev/null +++ b/libs/miniupnpc/src/testigddescparse.c @@ -0,0 +1,187 @@ +/* $Id: testigddescparse.c,v 1.11 2019/02/10 12:33:32 nanard Exp $ */ +/* Project : miniupnp + * http://miniupnp.free.fr/ + * Author : Thomas Bernard + * Copyright (c) 2008-2015 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#include +#include +#include +#include "igd_desc_parse.h" +#include "minixml.h" +#include "miniupnpc.h" + +/* count number of differences */ +int compare_service(struct IGDdatas_service * s, FILE * f) +{ + int n = 0; + char line[1024]; + + while(fgets(line, sizeof(line), f)) { + char * value; + char * equal; + char * name; + char * parsedvalue; + int l; + l = strlen(line); + while((l > 0) && ((line[l-1] == '\r') || (line[l-1] == '\n') || (line[l-1] == ' '))) + line[--l] = '\0'; + if(l == 0) + break; /* end on blank line */ + if(line[0] == '#') + continue; /* skip comments */ + equal = strchr(line, '='); + if(equal == NULL) { + fprintf(stderr, "Warning, line does not contain '=' : %s\n", line); + continue; + } + *equal = '\0'; + name = line; + while(*name == ' ' || *name == '\t') + name++; + l = strlen(name); + while((l > 0) && (name[l-1] == ' ' || name[l-1] == '\t')) + name[--l] = '\0'; + value = equal + 1; + while(*value == ' ' || *value == '\t') + value++; + if(strcmp(name, "controlurl") == 0) + parsedvalue = s->controlurl; + else if(strcmp(name, "eventsuburl") == 0) + parsedvalue = s->eventsuburl; + else if(strcmp(name, "scpdurl") == 0) + parsedvalue = s->scpdurl; + else if(strcmp(name, "servicetype") == 0) + parsedvalue = s->servicetype; + else { + fprintf(stderr, "unknown field '%s'\n", name); + continue; + } + if(0 != strcmp(parsedvalue, value)) { + fprintf(stderr, "difference : '%s' != '%s'\n", parsedvalue, value); + n++; + } + } + return n; +} + +int compare_igd(struct IGDdatas * p, FILE * f) +{ + int n = 0; + char line[1024]; + struct IGDdatas_service * s; + + while(fgets(line, sizeof(line), f)) { + char * colon; + int l = (int)strlen(line); + while((l > 0) && (line[l-1] == '\r' || (line[l-1] == '\n'))) + line[--l] = '\0'; + if(l == 0 || line[0] == '#') + continue; /* skip blank lines and comments */ + colon = strchr(line, ':'); + if(colon == NULL) { + fprintf(stderr, "Warning, no ':' : %s\n", line); + continue; + } + s = NULL; + *colon = '\0'; + if(strcmp(line, "CIF") == 0) + s = &p->CIF; + else if(strcmp(line, "first") == 0) + s = &p->first; + else if(strcmp(line, "second") == 0) + s = &p->second; + else if(strcmp(line, "IPv6FC") == 0) + s = &p->IPv6FC; + else { + s = NULL; + fprintf(stderr, "*** unknown service '%s' ***\n", line); + n++; + continue; + } + n += compare_service(s, f); + } + if(n > 0) + fprintf(stderr, "*** %d difference%s ***\n", n, (n > 1) ? "s" : ""); + return n; +} + +int test_igd_desc_parse(char * buffer, int len, FILE * f) +{ + int n; + struct IGDdatas igd; + struct xmlparser parser; + struct UPNPUrls urls; + + memset(&igd, 0, sizeof(struct IGDdatas)); + memset(&parser, 0, sizeof(struct xmlparser)); + parser.xmlstart = buffer; + parser.xmlsize = len; + parser.data = &igd; + parser.starteltfunc = IGDstartelt; + parser.endeltfunc = IGDendelt; + parser.datafunc = IGDdata; + parsexml(&parser); +#ifdef DEBUG + printIGD(&igd); +#endif /* DEBUG */ + GetUPNPUrls(&urls, &igd, "http://fake/desc/url/file.xml", 0); + printf("ipcondescURL='%s'\n", urls.ipcondescURL); + printf("controlURL='%s'\n", urls.controlURL); + printf("controlURL_CIF='%s'\n", urls.controlURL_CIF); + n = f ? compare_igd(&igd, f) : 0; + FreeUPNPUrls(&urls); + return n; +} + +int main(int argc, char * * argv) +{ + FILE * f; + char * buffer; + int len; + int r; + if(argc<2) { + fprintf(stderr, "Usage: %s file.xml [file.values]\n", argv[0]); + return 1; + } + f = fopen(argv[1], "rb"); + if(!f) { + fprintf(stderr, "Cannot open %s for reading.\n", argv[1]); + return 1; + } + fseek(f, 0, SEEK_END); + len = ftell(f); + fseek(f, 0, SEEK_SET); + buffer = malloc(len); + if(!buffer) { + fprintf(stderr, "Memory allocation error.\n"); + fclose(f); + return 1; + } + r = (int)fread(buffer, 1, len, f); + if(r != len) { + fprintf(stderr, "Failed to read file %s. %d out of %d bytes.\n", + argv[1], r, len); + fclose(f); + free(buffer); + return 1; + } + fclose(f); + f = NULL; + if(argc > 2) { + f = fopen(argv[2], "rb"); + if(!f) { + fprintf(stderr, "Cannot open %s for reading.\n", argv[2]); + free(buffer); + return 1; + } + } + r = test_igd_desc_parse(buffer, len, f); + free(buffer); + if(f) + fclose(f); + return r; +} + diff --git a/libs/miniupnpc/testminiwget.c b/libs/miniupnpc/src/testminiwget.c similarity index 70% rename from libs/miniupnpc/testminiwget.c rename to libs/miniupnpc/src/testminiwget.c index b68fbfeed..cfbb224de 100644 --- a/libs/miniupnpc/testminiwget.c +++ b/libs/miniupnpc/src/testminiwget.c @@ -1,7 +1,8 @@ -/* $Id: testminiwget.c,v 1.3 2011/05/06 16:33:53 nanard Exp $ */ -/* Project : miniupnp +/* $Id: testminiwget.c,v 1.7 2018/01/16 01:01:05 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * Project : miniupnp * Author : Thomas Bernard - * Copyright (c) 2005-2011 Thomas Bernard + * Copyright (c) 2005-2018 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. * */ @@ -11,7 +12,7 @@ /** * This program uses the miniwget / miniwget_getaddr function - * from miniwget.c in order to retreive a web ressource using + * from miniwget.c in order to retrieve a web ressource using * a GET HTTP method, and store it in a file. */ int main(int argc, char * * argv) @@ -20,15 +21,17 @@ int main(int argc, char * * argv) int size, writtensize; FILE *f; char addr[64]; + int status_code = -1; + if(argc < 3) { fprintf(stderr, "Usage:\t%s url file\n", argv[0]); fprintf(stderr, "Example:\t%s http://www.google.com/ out.html\n", argv[0]); return 1; } - /*data = miniwget(argv[1], &size);*/ - data = miniwget_getaddr(argv[1], &size, addr, sizeof(addr)); - if(!data) { - fprintf(stderr, "Error fetching %s\n", argv[1]); + data = miniwget_getaddr(argv[1], &size, addr, sizeof(addr), 0, &status_code); + if(!data || (status_code != 200)) { + if(data) free(data); + fprintf(stderr, "Error %d fetching %s\n", status_code, argv[1]); return 1; } printf("local address : %s\n", addr); diff --git a/libs/miniupnpc/testminixml.c b/libs/miniupnpc/src/testminixml.c similarity index 82% rename from libs/miniupnpc/testminixml.c rename to libs/miniupnpc/src/testminixml.c index 3d82527b7..57c4a85eb 100644 --- a/libs/miniupnpc/testminixml.c +++ b/libs/miniupnpc/src/testminixml.c @@ -1,7 +1,11 @@ -/* $Id: testminixml.c,v 1.6 2006/11/19 22:32:35 nanard Exp $ +/* $Id: testminixml.c,v 1.10 2014/11/17 17:19:13 nanard Exp $ + * MiniUPnP project + * Website : http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * Author : Thomas Bernard. + * Copyright (c) 2005-2014 Thomas Bernard + * * testminixml.c * test program for the "minixml" functions. - * Author : Thomas Bernard. */ #include #include @@ -9,18 +13,11 @@ #include "minixml.h" #include "igd_desc_parse.h" -#ifdef WIN32 -#define NO_BZERO -#endif - -#ifdef NO_BZERO -#define bzero(p, n) memset(p, 0, n) -#endif - /* ---------------------------------------------------------------------- */ void printeltname1(void * d, const char * name, int l) { int i; + (void)d; printf("element "); for(i=0;i +#include +#include "portlistingparse.h" + +struct port_mapping { + unsigned int leasetime; + unsigned short externalport; + unsigned short internalport; + const char * remotehost; + const char * client; + const char * proto; + const char * desc; + unsigned char enabled; +}; + +/* return the number of differences */ +int test(const char * portListingXml, int portListingXmlLen, + const struct port_mapping * ref, int count) +{ + int i; + int r = 0; + struct PortMappingParserData data; + struct PortMapping * pm; + + memset(&data, 0, sizeof(data)); + ParsePortListing(portListingXml, portListingXmlLen, &data); + for(i = 0, pm = data.l_head; + (pm != NULL) && (i < count); + i++, pm = pm->l_next) { + printf("%2d %s %5hu->%s:%-5hu '%s' '%s' %u\n", + i, pm->protocol, pm->externalPort, pm->internalClient, + pm->internalPort, + pm->description, pm->remoteHost, + (unsigned)pm->leaseTime); + if(0 != strcmp(pm->protocol, ref[i].proto)) { + printf("protocol : '%s' != '%s'\n", pm->protocol, ref[i].proto); + r++; + } + if(pm->externalPort != ref[i].externalport) { + printf("externalPort : %hu != %hu\n", + pm->externalPort, ref[i].externalport); + r++; + } + if(0 != strcmp(pm->internalClient, ref[i].client)) { + printf("client : '%s' != '%s'\n", + pm->internalClient, ref[i].client); + r++; + } + if(pm->internalPort != ref[i].internalport) { + printf("internalPort : %hu != %hu\n", + pm->internalPort, ref[i].internalport); + r++; + } + if(0 != strcmp(pm->description, ref[i].desc)) { + printf("description : '%s' != '%s'\n", + pm->description, ref[i].desc); + r++; + } + if(0 != strcmp(pm->remoteHost, ref[i].remotehost)) { + printf("remoteHost : '%s' != '%s'\n", + pm->remoteHost, ref[i].remotehost); + r++; + } + if((unsigned)pm->leaseTime != ref[i].leasetime) { + printf("leaseTime : %u != %u\n", + (unsigned)pm->leaseTime, ref[i].leasetime); + r++; + } + if(pm->enabled != ref[i].enabled) { + printf("enabled : %d != %d\n", + (int)pm->enabled, (int)ref[i].enabled); + r++; + } + } + if((i != count) || (pm != NULL)) { + printf("count mismatch : i=%d count=%d pm=%p\n", i, count, pm); + r++; + } + FreePortListing(&data); + return r; +} + +const char test_document[] = +"\n" +"\n" +" \n" +" \n" +" 5002\n" +" UDP\n" +" 4001\n" +" 192.168.1.123\n" +" 1\n" +" xxx\n" +" 0\n" +" \n" +" \n" +" 202.233.2.1\n" +" 2345\n" +" TCP\n" +" 2349\n" +" 192.168.1.137\n" +" 1\n" +" dooom\n" +" 346\n" +" \n" +" \n" +" 134.231.2.11\n" +" 12345\n" +" TCP\n" +" 12345\n" +" 192.168.1.137\n" +" 1\n" +" dooom A\n" +" 347\n" +" \n" +""; + +#define PORT_MAPPINGS_COUNT 3 +const struct port_mapping port_mappings[PORT_MAPPINGS_COUNT] = { +{347, 12345, 12345, "134.231.2.11", "192.168.1.137", "TCP", "dooom A", 1}, +{346, 2345, 2349, "202.233.2.1", "192.168.1.137", "TCP", "dooom", 1}, +{0, 5002, 4001, "", "192.168.1.123", "UDP", "xxx", 1} +}; + +/* --- main --- */ +int main(void) +{ + int r; + r = test(test_document, sizeof(test_document) - 1, + port_mappings, PORT_MAPPINGS_COUNT); + if(r == 0) { + printf("test of portlistingparse OK\n"); + return 0; + } else { + printf("test FAILED (%d differences counted)\n", r); + return 1; + } +} + diff --git a/libs/miniupnpc/src/testupnpreplyparse.c b/libs/miniupnpc/src/testupnpreplyparse.c new file mode 100644 index 000000000..ed3c3fe0d --- /dev/null +++ b/libs/miniupnpc/src/testupnpreplyparse.c @@ -0,0 +1,115 @@ +/* $Id: testupnpreplyparse.c,v 1.5 2017/12/12 11:18:46 nanard Exp $ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2017 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ +#include +#include +#include +#include "upnpreplyparse.h" + +int +test_parsing(const char * buf, int len, FILE * f) +{ + char line[1024]; + struct NameValueParserData pdata; + int ok = 1; + ParseNameValue(buf, len, &pdata); + /* check result */ + if(f != NULL) + { + while(fgets(line, sizeof(line), f)) + { + char * value; + char * equal; + char * parsedvalue; + int l; + l = strlen(line); + while((l > 0) && ((line[l-1] == '\r') || (line[l-1] == '\n'))) + line[--l] = '\0'; + /* skip empty lines */ + if(l == 0) + continue; + equal = strchr(line, '='); + if(equal == NULL) + { + fprintf(stderr, "Warning, line does not contain '=' : %s\n", line); + continue; + } + *equal = '\0'; + value = equal + 1; + parsedvalue = GetValueFromNameValueList(&pdata, line); + if((parsedvalue == NULL) || (strcmp(parsedvalue, value) != 0)) + { + fprintf(stderr, "Element <%s> : expecting value '%s', got '%s'\n", + line, value, parsedvalue ? parsedvalue : ""); + ok = 0; + } + } + } + ClearNameValueList(&pdata); + return ok; +} + +int main(int argc, char * * argv) +{ + FILE * f; + char * buffer; + long l; + int ok; + + if(argc<2) + { + fprintf(stderr, "Usage: %s file.xml [file.namevalues]\n", argv[0]); + return 1; + } + f = fopen(argv[1], "r"); + if(!f) + { + fprintf(stderr, "Error : can not open file %s\n", argv[1]); + return 2; + } + if(fseek(f, 0, SEEK_END) < 0) { + perror("fseek"); + return 1; + } + l = (int)ftell(f); + if(l < 0) { + perror("ftell"); + return 1; + } + if(fseek(f, 0, SEEK_SET) < 0) { + perror("fseek"); + return 1; + } + buffer = malloc(l + 1); + if(buffer == NULL) { + fprintf(stderr, "Error: failed to allocate %ld bytes\n", l+1); + return 1; + } + l = fread(buffer, 1, l, f); + fclose(f); + f = NULL; + buffer[l] = '\0'; + if(argc > 2) + { + f = fopen(argv[2], "r"); + if(!f) + { + fprintf(stderr, "Error : can not open file %s\n", argv[2]); + return 2; + } + } +#ifdef DEBUG + DisplayNameValueList(buffer, l); +#endif + ok = test_parsing(buffer, l, f); + if(f) + { + fclose(f); + } + free(buffer); + return ok ? 0 : 3; +} + diff --git a/libs/miniupnpc/upnpc.c b/libs/miniupnpc/src/upnpc.c similarity index 59% rename from libs/miniupnpc/upnpc.c rename to libs/miniupnpc/src/upnpc.c index b136d9d1f..23bb27830 100644 --- a/libs/miniupnpc/upnpc.c +++ b/libs/miniupnpc/src/upnpc.c @@ -1,7 +1,7 @@ -/* $Id: upnpc.c,v 1.88 2011/06/17 23:31:01 nanard Exp $ */ +/* $Id: upnpc.c,v 1.134 2023/06/11 23:23:10 nanard Exp $ */ /* Project : miniupnp * Author : Thomas Bernard - * Copyright (c) 2005-2011 Thomas Bernard + * Copyright (c) 2005-2023 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. */ @@ -9,16 +9,22 @@ #include #include #include -#ifdef WIN32 +#ifdef _WIN32 #include -#define snprintf _snprintf +#include "win32_snprintf.h" +#else +/* for IPPROTO_TCP / IPPROTO_UDP */ +#include #endif +#include #include "miniwget.h" #include "miniupnpc.h" #include "upnpcommands.h" +#include "portlistingparse.h" #include "upnperrors.h" +#include "miniupnpcstrings.h" -/* protofix() checks if protocol is "UDP" or "TCP" +/* protofix() checks if protocol is "UDP" or "TCP" * returns NULL if not */ const char * protofix(const char * proto) { @@ -26,7 +32,7 @@ const char * protofix(const char * proto) static const char proto_udp[4] = { 'U', 'D', 'P', 0}; int i, b; for(i=0, b=1; i<4; i++) - b = b && ( (proto[i] == proto_tcp[i]) + b = b && ( (proto[i] == proto_tcp[i]) || (proto[i] == (proto_tcp[i] | 32)) ); if(b) return proto_tcp; @@ -38,6 +44,22 @@ const char * protofix(const char * proto) return 0; } +/* is_int() checks if parameter is an integer or not + * 1 for integer + * 0 for not an integer */ +int is_int(char const* s) +{ + if(s == NULL) + return 0; + while(*s) { + /* #define isdigit(c) ((c) >= '0' && (c) <= '9') */ + if(!isdigit(*s)) + return 0; + s++; + } + return 1; +} + static void DisplayInfos(struct UPNPUrls * urls, struct IGDdatas * data) { @@ -45,48 +67,55 @@ static void DisplayInfos(struct UPNPUrls * urls, char connectionType[64]; char status[64]; char lastconnerr[64]; - unsigned int uptime; + unsigned int uptime = 0; unsigned int brUp, brDown; time_t timenow, timestarted; int r; - UPNP_GetConnectionTypeInfo(urls->controlURL, - data->first.servicetype, - connectionType); - if(connectionType[0]) - printf("Connection Type : %s\n", connectionType); - else + if(UPNP_GetConnectionTypeInfo(urls->controlURL, + data->first.servicetype, + connectionType) != UPNPCOMMAND_SUCCESS) printf("GetConnectionTypeInfo failed.\n"); - UPNP_GetStatusInfo(urls->controlURL, data->first.servicetype, - status, &uptime, lastconnerr); - printf("Status : %s, uptime=%us, LastConnectionError : %s\n", - status, uptime, lastconnerr); - timenow = time(NULL); - timestarted = timenow - uptime; - printf(" Time started : %s", ctime(×tarted)); - UPNP_GetLinkLayerMaxBitRates(urls->controlURL_CIF, data->CIF.servicetype, - &brDown, &brUp); - printf("MaxBitRateDown : %u bps", brDown); - if(brDown >= 1000000) { - printf(" (%u.%u Mbps)", brDown / 1000000, (brDown / 100000) % 10); - } else if(brDown >= 1000) { - printf(" (%u Kbps)", brDown / 1000); + else + printf("Connection Type : %s\n", connectionType); + if(UPNP_GetStatusInfo(urls->controlURL, data->first.servicetype, + status, &uptime, lastconnerr) != UPNPCOMMAND_SUCCESS) + printf("GetStatusInfo failed.\n"); + else + printf("Status : %s, uptime=%us, LastConnectionError : %s\n", + status, uptime, lastconnerr); + if(uptime > 0) { + timenow = time(NULL); + timestarted = timenow - uptime; + printf(" Time started : %s", ctime(×tarted)); } - printf(" MaxBitRateUp %u bps", brUp); - if(brUp >= 1000000) { - printf(" (%u.%u Mbps)", brUp / 1000000, (brUp / 100000) % 10); - } else if(brUp >= 1000) { - printf(" (%u Kbps)", brUp / 1000); + if(UPNP_GetLinkLayerMaxBitRates(urls->controlURL_CIF, data->CIF.servicetype, + &brDown, &brUp) != UPNPCOMMAND_SUCCESS) { + printf("GetLinkLayerMaxBitRates failed.\n"); + } else { + printf("MaxBitRateDown : %u bps", brDown); + if(brDown >= 1000000) { + printf(" (%u.%u Mbps)", brDown / 1000000, (brDown / 100000) % 10); + } else if(brDown >= 1000) { + printf(" (%u Kbps)", brDown / 1000); + } + printf(" MaxBitRateUp %u bps", brUp); + if(brUp >= 1000000) { + printf(" (%u.%u Mbps)", brUp / 1000000, (brUp / 100000) % 10); + } else if(brUp >= 1000) { + printf(" (%u Kbps)", brUp / 1000); + } + printf("\n"); } - printf("\n"); r = UPNP_GetExternalIPAddress(urls->controlURL, data->first.servicetype, externalIPAddress); - if(r != UPNPCOMMAND_SUCCESS) - printf("GetExternalIPAddress() returned %d\n", r); - if(externalIPAddress[0]) + if(r != UPNPCOMMAND_SUCCESS) { + printf("GetExternalIPAddress failed. (errorcode=%d)\n", r); + } else if(!externalIPAddress[0]) { + printf("GetExternalIPAddress failed. (empty string)\n"); + } else { printf("ExternalIPAddress = %s\n", externalIPAddress); - else - printf("GetExternalIPAddress failed.\n"); + } } static void GetConnectionStatus(struct UPNPUrls * urls, @@ -106,7 +135,7 @@ static void ListRedirections(struct UPNPUrls * urls, struct IGDdatas * data) { int r; - int i = 0; + unsigned short i = 0; char index[6]; char intClient[40]; char intPort[6]; @@ -119,8 +148,9 @@ static void ListRedirections(struct UPNPUrls * urls, /*unsigned int num=0; UPNP_GetPortMappingNumberOfEntries(urls->controlURL, data->servicetype, &num); printf("PortMappingNumberOfEntries : %u\n", num);*/ + printf(" i protocol exPort->inAddr:inPort description remoteHost leaseTime\n"); do { - snprintf(index, 6, "%d", i); + snprintf(index, 6, "%hu", i); rHost[0] = '\0'; enabled[0] = '\0'; duration[0] = '\0'; desc[0] = '\0'; extPort[0] = '\0'; intPort[0] = '\0'; intClient[0] = '\0'; @@ -132,20 +162,19 @@ static void ListRedirections(struct UPNPUrls * urls, rHost, duration); if(r==0) /* - printf("%02d - %s %s->%s:%s\tenabled=%s leaseDuration=%s\n" + printf("%02hu - %s %s->%s:%s\tenabled=%s leaseDuration=%s\n" " desc='%s' rHost='%s'\n", i, protocol, extPort, intClient, intPort, enabled, duration, desc, rHost); */ - printf("%2d %s %5s->%s:%-5s '%s' '%s' %s\n", + printf("%2hu %s %5s->%s:%-5s '%s' '%s' %s\n", i, protocol, extPort, intClient, intPort, desc, rHost, duration); else printf("GetGenericPortMappingEntry() returned %d (%s)\n", r, strupnperror(r)); - i++; - } while(r==0); + } while(r == 0 && i++ < 65535); } static void NewListRedirections(struct UPNPUrls * urls, @@ -159,14 +188,15 @@ static void NewListRedirections(struct UPNPUrls * urls, memset(&pdata, 0, sizeof(struct PortMappingParserData)); r = UPNP_GetListOfPortMappings(urls->controlURL, data->first.servicetype, - "0", + "1", "65535", "TCP", "1000", &pdata); if(r == UPNPCOMMAND_SUCCESS) { - for(pm = pdata.head.lh_first; pm != NULL; pm = pm->entries.le_next) + printf(" i protocol exPort->inAddr:inPort description remoteHost leaseTime\n"); + for(pm = pdata.l_head; pm != NULL; pm = pm->l_next) { printf("%2d %s %5hu->%s:%-5hu '%s' '%s' %u\n", i, pm->protocol, pm->externalPort, pm->internalClient, @@ -184,14 +214,14 @@ static void NewListRedirections(struct UPNPUrls * urls, } r = UPNP_GetListOfPortMappings(urls->controlURL, data->first.servicetype, - "0", + "1", "65535", "UDP", "1000", &pdata); if(r == UPNPCOMMAND_SUCCESS) { - for(pm = pdata.head.lh_first; pm != NULL; pm = pm->entries.le_next) + for(pm = pdata.l_head; pm != NULL; pm = pm->l_next) { printf("%2d %s %5hu->%s:%-5hu '%s' '%s' %u\n", i, pm->protocol, pm->externalPort, pm->internalClient, @@ -209,87 +239,145 @@ static void NewListRedirections(struct UPNPUrls * urls, } } -/* Test function +/* Test function * 1 - get connection type * 2 - get extenal ip address * 3 - Add port mapping * 4 - get this port mapping from the IGD */ -static void SetRedirectAndTest(struct UPNPUrls * urls, - struct IGDdatas * data, - const char * iaddr, - const char * iport, - const char * eport, - const char * proto, - const char * leaseDuration) +static int SetRedirectAndTest(struct UPNPUrls * urls, + struct IGDdatas * data, + const char * iaddr, + const char * iport, + const char * eport, + const char * proto, + const char * leaseDuration, + const char * remoteHost, + const char * description, + int addAny) { char externalIPAddress[40]; char intClient[40]; char intPort[6]; + char reservedPort[6]; char duration[16]; int r; if(!iaddr || !iport || !eport || !proto) { fprintf(stderr, "Wrong arguments\n"); - return; + return -1; } proto = protofix(proto); if(!proto) { fprintf(stderr, "invalid protocol\n"); - return; + return -1; } - - UPNP_GetExternalIPAddress(urls->controlURL, - data->first.servicetype, - externalIPAddress); - if(externalIPAddress[0]) - printf("ExternalIPAddress = %s\n", externalIPAddress); - else - printf("GetExternalIPAddress failed.\n"); - - r = UPNP_AddPortMapping(urls->controlURL, data->first.servicetype, - eport, iport, iaddr, 0, proto, 0, leaseDuration); + + r = UPNP_GetExternalIPAddress(urls->controlURL, + data->first.servicetype, + externalIPAddress); if(r!=UPNPCOMMAND_SUCCESS) - printf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n", - eport, iport, iaddr, r, strupnperror(r)); + printf("GetExternalIPAddress failed.\n"); + else + printf("ExternalIPAddress = %s\n", externalIPAddress); + + if (addAny) { + r = UPNP_AddAnyPortMapping(urls->controlURL, data->first.servicetype, + eport, iport, iaddr, description, + proto, remoteHost, leaseDuration, reservedPort); + if(r==UPNPCOMMAND_SUCCESS) + eport = reservedPort; + else + printf("AddAnyPortMapping(%s, %s, %s) failed with code %d (%s)\n", + eport, iport, iaddr, r, strupnperror(r)); + } else { + r = UPNP_AddPortMapping(urls->controlURL, data->first.servicetype, + eport, iport, iaddr, description, + proto, remoteHost, leaseDuration); + if(r!=UPNPCOMMAND_SUCCESS) { + printf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n", + eport, iport, iaddr, r, strupnperror(r)); + return -2; + } + } r = UPNP_GetSpecificPortMappingEntry(urls->controlURL, - data->first.servicetype, - eport, proto, - intClient, intPort, NULL/*desc*/, - NULL/*enabled*/, duration); - if(r!=UPNPCOMMAND_SUCCESS) + data->first.servicetype, + eport, proto, remoteHost, + intClient, intPort, NULL/*desc*/, + NULL/*enabled*/, duration); + if(r!=UPNPCOMMAND_SUCCESS) { printf("GetSpecificPortMappingEntry() failed with code %d (%s)\n", r, strupnperror(r)); - - if(intClient[0]) { + return -2; + } else { printf("InternalIP:Port = %s:%s\n", intClient, intPort); printf("external %s:%s %s is redirected to internal %s:%s (duration=%s)\n", externalIPAddress, eport, proto, intClient, intPort, duration); } + return 0; } -static void +static int RemoveRedirect(struct UPNPUrls * urls, struct IGDdatas * data, - const char * eport, - const char * proto) + const char * eport, + const char * proto, + const char * remoteHost) { int r; if(!proto || !eport) { fprintf(stderr, "invalid arguments\n"); - return; + return -1; } proto = protofix(proto); if(!proto) { fprintf(stderr, "protocol invalid\n"); - return; + return -1; } - r = UPNP_DeletePortMapping(urls->controlURL, data->first.servicetype, eport, proto, 0); - printf("UPNP_DeletePortMapping() returned : %d\n", r); + r = UPNP_DeletePortMapping(urls->controlURL, data->first.servicetype, eport, proto, remoteHost); + if(r!=UPNPCOMMAND_SUCCESS) { + printf("UPNP_DeletePortMapping() failed with code : %d\n", r); + return -2; + }else { + printf("UPNP_DeletePortMapping() returned : %d\n", r); + } + return 0; +} + +static int +RemoveRedirectRange(struct UPNPUrls * urls, + struct IGDdatas * data, + const char * ePortStart, char const * ePortEnd, + const char * proto, const char * manage) +{ + int r; + + if (!manage) + manage = "0"; + + if(!proto || !ePortStart || !ePortEnd) + { + fprintf(stderr, "invalid arguments\n"); + return -1; + } + proto = protofix(proto); + if(!proto) + { + fprintf(stderr, "protocol invalid\n"); + return -1; + } + r = UPNP_DeletePortMappingRange(urls->controlURL, data->first.servicetype, ePortStart, ePortEnd, proto, manage); + if(r!=UPNPCOMMAND_SUCCESS) { + printf("UPNP_DeletePortMappingRange() failed with code : %d\n", r); + return -2; + }else { + printf("UPNP_DeletePortMappingRange() returned : %d\n", r); + } + return 0; } /* IGD:2, functions for service WANIPv6FirewallControl:1 */ @@ -301,7 +389,7 @@ static void GetFirewallStatus(struct UPNPUrls * urls, struct IGDdatas * data) UPNP_GetFirewallStatus(urls->controlURL_6FC, data->IPv6FC.servicetype, &firewallEnabled, &inboundPinholeAllowed); printf("FirewallEnabled: %d & Inbound Pinhole Allowed: %d\n", firewallEnabled, inboundPinholeAllowed); printf("GetFirewallStatus:\n Firewall Enabled: %s\n Inbound Pinhole Allowed: %s\n", (firewallEnabled)? "Yes":"No", (inboundPinholeAllowed)? "Yes":"No"); - + bytessent = UPNP_GetTotalBytesSent(urls->controlURL_CIF, data->CIF.servicetype); bytesreceived = UPNP_GetTotalBytesReceived(urls->controlURL_CIF, data->CIF.servicetype); packetssent = UPNP_GetTotalPacketsSent(urls->controlURL_CIF, data->CIF.servicetype); @@ -310,7 +398,7 @@ static void GetFirewallStatus(struct UPNPUrls * urls, struct IGDdatas * data) printf("Packets: Sent: %8u\tRecv: %8u\n", packetssent, packetsreceived); } -/* Test function +/* Test function * 1 - Add pinhole * 2 - Check if pinhole is working from the IGD side */ static void SetPinholeAndTest(struct UPNPUrls * urls, struct IGDdatas * data, @@ -319,27 +407,43 @@ static void SetPinholeAndTest(struct UPNPUrls * urls, struct IGDdatas * data, const char * proto, const char * lease_time) { char uniqueID[8]; - //int isWorking = 0; + /*int isWorking = 0;*/ int r; + char proto_tmp[8]; if(!intaddr || !remoteaddr || !iport || !eport || !proto || !lease_time) { fprintf(stderr, "Wrong arguments\n"); return; } - /*proto = protofix(proto); - if(!proto) + if(atoi(proto) == 0) { - fprintf(stderr, "invalid protocol\n"); - return; - }*/ + const char * protocol; + protocol = protofix(proto); + if(protocol && (strcmp("TCP", protocol) == 0)) + { + snprintf(proto_tmp, sizeof(proto_tmp), "%d", IPPROTO_TCP); + proto = proto_tmp; + } + else if(protocol && (strcmp("UDP", protocol) == 0)) + { + snprintf(proto_tmp, sizeof(proto_tmp), "%d", IPPROTO_UDP); + proto = proto_tmp; + } + else + { + fprintf(stderr, "invalid protocol\n"); + return; + } + } r = UPNP_AddPinhole(urls->controlURL_6FC, data->IPv6FC.servicetype, remoteaddr, eport, intaddr, iport, proto, lease_time, uniqueID); if(r!=UPNPCOMMAND_SUCCESS) printf("AddPinhole([%s]:%s -> [%s]:%s) failed with code %d (%s)\n", - intaddr, iport, remoteaddr, eport, r, strupnperror(r)); + remoteaddr, eport, intaddr, iport, r, strupnperror(r)); else { - printf("AddPinhole: ([%s]:%s -> [%s]:%s) / Pinhole ID = %s\n", intaddr, iport, remoteaddr, eport, uniqueID); + printf("AddPinhole: ([%s]:%s -> [%s]:%s) / Pinhole ID = %s\n", + remoteaddr, eport, intaddr, iport, uniqueID); /*r = UPNP_CheckPinholeWorking(urls->controlURL_6FC, data->servicetype_6FC, uniqueID, &isWorking); if(r!=UPNPCOMMAND_SUCCESS) printf("CheckPinholeWorking() failed with code %d (%s)\n", r, strupnperror(r)); @@ -361,11 +465,20 @@ static void GetPinholeAndUpdate(struct UPNPUrls * urls, struct IGDdatas * data, fprintf(stderr, "Wrong arguments\n"); return; } + /* CheckPinholeWorking is an Optional Action, error 602 should be + * returned if it is not implemented */ r = UPNP_CheckPinholeWorking(urls->controlURL_6FC, data->IPv6FC.servicetype, uniqueID, &isWorking); - printf("CheckPinholeWorking: Pinhole ID = %s / IsWorking = %s\n", uniqueID, (isWorking)? "Yes":"No"); - if(r!=UPNPCOMMAND_SUCCESS) - printf("CheckPinholeWorking() failed with code %d (%s)\n", r, strupnperror(r)); - if(isWorking || r==709) + if(r==UPNPCOMMAND_SUCCESS) + printf("CheckPinholeWorking: Pinhole ID = %s / IsWorking = %s\n", uniqueID, (isWorking)? "Yes":"No"); + else + printf("CheckPinholeWorking(%s) failed with code %d (%s)\n", uniqueID, r, strupnperror(r)); + /* 702 FirewallDisabled Firewall is disabled and this action is disabled + * 703 InboundPinholeNotAllowed Creation of inbound pinholes by UPnP CPs + * are not allowed and this action is disabled + * 704 NoSuchEntry There is no pinhole with the specified UniqueID. + * 709 NoTrafficReceived No traffic corresponding to this pinhole has + * been received by the gateway. */ + if(isWorking || (r!=702 && r!=703 && r!=704)) { r = UPNP_UpdatePinhole(urls->controlURL_6FC, data->IPv6FC.servicetype, uniqueID, lease_time); printf("UpdatePinhole: Pinhole ID = %s with Lease Time: %s\n", uniqueID, lease_time); @@ -374,7 +487,7 @@ static void GetPinholeAndUpdate(struct UPNPUrls * urls, struct IGDdatas * data, } } -/* Test function +/* Test function * Get pinhole timeout */ static void GetPinholeOutboundTimeout(struct UPNPUrls * urls, struct IGDdatas * data, @@ -455,16 +568,20 @@ int main(int argc, char ** argv) char ** commandargv = 0; int commandargc = 0; struct UPNPDev * devlist = 0; - char lanaddr[64]; /* my ip address on the LAN */ + char lanaddr[64] = "unset"; /* my ip address on the LAN */ int i; const char * rootdescurl = 0; const char * multicastif = 0; const char * minissdpdpath = 0; + int localport = UPNP_LOCAL_PORT_ANY; int retcode = 0; int error = 0; int ipv6 = 0; + int ignore = 0; + unsigned char ttl = 2; /* defaulting to 2 */ + const char * description = 0; -#ifdef WIN32 +#ifdef _WIN32 WSADATA wsaData; int nResult = WSAStartup(MAKEWORD(2,2), &wsaData); if(nResult != NO_ERROR) @@ -473,22 +590,49 @@ int main(int argc, char ** argv) return -1; } #endif - printf("upnpc : miniupnpc library test client. (c) 2006-2011 Thomas Bernard\n"); - printf("Go to http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/\n" + printf("upnpc : miniupnpc library test client, version %s.\n", MINIUPNPC_VERSION_STRING); + printf(" (c) 2005-2023 Thomas Bernard.\n"); + printf("Go to http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/\n" "for more information.\n"); /* command line processing */ for(i=1; i65535 || + (localport >1 && localport < 1024)) + { + fprintf(stderr, "Invalid localport '%s'\n", argv[i]); + localport = UPNP_LOCAL_PORT_ANY; + break; + } + } else if(argv[i][1] == 'p') minissdpdpath = argv[++i]; else if(argv[i][1] == '6') ipv6 = 1; + else if(argv[i][1] == 'e') + description = argv[++i]; + else if(argv[i][1] == 't') + ttl = (unsigned char)atoi(argv[++i]); + else if(argv[i][1] == 'i') + ignore = 1; else { command = argv[i][1]; @@ -504,19 +648,22 @@ int main(int argc, char ** argv) } } - if(!command || (command == 'a' && commandargc<4) + if(!command + || (command == 'a' && commandargc<4) || (command == 'd' && argc<2) || (command == 'r' && argc<2) || (command == 'A' && commandargc<6) || (command == 'U' && commandargc<2) || (command == 'D' && commandargc<1)) { - fprintf(stderr, "Usage :\t%s [options] -a ip port external_port protocol [duration]\n\t\tAdd port redirection\n", argv[0]); - fprintf(stderr, " \t%s [options] -d external_port protocol [port2 protocol2] [...]\n\t\tDelete port redirection\n", argv[0]); + fprintf(stderr, "Usage :\t%s [options] -a ip port external_port protocol [duration] [remote host]\n\t\tAdd port redirection\n", argv[0]); + fprintf(stderr, " \t%s [options] -d external_port protocol [remote host]\n\t\tDelete port redirection\n", argv[0]); fprintf(stderr, " \t%s [options] -s\n\t\tGet Connection status\n", argv[0]); fprintf(stderr, " \t%s [options] -l\n\t\tList redirections\n", argv[0]); - fprintf(stderr, " \t%s [options] -L\n\t\tList redirections (using GetListOfPortMappings, IGD v2)\n", argv[0]); - fprintf(stderr, " \t%s [options] -r port1 protocol1 [port2 protocol2] [...]\n\t\tAdd all redirections to the current host\n", argv[0]); + fprintf(stderr, " \t%s [options] -L\n\t\tList redirections (using GetListOfPortMappings (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -n ip port external_port protocol [duration] [remote host]\n\t\tAdd (any) port redirection allowing IGD to use alternative external_port (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -N external_port_start external_port_end protocol [manage]\n\t\tDelete range of port redirections (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -r port1 [external_port1] protocol1 [port2 [external_port2] protocol2] [...]\n\t\tAdd all redirections to the current host\n", argv[0]); fprintf(stderr, " \t%s [options] -A remote_ip remote_port internal_ip internal_port protocol lease_time\n\t\tAdd Pinhole (for IGD:2 only)\n", argv[0]); fprintf(stderr, " \t%s [options] -U uniqueID new_lease_time\n\t\tUpdate Pinhole (for IGD:2 only)\n", argv[0]); fprintf(stderr, " \t%s [options] -C uniqueID\n\t\tCheck if Pinhole is Working (for IGD:2 only)\n", argv[0]); @@ -526,17 +673,22 @@ int main(int argc, char ** argv) fprintf(stderr, " \t%s [options] -G remote_ip remote_port internal_ip internal_port protocol\n\t\tGet Outbound Pinhole Timeout (for IGD:2 only)\n", argv[0]); fprintf(stderr, " \t%s [options] -P\n\t\tGet Presentation url\n", argv[0]); fprintf(stderr, "\nprotocol is UDP or TCP\n"); + fprintf(stderr, "@ can be used in option -a, -n, -A and -G to represent local LAN address.\n"); fprintf(stderr, "Options:\n"); + fprintf(stderr, " -e description : set description for port mapping.\n"); fprintf(stderr, " -6 : use ip v6 instead of ip v4.\n"); fprintf(stderr, " -u url : bypass discovery process by providing the XML root description url.\n"); - fprintf(stderr, " -m address/interface : provide ip address (ip v4) or interface name (ip v6) to use for sending SSDP multicast packets.\n"); + fprintf(stderr, " -m address/interface : provide ip address (ip v4) or interface name (ip v4 or v6) to use for sending SSDP multicast packets.\n"); + fprintf(stderr, " -z localport : SSDP packets local (source) port (1024-65535).\n"); fprintf(stderr, " -p path : use this path for MiniSSDPd socket.\n"); + fprintf(stderr, " -t ttl : set multicast TTL. Default value is 2.\n"); + fprintf(stderr, " -i : ignore errors and try to use also disconnected IGD or non-IGD device.\n"); return 1; } if( rootdescurl || (devlist = upnpDiscover(2000, multicastif, minissdpdpath, - 0/*sameport*/, ipv6, &error))) + localport, ipv6, ttl, &error))) { struct UPNPDev * device; struct UPNPUrls urls; @@ -550,7 +702,7 @@ int main(int argc, char ** argv) device->descURL, device->st); } } - else + else if(!rootdescurl) { printf("upnpDiscover() error code=%d\n", error); } @@ -564,16 +716,18 @@ int main(int argc, char ** argv) break; case 2: printf("Found a (not connected?) IGD : %s\n", urls.controlURL); - printf("Trying to continue anyway\n"); + if (ignore) printf("Trying to continue anyway\n"); break; case 3: printf("UPnP device found. Is it an IGD ? : %s\n", urls.controlURL); - printf("Trying to continue anyway\n"); + if (ignore) printf("Trying to continue anyway\n"); break; default: printf("Found device (igd ?) : %s\n", urls.controlURL); - printf("Trying to continue anyway\n"); + if (ignore) printf("Trying to continue anyway\n"); } + if(i==1 || ignore) { + printf("Local LAN ip address : %s\n", lanaddr); #if 0 printf("getting \"%s\"\n", urls.ipcondescURL); @@ -585,6 +739,12 @@ int main(int argc, char ** argv) } #endif + /* replace '@' with the local LAN ip address */ + if ((command == 'a' || command == 'n') && 0 == strcmp(commandargv[0], "@")) + commandargv[0] = lanaddr; + else if ((command == 'A' || command == 'G') && 0 == strcmp(commandargv[2], "@")) + commandargv[2] = lanaddr; + switch(command) { case 'l': @@ -595,27 +755,65 @@ int main(int argc, char ** argv) NewListRedirections(&urls, &data); break; case 'a': - SetRedirectAndTest(&urls, &data, - commandargv[0], commandargv[1], - commandargv[2], commandargv[3], - (commandargc > 4)?commandargv[4]:"0"); + if (SetRedirectAndTest(&urls, &data, + commandargv[0], commandargv[1], + commandargv[2], commandargv[3], + (commandargc > 4) && is_int(commandargv[4]) ? commandargv[4] : "0", + (commandargc > 4) && !is_int(commandargv[4]) ? commandargv[4] : (commandargc > 5) ? commandargv[5] : NULL, + description, 0) < 0) + retcode = 2; break; case 'd': - for(i=0; i 2 ? commandargv[2] : NULL) < 0) + retcode = 2; + break; + case 'n': /* aNy */ + if (SetRedirectAndTest(&urls, &data, + commandargv[0], commandargv[1], + commandargv[2], commandargv[3], + (commandargc > 4) && is_int(commandargv[4]) ? commandargv[4] : "0", + (commandargc > 4) && !is_int(commandargv[4]) ? commandargv[4] : (commandargc > 5) ? commandargv[5] : NULL, + description, 1) < 0) + retcode = 2; + break; + case 'N': + if (commandargc < 3) + fprintf(stderr, "too few arguments\n"); + + if (RemoveRedirectRange(&urls, &data, commandargv[0], commandargv[1], commandargv[2], + commandargc > 3 ? commandargv[3] : NULL) < 0) + retcode = 2; break; case 's': GetConnectionStatus(&urls, &data); break; case 'r': - for(i=0; i */ + if (SetRedirectAndTest(&urls, &data, + lanaddr, commandargv[i], + commandargv[i+1], commandargv[i+2], "0", NULL, + description, 0) < 0) + retcode = 2; + i+=3; /* 3 parameters parsed */ + } else { + /* 2nd parameter not an integer : */ + if (SetRedirectAndTest(&urls, &data, + lanaddr, commandargv[i], + commandargv[i], commandargv[i+1], "0", NULL, + description, 0) < 0) + retcode = 2; + i+=2; /* 2 parameters parsed */ + } } break; case 'A': @@ -664,6 +862,10 @@ int main(int argc, char ** argv) retcode = 1; } + } else { + fprintf(stderr, "No valid UPNP Internet Gateway Device found.\n"); + retcode = 1; + } FreeUPNPUrls(&urls); } else @@ -678,6 +880,12 @@ int main(int argc, char ** argv) fprintf(stderr, "No IGD UPnP Device found on the network !\n"); retcode = 1; } +#ifdef _WIN32 + nResult = WSACleanup(); + if(nResult != NO_ERROR) { + fprintf(stderr, "WSACleanup() failed.\n"); + } +#endif /* _WIN32 */ return retcode; } diff --git a/libs/miniupnpc/upnpcommands.c b/libs/miniupnpc/src/upnpcommands.c similarity index 79% rename from libs/miniupnpc/upnpcommands.c rename to libs/miniupnpc/src/upnpcommands.c index 1114759a0..1e1ee6786 100644 --- a/libs/miniupnpc/upnpcommands.c +++ b/libs/miniupnpc/src/upnpcommands.c @@ -1,7 +1,8 @@ -/* $Id: upnpcommands.c,v 1.37 2011/06/04 15:56:23 nanard Exp $ */ -/* Project : miniupnp +/* $Id: upnpcommands.c,v 1.51 2019/04/23 11:45:15 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * Project : miniupnp * Author : Thomas Bernard - * Copyright (c) 2005-2011 Thomas Bernard + * Copyright (c) 2005-2018 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. * */ @@ -11,6 +12,7 @@ #include "upnpcommands.h" #include "miniupnpc.h" #include "portlistingparse.h" +#include "upnpreplyparse.h" static UNSIGNED_INTEGER my_atoui(const char * s) @@ -20,7 +22,7 @@ my_atoui(const char * s) /* * */ -LIBSPEC UNSIGNED_INTEGER +MINIUPNP_LIBSPEC UNSIGNED_INTEGER UPNP_GetTotalBytesSent(const char * controlURL, const char * servicetype) { @@ -31,11 +33,11 @@ UPNP_GetTotalBytesSent(const char * controlURL, char * p; if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, "GetTotalBytesSent", 0, &bufsize))) { - return UPNPCOMMAND_HTTP_ERROR; + return (UNSIGNED_INTEGER)UPNPCOMMAND_HTTP_ERROR; } ParseNameValue(buffer, bufsize, &pdata); /*DisplayNameValueList(buffer, bufsize);*/ - free(buffer); buffer = NULL; + free(buffer); p = GetValueFromNameValueList(&pdata, "NewTotalBytesSent"); r = my_atoui(p); ClearNameValueList(&pdata); @@ -44,7 +46,7 @@ UPNP_GetTotalBytesSent(const char * controlURL, /* * */ -LIBSPEC UNSIGNED_INTEGER +MINIUPNP_LIBSPEC UNSIGNED_INTEGER UPNP_GetTotalBytesReceived(const char * controlURL, const char * servicetype) { @@ -55,11 +57,11 @@ UPNP_GetTotalBytesReceived(const char * controlURL, char * p; if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, "GetTotalBytesReceived", 0, &bufsize))) { - return UPNPCOMMAND_HTTP_ERROR; + return (UNSIGNED_INTEGER)UPNPCOMMAND_HTTP_ERROR; } ParseNameValue(buffer, bufsize, &pdata); /*DisplayNameValueList(buffer, bufsize);*/ - free(buffer); buffer = NULL; + free(buffer); p = GetValueFromNameValueList(&pdata, "NewTotalBytesReceived"); r = my_atoui(p); ClearNameValueList(&pdata); @@ -68,7 +70,7 @@ UPNP_GetTotalBytesReceived(const char * controlURL, /* * */ -LIBSPEC UNSIGNED_INTEGER +MINIUPNP_LIBSPEC UNSIGNED_INTEGER UPNP_GetTotalPacketsSent(const char * controlURL, const char * servicetype) { @@ -79,11 +81,11 @@ UPNP_GetTotalPacketsSent(const char * controlURL, char * p; if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, "GetTotalPacketsSent", 0, &bufsize))) { - return UPNPCOMMAND_HTTP_ERROR; + return (UNSIGNED_INTEGER)UPNPCOMMAND_HTTP_ERROR; } ParseNameValue(buffer, bufsize, &pdata); /*DisplayNameValueList(buffer, bufsize);*/ - free(buffer); buffer = NULL; + free(buffer); p = GetValueFromNameValueList(&pdata, "NewTotalPacketsSent"); r = my_atoui(p); ClearNameValueList(&pdata); @@ -92,7 +94,7 @@ UPNP_GetTotalPacketsSent(const char * controlURL, /* * */ -LIBSPEC UNSIGNED_INTEGER +MINIUPNP_LIBSPEC UNSIGNED_INTEGER UPNP_GetTotalPacketsReceived(const char * controlURL, const char * servicetype) { @@ -103,11 +105,11 @@ UPNP_GetTotalPacketsReceived(const char * controlURL, char * p; if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, "GetTotalPacketsReceived", 0, &bufsize))) { - return UPNPCOMMAND_HTTP_ERROR; + return (UNSIGNED_INTEGER)UPNPCOMMAND_HTTP_ERROR; } ParseNameValue(buffer, bufsize, &pdata); /*DisplayNameValueList(buffer, bufsize);*/ - free(buffer); buffer = NULL; + free(buffer); p = GetValueFromNameValueList(&pdata, "NewTotalPacketsReceived"); r = my_atoui(p); ClearNameValueList(&pdata); @@ -116,10 +118,10 @@ UPNP_GetTotalPacketsReceived(const char * controlURL, /* UPNP_GetStatusInfo() call the corresponding UPNP method * returns the current status and uptime */ -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_GetStatusInfo(const char * controlURL, const char * servicetype, - char * status, + char * status, unsigned int * uptime, char * lastconnerror) { @@ -140,7 +142,7 @@ UPNP_GetStatusInfo(const char * controlURL, } ParseNameValue(buffer, bufsize, &pdata); /*DisplayNameValueList(buffer, bufsize);*/ - free(buffer); buffer = NULL; + free(buffer); up = GetValueFromNameValueList(&pdata, "NewUptime"); p = GetValueFromNameValueList(&pdata, "NewConnectionStatus"); err = GetValueFromNameValueList(&pdata, "NewLastConnectionError"); @@ -159,7 +161,7 @@ UPNP_GetStatusInfo(const char * controlURL, if(up) sscanf(up,"%u",uptime); else - uptime = 0; + *uptime = 0; } if(lastconnerror) { @@ -181,7 +183,7 @@ UPNP_GetStatusInfo(const char * controlURL, /* UPNP_GetConnectionTypeInfo() call the corresponding UPNP method * returns the connection type */ -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_GetConnectionTypeInfo(const char * controlURL, const char * servicetype, char * connectionType) @@ -200,7 +202,7 @@ UPNP_GetConnectionTypeInfo(const char * controlURL, return UPNPCOMMAND_HTTP_ERROR; } ParseNameValue(buffer, bufsize, &pdata); - free(buffer); buffer = NULL; + free(buffer); p = GetValueFromNameValueList(&pdata, "NewConnectionType"); /*p = GetValueFromNameValueList(&pdata, "NewPossibleConnectionTypes");*/ /* PossibleConnectionTypes will have several values.... */ @@ -221,10 +223,10 @@ UPNP_GetConnectionTypeInfo(const char * controlURL, /* UPNP_GetLinkLayerMaxBitRate() call the corresponding UPNP method. * Returns 2 values: Downloadlink bandwidth and Uplink bandwidth. - * One of the values can be null - * Note : GetLinkLayerMaxBitRates belongs to WANPPPConnection:1 only + * One of the values can be null + * Note : GetLinkLayerMaxBitRates belongs to WANPPPConnection:1 only * We can use the GetCommonLinkProperties from WANCommonInterfaceConfig:1 */ -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_GetLinkLayerMaxBitRates(const char * controlURL, const char * servicetype, unsigned int * bitrateDown, @@ -249,7 +251,7 @@ UPNP_GetLinkLayerMaxBitRates(const char * controlURL, } /*DisplayNameValueList(buffer, bufsize);*/ ParseNameValue(buffer, bufsize, &pdata); - free(buffer); buffer = NULL; + free(buffer); /*down = GetValueFromNameValueList(&pdata, "NewDownstreamMaxBitRate");*/ /*up = GetValueFromNameValueList(&pdata, "NewUpstreamMaxBitRate");*/ down = GetValueFromNameValueList(&pdata, "NewLayer1DownstreamMaxBitRate"); @@ -285,7 +287,7 @@ UPNP_GetLinkLayerMaxBitRates(const char * controlURL, /* UPNP_GetExternalIPAddress() call the corresponding UPNP method. * if the third arg is not null the value is copied to it. * at least 16 bytes must be available - * + * * Return values : * 0 : SUCCESS * NON ZERO : ERROR Either an UPnP error code or an unknown error. @@ -293,7 +295,7 @@ UPNP_GetLinkLayerMaxBitRates(const char * controlURL, * 402 Invalid Args - See UPnP Device Architecture section on Control. * 501 Action Failed - See UPnP Device Architecture section on Control. */ -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_GetExternalIPAddress(const char * controlURL, const char * servicetype, char * extIpAdd) @@ -313,7 +315,7 @@ UPNP_GetExternalIPAddress(const char * controlURL, } /*DisplayNameValueList(buffer, bufsize);*/ ParseNameValue(buffer, bufsize, &pdata); - free(buffer); buffer = NULL; + free(buffer); /*printf("external ip = %s\n", GetValueFromNameValueList(&pdata, "NewExternalIPAddress") );*/ p = GetValueFromNameValueList(&pdata, "NewExternalIPAddress"); if(p) { @@ -333,15 +335,15 @@ UPNP_GetExternalIPAddress(const char * controlURL, return ret; } -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_AddPortMapping(const char * controlURL, const char * servicetype, - const char * extPort, - const char * inPort, - const char * inClient, - const char * desc, - const char * proto, - const char * remoteHost, - const char * leaseDuration) + const char * extPort, + const char * inPort, + const char * inClient, + const char * desc, + const char * proto, + const char * remoteHost, + const char * leaseDuration) { struct UPNParg * AddPortMappingArgs; char * buffer; @@ -354,6 +356,8 @@ UPNP_AddPortMapping(const char * controlURL, const char * servicetype, return UPNPCOMMAND_INVALID_ARGS; AddPortMappingArgs = calloc(9, sizeof(struct UPNParg)); + if(AddPortMappingArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; AddPortMappingArgs[0].elt = "NewRemoteHost"; AddPortMappingArgs[0].val = remoteHost; AddPortMappingArgs[1].elt = "NewExternalPort"; @@ -370,17 +374,18 @@ UPNP_AddPortMapping(const char * controlURL, const char * servicetype, AddPortMappingArgs[6].val = desc?desc:"libminiupnpc"; AddPortMappingArgs[7].elt = "NewLeaseDuration"; AddPortMappingArgs[7].val = leaseDuration?leaseDuration:"0"; - if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, - "AddPortMapping", AddPortMappingArgs, - &bufsize))) { - free(AddPortMappingArgs); + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "AddPortMapping", AddPortMappingArgs, + &bufsize); + free(AddPortMappingArgs); + if(!buffer) { return UPNPCOMMAND_HTTP_ERROR; } /*DisplayNameValueList(buffer, bufsize);*/ /*buffer[bufsize] = '\0';*/ /*puts(buffer);*/ ParseNameValue(buffer, bufsize, &pdata); - free(buffer); buffer = NULL; + free(buffer); resVal = GetValueFromNameValueList(&pdata, "errorCode"); if(resVal) { /*printf("AddPortMapping errorCode = '%s'\n", resVal); */ @@ -390,11 +395,79 @@ UPNP_AddPortMapping(const char * controlURL, const char * servicetype, ret = UPNPCOMMAND_SUCCESS; } ClearNameValueList(&pdata); - free(AddPortMappingArgs); return ret; } -LIBSPEC int +MINIUPNP_LIBSPEC int +UPNP_AddAnyPortMapping(const char * controlURL, const char * servicetype, + const char * extPort, + const char * inPort, + const char * inClient, + const char * desc, + const char * proto, + const char * remoteHost, + const char * leaseDuration, + char * reservedPort) +{ + struct UPNParg * AddPortMappingArgs; + char * buffer; + int bufsize; + struct NameValueParserData pdata; + const char * resVal; + int ret; + + if(!inPort || !inClient || !proto || !extPort) + return UPNPCOMMAND_INVALID_ARGS; + + AddPortMappingArgs = calloc(9, sizeof(struct UPNParg)); + if(AddPortMappingArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + AddPortMappingArgs[0].elt = "NewRemoteHost"; + AddPortMappingArgs[0].val = remoteHost; + AddPortMappingArgs[1].elt = "NewExternalPort"; + AddPortMappingArgs[1].val = extPort; + AddPortMappingArgs[2].elt = "NewProtocol"; + AddPortMappingArgs[2].val = proto; + AddPortMappingArgs[3].elt = "NewInternalPort"; + AddPortMappingArgs[3].val = inPort; + AddPortMappingArgs[4].elt = "NewInternalClient"; + AddPortMappingArgs[4].val = inClient; + AddPortMappingArgs[5].elt = "NewEnabled"; + AddPortMappingArgs[5].val = "1"; + AddPortMappingArgs[6].elt = "NewPortMappingDescription"; + AddPortMappingArgs[6].val = desc?desc:"libminiupnpc"; + AddPortMappingArgs[7].elt = "NewLeaseDuration"; + AddPortMappingArgs[7].val = leaseDuration?leaseDuration:"0"; + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "AddAnyPortMapping", AddPortMappingArgs, + &bufsize); + free(AddPortMappingArgs); + if(!buffer) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); + resVal = GetValueFromNameValueList(&pdata, "errorCode"); + if(resVal) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(resVal, "%d", &ret); + } else { + char *p; + + p = GetValueFromNameValueList(&pdata, "NewReservedPort"); + if(p) { + strncpy(reservedPort, p, 6); + reservedPort[5] = '\0'; + ret = UPNPCOMMAND_SUCCESS; + } else { + ret = UPNPCOMMAND_INVALID_RESPONSE; + } + } + ClearNameValueList(&pdata); + return ret; +} + +MINIUPNP_LIBSPEC int UPNP_DeletePortMapping(const char * controlURL, const char * servicetype, const char * extPort, const char * proto, const char * remoteHost) @@ -411,21 +484,24 @@ UPNP_DeletePortMapping(const char * controlURL, const char * servicetype, return UPNPCOMMAND_INVALID_ARGS; DeletePortMappingArgs = calloc(4, sizeof(struct UPNParg)); + if(DeletePortMappingArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; DeletePortMappingArgs[0].elt = "NewRemoteHost"; DeletePortMappingArgs[0].val = remoteHost; DeletePortMappingArgs[1].elt = "NewExternalPort"; DeletePortMappingArgs[1].val = extPort; DeletePortMappingArgs[2].elt = "NewProtocol"; DeletePortMappingArgs[2].val = proto; - if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, - "DeletePortMapping", - DeletePortMappingArgs, &bufsize))) { - free(DeletePortMappingArgs); + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "DeletePortMapping", + DeletePortMappingArgs, &bufsize); + free(DeletePortMappingArgs); + if(!buffer) { return UPNPCOMMAND_HTTP_ERROR; } /*DisplayNameValueList(buffer, bufsize);*/ ParseNameValue(buffer, bufsize, &pdata); - free(buffer); buffer = NULL; + free(buffer); resVal = GetValueFromNameValueList(&pdata, "errorCode"); if(resVal) { ret = UPNPCOMMAND_UNKNOWN_ERROR; @@ -434,11 +510,58 @@ UPNP_DeletePortMapping(const char * controlURL, const char * servicetype, ret = UPNPCOMMAND_SUCCESS; } ClearNameValueList(&pdata); - free(DeletePortMappingArgs); return ret; } -LIBSPEC int +MINIUPNP_LIBSPEC int +UPNP_DeletePortMappingRange(const char * controlURL, const char * servicetype, + const char * extPortStart, const char * extPortEnd, + const char * proto, + const char * manage) +{ + struct UPNParg * DeletePortMappingArgs; + char * buffer; + int bufsize; + struct NameValueParserData pdata; + const char * resVal; + int ret; + + if(!extPortStart || !extPortEnd || !proto || !manage) + return UPNPCOMMAND_INVALID_ARGS; + + DeletePortMappingArgs = calloc(5, sizeof(struct UPNParg)); + if(DeletePortMappingArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + DeletePortMappingArgs[0].elt = "NewStartPort"; + DeletePortMappingArgs[0].val = extPortStart; + DeletePortMappingArgs[1].elt = "NewEndPort"; + DeletePortMappingArgs[1].val = extPortEnd; + DeletePortMappingArgs[2].elt = "NewProtocol"; + DeletePortMappingArgs[2].val = proto; + DeletePortMappingArgs[3].elt = "NewManage"; + DeletePortMappingArgs[3].val = manage; + + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "DeletePortMappingRange", + DeletePortMappingArgs, &bufsize); + free(DeletePortMappingArgs); + if(!buffer) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); + resVal = GetValueFromNameValueList(&pdata, "errorCode"); + if(resVal) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(resVal, "%d", &ret); + } else { + ret = UPNPCOMMAND_SUCCESS; + } + ClearNameValueList(&pdata); + return ret; +} + +MINIUPNP_LIBSPEC int UPNP_GetGenericPortMappingEntry(const char * controlURL, const char * servicetype, const char * index, @@ -462,16 +585,19 @@ UPNP_GetGenericPortMappingEntry(const char * controlURL, intClient[0] = '\0'; intPort[0] = '\0'; GetPortMappingArgs = calloc(2, sizeof(struct UPNParg)); + if(GetPortMappingArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; GetPortMappingArgs[0].elt = "NewPortMappingIndex"; GetPortMappingArgs[0].val = index; - if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, - "GetGenericPortMappingEntry", - GetPortMappingArgs, &bufsize))) { - free(GetPortMappingArgs); + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetGenericPortMappingEntry", + GetPortMappingArgs, &bufsize); + free(GetPortMappingArgs); + if(!buffer) { return UPNPCOMMAND_HTTP_ERROR; } ParseNameValue(buffer, bufsize, &pdata); - free(buffer); buffer = NULL; + free(buffer); p = GetValueFromNameValueList(&pdata, "NewRemoteHost"); if(p && rHost) @@ -493,14 +619,14 @@ UPNP_GetGenericPortMappingEntry(const char * controlURL, protocol[3] = '\0'; } p = GetValueFromNameValueList(&pdata, "NewInternalClient"); - if(p && intClient) + if(p) { strncpy(intClient, p, 16); intClient[15] = '\0'; r = 0; } p = GetValueFromNameValueList(&pdata, "NewInternalPort"); - if(p && intPort) + if(p) { strncpy(intPort, p, 6); intPort[5] = '\0'; @@ -529,11 +655,10 @@ UPNP_GetGenericPortMappingEntry(const char * controlURL, sscanf(p, "%d", &r); } ClearNameValueList(&pdata); - free(GetPortMappingArgs); return r; } -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_GetPortMappingNumberOfEntries(const char * controlURL, const char * servicetype, unsigned int * numEntries) @@ -552,7 +677,7 @@ UPNP_GetPortMappingNumberOfEntries(const char * controlURL, DisplayNameValueList(buffer, bufsize); #endif ParseNameValue(buffer, bufsize, &pdata); - free(buffer); buffer = NULL; + free(buffer); p = GetValueFromNameValueList(&pdata, "NewPortMappingNumberOfEntries"); if(numEntries && p) { @@ -574,11 +699,12 @@ UPNP_GetPortMappingNumberOfEntries(const char * controlURL, /* UPNP_GetSpecificPortMappingEntry retrieves an existing port mapping * the result is returned in the intClient and intPort strings * please provide 16 and 6 bytes of data */ -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_GetSpecificPortMappingEntry(const char * controlURL, const char * servicetype, const char * extPort, - const char * proto, + const char * proto, + const char * remoteHost, char * intClient, char * intPort, char * desc, @@ -596,21 +722,24 @@ UPNP_GetSpecificPortMappingEntry(const char * controlURL, return UPNPCOMMAND_INVALID_ARGS; GetPortMappingArgs = calloc(4, sizeof(struct UPNParg)); + if(GetPortMappingArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; GetPortMappingArgs[0].elt = "NewRemoteHost"; - /* TODO : add remote host ? */ + GetPortMappingArgs[0].val = remoteHost; GetPortMappingArgs[1].elt = "NewExternalPort"; GetPortMappingArgs[1].val = extPort; GetPortMappingArgs[2].elt = "NewProtocol"; GetPortMappingArgs[2].val = proto; - if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, - "GetSpecificPortMappingEntry", - GetPortMappingArgs, &bufsize))) { - free(GetPortMappingArgs); + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetSpecificPortMappingEntry", + GetPortMappingArgs, &bufsize); + free(GetPortMappingArgs); + if(!buffer) { return UPNPCOMMAND_HTTP_ERROR; } /*DisplayNameValueList(buffer, bufsize);*/ ParseNameValue(buffer, bufsize, &pdata); - free(buffer); buffer = NULL; + free(buffer); p = GetValueFromNameValueList(&pdata, "NewInternalClient"); if(p) { @@ -653,7 +782,6 @@ UPNP_GetSpecificPortMappingEntry(const char * controlURL, } ClearNameValueList(&pdata); - free(GetPortMappingArgs); return ret; } @@ -665,7 +793,7 @@ UPNP_GetSpecificPortMappingEntry(const char * controlURL, * 733 InconsistantParameters - NewStartPort and NewEndPort values are not * consistent. */ -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_GetListOfPortMappings(const char * controlURL, const char * servicetype, const char * startPort, @@ -685,6 +813,8 @@ UPNP_GetListOfPortMappings(const char * controlURL, return UPNPCOMMAND_INVALID_ARGS; GetListOfPortMappingsArgs = calloc(6, sizeof(struct UPNParg)); + if(GetListOfPortMappingsArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; GetListOfPortMappingsArgs[0].elt = "NewStartPort"; GetListOfPortMappingsArgs[0].val = startPort; GetListOfPortMappingsArgs[1].elt = "NewEndPort"; @@ -696,17 +826,17 @@ UPNP_GetListOfPortMappings(const char * controlURL, GetListOfPortMappingsArgs[4].elt = "NewNumberOfPorts"; GetListOfPortMappingsArgs[4].val = numberOfPorts?numberOfPorts:"1000"; - if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, - "GetListOfPortMappings", - GetListOfPortMappingsArgs, &bufsize))) { - free(GetListOfPortMappingsArgs); + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetListOfPortMappings", + GetListOfPortMappingsArgs, &bufsize); + free(GetListOfPortMappingsArgs); + if(!buffer) { return UPNPCOMMAND_HTTP_ERROR; } - free(GetListOfPortMappingsArgs); /*DisplayNameValueList(buffer, bufsize);*/ ParseNameValue(buffer, bufsize, &pdata); - free(buffer); buffer = NULL; + free(buffer); /*p = GetValueFromNameValueList(&pdata, "NewPortListing");*/ /*if(p) { @@ -741,16 +871,16 @@ UPNP_GetListOfPortMappings(const char * controlURL, } ClearNameValueList(&pdata); - //printf("%.*s", bufsize, buffer); + /*printf("%.*s", bufsize, buffer);*/ return ret; } -/* IGD:2, functions for service WANIPv6FirewallControl:1 */ -LIBSPEC int +/* IGD:2, functions for service WANIPv6FirewallControl:1 */ +MINIUPNP_LIBSPEC int UPNP_GetFirewallStatus(const char * controlURL, const char * servicetype, - int * firewallEnabled, + int * firewallEnabled, int * inboundPinholeAllowed) { struct NameValueParserData pdata; @@ -759,7 +889,7 @@ UPNP_GetFirewallStatus(const char * controlURL, char * fe, *ipa, *p; int ret = UPNPCOMMAND_UNKNOWN_ERROR; - if(!firewallEnabled && !inboundPinholeAllowed) + if(!firewallEnabled || !inboundPinholeAllowed) return UPNPCOMMAND_INVALID_ARGS; buffer = simpleUPnPcommand(-1, controlURL, servicetype, @@ -768,7 +898,7 @@ UPNP_GetFirewallStatus(const char * controlURL, return UPNPCOMMAND_HTTP_ERROR; } ParseNameValue(buffer, bufsize, &pdata); - free(buffer); buffer = NULL; + free(buffer); fe = GetValueFromNameValueList(&pdata, "FirewallEnabled"); ipa = GetValueFromNameValueList(&pdata, "InboundPinholeAllowed"); if(ipa && fe) @@ -791,7 +921,7 @@ UPNP_GetFirewallStatus(const char * controlURL, return ret; } -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_GetOutboundPinholeTimeout(const char * controlURL, const char * servicetype, const char * remoteHost, const char * remotePort, @@ -805,13 +935,14 @@ UPNP_GetOutboundPinholeTimeout(const char * controlURL, const char * servicetype int bufsize; struct NameValueParserData pdata; const char * resVal; - char * p; int ret; if(!intPort || !intClient || !proto || !remotePort || !remoteHost) return UPNPCOMMAND_INVALID_ARGS; GetOutboundPinholeTimeoutArgs = calloc(6, sizeof(struct UPNParg)); + if(GetOutboundPinholeTimeoutArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; GetOutboundPinholeTimeoutArgs[0].elt = "RemoteHost"; GetOutboundPinholeTimeoutArgs[0].val = remoteHost; GetOutboundPinholeTimeoutArgs[1].elt = "RemotePort"; @@ -824,10 +955,11 @@ UPNP_GetOutboundPinholeTimeout(const char * controlURL, const char * servicetype GetOutboundPinholeTimeoutArgs[4].val = intClient; buffer = simpleUPnPcommand(-1, controlURL, servicetype, "GetOutboundPinholeTimeout", GetOutboundPinholeTimeoutArgs, &bufsize); + free(GetOutboundPinholeTimeoutArgs); if(!buffer) return UPNPCOMMAND_HTTP_ERROR; ParseNameValue(buffer, bufsize, &pdata); - free(buffer); buffer = NULL; + free(buffer); resVal = GetValueFromNameValueList(&pdata, "errorCode"); if(resVal) { @@ -836,17 +968,16 @@ UPNP_GetOutboundPinholeTimeout(const char * controlURL, const char * servicetype } else { - ret = UPNPCOMMAND_SUCCESS; - p = GetValueFromNameValueList(&pdata, "OutboundPinholeTimeout"); + const char * p = GetValueFromNameValueList(&pdata, "OutboundPinholeTimeout"); if(p) *opTimeout = my_atoui(p); + ret = UPNPCOMMAND_SUCCESS; } ClearNameValueList(&pdata); - free(GetOutboundPinholeTimeoutArgs); return ret; } -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_AddPinhole(const char * controlURL, const char * servicetype, const char * remoteHost, const char * remotePort, @@ -868,7 +999,9 @@ UPNP_AddPinhole(const char * controlURL, const char * servicetype, return UPNPCOMMAND_INVALID_ARGS; AddPinholeArgs = calloc(7, sizeof(struct UPNParg)); - // RemoteHost can be wilcarded + if(AddPinholeArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + /* RemoteHost can be wilcarded */ if(strncmp(remoteHost, "empty", 5)==0) { AddPinholeArgs[0].elt = "RemoteHost"; @@ -899,10 +1032,11 @@ UPNP_AddPinhole(const char * controlURL, const char * servicetype, AddPinholeArgs[5].val = leaseTime; buffer = simpleUPnPcommand(-1, controlURL, servicetype, "AddPinhole", AddPinholeArgs, &bufsize); + free(AddPinholeArgs); if(!buffer) return UPNPCOMMAND_HTTP_ERROR; ParseNameValue(buffer, bufsize, &pdata); - free(buffer); buffer = NULL; + free(buffer); p = GetValueFromNameValueList(&pdata, "UniqueID"); if(p) { @@ -912,7 +1046,7 @@ UPNP_AddPinhole(const char * controlURL, const char * servicetype, resVal = GetValueFromNameValueList(&pdata, "errorCode"); if(resVal) { - //printf("AddPortMapping errorCode = '%s'\n", resVal); + /*printf("AddPortMapping errorCode = '%s'\n", resVal);*/ ret = UPNPCOMMAND_UNKNOWN_ERROR; sscanf(resVal, "%d", &ret); } @@ -921,11 +1055,10 @@ UPNP_AddPinhole(const char * controlURL, const char * servicetype, ret = UPNPCOMMAND_SUCCESS; } ClearNameValueList(&pdata); - free(AddPinholeArgs); return ret; } -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_UpdatePinhole(const char * controlURL, const char * servicetype, const char * uniqueID, const char * leaseTime) @@ -941,16 +1074,19 @@ UPNP_UpdatePinhole(const char * controlURL, const char * servicetype, return UPNPCOMMAND_INVALID_ARGS; UpdatePinholeArgs = calloc(3, sizeof(struct UPNParg)); + if(UpdatePinholeArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; UpdatePinholeArgs[0].elt = "UniqueID"; UpdatePinholeArgs[0].val = uniqueID; UpdatePinholeArgs[1].elt = "NewLeaseTime"; UpdatePinholeArgs[1].val = leaseTime; buffer = simpleUPnPcommand(-1, controlURL, servicetype, "UpdatePinhole", UpdatePinholeArgs, &bufsize); + free(UpdatePinholeArgs); if(!buffer) return UPNPCOMMAND_HTTP_ERROR; ParseNameValue(buffer, bufsize, &pdata); - free(buffer); buffer = NULL; + free(buffer); resVal = GetValueFromNameValueList(&pdata, "errorCode"); if(resVal) { @@ -963,11 +1099,10 @@ UPNP_UpdatePinhole(const char * controlURL, const char * servicetype, ret = UPNPCOMMAND_SUCCESS; } ClearNameValueList(&pdata); - free(UpdatePinholeArgs); return ret; } -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_DeletePinhole(const char * controlURL, const char * servicetype, const char * uniqueID) { /*struct NameValueParserData pdata;*/ @@ -982,15 +1117,18 @@ UPNP_DeletePinhole(const char * controlURL, const char * servicetype, const char return UPNPCOMMAND_INVALID_ARGS; DeletePinholeArgs = calloc(2, sizeof(struct UPNParg)); + if(DeletePinholeArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; DeletePinholeArgs[0].elt = "UniqueID"; DeletePinholeArgs[0].val = uniqueID; buffer = simpleUPnPcommand(-1, controlURL, servicetype, "DeletePinhole", DeletePinholeArgs, &bufsize); + free(DeletePinholeArgs); if(!buffer) return UPNPCOMMAND_HTTP_ERROR; /*DisplayNameValueList(buffer, bufsize);*/ ParseNameValue(buffer, bufsize, &pdata); - free(buffer); buffer = NULL; + free(buffer); resVal = GetValueFromNameValueList(&pdata, "errorCode"); if(resVal) { @@ -1002,11 +1140,10 @@ UPNP_DeletePinhole(const char * controlURL, const char * servicetype, const char ret = UPNPCOMMAND_SUCCESS; } ClearNameValueList(&pdata); - free(DeletePinholeArgs); return ret; } -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_CheckPinholeWorking(const char * controlURL, const char * servicetype, const char * uniqueID, int * isWorking) { @@ -1021,14 +1158,19 @@ UPNP_CheckPinholeWorking(const char * controlURL, const char * servicetype, return UPNPCOMMAND_INVALID_ARGS; CheckPinholeWorkingArgs = calloc(4, sizeof(struct UPNParg)); + if(CheckPinholeWorkingArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; CheckPinholeWorkingArgs[0].elt = "UniqueID"; CheckPinholeWorkingArgs[0].val = uniqueID; buffer = simpleUPnPcommand(-1, controlURL, servicetype, "CheckPinholeWorking", CheckPinholeWorkingArgs, &bufsize); + free(CheckPinholeWorkingArgs); if(!buffer) + { return UPNPCOMMAND_HTTP_ERROR; + } ParseNameValue(buffer, bufsize, &pdata); - free(buffer); buffer = NULL; + free(buffer); p = GetValueFromNameValueList(&pdata, "IsWorking"); if(p) @@ -1047,11 +1189,10 @@ UPNP_CheckPinholeWorking(const char * controlURL, const char * servicetype, } ClearNameValueList(&pdata); - free(CheckPinholeWorkingArgs); return ret; } -LIBSPEC int +MINIUPNP_LIBSPEC int UPNP_GetPinholePackets(const char * controlURL, const char * servicetype, const char * uniqueID, int * packets) { @@ -1066,14 +1207,17 @@ UPNP_GetPinholePackets(const char * controlURL, const char * servicetype, return UPNPCOMMAND_INVALID_ARGS; GetPinholePacketsArgs = calloc(4, sizeof(struct UPNParg)); + if(GetPinholePacketsArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; GetPinholePacketsArgs[0].elt = "UniqueID"; GetPinholePacketsArgs[0].val = uniqueID; buffer = simpleUPnPcommand(-1, controlURL, servicetype, "GetPinholePackets", GetPinholePacketsArgs, &bufsize); + free(GetPinholePacketsArgs); if(!buffer) return UPNPCOMMAND_HTTP_ERROR; ParseNameValue(buffer, bufsize, &pdata); - free(buffer); buffer = NULL; + free(buffer); p = GetValueFromNameValueList(&pdata, "PinholePackets"); if(p) @@ -1090,8 +1234,5 @@ UPNP_GetPinholePackets(const char * controlURL, const char * servicetype, } ClearNameValueList(&pdata); - free(GetPinholePacketsArgs); return ret; } - - diff --git a/libs/miniupnpc/src/upnpdev.c b/libs/miniupnpc/src/upnpdev.c new file mode 100644 index 000000000..d89a9934c --- /dev/null +++ b/libs/miniupnpc/src/upnpdev.c @@ -0,0 +1,23 @@ +/* $Id: upnpdev.c,v 1.1 2015/08/28 12:14:19 nanard Exp $ */ +/* Project : miniupnp + * Web : http://miniupnp.free.fr/ + * Author : Thomas BERNARD + * copyright (c) 2005-2015 Thomas Bernard + * This software is subjet to the conditions detailed in the + * provided LICENSE file. */ +#include +#include "upnpdev.h" + +/* freeUPNPDevlist() should be used to + * free the chained list returned by upnpDiscover() */ +void freeUPNPDevlist(struct UPNPDev * devlist) +{ + struct UPNPDev * next; + while(devlist) + { + next = devlist->pNext; + free(devlist); + devlist = next; + } +} + diff --git a/libs/miniupnpc/upnperrors.c b/libs/miniupnpc/src/upnperrors.c similarity index 69% rename from libs/miniupnpc/upnperrors.c rename to libs/miniupnpc/src/upnperrors.c index a48ae10d3..eec403738 100644 --- a/libs/miniupnpc/upnperrors.c +++ b/libs/miniupnpc/src/upnperrors.c @@ -1,9 +1,10 @@ -/* $Id: upnperrors.c,v 1.5 2011/04/10 11:19:36 nanard Exp $ */ -/* Project : miniupnp +/* $Id: upnperrors.c,v 1.11 2023/05/29 21:59:15 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * Project : miniupnp * Author : Thomas BERNARD - * copyright (c) 2007 Thomas Bernard + * copyright (c) 2007-2023 Thomas Bernard * All Right reserved. - * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * This software is subjet to the conditions detailed in the * provided LICENCE file. */ #include @@ -24,10 +25,17 @@ const char * strupnperror(int err) case UPNPCOMMAND_INVALID_ARGS: s = "Miniupnpc Invalid Arguments"; break; + case UPNPCOMMAND_INVALID_RESPONSE: + s = "Miniupnpc Invalid response"; + break; + case UPNPCOMMAND_HTTP_ERROR: + s = "Miniupnpc HTTP error"; + break; case UPNPDISCOVER_SOCKET_ERROR: s = "Miniupnpc Socket error"; break; case UPNPDISCOVER_MEMORY_ERROR: + case UPNPCOMMAND_MEM_ALLOC_ERROR: s = "Miniupnpc Memory allocation error"; break; case 401: @@ -39,6 +47,24 @@ const char * strupnperror(int err) case 501: s = "Action Failed"; break; + case 600: + s = "Argument Value Invalid"; + break; + case 601: + s = "Argument Value Out of Range"; + break; + case 602: + s = "Optional Action Not Implemented"; + break; + case 603: + s = "Out of Memory"; + break; + case 604: + s = "Human Intervention Required"; + break; + case 605: + s = "String Argument Too Long"; + break; case 606: s = "Action not authorized"; break; @@ -64,7 +90,7 @@ const char * strupnperror(int err) s = "ProtocolWildcardingNotAllowed"; break; case 708: - s = "WildcardNotPermittedInSrcIP"; + s = "InvalidLayer2Address"; break; case 709: s = "NoPacketSent"; @@ -97,7 +123,8 @@ const char * strupnperror(int err) s = "ExternalPortOnlySupportsWildcard"; break; default: - s = NULL; + s = "UnknownError"; + break; } return s; } diff --git a/libs/miniupnpc/upnpreplyparse.c b/libs/miniupnpc/src/upnpreplyparse.c similarity index 58% rename from libs/miniupnpc/upnpreplyparse.c rename to libs/miniupnpc/src/upnpreplyparse.c index 482030b35..afbfcc1af 100644 --- a/libs/miniupnpc/upnpreplyparse.c +++ b/libs/miniupnpc/src/upnpreplyparse.c @@ -1,7 +1,8 @@ -/* $Id: upnpreplyparse.c,v 1.11 2011/02/07 16:17:06 nanard Exp $ */ -/* MiniUPnP project +/* $Id: upnpreplyparse.c,v 1.21 2019/04/08 13:30:51 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * (c) 2006-2011 Thomas Bernard + * (c) 2006-2019 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ @@ -15,25 +16,77 @@ static void NameValueParserStartElt(void * d, const char * name, int l) { - struct NameValueParserData * data = (struct NameValueParserData *)d; + struct NameValueParserData * data = (struct NameValueParserData *)d; + data->topelt = 1; if(l>63) l = 63; memcpy(data->curelt, name, l); data->curelt[l] = '\0'; + data->cdata = NULL; + data->cdatalen = 0; +} + +static void +NameValueParserEndElt(void * d, const char * name, int namelen) +{ + struct NameValueParserData * data = (struct NameValueParserData *)d; + struct NameValue * nv; + (void)name; + (void)namelen; + if(!data->topelt) + return; + if(strcmp(data->curelt, "NewPortListing") != 0) + { + int l; + /* standard case. Limited to n chars strings */ + l = data->cdatalen; + nv = malloc(sizeof(struct NameValue)); + if(nv == NULL) + { + /* malloc error */ +#ifdef DEBUG + fprintf(stderr, "%s: error allocating memory", + "NameValueParserEndElt"); +#endif /* DEBUG */ + return; + } + if(l>=(int)sizeof(nv->value)) + l = sizeof(nv->value) - 1; + strncpy(nv->name, data->curelt, 64); + nv->name[63] = '\0'; + if(data->cdata != NULL) + { + memcpy(nv->value, data->cdata, l); + nv->value[l] = '\0'; + } + else + { + nv->value[0] = '\0'; + } + nv->l_next = data->l_head; /* insert in list */ + data->l_head = nv; + } + data->cdata = NULL; + data->cdatalen = 0; + data->topelt = 0; } static void NameValueParserGetData(void * d, const char * datas, int l) { struct NameValueParserData * data = (struct NameValueParserData *)d; - struct NameValue * nv; if(strcmp(data->curelt, "NewPortListing") == 0) { /* specific case for NewPortListing which is a XML Document */ + free(data->portListing); data->portListing = malloc(l + 1); if(!data->portListing) { /* malloc error */ +#ifdef DEBUG + fprintf(stderr, "%s: error allocating memory", + "NameValueParserGetData"); +#endif /* DEBUG */ return; } memcpy(data->portListing, datas, l); @@ -42,15 +95,9 @@ NameValueParserGetData(void * d, const char * datas, int l) } else { - /* standard case. Limited to 63 chars strings */ - nv = malloc(sizeof(struct NameValue)); - if(l>63) - l = 63; - strncpy(nv->name, data->curelt, 64); - nv->name[63] = '\0'; - memcpy(nv->value, datas, l); - nv->value[l] = '\0'; - LIST_INSERT_HEAD( &(data->head), nv, entries); + /* standard case. */ + data->cdata = datas; + data->cdatalen = l; } } @@ -58,19 +105,17 @@ void ParseNameValue(const char * buffer, int bufsize, struct NameValueParserData * data) { - struct xmlparser parser; - LIST_INIT(&(data->head)); - data->portListing = NULL; - data->portListingLength = 0; - /* init xmlparser object */ - parser.xmlstart = buffer; - parser.xmlsize = bufsize; - parser.data = data; - parser.starteltfunc = NameValueParserStartElt; - parser.endeltfunc = 0; - parser.datafunc = NameValueParserGetData; + struct xmlparser parser; + memset(data, 0, sizeof(struct NameValueParserData)); + /* init xmlparser object */ + parser.xmlstart = buffer; + parser.xmlsize = bufsize; + parser.data = data; + parser.starteltfunc = NameValueParserStartElt; + parser.endeltfunc = NameValueParserEndElt; + parser.datafunc = NameValueParserGetData; parser.attfunc = 0; - parsexml(&parser); + parsexml(&parser); } void @@ -83,22 +128,22 @@ ClearNameValueList(struct NameValueParserData * pdata) pdata->portListing = NULL; pdata->portListingLength = 0; } - while((nv = pdata->head.lh_first) != NULL) + while((nv = pdata->l_head) != NULL) { - LIST_REMOVE(nv, entries); + pdata->l_head = nv->l_next; free(nv); } } -char * +char * GetValueFromNameValueList(struct NameValueParserData * pdata, const char * Name) { struct NameValue * nv; char * p = NULL; - for(nv = pdata->head.lh_first; + for(nv = pdata->l_head; (nv != NULL) && (p == NULL); - nv = nv->entries.le_next) + nv = nv->l_next) { if(strcmp(nv->name, Name) == 0) p = nv->value; @@ -131,7 +176,7 @@ GetValueFromNameValueListIgnoreNS(struct NameValueParserData * pdata, } #endif -/* debug all-in-one function +/* debug all-in-one function * do parsing then display to stdout */ #ifdef DEBUG void @@ -140,13 +185,13 @@ DisplayNameValueList(char * buffer, int bufsize) struct NameValueParserData pdata; struct NameValue * nv; ParseNameValue(buffer, bufsize, &pdata); - for(nv = pdata.head.lh_first; + for(nv = pdata.l_head; nv != NULL; - nv = nv->entries.le_next) + nv = nv->l_next) { printf("%s = %s\n", nv->name, nv->value); } ClearNameValueList(&pdata); } -#endif +#endif /* DEBUG */ diff --git a/libs/miniupnpc/src/win32_snprintf.h b/libs/miniupnpc/src/win32_snprintf.h new file mode 100644 index 000000000..1fc284ecf --- /dev/null +++ b/libs/miniupnpc/src/win32_snprintf.h @@ -0,0 +1,71 @@ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * MiniUPnP project + * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ + * (c) 2020 Pali Rohár + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#ifndef WIN32_SNPRINTF_H +#define WIN32_SNPRINTF_H + +#ifdef _WIN32 + +#include + +/* snprintf is supported by: + * - Visual Studio 2015 or new + * - mingw32 with iso c ext + * - mingw-w64 with ansi stdio + * - mingw-w64 6.0.0 or new with ucrt + * - mingw-w64 8.0.0 or new with iso c ext + */ +#if ( \ + (defined(_MSC_VER) && _MSC_VER < 1900) /* Visual Studio older than 2015 */ || \ + (defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) && defined(__NO_ISOCEXT)) /* mingw32 without iso c ext */ || \ + (defined(__MINGW64_VERSION_MAJOR) && /* mingw-w64 not ... */ !( \ + (defined (__USE_MINGW_ANSI_STDIO) && __USE_MINGW_ANSI_STDIO != 0)) /* ... with ansi stdio */ || \ + (__MINGW64_VERSION_MAJOR >= 6 && defined(_UCRT)) /* ... at least 6.0.0 with ucrt */ || \ + (__MINGW64_VERSION_MAJOR >= 8 && !defined(__NO_ISOCEXT)) /* ... at least 8.0.0 with iso c ext */ || \ + 0) || \ +0) + +/* _scprintf is supported by: + * - Visual Studio 2002 or new + * - msvcr70.dll or new + * - msvcrt.dll on Windows XP or new + */ +#if ( \ + (defined(_MSC_VER) && _MSC_VER < 1300) /* Visual Studio older than 2002 */ || \ + (defined(__MSVCRT_VERSION__) && __MSVCRT_VERSION__ < 0x700) /* msvcrt older than 7.0 */ || \ +0) +#define CHECK_SCPRINTF 0 +#define IF_SCPRINTF(expr) 0 +#define ELSE_SCPRINTF(expr) expr +#else +#define CHECK_SCPRINTF 1 +#define IF_SCPRINTF(expr) expr +#define ELSE_SCPRINTF(expr) 0 +#endif + +/* Emulation of snprintf for win32 */ +#define snprintf(buf, size, fmt, ...) ( \ + (((size) != 0 && (buf) != NULL) ? ( /* _snprintf does not work with NULL buffer */ \ + _snprintf((buf), (size), (fmt), __VA_ARGS__), /* _snprintf returns -1 on overflow, so ignore its value */ \ + (((char *)buf)[(size_t)(size)-1] = 0), /* _snprintf does not fill nul byte on overflow */ \ + 0) : 0), \ + (CHECK_SCPRINTF ? IF_SCPRINTF( \ + _scprintf((fmt), __VA_ARGS__) /* calculate return value for snprintf via _scprintf */ \ + ) : ELSE_SCPRINTF( \ + ((size) != 0 && (buf) != NULL) ? \ + strlen((buf)) /* return just length of buffer */ \ + : \ + 1 /* no buffer, impossible to calculate, return just non-zero number */ \ + ) \ + ) \ +) + +#endif + +#endif /* _WIN32 */ + +#endif /* WIN32_SNPRINTF_H */ diff --git a/libs/miniupnpc/testdesc/linksys_WAG200G_desc.values b/libs/miniupnpc/testdesc/linksys_WAG200G_desc.values new file mode 100644 index 000000000..cf4222187 --- /dev/null +++ b/libs/miniupnpc/testdesc/linksys_WAG200G_desc.values @@ -0,0 +1,14 @@ +# values for linksys_WAG200G_desc.xml + +CIF: + servicetype = urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1 + controlurl = /upnp/control/WANCommonIFC1 + eventsuburl = /upnp/event/WANCommonIFC1 + scpdurl = /cmnicfg.xml + +first: + servicetype = urn:schemas-upnp-org:service:WANPPPConnection:1 + controlurl = /upnp/control/WANPPPConn1 + eventsuburl = /upnp/event/WANPPPConn1 + scpdurl = /pppcfg.xml + diff --git a/libs/miniupnpc/testdesc/linksys_WAG200G_desc.xml b/libs/miniupnpc/testdesc/linksys_WAG200G_desc.xml new file mode 100644 index 000000000..d428d73b0 --- /dev/null +++ b/libs/miniupnpc/testdesc/linksys_WAG200G_desc.xml @@ -0,0 +1,110 @@ + + + +1 +0 + +http://192.168.1.1:49152 + +urn:schemas-upnp-org:device:InternetGatewayDevice:1 +LINKSYS WAG200G Gateway +LINKSYS +http://www.linksys.com +LINKSYS WAG200G Gateway +Wireless-G ADSL Home Gateway +WAG200G +http://www.linksys.com +123456789 +uuid:8ca2eb37-1dd2-11b2-86f1-001a709b5aa8 +WAG200G + + +urn:schemas-upnp-org:service:Layer3Forwarding:1 +urn:upnp-org:serviceId:L3Forwarding1 +/upnp/control/L3Forwarding1 +/upnp/event/L3Forwarding1 +/l3frwd.xml + + + + +urn:schemas-upnp-org:device:WANDevice:1 +WANDevice +LINKSYS +http://www.linksys.com/ +Residential Gateway +Internet Connection Sharing +1 +http://www.linksys.com/ +0000001 +uuid:8ca2eb36-1dd2-11b2-86f1-001a709b5aa8 +WAG200G + + +urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1 +urn:upnp-org:serviceId:WANCommonIFC1 +/upnp/control/WANCommonIFC1 +/upnp/event/WANCommonIFC1 +/cmnicfg.xml + + + + +urn:schemas-upnp-org:device:WANConnectionDevice:1 +WANConnectionDevice +LINKSYS +http://www.linksys.com/ +Residential Gateway +Internet Connection Sharing +1 +http://www.linksys.com/ +0000001 +uuid:8ca2eb37-1dd2-11b2-86f0-001a709b5aa8 +WAG200G + + +urn:schemas-upnp-org:service:WANEthernetLinkConfig:1 +urn:upnp-org:serviceId:WANEthLinkC1 +/upnp/control/WANEthLinkC1 +/upnp/event/WANEthLinkC1 +/wanelcfg.xml + + +urn:schemas-upnp-org:service:WANPPPConnection:1 +urn:upnp-org:serviceId:WANPPPConn1 +/upnp/control/WANPPPConn1 +/upnp/event/WANPPPConn1 +/pppcfg.xml + + + + + + +urn:schemas-upnp-org:device:LANDevice:1 +LANDevice +LINKSYS +http://www.linksys.com/ +Residential Gateway +Residential Gateway +1 +http://www.linksys.com/ +0000001 +uuid:8ca2eb36-1dd2-11b2-86f0-001a709b5aa +8 +WAG200G + + +urn:schemas-upnp-org:service:LANHostConfigManagement:1 +urn:upnp-org:serviceId:LANHostCfg1 +/upnp/control/LANHostCfg1 +/upnp/event/LANHostCfg1 +/lanhostc.xml + + + + +http://192.168.1.1/index.htm + + + diff --git a/libs/miniupnpc/testdesc/new_LiveBox_desc.values b/libs/miniupnpc/testdesc/new_LiveBox_desc.values new file mode 100644 index 000000000..c55552e58 --- /dev/null +++ b/libs/miniupnpc/testdesc/new_LiveBox_desc.values @@ -0,0 +1,20 @@ +# values for new_LiveBox_desc.xml + +CIF: + servicetype = urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1 + controlurl = /87895a19/upnp/control/WANCommonIFC1 + eventsuburl = /87895a19/upnp/control/WANCommonIFC1 + scpdurl = /87895a19/gateicfgSCPD.xml + +first: + servicetype = urn:schemas-upnp-org:service:WANPPPConnection:2 + controlurl = /87895a19/upnp/control/WANIPConn1 + eventsuburl = /87895a19/upnp/control/WANIPConn1 + scpdurl = /87895a19/gateconnSCPD_PPP.xml + +IPv6FC: + servicetype = urn:schemas-upnp-org:service:WANIPv6FirewallControl:1 + controlurl = /87895a19/upnp/control/WANIPv6FwCtrl1 + eventsuburl = /87895a19/upnp/control/WANIPv6FwCtrl1 + scpdurl = /87895a19/wanipv6fwctrlSCPD.xml + diff --git a/libs/miniupnpc/testdesc/new_LiveBox_desc.xml b/libs/miniupnpc/testdesc/new_LiveBox_desc.xml new file mode 100644 index 000000000..3e522df59 --- /dev/null +++ b/libs/miniupnpc/testdesc/new_LiveBox_desc.xml @@ -0,0 +1,90 @@ + + + + 1 + 0 + + + VEN_0129&DEV_0000&SUBSYS_03&REV_250417 + GenericUmPass + NetworkInfrastructure.Gateway + Network.Gateway + urn:schemas-upnp-org:device:InternetGatewayDevice:2 + Orange Livebox + Sagemcom + http://www.sagemcom.com/ + Residential Livebox,(DSL,WAN Ethernet) + uuid:87895a19-50f9-3736-a87f-115c230155f8 + Sagemcom,fr,SG30_sip-fr-4.28.35.1 + 3 + LK14129DP441489 + http://192.168.1.1 + + + + image/png + 16 + 16 + 8 + /87895a19/ligd.png + + + + + urn:schemas-upnp-org:device:WANDevice:2 + WANDevice + Sagemcom + http://www.sagemcom.com/ + WAN Device on Sagemcom,fr,SG30_sip-fr-4.28.35.1 + Residential Livebox,(DSL,WAN Ethernet) + 3 + http://www.sagemcom.com/ + LK14129DP441489 + http://192.168.1.1 + uuid:e2397374-53d8-3fc6-8306-593ba1a34625 + + + + urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1 + urn:upnp-org:serviceId:WANCommonIFC1 + /87895a19/upnp/control/WANCommonIFC1 + /87895a19/upnp/control/WANCommonIFC1 + /87895a19/gateicfgSCPD.xml + + + + + urn:schemas-upnp-org:device:WANConnectionDevice:2 + WANConnectionDevice + Sagemcom + http://www.sagemcom.com/ + WanConnectionDevice on Sagemcom,fr,SG30_sip-fr-4.28.35.1 + Residential Livebox,(DSL,WAN Ethernet) + 3 + http://www.sagemcom.com/ + LK14129DP441489 + http://192.168.1.1 + uuid:44598a08-288e-32c9-8a4d-d3c008ede331 + + + + urn:schemas-upnp-org:service:WANPPPConnection:2 + urn:upnp-org:serviceId:WANIPConn1 + /87895a19/upnp/control/WANIPConn1 + /87895a19/upnp/control/WANIPConn1 + /87895a19/gateconnSCPD_PPP.xml + + + urn:schemas-upnp-org:service:WANIPv6FirewallControl:1 + urn:upnp-org:serviceId:WANIPv6FwCtrl1 + /87895a19/upnp/control/WANIPv6FwCtrl1 + /87895a19/upnp/control/WANIPv6FwCtrl1 + /87895a19/wanipv6fwctrlSCPD.xml + + + + + + + + \ No newline at end of file diff --git a/libs/miniupnpc/testigddescparse.c b/libs/miniupnpc/testigddescparse.c deleted file mode 100644 index 1b0cde90e..000000000 --- a/libs/miniupnpc/testigddescparse.c +++ /dev/null @@ -1,64 +0,0 @@ -/* $Id: testigddescparse.c,v 1.2 2009/12/03 13:50:06 nanard Exp $ */ -/* Project : miniupnp - * http://miniupnp.free.fr/ - * Author : Thomas Bernard - * Copyright (c) 2008-2009 Thomas Bernard - * This software is subject to the conditions detailed in the - * LICENCE file provided in this distribution. - * */ -#include -#include -#include -#include "igd_desc_parse.h" -#include "minixml.h" -#include "miniupnpc.h" - -int test_igd_desc_parse(char * buffer, int len) -{ - struct IGDdatas igd; - struct xmlparser parser; - struct UPNPUrls urls; - memset(&igd, 0, sizeof(struct IGDdatas)); - memset(&parser, 0, sizeof(struct xmlparser)); - parser.xmlstart = buffer; - parser.xmlsize = len; - parser.data = &igd; - parser.starteltfunc = IGDstartelt; - parser.endeltfunc = IGDendelt; - parser.datafunc = IGDdata; - parsexml(&parser); - printIGD(&igd); - GetUPNPUrls(&urls, &igd, "http://fake/desc/url/file.xml"); - printf("ipcondescURL='%s'\n", urls.ipcondescURL); - printf("controlURL='%s'\n", urls.controlURL); - printf("controlURL_CIF='%s'\n", urls.controlURL_CIF); - FreeUPNPUrls(&urls); - return 0; -} - -int main(int argc, char * * argv) -{ - FILE * f; - char * buffer; - int len; - int r = 0; - if(argc<2) { - fprintf(stderr, "Usage: %s file.xml\n", argv[0]); - return 1; - } - f = fopen(argv[1], "r"); - if(!f) { - fprintf(stderr, "Cannot open %s for reading.\n", argv[1]); - return 1; - } - fseek(f, 0, SEEK_END); - len = ftell(f); - fseek(f, 0, SEEK_SET); - buffer = malloc(len); - fread(buffer, 1, len, f); - fclose(f); - r = test_igd_desc_parse(buffer, len); - free(buffer); - return r; -} - diff --git a/libs/miniupnpc/testminiwget.sh b/libs/miniupnpc/testminiwget.sh index c048e5bd1..80f90481e 100755 --- a/libs/miniupnpc/testminiwget.sh +++ b/libs/miniupnpc/testminiwget.sh @@ -1,42 +1,91 @@ #!/bin/sh -# $Id: testminiwget.sh,v 1.4 2011/05/09 08:53:15 nanard Exp $ +# $Id: testminiwget.sh,v 1.20 2022/10/21 21:09:42 nanard Exp $ +# vim: tabstop=4 shiftwidth=4 noexpandtab # project miniupnp : http://miniupnp.free.fr/ -# (c) 2011 Thomas Bernard +# or https://miniupnp.tuxfamily.org/ +# (c) 2011-2022 Thomas Bernard # # test program for miniwget.c # is usually invoked by "make check" # # This test program : # 1 - launches a local HTTP server (minihttptestserver) -# 2 - uses testminiwget to retreive data from this server +# 2 - uses testminiwget to retrieve data from this server # 3 - compares served and received data # 4 - kills the local HTTP server and exits # +# The script was tested and works with ksh, bash +# it should now also run with dash -HTTPSERVEROUT=/tmp/httpserverout -EXPECTEDFILE=/tmp/expectedfile -DOWNLOADEDFILE=/tmp/downloadedfile -#ADDR=localhost -ADDR="[::1]" +TMPD=`mktemp -d -t miniwgetXXXXXXXXXX` +if [ -z "$TESTSERVER" ] ; then + TESTSERVER=./build/minihttptestserver +fi +if [ -z "$TESTMINIWGET" ] ; then + TESTMINIWGET=./build/testminiwget +fi +HTTPSERVEROUT="${TMPD}/httpserverout" +EXPECTEDFILE="${TMPD}/expectedfile" +DOWNLOADEDFILE="${TMPD}/downloadedfile" PORT= RET=0 +IPCONFIG=$(which ifconfig) +IP=$(which ip) +if [ "$IP" ] ; then + if ! $IP addr | grep inet6 ; then + HAVE_IPV6=no + fi +else + if [ -z "$IPCONFIG" ] ; then + IPCONFIG="/sbin/ifconfig" + fi -#make minihttptestserver -#make testminiwget + if ! $IPCONFIG -a | grep inet6 ; then + HAVE_IPV6=no + fi +fi + +case "$HAVE_IPV6" in + n|no|0) + ADDR=localhost + SERVERARGS="" + ;; + *) + ADDR="[::1]" + SERVERARGS="-6" + ;; + +esac + +if [ ! -x "$TESTSERVER" ] || [ ! -x "$TESTMINIWGET" ] ; then + echo "Please build $TESTSERVER and $TESTMINIWGET" + #make minihttptestserver + #make testminiwget + exit 1 +fi # launching the test HTTP server -./minihttptestserver -6 -e $EXPECTEDFILE > $HTTPSERVEROUT & -while [ "$PORT" == "" ]; do +$TESTSERVER $SERVERARGS -e $EXPECTEDFILE > $HTTPSERVEROUT & +SERVERPID=$! +while [ -z "$PORT" ]; do + sleep 1 PORT=`cat $HTTPSERVEROUT | sed 's/Listening on port \([0-9]*\)/\1/' ` done +if [ "$PORT" = "*** ERROR ***" ]; then + echo "HTTP test server error" + echo "Network config :" + $IPCONFIG -a + exit 2 +fi echo "Test HTTP server is listening on $PORT" URL1="http://$ADDR:$PORT/index.html" URL2="http://$ADDR:$PORT/chunked" URL3="http://$ADDR:$PORT/addcrap" +URL4="http://$ADDR:$PORT/malformed" echo "standard test ..." -./testminiwget $URL1 "${DOWNLOADEDFILE}.1" +$TESTMINIWGET $URL1 "${DOWNLOADEDFILE}.1" if cmp $EXPECTEDFILE "${DOWNLOADEDFILE}.1" ; then echo "ok" else @@ -45,7 +94,7 @@ else fi echo "chunked transfert encoding test ..." -./testminiwget $URL2 "${DOWNLOADEDFILE}.2" +$TESTMINIWGET $URL2 "${DOWNLOADEDFILE}.2" if cmp $EXPECTEDFILE "${DOWNLOADEDFILE}.2" ; then echo "ok" else @@ -54,7 +103,7 @@ else fi echo "response too long test ..." -./testminiwget $URL3 "${DOWNLOADEDFILE}.3" +$TESTMINIWGET $URL3 "${DOWNLOADEDFILE}.3" if cmp $EXPECTEDFILE "${DOWNLOADEDFILE}.3" ; then echo "ok" else @@ -62,9 +111,12 @@ else RET=1 fi +echo "malformed response test ..." +$TESTMINIWGET $URL4 "${DOWNLOADEDFILE}.4" + # kill the test HTTP server -kill %1 -wait %1 +kill $SERVERPID +wait $SERVERPID # remove temporary files (for success cases) if [ $RET -eq 0 ]; then @@ -72,8 +124,10 @@ if [ $RET -eq 0 ]; then rm -f "${DOWNLOADEDFILE}.2" rm -f "${DOWNLOADEDFILE}.3" rm -f $EXPECTEDFILE $HTTPSERVEROUT + rmdir ${TMPD} else echo "at least one of the test FAILED" + echo "directory ${TMPD} is left intact" fi exit $RET diff --git a/libs/miniupnpc/testreplyparse/DeletePortMapping.namevalue b/libs/miniupnpc/testreplyparse/DeletePortMapping.namevalue new file mode 100644 index 000000000..48ca0cccb --- /dev/null +++ b/libs/miniupnpc/testreplyparse/DeletePortMapping.namevalue @@ -0,0 +1,3 @@ +NewRemoteHost= +NewExternalPort=123 +NewProtocol=TCP diff --git a/libs/miniupnpc/testreplyparse/DeletePortMapping.xml b/libs/miniupnpc/testreplyparse/DeletePortMapping.xml new file mode 100644 index 000000000..a955c53fc --- /dev/null +++ b/libs/miniupnpc/testreplyparse/DeletePortMapping.xml @@ -0,0 +1,6 @@ + +123 +TCP + + + diff --git a/libs/miniupnpc/testreplyparse/GetExternalIPAddress.namevalue b/libs/miniupnpc/testreplyparse/GetExternalIPAddress.namevalue new file mode 100644 index 000000000..5aa75f882 --- /dev/null +++ b/libs/miniupnpc/testreplyparse/GetExternalIPAddress.namevalue @@ -0,0 +1,2 @@ +NewExternalIPAddress=1.2.3.4 + diff --git a/libs/miniupnpc/testreplyparse/GetExternalIPAddress.xml b/libs/miniupnpc/testreplyparse/GetExternalIPAddress.xml new file mode 100644 index 000000000..db7ec1f9c --- /dev/null +++ b/libs/miniupnpc/testreplyparse/GetExternalIPAddress.xml @@ -0,0 +1,2 @@ +1.2.3.4 + diff --git a/libs/miniupnpc/testreplyparse/GetSpecificPortMappingEntryReq.namevalue b/libs/miniupnpc/testreplyparse/GetSpecificPortMappingEntryReq.namevalue new file mode 100644 index 000000000..26b169c35 --- /dev/null +++ b/libs/miniupnpc/testreplyparse/GetSpecificPortMappingEntryReq.namevalue @@ -0,0 +1,3 @@ +NewProtocol=UDP +NewExternalPort=12345 +NewRemoteHost= diff --git a/libs/miniupnpc/testreplyparse/GetSpecificPortMappingEntryReq.xml b/libs/miniupnpc/testreplyparse/GetSpecificPortMappingEntryReq.xml new file mode 100644 index 000000000..bbb540eac --- /dev/null +++ b/libs/miniupnpc/testreplyparse/GetSpecificPortMappingEntryReq.xml @@ -0,0 +1,3 @@ + +12345UDP + diff --git a/libs/miniupnpc/testreplyparse/GetSpecificPortMappingEntryResp.namevalue b/libs/miniupnpc/testreplyparse/GetSpecificPortMappingEntryResp.namevalue new file mode 100644 index 000000000..2189789b4 --- /dev/null +++ b/libs/miniupnpc/testreplyparse/GetSpecificPortMappingEntryResp.namevalue @@ -0,0 +1,5 @@ +NewInternalPort=12345 +NewInternalClient=192.168.10.110 +NewEnabled=1 +NewPortMappingDescription=libminiupnpc +NewLeaseDuration=0 diff --git a/libs/miniupnpc/testreplyparse/GetSpecificPortMappingEntryResp.xml b/libs/miniupnpc/testreplyparse/GetSpecificPortMappingEntryResp.xml new file mode 100644 index 000000000..77e8d9c7c --- /dev/null +++ b/libs/miniupnpc/testreplyparse/GetSpecificPortMappingEntryResp.xml @@ -0,0 +1,2 @@ +12345192.168.10.1101libminiupnpc0 + diff --git a/libs/miniupnpc/testreplyparse/SetDefaultConnectionService.namevalue b/libs/miniupnpc/testreplyparse/SetDefaultConnectionService.namevalue new file mode 100644 index 000000000..f78c7e2ae --- /dev/null +++ b/libs/miniupnpc/testreplyparse/SetDefaultConnectionService.namevalue @@ -0,0 +1 @@ +NewDefaultConnectionService=uuid:c6c05a33-f704-48df-9910-e099b3471d81:WANConnectionDevice:1,INVALID_SERVICE_ID diff --git a/libs/miniupnpc/testreplyparse/SetDefaultConnectionService.xml b/libs/miniupnpc/testreplyparse/SetDefaultConnectionService.xml new file mode 100644 index 000000000..ac04c07a9 --- /dev/null +++ b/libs/miniupnpc/testreplyparse/SetDefaultConnectionService.xml @@ -0,0 +1 @@ +uuid:c6c05a33-f704-48df-9910-e099b3471d81:WANConnectionDevice:1,INVALID_SERVICE_ID diff --git a/libs/miniupnpc/testreplyparse/readme.txt b/libs/miniupnpc/testreplyparse/readme.txt new file mode 100644 index 000000000..3eb1f015f --- /dev/null +++ b/libs/miniupnpc/testreplyparse/readme.txt @@ -0,0 +1,7 @@ +This directory contains files used for validation of upnpreplyparse.c code. + +Each .xml file to parse should give the results which are in the .namevalue +file. + +A .namevalue file contain name=value lines. + diff --git a/libs/miniupnpc/testupnpigd.py b/libs/miniupnpc/testupnpigd.py index 6d167a4ce..806b4f4f8 100644 --- a/libs/miniupnpc/testupnpigd.py +++ b/libs/miniupnpc/testupnpigd.py @@ -1,14 +1,18 @@ -#! /usr/bin/python -# $Id: testupnpigd.py,v 1.4 2008/10/11 10:27:20 nanard Exp $ +#! /usr/bin/env python +# $Id: testupnpigd.py,v 1.7 2020/04/06 10:23:02 nanard Exp $ # MiniUPnP project # Author : Thomas Bernard # This Sample code is public domain. -# website : http://miniupnp.tuxfamily.org/ +# website : https://miniupnp.tuxfamily.org/ # import the python miniupnpc module import miniupnpc import socket -import BaseHTTPServer + +try: + from http.server import BaseHTTPRequestHandler, HTTPServer +except ImportError: + from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer # function definition def list_redirections(): @@ -17,15 +21,15 @@ def list_redirections(): p = u.getgenericportmapping(i) if p==None: break - print i, p + print(i, p) i = i + 1 #define the handler class for HTTP connections -class handler_class(BaseHTTPServer.BaseHTTPRequestHandler): +class handler_class(BaseHTTPRequestHandler): def do_GET(self): self.send_response(200) self.end_headers() - self.wfile.write("OK MON GARS") + self.wfile.write(b"OK MON GARS") # create the object u = miniupnpc.UPnP() @@ -37,20 +41,20 @@ u = miniupnpc.UPnP() u.discoverdelay = 200; try: - print 'Discovering... delay=%ums' % u.discoverdelay + print('Discovering... delay=%ums' % u.discoverdelay) ndevices = u.discover() - print ndevices, 'device(s) detected' + print(ndevices, 'device(s) detected') # select an igd u.selectigd() # display information about the IGD and the internet connection - print 'local ip address :', u.lanaddr + print('local ip address :', u.lanaddr) externalipaddress = u.externalipaddress() - print 'external ip address :', externalipaddress - print u.statusinfo(), u.connectiontype() + print('external ip address :', externalipaddress) + print(u.statusinfo(), u.connectiontype()) #instanciate a HTTPd object. The port is assigned by the system. - httpd = BaseHTTPServer.HTTPServer((u.lanaddr, 0), handler_class) + httpd = HTTPServer((u.lanaddr, 0), handler_class) eport = httpd.server_port # find a free port for the redirection @@ -59,26 +63,26 @@ try: eport = eport + 1 r = u.getspecificportmapping(eport, 'TCP') - print 'trying to redirect %s port %u TCP => %s port %u TCP' % (externalipaddress, eport, u.lanaddr, httpd.server_port) + print('trying to redirect %s port %u TCP => %s port %u TCP' % (externalipaddress, eport, u.lanaddr, httpd.server_port)) b = u.addportmapping(eport, 'TCP', u.lanaddr, httpd.server_port, 'UPnP IGD Tester port %u' % eport, '') if b: - print 'Success. Now waiting for some HTTP request on http://%s:%u' % (externalipaddress ,eport) + print('Success. Now waiting for some HTTP request on http://%s:%u' % (externalipaddress ,eport)) try: httpd.handle_request() httpd.server_close() - except KeyboardInterrupt, details: - print "CTRL-C exception!", details + except KeyboardInterrupt as details: + print("CTRL-C exception!", details) b = u.deleteportmapping(eport, 'TCP') if b: - print 'Successfully deleted port mapping' + print('Successfully deleted port mapping') else: - print 'Failed to remove port mapping' + print('Failed to remove port mapping') else: - print 'Failed' + print('Failed') httpd.server_close() -except Exception, e: - print 'Exception :', e +except Exception as e: + print('Exception :', e) diff --git a/libs/miniupnpc/testupnpreplyparse.c b/libs/miniupnpc/testupnpreplyparse.c deleted file mode 100644 index a02e8f6a6..000000000 --- a/libs/miniupnpc/testupnpreplyparse.c +++ /dev/null @@ -1,44 +0,0 @@ -/* $Id: testupnpreplyparse.c,v 1.2 2008/02/21 13:05:27 nanard Exp $ */ -/* MiniUPnP project - * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * (c) 2006-2007 Thomas Bernard - * This software is subject to the conditions detailed - * in the LICENCE file provided within the distribution */ -#include -#include -#include "upnpreplyparse.h" - -void -test_parsing(const char * buf, int len) -{ - struct NameValueParserData pdata; - ParseNameValue(buf, len, &pdata); - ClearNameValueList(&pdata); -} - -int main(int argc, char * * argv) -{ - FILE * f; - char buffer[4096]; - int l; - if(argc<2) - { - fprintf(stderr, "Usage: %s file.xml\n", argv[0]); - return 1; - } - f = fopen(argv[1], "r"); - if(!f) - { - fprintf(stderr, "Error : can not open file %s\n", argv[1]); - return 2; - } - l = fread(buffer, 1, sizeof(buffer)-1, f); - fclose(f); - buffer[l] = '\0'; -#ifdef DEBUG - DisplayNameValueList(buffer, l); -#endif - test_parsing(buffer, l); - return 0; -} - diff --git a/libs/miniupnpc/testupnpreplyparse.sh b/libs/miniupnpc/testupnpreplyparse.sh new file mode 100644 index 000000000..a917c4b84 --- /dev/null +++ b/libs/miniupnpc/testupnpreplyparse.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +if [ -z "$TESTUPNPREPLYPARSE" ] ; then + TESTUPNPREPLYPARSE=./build/testupnpreplyparse +fi + +for f in testreplyparse/*.xml ; do + bf="`dirname $f`/`basename $f .xml`" + if $TESTUPNPREPLYPARSE $f $bf.namevalue ; then + echo "$f : passed" + else + echo "$f : FAILED" + exit 1 + fi +done + +exit 0 diff --git a/libs/miniupnpc/updateminiupnpcstrings.sh b/libs/miniupnpc/updateminiupnpcstrings.sh index dde4354a8..7c42eea5b 100755 --- a/libs/miniupnpc/updateminiupnpcstrings.sh +++ b/libs/miniupnpc/updateminiupnpcstrings.sh @@ -1,12 +1,23 @@ #! /bin/sh -# $Id: updateminiupnpcstrings.sh,v 1.7 2011/01/04 11:41:53 nanard Exp $ +# $Id: updateminiupnpcstrings.sh,v 1.10 2022/10/16 05:28:15 nanard Exp $ # project miniupnp : http://miniupnp.free.fr/ -# (c) 2009 Thomas Bernard +# (c) 2009-2021 Thomas Bernard FILE=miniupnpcstrings.h -TMPFILE=miniupnpcstrings.h.tmp TEMPLATE_FILE=${FILE}.in +if [ -n "$1" ] ; then + FILE="$1" +fi +if [ -n "$2" ] ; then + TEMPLATE_FILE="$2" +fi +TMPFILE=`mktemp -t miniupnpcstringsXXXXXX` +if [ ! -f "$TMPFILE" ] ; then + echo "mktemp failure" + exit 1 +fi + # detecting the OS name and version OS_NAME=`uname -s` OS_VERSION=`uname -r` @@ -14,11 +25,14 @@ if [ -f /etc/debian_version ]; then OS_NAME=Debian OS_VERSION=`cat /etc/debian_version` fi + # use lsb_release (Linux Standard Base) when available LSB_RELEASE=`which lsb_release` if [ 0 -eq $? -a -x "${LSB_RELEASE}" ]; then - OS_NAME=`${LSB_RELEASE} -i -s` - OS_VERSION=`${LSB_RELEASE} -r -s` + # On NixOS, lsb_release returns strings such as "NixOS" (with quotes), + # so we need to stript them with the following xargs trick: + OS_NAME=`${LSB_RELEASE} -i -s | xargs echo` + OS_VERSION=`${LSB_RELEASE} -r -s | xargs echo` case $OS_NAME in Debian) #OS_VERSION=`${LSB_RELEASE} -c -s` @@ -49,5 +63,5 @@ sed -e "$EXPR" < $TEMPLATE_FILE > $TMPFILE EXPR="s|MINIUPNPC_VERSION_STRING \".*\"|MINIUPNPC_VERSION_STRING \"${MINIUPNPC_VERSION}\"|" echo "setting MINIUPNPC_VERSION_STRING macro value to ${MINIUPNPC_VERSION} in $FILE." sed -e "$EXPR" < $TMPFILE > $FILE -rm $TMPFILE +rm $TMPFILE && echo "$TMPFILE deleted" diff --git a/libs/miniupnpc/wingenminiupnpcstrings.c b/libs/miniupnpc/wingenminiupnpcstrings.c index 38dd01783..6dcbfa4ae 100644 --- a/libs/miniupnpc/wingenminiupnpcstrings.c +++ b/libs/miniupnpc/wingenminiupnpcstrings.c @@ -1,8 +1,8 @@ -/* $Id: wingenminiupnpcstrings.c,v 1.2 2011/01/11 15:31:13 nanard Exp $ */ +/* $Id: wingenminiupnpcstrings.c,v 1.6 2021/08/21 09:43:40 nanard Exp $ */ /* Project: miniupnp - * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * Author: Thomas Bernard - * Copyright (c) 2005-2009 Thomas Bernard + * Copyright (c) 2005-2021 Thomas Bernard * This software is subjects to the conditions detailed * in the LICENSE file provided within this distribution */ #include @@ -26,7 +26,7 @@ int main(int argc, char * * argv) { dwBuildNumber : The build number of the operating system. dwPlatformId - The operating system platform. This member can be the following value. + The operating system platform. This member can be the following value. szCSDVersion A null-terminated string, such as "Service Pack 3", that indicates the latest Service Pack installed on the system. If no Service Pack has @@ -44,7 +44,7 @@ int main(int argc, char * * argv) { fin = fopen("VERSION", "r"); fgets(miniupnpcVersion, sizeof(miniupnpcVersion), fin); fclose(fin); - for(n = 0; n < sizeof(miniupnpcVersion); n++) { + for(n = 0; n < (int)sizeof(miniupnpcVersion); n++) { if(miniupnpcVersion[n] < ' ') miniupnpcVersion[n] = '\0'; } @@ -59,6 +59,7 @@ int main(int argc, char * * argv) { fout = fopen(argv[2], "w"); if(!fout) { fprintf(stderr, "Cannot open %s for writing.\n", argv[2]); + fclose(fin); return 1; } n = 0; @@ -78,5 +79,37 @@ int main(int argc, char * * argv) { fclose(fout); printf("%d lines written to %s.\n", n, argv[2]); } + if(argc >= 4) { + fout = fopen(argv[3], "w"); + if(fout == NULL) { + fprintf(stderr, "Cannot open %s for writing.\n", argv[2]); + return 1; + } else { + char * cur, * next; + fprintf(fout, "#define LIBMINIUPNPC_DOTTED_VERSION \"%s\"\n", miniupnpcVersion); + next = strchr(miniupnpcVersion, '.'); + if (next && *next) { + *next = '\0'; + next++; + } + fprintf(fout, "#define LIBMINIUPNPC_MAJOR_VERSION %s\n", miniupnpcVersion); + cur = next; + next = strchr(cur, '.'); + if (next && *next) { + *next = '\0'; + next++; + } + fprintf(fout, "#define LIBMINIUPNPC_MINOR_VERSION %s\n", cur); + cur = next; + next = strchr(cur, '.'); + if (next && *next) { + *next = '\0'; + next++; + } + fprintf(fout, "#define LIBMINIUPNPC_MICRO_VERSION %s\n", cur); + fclose(fout); + printf("%s written\n", argv[3]); + } + } return 0; } From 95803eca16fb77c3e0a94f4a4f50ca58f095f81a Mon Sep 17 00:00:00 2001 From: Logan-A Date: Sat, 30 Dec 2023 13:29:22 -0500 Subject: [PATCH 195/227] Update win32.mk remove miniupnc block --- src/Makefile.d/win32.mk | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Makefile.d/win32.mk b/src/Makefile.d/win32.mk index 73a3d9e45..b02276cfc 100644 --- a/src/Makefile.d/win32.mk +++ b/src/Makefile.d/win32.mk @@ -34,11 +34,9 @@ endif endif ifndef NONET -ifndef MINGW64 # miniupnc is broken with MINGW64 opts+=-I../libs -DSTATIC_MINIUPNPC libs+=-L../libs/miniupnpc/mingw$(32) -lws2_32 -liphlpapi endif -endif ifndef MINGW64 32=32 From 322dfac85fcc031d00154caaf7b62fbc1b554727 Mon Sep 17 00:00:00 2001 From: Logan-A Date: Sat, 30 Dec 2023 18:00:21 -0500 Subject: [PATCH 196/227] update SRB2 to use miniUPnPc API version 17 update miniUPnPc function calls to API version 17 update include statements for miniUPnPcc use PKGCONFIG to find lib and inc forminiUPnPc --- src/Makefile.d/features.mk | 9 ++++++--- src/Makefile.d/win32.mk | 10 +++++----- src/i_tcp.c | 18 ++++++++---------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/Makefile.d/features.mk b/src/Makefile.d/features.mk index 20963adac..2275754b7 100644 --- a/src/Makefile.d/features.mk +++ b/src/Makefile.d/features.mk @@ -49,10 +49,13 @@ CURLCONFIG?=curl-config $(eval $(call Configure,CURL,$(CURLCONFIG))) opts+=-DHAVE_CURL endif -endif -ifdef HAVE_MINIUPNPC -libs+=-lminiupnpc +ifndef NOUPNP +MINIUPNPC_PKGCONFIG?=miniupnpc +$(eval $(call Use_pkg_config,MINIUPNPC)) +HAVE_MINIUPNPC=1 +opts+=-DHAVE_MINIUPNP +endif endif # (Valgrind is a memory debugger.) diff --git a/src/Makefile.d/win32.mk b/src/Makefile.d/win32.mk index b02276cfc..3e60e2416 100644 --- a/src/Makefile.d/win32.mk +++ b/src/Makefile.d/win32.mk @@ -33,11 +33,6 @@ libs+=-lws2_32 endif endif -ifndef NONET -opts+=-I../libs -DSTATIC_MINIUPNPC -libs+=-L../libs/miniupnpc/mingw$(32) -lws2_32 -liphlpapi -endif - ifndef MINGW64 32=32 x86=x86 @@ -100,3 +95,8 @@ lib:=../libs/curl CURL_opts:=-I$(lib)/include CURL_libs:=-L$(lib)/lib$(32) -lcurl $(eval $(call _set,CURL)) + +lib:=../libs/miniupnpc +MINIUPNPC_opts:=-I$(lib)/include -DMINIUPNP_STATICLIB +MINIUPNPC_libs:=-L$(lib)/mingw$(32) -lminiupnpc -lws2_32 -liphlpapi +$(eval $(call _set,MINIUPNPC)) diff --git a/src/i_tcp.c b/src/i_tcp.c index d95b381f4..bd960f2ca 100644 --- a/src/i_tcp.c +++ b/src/i_tcp.c @@ -117,13 +117,9 @@ } mysockaddr_t; #ifdef HAVE_MINIUPNPC - #ifdef STATIC_MINIUPNPC - #define STATICLIB - #endif - #include "miniupnpc/miniwget.h" - #include "miniupnpc/miniupnpc.h" - #include "miniupnpc/upnpcommands.h" - #undef STATICLIB + #include "miniwget.h" + #include "miniupnpc.h" + #include "upnpcommands.h" static UINT8 UPNP_support = TRUE; #endif // HAVE_MINIUPNC @@ -279,8 +275,10 @@ static inline void I_InitUPnP(void) { struct UPNPDev * devlist = NULL; int upnp_error = -2; + int scope_id = 0; + int status_code = 0; CONS_Printf(M_GetText("Looking for UPnP Internet Gateway Device\n")); - devlist = upnpDiscover(2000, NULL, NULL, 0, false, &upnp_error); + devlist = upnpDiscover(2000, NULL, NULL, 0, false, 2, &upnp_error); if (devlist) { struct UPNPDev *dev = devlist; @@ -300,7 +298,7 @@ static inline void I_InitUPnP(void) UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr)); CONS_Printf(M_GetText("Local LAN IP address: %s\n"), lanaddr); - descXML = miniwget(dev->descURL, &descXMLsize); + descXML = miniwget(dev->descURL, &descXMLsize, scope_id, &status_code); if (descXML) { parserootdesc(descXML, descXMLsize, &data); @@ -308,7 +306,7 @@ static inline void I_InitUPnP(void) descXML = NULL; memset(&urls, 0, sizeof(struct UPNPUrls)); memset(&data, 0, sizeof(struct IGDdatas)); - GetUPNPUrls(&urls, &data, dev->descURL); + GetUPNPUrls(&urls, &data, dev->descURL, status_code); I_AddExitFunc(I_ShutdownUPnP); } freeUPNPDevlist(devlist); From 144cbd538519b0dc6f525904e4badbf7c2daed8b Mon Sep 17 00:00:00 2001 From: Logan Aerl Arias Date: Sat, 30 Dec 2023 23:32:54 +0000 Subject: [PATCH 197/227] Update .gitlab-ci.yml file add libminiupnpc-dev to the build --- .gitlab-ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 46fa6584d..77ae9fb6b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -194,7 +194,7 @@ Debian testing GCC: - - | # apt_development echo -e "\e[0Ksection_start:`date +%s`:apt_development[collapsed=true]\r\e[0KInstalling development packages" - - apt-get install libsdl2-mixer-dev libpng-dev libcurl4-openssl-dev libgme-dev libopenmpt-dev + - apt-get install libsdl2-mixer-dev libpng-dev libcurl4-openssl-dev libgme-dev libopenmpt-dev libminiupnpc-dev - | # apt_development echo -e "\e[0Ksection_end:`date +%s`:apt_development\r\e[0K" @@ -270,7 +270,7 @@ Debian stable:amd64: - - | # apt_development echo -e "\e[0Ksection_start:`date +%s`:apt_development[collapsed=true]\r\e[0KInstalling development packages" - - apt-get install libsdl2-mixer-dev:amd64 libpng-dev:amd64 libcurl4-openssl-dev:amd64 libgme-dev:amd64 libopenmpt-dev:amd64 + - apt-get install libsdl2-mixer-dev:amd64 libpng-dev:amd64 libcurl4-openssl-dev:amd64 libgme-dev:amd64 libopenmpt-dev:amd64 libminiupnpc-dev:amd64 - | # apt_development echo -e "\e[0Ksection_end:`date +%s`:apt_development\r\e[0K" @@ -313,7 +313,7 @@ Debian stable:i386: - - | # apt_development echo -e "\e[0Ksection_start:`date +%s`:apt_development[collapsed=true]\r\e[0KInstalling development packages" - - apt-get install libsdl2-mixer-dev:i386 libpng-dev:i386 libcurl4-openssl-dev:i386 libgme-dev:i386 libopenmpt-dev:i386 + - apt-get install libsdl2-mixer-dev:i386 libpng-dev:i386 libcurl4-openssl-dev:i386 libgme-dev:i386 libopenmpt-dev:i386 libminiupnpc-dev:i386 - | # apt_development echo -e "\e[0Ksection_end:`date +%s`:apt_development\r\e[0K" @@ -357,7 +357,7 @@ Debian stable:arm64: - - | # apt_development echo -e "\e[0Ksection_start:`date +%s`:apt_development[collapsed=true]\r\e[0KInstalling development packages" - - apt-get install libsdl2-mixer-dev:arm64 libpng-dev:arm64 libcurl4-openssl-dev:arm64 libgme-dev:arm64 libopenmpt-dev:arm64 + - apt-get install libsdl2-mixer-dev:arm64 libpng-dev:arm64 libcurl4-openssl-dev:arm64 libgme-dev:arm64 libopenmpt-dev:arm64 libminiupnpc-dev:arm64 - | # apt_development echo -e "\e[0Ksection_end:`date +%s`:apt_development\r\e[0K" @@ -434,7 +434,7 @@ Debian stable Clang: - - | # apt_development echo -e "\e[0Ksection_start:`date +%s`:apt_development[collapsed=true]\r\e[0KInstalling development packages" - - apt-get install libsdl2-mixer-dev libpng-dev libcurl4-openssl-dev libgme-dev libopenmpt-dev + - apt-get install libsdl2-mixer-dev libpng-dev libcurl4-openssl-dev libgme-dev libopenmpt-dev libminiupnpc-dev - | # apt_development echo -e "\e[0Ksection_end:`date +%s`:apt_development\r\e[0K" @@ -628,7 +628,7 @@ Alpine 3 GCC: - - | # apk_development echo -e "\e[0Ksection_start:`date +%s`:apk_development[collapsed=true]\r\e[0KInstalling development packages" - - apk add musl-dev sdl2_mixer-dev libpng-dev curl-dev libgme-dev libopenmpt-dev + - apk add musl-dev sdl2_mixer-dev libpng-dev curl-dev libgme-dev libopenmpt-dev miniupnpc-dev - | # apk_development echo -e "\e[0Ksection_end:`date +%s`:apk_development\r\e[0K" From 9c3ac6c86f3912b57668efbc9f95eb0a7bb0a7a1 Mon Sep 17 00:00:00 2001 From: Alam Ed Arias Date: Sat, 30 Dec 2023 18:46:20 -0500 Subject: [PATCH 198/227] Fix compiling on Linux/GNU systems with miniupnpc --- src/i_tcp.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/i_tcp.c b/src/i_tcp.c index bd960f2ca..387cf4f5e 100644 --- a/src/i_tcp.c +++ b/src/i_tcp.c @@ -117,10 +117,16 @@ } mysockaddr_t; #ifdef HAVE_MINIUPNPC + #ifdef MINIUPNP_STATICLIB #include "miniwget.h" #include "miniupnpc.h" #include "upnpcommands.h" - static UINT8 UPNP_support = TRUE; + #else + #include "miniupnpc/miniwget.h" + #include "miniupnpc/miniupnpc.h" + #include "miniupnpc/upnpcommands.h" + #endif + static boolean UPNP_support = true; #endif // HAVE_MINIUPNC #endif // !NONET From acc78d0ee511bbd5dd787bf16992014875e2ce85 Mon Sep 17 00:00:00 2001 From: Logan-A Date: Sat, 30 Dec 2023 23:41:34 -0500 Subject: [PATCH 199/227] Update i_tcp.c made UPnP port mapping enabled by default lowered UPnP Discover delay from 2000 ms to 500 ms only look for first InternetGatewayDevice found remove UPnP port mapping on shutdown --- src/i_tcp.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/i_tcp.c b/src/i_tcp.c index 387cf4f5e..05e678acd 100644 --- a/src/i_tcp.c +++ b/src/i_tcp.c @@ -268,23 +268,24 @@ static const char* inet_ntopA(short af, const void *cp, char *buf, socklen_t len #endif #ifdef HAVE_MINIUPNPC // based on old XChat patch +static void I_ShutdownUPnP(void); static struct UPNPUrls urls; static struct IGDdatas data; static char lanaddr[64]; -static void I_ShutdownUPnP(void) -{ - FreeUPNPUrls(&urls); -} - static inline void I_InitUPnP(void) { + const char * const deviceTypes[] = { + "urn:schemas-upnp-org:device:InternetGatewayDevice:2", + "urn:schemas-upnp-org:device:InternetGatewayDevice:1", + 0 + }; struct UPNPDev * devlist = NULL; int upnp_error = -2; int scope_id = 0; int status_code = 0; CONS_Printf(M_GetText("Looking for UPnP Internet Gateway Device\n")); - devlist = upnpDiscover(2000, NULL, NULL, 0, false, 2, &upnp_error); + devlist = upnpDiscoverDevices(deviceTypes, 500, NULL, NULL, 0, false, 2, &upnp_error, 0); if (devlist) { struct UPNPDev *dev = devlist; @@ -340,6 +341,12 @@ static inline void I_UPnP_rem(const char *port, const char * servicetype) UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port, servicetype, NULL); } + +static void I_ShutdownUPnP(void) +{ + I_UPnP_rem(serverport_name, "UDP"); + FreeUPNPUrls(&urls); +} #endif static const char *SOCK_AddrToStr(mysockaddr_t *sk) @@ -1130,10 +1137,10 @@ boolean I_InitTcpDriver(void) { I_AddExitFunc(I_ShutdownTcpDriver); #ifdef HAVE_MINIUPNPC - if (M_CheckParm("-useUPnP")) - I_InitUPnP(); - else + if (M_CheckParm("-noUPnP")) UPNP_support = false; + else + I_InitUPnP(); #endif } return init_tcp_driver; From 40d43dfa13fb4114d344ac48b687c7add20c34ba Mon Sep 17 00:00:00 2001 From: Logan Aerl Arias Date: Sun, 31 Dec 2023 05:06:38 +0000 Subject: [PATCH 200/227] Update .gitlab-ci.yml file disable uPnP in Debian stable musl --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 77ae9fb6b..40b418c0f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -485,7 +485,7 @@ Debian stable musl: - - | # make echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" - - make --directory=src --keep-going CCACHE=1 ERRORMODE=1 NONX86=1 SDL=0 NOHW=1 NOZLIB=1 NOCURL=1 NOGME=1 NOOPENMPT=1 || make --directory=src --keep-going CCACHE=1 ERRORMODE=1 NONX86=1 SDL=0 NOHW=1 NOZLIB=1 NOCURL=1 NOGME=1 NOOPENMPT=1 + - make --directory=src --keep-going CCACHE=1 ERRORMODE=1 NONX86=1 SDL=0 NOHW=1 NOZLIB=1 NOCURL=1 NOGME=1 NOOPENMPT=1 || make --directory=src --keep-going CCACHE=1 ERRORMODE=1 NONX86=1 SDL=0 NOHW=1 NOZLIB=1 NOCURL=1 NOGME=1 NOOPENMPT=1 NOUPNP=1 - | # make echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" From 9dea09a7cccfd8f8ddfb5875e9f6f9c6355e2146 Mon Sep 17 00:00:00 2001 From: Logan-A Date: Sun, 31 Dec 2023 00:13:13 -0500 Subject: [PATCH 201/227] Update features.mk change HAVE_MINIUPNPC to NOUPNP --- src/Makefile.d/features.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Makefile.d/features.mk b/src/Makefile.d/features.mk index 2275754b7..6a5df757a 100644 --- a/src/Makefile.d/features.mk +++ b/src/Makefile.d/features.mk @@ -5,7 +5,7 @@ passthru_opts+=\ NONET NO_IPV6 NOHW NOMD5 NOPOSTPROCESSING\ MOBJCONSISTANCY PACKETDROP ZDEBUG\ - HAVE_MINIUPNPC NOEXECINFO\ + NOUPNP NOEXECINFO\ # build with debugging information ifdef DEBUGMODE From e7d972757bb2a104e6c4d1fc36a10c0186463a8d Mon Sep 17 00:00:00 2001 From: Logan Aerl Arias Date: Sun, 31 Dec 2023 14:32:06 +0000 Subject: [PATCH 202/227] Revert "Merge branch 'update-quittime-while-idling' into 'next'" This reverts merge request !2210 --- src/netcode/d_clisrv.c | 33 --------------------------------- src/p_tick.c | 16 +++++++++++++++- 2 files changed, 15 insertions(+), 34 deletions(-) diff --git a/src/netcode/d_clisrv.c b/src/netcode/d_clisrv.c index fc5aa9afd..d222920c3 100644 --- a/src/netcode/d_clisrv.c +++ b/src/netcode/d_clisrv.c @@ -116,8 +116,6 @@ consvar_t cv_playbackspeed = CVAR_INIT ("playbackspeed", "1", 0, playbackspeed_c consvar_t cv_idletime = CVAR_INIT ("idletime", "0", CV_SAVE, CV_Unsigned, NULL); consvar_t cv_dedicatedidletime = CVAR_INIT ("dedicatedidletime", "10", CV_SAVE, CV_Unsigned, NULL); -static INT32 D_NumNodes(boolean skiphost); - void ResetNode(INT32 node) { memset(&netnodes[node], 0, sizeof(*netnodes)); @@ -1282,7 +1280,6 @@ static void UpdatePingTable(void) } } -// Handle idle and disconnected player timers static void IdleUpdate(void) { INT32 i; @@ -1305,26 +1302,7 @@ static void IdleUpdate(void) } } else - { players[i].lastinputtime = 0; - - if (players[i].quittime && playeringame[i]) - { - players[i].quittime++; - - if (players[i].quittime == 30 * TICRATE && G_TagGametype()) - P_CheckSurvivors(); - - if (server && players[i].quittime >= (tic_t)FixedMul(cv_rejointimeout.value, 60 * TICRATE) - && !(players[i].quittime % TICRATE)) - { - if (D_NumNodes(true) > 0) - SendKick(i, KICK_MSG_PLAYER_QUIT); - else // If the server is empty, don't send a NetXCmd - that would wake an idling dedicated server - CL_RemovePlayer(i, KICK_MSG_PLAYER_QUIT); - } - } - } } } @@ -1669,17 +1647,6 @@ INT32 D_NumBots(void) return num; } -// Returns the number of currently-connected nodes in a netgame -// Not necessarily equivalent to D_NumPlayers() minus D_NumBots() -static INT32 D_NumNodes(boolean skiphost) -{ - INT32 num = 0, ix; - for (ix = skiphost ? 1 : 0; ix < MAXNETNODES; ix++) - if (netnodes[ix].ingame) - num++; - return num; -} - // // Consistancy diff --git a/src/p_tick.c b/src/p_tick.c index db6777bca..dca806ebe 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -701,11 +701,25 @@ void P_Ticker(boolean run) { INT32 i; - // Increment jointime even if paused + // Increment jointime and quittime even if paused for (i = 0; i < MAXPLAYERS; i++) if (playeringame[i]) + { players[i].jointime++; + if (players[i].quittime) + { + players[i].quittime++; + + if (players[i].quittime == 30 * TICRATE && G_TagGametype()) + P_CheckSurvivors(); + + if (server && players[i].quittime >= (tic_t)FixedMul(cv_rejointimeout.value, 60 * TICRATE) + && !(players[i].quittime % TICRATE)) + SendKick(i, KICK_MSG_PLAYER_QUIT); + } + } + if (objectplacing) { if (OP_FreezeObjectplace()) From 8d179825d9ddfd8d3f6020f90c2f740d33b7f92e Mon Sep 17 00:00:00 2001 From: Logan Aerl Arias Date: Sun, 31 Dec 2023 15:56:37 +0000 Subject: [PATCH 203/227] switch to using sanitized subject for compnote --- comptime.bat | 2 +- comptime.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/comptime.bat b/comptime.bat index 77879d5ee..c03f088b2 100644 --- a/comptime.bat +++ b/comptime.bat @@ -16,7 +16,7 @@ set GIT=%2 if "%GIT%"=="" set GIT=git for /f "tokens=* usebackq" %%s in (`%GIT% rev-parse --abbrev-ref HEAD`) do @set BRA=%%s for /f "tokens=* usebackq" %%s in (`%GIT% rev-parse HEAD`) do @set REV=%%s -for /f "tokens=* usebackq" %%s in (`%GIT% log -1 --format^=%%s`) do @set GL1=%%s +for /f "tokens=* usebackq" %%s in (`%GIT% log -1 --format^=%%f`) do @set GL1=%%s set REV=%REV:~0,8% goto filwri diff --git a/comptime.sh b/comptime.sh index e37ba6ad7..3338ecc35 100755 --- a/comptime.sh +++ b/comptime.sh @@ -19,7 +19,7 @@ EOF versiongit() { gitbranch="$(git rev-parse --abbrev-ref HEAD)" gitversion="$(git rev-parse HEAD | cut -c -8)" - gitsubject="$(git log -1 --format=%s)" + gitsubject="$(git log -1 --format=%f)" version "$gitbranch" "$gitversion" "$gitsubject"; exit 0 } From 1284ec6494bb73883956cbe98cd2337311159091 Mon Sep 17 00:00:00 2001 From: Monster Iestyn Date: Mon, 1 Jan 2024 02:44:07 +0000 Subject: [PATCH 204/227] correct sidenum_get to use UINT32 instead of UINT16 --- src/lua_maplib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lua_maplib.c b/src/lua_maplib.c index 0c4ba6fd3..5b80d4d38 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -1195,7 +1195,7 @@ static int line_num(lua_State *L) static int sidenum_get(lua_State *L) { - UINT16 *sidenum = *((UINT16 **)luaL_checkudata(L, 1, META_SIDENUM)); + UINT32 *sidenum = *((UINT32 **)luaL_checkudata(L, 1, META_SIDENUM)); int i; lua_settop(L, 2); if (!lua_isnumber(L, 2)) From b52836e5f1ae8ccfd8f5a262be09f313d692030c Mon Sep 17 00:00:00 2001 From: katsy <205-katsy@users.noreply.git.do.srb2.org> Date: Mon, 1 Jan 2024 03:10:09 +0000 Subject: [PATCH 205/227] Move player friction reset after movement code for lua qol --- src/p_mobj.c | 2 -- src/p_user.c | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index ec7455a3c..2605f6777 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -1676,8 +1676,6 @@ static void P_XYFriction(mobj_t *mo, fixed_t oldx, fixed_t oldy) mo->momx = FixedMul(mo->momx, mo->friction); mo->momy = FixedMul(mo->momy, mo->friction); } - - mo->friction = ORIG_FRICTION; } } else diff --git a/src/p_user.c b/src/p_user.c index 5cb1d9d5a..f6e12ecfa 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -6000,6 +6000,7 @@ static void P_2dMovement(player_t *player) else if (player->rmomx > -topspeed && cmd->sidemove < 0) P_Thrust(player->mo, movepushangle, movepushforward); } + player->mo->friction = ORIG_FRICTION; //katsy: reset player friction AFTER movement code } //#define OLD_MOVEMENT_CODE 1 @@ -6321,6 +6322,7 @@ static void P_3dMovement(player_t *player) player->mo->momy = tempmomy + player->cmomy; } } + player->mo->friction = ORIG_FRICTION; //katsy: reset player friction AFTER movement code } // From e29085580c9aa50f09dc6a696250edb90cebe3a8 Mon Sep 17 00:00:00 2001 From: ChaoLoveIceMDBoy Date: Mon, 1 Jan 2024 19:48:47 +0000 Subject: [PATCH 206/227] OpenGL: Fix linedef type 10 not culling FOFs (Closes #438) --- src/hardware/hw_main.c | 120 ++++++++++++++++++++++------------------- 1 file changed, 64 insertions(+), 56 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index fa3f27595..e92e9b476 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -2927,6 +2927,46 @@ static FBITFIELD HWR_RippleBlend(sector_t *sector, ffloor_t *rover, boolean ceil return /*R_IsRipplePlane(sector, rover, ceiling)*/ (rover->fofflags & FOF_RIPPLE) ? PF_Ripple : 0; } +// +// HWR_DoCulling +// Hardware version of R_DoCulling +// (see r_main.c) +static boolean HWR_DoCulling(line_t *cullheight, line_t *viewcullheight, float vz, float bottomh, float toph) +{ + float cullplane; + + if (!cullheight) + return false; + + cullplane = FIXED_TO_FLOAT(cullheight->frontsector->floorheight); + if (cullheight->args[1]) // Group culling + { + if (!viewcullheight) + return false; + + // Make sure this is part of the same group + if (viewcullheight->frontsector == cullheight->frontsector) + { + // OK, we can cull + if (vz > cullplane && toph < cullplane) // Cull if below plane + return true; + + if (bottomh > cullplane && vz <= cullplane) // Cull if above plane + return true; + } + } + else // Quick culling + { + if (vz > cullplane && toph < cullplane) // Cull if below plane + return true; + + if (bottomh > cullplane && vz <= cullplane) // Cull if above plane + return true; + } + + return false; +} + // -----------------+ // HWR_Subsector : Determine floor/ceiling planes. // : Add sprites of things in sector. @@ -3101,27 +3141,36 @@ static void HWR_Subsector(size_t num) for (rover = gl_frontsector->ffloors; rover; rover = rover->next) { - fixed_t cullHeight, centerHeight; - - // bottom plane - cullHeight = P_GetFFloorBottomZAt(rover, viewx, viewy); - centerHeight = P_GetFFloorBottomZAt(rover, gl_frontsector->soundorg.x, gl_frontsector->soundorg.y); + fixed_t bottomCullHeight, topCullHeight, centerHeight; if (!(rover->fofflags & FOF_EXISTS) || !(rover->fofflags & FOF_RENDERPLANES)) continue; if (sub->validcount == validcount) continue; + + // rendering heights for bottom and top planes + bottomCullHeight = P_GetFFloorBottomZAt(rover, viewx, viewy); + topCullHeight = P_GetFFloorTopZAt(rover, viewx, viewy); + + if (gl_frontsector->cullheight) + { + if (HWR_DoCulling(gl_frontsector->cullheight, viewsector->cullheight, gl_viewz, FIXED_TO_FLOAT(bottomCullHeight), FIXED_TO_FLOAT(topCullHeight))) + continue; + } + + // bottom plane + centerHeight = P_GetFFloorBottomZAt(rover, gl_frontsector->soundorg.x, gl_frontsector->soundorg.y); if (centerHeight <= locCeilingHeight && centerHeight >= locFloorHeight && - ((dup_viewz < cullHeight && (rover->fofflags & FOF_BOTHPLANES || !(rover->fofflags & FOF_INVERTPLANES))) || - (dup_viewz > cullHeight && (rover->fofflags & FOF_BOTHPLANES || rover->fofflags & FOF_INVERTPLANES)))) + ((dup_viewz < bottomCullHeight && (rover->fofflags & FOF_BOTHPLANES || !(rover->fofflags & FOF_INVERTPLANES))) || + (dup_viewz > bottomCullHeight && (rover->fofflags & FOF_BOTHPLANES || rover->fofflags & FOF_INVERTPLANES)))) { if (rover->fofflags & FOF_FOG) { UINT8 alpha; - light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < cullHeight ? true : false); + light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < bottomCullHeight ? true : false); alpha = HWR_FogBlockAlpha(*gl_frontsector->lightlist[light].lightlevel, rover->master->frontsector->extra_colormap); HWR_AddTransparentFloor(0, @@ -3134,7 +3183,7 @@ static void HWR_Subsector(size_t num) } else if ((rover->fofflags & FOF_TRANSLUCENT && !(rover->fofflags & FOF_SPLAT)) || rover->blend) // SoM: Flags are more efficient { - light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < cullHeight ? true : false); + light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < bottomCullHeight ? true : false); HWR_AddTransparentFloor(&levelflats[*rover->bottompic], &extrasubsectors[num], @@ -3148,26 +3197,25 @@ static void HWR_Subsector(size_t num) else { HWR_GetLevelFlat(&levelflats[*rover->bottompic]); - light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < cullHeight ? true : false); + light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < bottomCullHeight ? true : false); HWR_RenderPlane(sub, &extrasubsectors[num], false, *rover->bottomheight, HWR_RippleBlend(gl_frontsector, rover, false)|PF_Occlude, *gl_frontsector->lightlist[light].lightlevel, &levelflats[*rover->bottompic], rover->master->frontsector, 255, *gl_frontsector->lightlist[light].extra_colormap); } } // top plane - cullHeight = P_GetFFloorTopZAt(rover, viewx, viewy); centerHeight = P_GetFFloorTopZAt(rover, gl_frontsector->soundorg.x, gl_frontsector->soundorg.y); if (centerHeight >= locFloorHeight && centerHeight <= locCeilingHeight && - ((dup_viewz > cullHeight && (rover->fofflags & FOF_BOTHPLANES || !(rover->fofflags & FOF_INVERTPLANES))) || - (dup_viewz < cullHeight && (rover->fofflags & FOF_BOTHPLANES || rover->fofflags & FOF_INVERTPLANES)))) + ((dup_viewz > topCullHeight && (rover->fofflags & FOF_BOTHPLANES || !(rover->fofflags & FOF_INVERTPLANES))) || + (dup_viewz < topCullHeight && (rover->fofflags & FOF_BOTHPLANES || rover->fofflags & FOF_INVERTPLANES)))) { if (rover->fofflags & FOF_FOG) { UINT8 alpha; - light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < cullHeight ? true : false); + light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < topCullHeight ? true : false); alpha = HWR_FogBlockAlpha(*gl_frontsector->lightlist[light].lightlevel, rover->master->frontsector->extra_colormap); HWR_AddTransparentFloor(0, @@ -3180,7 +3228,7 @@ static void HWR_Subsector(size_t num) } else if ((rover->fofflags & FOF_TRANSLUCENT && !(rover->fofflags & FOF_SPLAT)) || rover->blend) { - light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < cullHeight ? true : false); + light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < topCullHeight ? true : false); HWR_AddTransparentFloor(&levelflats[*rover->toppic], &extrasubsectors[num], @@ -3194,7 +3242,7 @@ static void HWR_Subsector(size_t num) else { HWR_GetLevelFlat(&levelflats[*rover->toppic]); - light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < cullHeight ? true : false); + light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < topCullHeight ? true : false); HWR_RenderPlane(sub, &extrasubsectors[num], true, *rover->topheight, HWR_RippleBlend(gl_frontsector, rover, false)|PF_Occlude, *gl_frontsector->lightlist[light].lightlevel, &levelflats[*rover->toppic], rover->master->frontsector, 255, *gl_frontsector->lightlist[light].extra_colormap); } @@ -3548,46 +3596,6 @@ static void HWR_LinkDrawHackFinish(void) linkdrawcount = 0; } -// -// HWR_DoCulling -// Hardware version of R_DoCulling -// (see r_main.c) -static boolean HWR_DoCulling(line_t *cullheight, line_t *viewcullheight, float vz, float bottomh, float toph) -{ - float cullplane; - - if (!cullheight) - return false; - - cullplane = FIXED_TO_FLOAT(cullheight->frontsector->floorheight); - if (cullheight->args[1]) // Group culling - { - if (!viewcullheight) - return false; - - // Make sure this is part of the same group - if (viewcullheight->frontsector == cullheight->frontsector) - { - // OK, we can cull - if (vz > cullplane && toph < cullplane) // Cull if below plane - return true; - - if (bottomh > cullplane && vz <= cullplane) // Cull if above plane - return true; - } - } - else // Quick culling - { - if (vz > cullplane && toph < cullplane) // Cull if below plane - return true; - - if (bottomh > cullplane && vz <= cullplane) // Cull if above plane - return true; - } - - return false; -} - static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale) { patch_t *gpatch; From 920f375f4ab9cb2d139266288b231f6d7ea7983b Mon Sep 17 00:00:00 2001 From: Logan Aerl Arias Date: Mon, 1 Jan 2024 20:40:11 -0500 Subject: [PATCH 207/227] Update features.mk fix miniupnpc typo in features.mk --- src/Makefile.d/features.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Makefile.d/features.mk b/src/Makefile.d/features.mk index 6a5df757a..b0ff2a71f 100644 --- a/src/Makefile.d/features.mk +++ b/src/Makefile.d/features.mk @@ -54,7 +54,7 @@ ifndef NOUPNP MINIUPNPC_PKGCONFIG?=miniupnpc $(eval $(call Use_pkg_config,MINIUPNPC)) HAVE_MINIUPNPC=1 -opts+=-DHAVE_MINIUPNP +opts+=-DHAVE_MINIUPNPC endif endif From e717731ea315d5cb46c7274449f9693cc25dc069 Mon Sep 17 00:00:00 2001 From: Zwip-Zwap Zapony Date: Tue, 2 Jan 2024 18:58:22 +0100 Subject: [PATCH 208/227] Update player->quittime when a server idles, again --- src/netcode/d_clisrv.c | 29 ++++++++++++++++++++++++++--- src/netcode/d_clisrv.h | 2 +- src/netcode/server_connection.c | 2 +- src/p_tick.c | 16 +--------------- 4 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/netcode/d_clisrv.c b/src/netcode/d_clisrv.c index d84759b42..f4251ef08 100644 --- a/src/netcode/d_clisrv.c +++ b/src/netcode/d_clisrv.c @@ -1280,6 +1280,7 @@ static void UpdatePingTable(void) } } +// Handle idle and disconnected player timers static void IdleUpdate(void) { INT32 i; @@ -1302,7 +1303,26 @@ static void IdleUpdate(void) } } else + { players[i].lastinputtime = 0; + + if (players[i].quittime && playeringame[i]) + { + players[i].quittime++; + + if (players[i].quittime == 30 * TICRATE && G_TagGametype()) + P_CheckSurvivors(); + + if (server && players[i].quittime >= (tic_t)FixedMul(cv_rejointimeout.value, 60 * TICRATE) + && !(players[i].quittime % TICRATE)) + { + if (D_NumNodes(true) > 0) + SendKick(i, KICK_MSG_PLAYER_QUIT); + else // If the server is empty, don't send a NetXCmd - that would wake an idling dedicated server + CL_RemovePlayer(i, KICK_MSG_PLAYER_QUIT); + } + } + } } } @@ -1623,12 +1643,15 @@ INT32 D_NumPlayers(void) return num; } -/** Returns the number of nodes on the server. +/** Returns the number of currently-connected nodes in a netgame. + * Not necessarily equivalent to D_NumPlayers() minus D_NumBots(). + * + * \param skiphost Skip the server's own node. */ -INT32 D_NumNodes(void) +INT32 D_NumNodes(boolean skiphost) { INT32 num = 0; - for (INT32 ix = 0; ix < MAXNETNODES; ix++) + for (INT32 ix = skiphost ? 1 : 0; ix < MAXNETNODES; ix++) if (netnodes[ix].ingame) num++; return num; diff --git a/src/netcode/d_clisrv.h b/src/netcode/d_clisrv.h index f9cbf8876..61823e65d 100644 --- a/src/netcode/d_clisrv.h +++ b/src/netcode/d_clisrv.h @@ -121,7 +121,7 @@ extern char motd[254], server_context[8]; extern UINT8 playernode[MAXPLAYERS]; INT32 D_NumPlayers(void); -INT32 D_NumNodes(void); +INT32 D_NumNodes(boolean skiphost); INT32 D_NumBots(void); tic_t GetLag(INT32 node); diff --git a/src/netcode/server_connection.c b/src/netcode/server_connection.c index bfbe30a08..376700f0d 100644 --- a/src/netcode/server_connection.c +++ b/src/netcode/server_connection.c @@ -109,7 +109,7 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime) netbuffer->u.serverinfo.leveltime = (tic_t)LONG(leveltime); // Exclude bots from both counts - netbuffer->u.serverinfo.numberofplayer = (UINT8)(D_NumNodes() - (dedicated ? 1 : 0)); + netbuffer->u.serverinfo.numberofplayer = (UINT8)D_NumNodes(dedicated); netbuffer->u.serverinfo.maxplayer = (UINT8)(cv_maxplayers.value - D_NumBots()); netbuffer->u.serverinfo.refusereason = GetRefuseReason(node); diff --git a/src/p_tick.c b/src/p_tick.c index 143fa7e36..4ab388486 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -701,25 +701,11 @@ void P_Ticker(boolean run) { INT32 i; - // Increment jointime and quittime even if paused + // Increment jointime even if paused for (i = 0; i < MAXPLAYERS; i++) if (playeringame[i]) - { players[i].jointime++; - if (players[i].quittime) - { - players[i].quittime++; - - if (players[i].quittime == 30 * TICRATE && G_TagGametype()) - P_CheckSurvivors(); - - if (server && players[i].quittime >= (tic_t)FixedMul(cv_rejointimeout.value, 60 * TICRATE) - && !(players[i].quittime % TICRATE)) - SendKick(i, KICK_MSG_PLAYER_QUIT); - } - } - if (objectplacing) { if (OP_FreezeObjectplace()) From 29c7a0bfd81add1c3a513e1ea9fbcb54dd18ad60 Mon Sep 17 00:00:00 2001 From: Zwip-Zwap Zapony Date: Tue, 2 Jan 2024 20:38:18 +0100 Subject: [PATCH 209/227] Fix a -Winline warning --- src/deh_lua.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deh_lua.c b/src/deh_lua.c index 9679780d7..8552360e0 100644 --- a/src/deh_lua.c +++ b/src/deh_lua.c @@ -600,7 +600,7 @@ static int ScanConstants(lua_State *L, boolean mathlib, const char *word) return 0; } -static inline int getEnum(lua_State *L, boolean mathlib, const char *word) +FUNCINLINE static ATTRINLINE int getEnum(lua_State *L, boolean mathlib, const char *word) { fixed_t i; From 31f7616d3a0515d158d2d5da7785ce26fd8b151b Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Tue, 2 Jan 2024 20:58:55 -0300 Subject: [PATCH 210/227] Fix #1166 --- src/g_demo.c | 4 ++-- src/lua_script.c | 2 +- src/p_enemy.c | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/g_demo.c b/src/g_demo.c index 65fc90868..f62e1161a 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -929,7 +929,7 @@ void G_GhostTicker(void) follow->colorized = true; if (followtic & FZT_SKIN) - follow->skin = &skins[READUINT8(g->p)]; + follow->skin = skins[READUINT8(g->p)]; } } if (follow) @@ -1199,7 +1199,7 @@ void G_ReadMetalTic(mobj_t *metal) follow->colorized = true; if (followtic & FZT_SKIN) - follow->skin = &skins[READUINT8(metal_p)]; + follow->skin = skins[READUINT8(metal_p)]; } } if (follow) diff --git a/src/lua_script.c b/src/lua_script.c index 72e5a3f45..482914102 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -1646,7 +1646,7 @@ static UINT8 UnArchiveValue(int TABLESINDEX) LUA_PushUserdata(gL, READUINT16(save_p) == 1 ? &mouse : &mouse2, META_MOUSE); break; case ARCH_SKIN: - LUA_PushUserdata(gL, &skins[READUINT8(save_p)], META_SKIN); + LUA_PushUserdata(gL, skins[READUINT8(save_p)], META_SKIN); break; case ARCH_TEND: return 1; diff --git a/src/p_enemy.c b/src/p_enemy.c index a038ee1e3..1073fd491 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -3786,7 +3786,7 @@ void A_MonitorPop(mobj_t *actor) P_SetTarget(&newmobj->tracer, livesico); livesico->color = newmobj->target->player->mo->color; - livesico->skin = &skins[newmobj->target->player->skin]; + livesico->skin = skins[newmobj->target->player->skin]; P_SetMobjState(livesico, newmobj->info->seestate); } @@ -3877,7 +3877,7 @@ void A_GoldMonitorPop(mobj_t *actor) P_SetTarget(&newmobj->tracer, livesico); livesico->color = newmobj->target->player->mo->color; - livesico->skin = &skins[newmobj->target->player->skin]; + livesico->skin = skins[newmobj->target->player->skin]; P_SetMobjState(livesico, newmobj->info->seestate); } From 3992e83e8f5880d4090b3a6c693546bcb8f33e94 Mon Sep 17 00:00:00 2001 From: Logan Aerl Arias Date: Wed, 3 Jan 2024 03:46:26 +0000 Subject: [PATCH 211/227] Revert "Merge branch 'thokking-hell' into 'next'" This reverts merge request !1551 --- src/deh_tables.c | 6 ++---- src/g_demo.c | 25 ------------------------- src/hardware/hw_light.c | 1 - src/info.c | 33 ++------------------------------- src/info.h | 9 +++------ src/p_user.c | 14 +------------- 6 files changed, 8 insertions(+), 80 deletions(-) diff --git a/src/deh_tables.c b/src/deh_tables.c index baa8ece16..b53cd00c8 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -370,9 +370,8 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_XDEATHSTATE", "S_RAISESTATE", - // Thok effect and spin trail + // Thok "S_THOK", - "S_THOKEFFECT", // Player "S_PLAY_STND", @@ -3561,8 +3560,7 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_NULL", "MT_UNKNOWN", - "MT_THOK", // Spin trail mobj - "MT_THOKEFFECT", // Thok boom effect + "MT_THOK", // Thok! mobj "MT_PLAYER", "MT_TAILSOVERLAY", // c: "MT_METALJETFUME", diff --git a/src/g_demo.c b/src/g_demo.c index f62e1161a..c30b07f25 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -801,19 +801,6 @@ void G_GhostTicker(void) if (!P_MobjWasRemoved(mobj)) mobj->frame = (mobj->frame & ~FF_FRAMEMASK)|tr_trans60<mo, 0, 0, FixedDiv(g->mo->height, g->mo->scale)*3/4, type); - mobj->angle = g->mo->angle + ANGLE_90; - mobj->fuse = 7; - mobj->scale = g->mo->scale / 3; - mobj->destscale = 10 * g->mo->scale; - mobj->colorized = true; - mobj->color = g->mo->color; - mobj->momx = -g->mo->momx / 2; - mobj->momy = -g->mo->momy / 2; - } - else { mobj = P_SpawnMobjFromMobj(g->mo, 0, 0, -FixedDiv(FixedMul(g->mo->info->height, g->mo->scale) - g->mo->height,3*FRACUNIT), MT_THOK); @@ -1120,18 +1107,6 @@ void G_ReadMetalTic(mobj_t *metal) { mobj = P_SpawnGhostMobj(metal); // does a large portion of the work for us } - else if (type == MT_THOKEFFECT) - { - mobj = P_SpawnMobjFromMobj(metal, 0, 0, FixedDiv(metal->height, metal->scale)*3/4, type); - mobj->angle = metal->angle + ANGLE_90; - mobj->fuse = 7; - mobj->scale = metal->scale / 3; - mobj->destscale = 10 * metal->scale; - mobj->colorized = true; - mobj->color = metal->color; - mobj->momx = -metal->momx / 2; - mobj->momy = -metal->momy / 2; - } else { mobj = P_SpawnMobjFromMobj(metal, 0, 0, -FixedDiv(FixedMul(metal->info->height, metal->scale) - metal->height,3*FRACUNIT), MT_THOK); diff --git a/src/hardware/hw_light.c b/src/hardware/hw_light.c index 94f01e299..bcfdfa960 100644 --- a/src/hardware/hw_light.c +++ b/src/hardware/hw_light.c @@ -138,7 +138,6 @@ light_t *t_lspr[NUMSPRITES] = &lspr[NOLIGHT], // SPR_UNKN &lspr[NOLIGHT], // SPR_THOK - &lspr[NOLIGHT], // SPR_THKE &lspr[SUPERSONIC_L],// SPR_PLAY // Enemies diff --git a/src/info.c b/src/info.c index d1707c86a..5790dd7c5 100644 --- a/src/info.c +++ b/src/info.c @@ -33,8 +33,7 @@ char sprnames[NUMSPRITES + 1][5] = "NULL", // invisible object "UNKN", - "THOK", // Spin trail mobj - "THKE", // Thok boom effect + "THOK", // Thok! mobj "PLAY", // Enemies @@ -704,9 +703,8 @@ state_t states[NUMSTATES] = {SPR_UNKN, FF_FULLBRIGHT, -1, {A_InfoState}, 5, 0, S_NULL}, // S_XDEATHSTATE {SPR_UNKN, FF_FULLBRIGHT, -1, {A_InfoState}, 6, 0, S_NULL}, // S_RAISESTATE - // Spin trail and thok boom effect + // Thok {SPR_THOK, FF_TRANS50, 8, {NULL}, 0, 0, S_NULL}, // S_THOK - {SPR_THKE, FF_TRANS50|FF_PAPERSPRITE, 8, {NULL}, 0, 0, S_NULL}, // S_THOKEFFECT // Player {SPR_PLAY, SPR2_STND|FF_ANIMATE, 105, {NULL}, 0, 7, S_PLAY_WAIT}, // S_PLAY_STND @@ -4080,33 +4078,6 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags S_NULL // raisestate }, - - { // MT_THOKEFFECT - -1, // doomednum - S_THOKEFFECT, // spawnstate - 1000, // spawnhealth - S_NULL, // seestate - sfx_None, // seesound - 8, // reactiontime - sfx_None, // attacksound - S_NULL, // painstate - 0, // painchance - sfx_None, // painsound - S_NULL, // meleestate - S_NULL, // missilestate - S_NULL, // deathstate - S_NULL, // xdeathstate - sfx_None, // deathsound - 8, // speed - 32*FRACUNIT, // radius - 64*FRACUNIT, // height - 0, // display offset - 16, // mass - 0, // damage - sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags - S_NULL // raisestate - }, { // MT_PLAYER -1, // doomednum diff --git a/src/info.h b/src/info.h index 32aff18fb..a2d87dbdc 100644 --- a/src/info.h +++ b/src/info.h @@ -580,8 +580,7 @@ typedef enum sprite SPR_NULL, // invisible object SPR_UNKN, - SPR_THOK, // Spin trail mobj - SPR_THKE, // Thok boom effect + SPR_THOK, // Thok! mobj SPR_PLAY, // Enemies @@ -1183,9 +1182,8 @@ typedef enum state S_XDEATHSTATE, S_RAISESTATE, - // Thok boom effect and spin trail + // Thok S_THOK, - S_THOKEFFECT, // Player S_PLAY_STND, @@ -4394,8 +4392,7 @@ typedef enum mobj_type MT_NULL, MT_UNKNOWN, - MT_THOK, // Spin trail mobj - MT_THOKEFFECT, // Thok boom effect + MT_THOK, // Thok! mobj MT_PLAYER, MT_TAILSOVERLAY, // c: MT_METALJETFUME, diff --git a/src/p_user.c b/src/p_user.c index fe514f0bb..e6165c2a3 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2122,19 +2122,7 @@ void P_SpawnThokMobj(player_t *player) if (type == MT_GHOST) mobj = P_SpawnGhostMobj(player->mo); // virtually does everything here for us - else if (type == MT_THOKEFFECT) // Thok boom effect for Sonic - { - mobj = P_SpawnMobjFromMobj(player->mo, 0, 0, FixedDiv(player->mo->height, player->mo->scale)*3/4, type); - mobj->angle = player->mo->angle + ANGLE_90; - mobj->fuse = 7; - mobj->scale = player->mo->scale / 3; - mobj->destscale = 10 * player->mo->scale; - mobj->colorized = true; - mobj->color = player->mo->color; - mobj->momx = -player->mo->momx / 2; - mobj->momy = -player->mo->momy / 2; - } - else // Normal thok object handling + else { if (player->mo->eflags & MFE_VERTICALFLIP) zheight = player->mo->z + player->mo->height + FixedDiv(P_GetPlayerHeight(player) - player->mo->height, 3*FRACUNIT) - FixedMul(mobjinfo[type].height, player->mo->scale); From 3f7dbcd15b1012b35811c74f93b7b14c002043c4 Mon Sep 17 00:00:00 2001 From: Logan Aerl Arias Date: Fri, 5 Jan 2024 15:42:28 +0000 Subject: [PATCH 212/227] Update config.yml added libminiupnpc-dev to CircleCI build --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b3b97363e..a7ced28d5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -53,7 +53,7 @@ jobs: command: sudo apt-get -o Dir::Cache="/home/circleci/.cache/apt" -qq -y --no-install-recommends remove libcurl4-openssl-dev:amd64 - run: name: Install i386 SDK - command: sudo apt-get -o Dir::Cache="/home/circleci/.cache/apt" -qq -y --no-install-recommends install git build-essential libpng-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 libcurl4-openssl-dev:i386 libopenmpt-dev:i386 gettext ccache wget gcc-multilib upx openssh-client + command: sudo apt-get -o Dir::Cache="/home/circleci/.cache/apt" -qq -y --no-install-recommends install git build-essential libpng-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 libcurl4-openssl-dev:i386 libopenmpt-dev:i386 libminiupnpc-dev:i386 gettext ccache wget gcc-multilib upx openssh-client - run: name: make md5sum command: sudo find /home/circleci/.cache/apt/archives -type f -print0 | sort -z | sudo xargs -r0 md5sum > /home/circleci/.cache/apt_archives.md5 From e2164309a2b23bb4e377cc123529bede8d89fd22 Mon Sep 17 00:00:00 2001 From: Logan Aerl Arias Date: Fri, 5 Jan 2024 15:46:05 +0000 Subject: [PATCH 213/227] added libminiupnpc-dev to CircleCI build, try number two --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a7ced28d5..a5fd80998 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -53,7 +53,7 @@ jobs: command: sudo apt-get -o Dir::Cache="/home/circleci/.cache/apt" -qq -y --no-install-recommends remove libcurl4-openssl-dev:amd64 - run: name: Install i386 SDK - command: sudo apt-get -o Dir::Cache="/home/circleci/.cache/apt" -qq -y --no-install-recommends install git build-essential libpng-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 libcurl4-openssl-dev:i386 libopenmpt-dev:i386 libminiupnpc-dev:i386 gettext ccache wget gcc-multilib upx openssh-client + command: sudo apt-get -o Dir::Cache="/home/circleci/.cache/apt" -qq -y --no-install-recommends install git build-essential libpng-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 libcurl4-openssl-dev:i386 libopenmpt-dev:i386 libminiupnpc-dev gettext ccache wget gcc-multilib upx openssh-client - run: name: make md5sum command: sudo find /home/circleci/.cache/apt/archives -type f -print0 | sort -z | sudo xargs -r0 md5sum > /home/circleci/.cache/apt_archives.md5 From 1d88da8f052d1d73b4cd42ff8a4c44b5d6a567b7 Mon Sep 17 00:00:00 2001 From: Logan Aerl Arias Date: Fri, 5 Jan 2024 16:24:47 +0000 Subject: [PATCH 214/227] CirecleCI build bot doesn't have libminiupnpc-dev --- .circleci/config.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a5fd80998..3fdf1facb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -53,7 +53,7 @@ jobs: command: sudo apt-get -o Dir::Cache="/home/circleci/.cache/apt" -qq -y --no-install-recommends remove libcurl4-openssl-dev:amd64 - run: name: Install i386 SDK - command: sudo apt-get -o Dir::Cache="/home/circleci/.cache/apt" -qq -y --no-install-recommends install git build-essential libpng-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 libcurl4-openssl-dev:i386 libopenmpt-dev:i386 libminiupnpc-dev gettext ccache wget gcc-multilib upx openssh-client + command: sudo apt-get -o Dir::Cache="/home/circleci/.cache/apt" -qq -y --no-install-recommends install git build-essential libpng-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 libcurl4-openssl-dev:i386 libopenmpt-dev:i386 gettext ccache wget gcc-multilib upx openssh-client - run: name: make md5sum command: sudo find /home/circleci/.cache/apt/archives -type f -print0 | sort -z | sudo xargs -r0 md5sum > /home/circleci/.cache/apt_archives.md5 @@ -64,13 +64,13 @@ jobs: - checkout - run: name: Compile without network support - command: make -C src LINUX=1 ERRORMODE=1 -k NONET=1 -j4 + command: make -C src LINUX=1 ERRORMODE=1 -k NONET=1 NOUPNP=1 -j4 - run: name: wipe build - command: make -C src LINUX=1 cleandep + command: make -C src LINUX=1 NOUPNP=1 cleandep - run: name: rebuild depend - command: make -C src LINUX=1 clean + command: make -C src LINUX=1 NOUPNP=1 clean - run: name: make master depend file command: find make/linux/SDL/deps/ -type f -print0 | sort -z | xargs -r0 cat > make/linux/SDL.deps @@ -79,7 +79,7 @@ jobs: - v1-SRB2-{{ .Branch }}-{{ checksum "make/linux/SDL.deps" }} - run: name: Compile - command: make -C src LINUX=1 ERRORMODE=1 -k -j4 + command: make -C src LINUX=1 ERRORMODE=1 NOUPNP=1 -k -j4 - store_artifacts: path: /home/circleci/SRB2/bin/ destination: bin From f613bd1279db3d00d50d4c19ee28f3f7efa35e23 Mon Sep 17 00:00:00 2001 From: Alam Ed Arias Date: Fri, 5 Jan 2024 16:47:48 +0000 Subject: [PATCH 215/227] Update config.yml --- .circleci/config.yml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3fdf1facb..68480ae91 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ jobs: build: working_directory: /home/circleci/SRB2 docker: - - image: cimg/base:current + - image: cimg/base:edge environment: CC: ccache gcc -m32 PKG_CONFIG_LIBDIR: /usr/lib/i386-linux-gnu/pkgconfig @@ -30,9 +30,7 @@ jobs: name: Add STJr PPA command: | sudo apt-get -qq update - sudo apt-get -qq -y install dirmngr - sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 0B1702D71499D9C25F986507F240F4449D3B0EC6 - echo "deb http://ppa.launchpad.net/stjr/srb2/ubuntu trusty main" | sudo tee -a /etc/apt/sources.list + sudo apt-get -qq install apt-utils - run: name: Make APT cache folder command: mkdir -p /home/circleci/.cache/apt/archives/partial @@ -53,7 +51,7 @@ jobs: command: sudo apt-get -o Dir::Cache="/home/circleci/.cache/apt" -qq -y --no-install-recommends remove libcurl4-openssl-dev:amd64 - run: name: Install i386 SDK - command: sudo apt-get -o Dir::Cache="/home/circleci/.cache/apt" -qq -y --no-install-recommends install git build-essential libpng-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 libcurl4-openssl-dev:i386 libopenmpt-dev:i386 gettext ccache wget gcc-multilib upx openssh-client + command: sudo apt-get -o Dir::Cache="/home/circleci/.cache/apt" -qq -y --no-install-recommends install git build-essential libpng-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 libcurl4-openssl-dev:i386 libopenmpt-dev:i386 libminiupnpc-dev:i386 gettext ccache wget gcc-multilib upx openssh-client - run: name: make md5sum command: sudo find /home/circleci/.cache/apt/archives -type f -print0 | sort -z | sudo xargs -r0 md5sum > /home/circleci/.cache/apt_archives.md5 @@ -64,13 +62,13 @@ jobs: - checkout - run: name: Compile without network support - command: make -C src LINUX=1 ERRORMODE=1 -k NONET=1 NOUPNP=1 -j4 + command: make -C src LINUX=1 ERRORMODE=1 -k NONET=1 -j4 - run: name: wipe build - command: make -C src LINUX=1 NOUPNP=1 cleandep + command: make -C src LINUX=1 cleandep - run: name: rebuild depend - command: make -C src LINUX=1 NOUPNP=1 clean + command: make -C src LINUX=1 clean - run: name: make master depend file command: find make/linux/SDL/deps/ -type f -print0 | sort -z | xargs -r0 cat > make/linux/SDL.deps @@ -79,7 +77,7 @@ jobs: - v1-SRB2-{{ .Branch }}-{{ checksum "make/linux/SDL.deps" }} - run: name: Compile - command: make -C src LINUX=1 ERRORMODE=1 NOUPNP=1 -k -j4 + command: make -C src LINUX=1 ERRORMODE=1 -k -j4 - store_artifacts: path: /home/circleci/SRB2/bin/ destination: bin From c05f6e8053d09dc1a0eab4b3370ccddda5136684 Mon Sep 17 00:00:00 2001 From: Alam Ed Arias Date: Fri, 5 Jan 2024 16:49:52 +0000 Subject: [PATCH 216/227] Update config.yml --- .circleci/config.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 68480ae91..13085c40c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -51,7 +51,7 @@ jobs: command: sudo apt-get -o Dir::Cache="/home/circleci/.cache/apt" -qq -y --no-install-recommends remove libcurl4-openssl-dev:amd64 - run: name: Install i386 SDK - command: sudo apt-get -o Dir::Cache="/home/circleci/.cache/apt" -qq -y --no-install-recommends install git build-essential libpng-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 libcurl4-openssl-dev:i386 libopenmpt-dev:i386 libminiupnpc-dev:i386 gettext ccache wget gcc-multilib upx openssh-client + command: sudo apt-get -o Dir::Cache="/home/circleci/.cache/apt" -qq -y --no-install-recommends install git build-essential libpng-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 libcurl4-openssl-dev:i386 libopenmpt-dev:i386 gettext ccache wget gcc-multilib upx openssh-client - run: name: make md5sum command: sudo find /home/circleci/.cache/apt/archives -type f -print0 | sort -z | sudo xargs -r0 md5sum > /home/circleci/.cache/apt_archives.md5 @@ -62,13 +62,13 @@ jobs: - checkout - run: name: Compile without network support - command: make -C src LINUX=1 ERRORMODE=1 -k NONET=1 -j4 + command: make -C src LINUX=1 ERRORMODE=1 -k NONET=1 NOUPNP=1 -j4 - run: name: wipe build - command: make -C src LINUX=1 cleandep + command: make -C src LINUX=1 NOUPNP=1 cleandep - run: name: rebuild depend - command: make -C src LINUX=1 clean + command: make -C src LINUX=1 NOUPNP=1 clean - run: name: make master depend file command: find make/linux/SDL/deps/ -type f -print0 | sort -z | xargs -r0 cat > make/linux/SDL.deps @@ -77,7 +77,7 @@ jobs: - v1-SRB2-{{ .Branch }}-{{ checksum "make/linux/SDL.deps" }} - run: name: Compile - command: make -C src LINUX=1 ERRORMODE=1 -k -j4 + command: make -C src LINUX=1 ERRORMODE=1 NOUPNP=1 -k -j4 - store_artifacts: path: /home/circleci/SRB2/bin/ destination: bin From 8fefb8a87b488bf8eb73e9db8e3b7778a894ad6f Mon Sep 17 00:00:00 2001 From: Logan Aerl Arias Date: Fri, 5 Jan 2024 22:17:00 +0000 Subject: [PATCH 217/227] CircleCI: move to LINUX64 build --- .circleci/config.yml | 36 ++++++++---------------------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 13085c40c..b86b39f75 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,15 +3,10 @@ jobs: build: working_directory: /home/circleci/SRB2 docker: - - image: cimg/base:edge + - image: cimg/base:current environment: - CC: ccache gcc -m32 - PKG_CONFIG_LIBDIR: /usr/lib/i386-linux-gnu/pkgconfig - LIBGME_CFLAGS: -I/usr/include - LIBGME_LDFLAGS: -lgme + CC: ccache gcc CCACHE_COMPRESS: true - WFLAGS: -Wno-unsuffixed-float-constants - GCC81: true #- image: ubuntu:trusty # environment: # CC: ccache gcc -m32 @@ -23,9 +18,6 @@ jobs: # GCC48: true resource_class: large steps: - - run: - name: Add i386 arch - command: sudo dpkg --add-architecture i386 - run: name: Add STJr PPA command: | @@ -47,11 +39,8 @@ jobs: keys: - v1-SRB2-APT - run: - name: Uninstall amd64 SDK - command: sudo apt-get -o Dir::Cache="/home/circleci/.cache/apt" -qq -y --no-install-recommends remove libcurl4-openssl-dev:amd64 - - run: - name: Install i386 SDK - command: sudo apt-get -o Dir::Cache="/home/circleci/.cache/apt" -qq -y --no-install-recommends install git build-essential libpng-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 libcurl4-openssl-dev:i386 libopenmpt-dev:i386 gettext ccache wget gcc-multilib upx openssh-client + name: Install SDK + command: sudo apt-get -o Dir::Cache="/home/circleci/.cache/apt" -qq -y --no-install-recommends install git build-essential libpng-dev libsdl2-mixer-dev libgme-dev libcurl4-openssl-dev libopenmpt-dev libminiupnpc-dev gettext ccache wget gcc-multilib upx openssh-client - run: name: make md5sum command: sudo find /home/circleci/.cache/apt/archives -type f -print0 | sort -z | sudo xargs -r0 md5sum > /home/circleci/.cache/apt_archives.md5 @@ -60,28 +49,19 @@ jobs: paths: - /home/circleci/.cache/apt - checkout - - run: - name: Compile without network support - command: make -C src LINUX=1 ERRORMODE=1 -k NONET=1 NOUPNP=1 -j4 - - run: - name: wipe build - command: make -C src LINUX=1 NOUPNP=1 cleandep - - run: - name: rebuild depend - command: make -C src LINUX=1 NOUPNP=1 clean - run: name: make master depend file - command: find make/linux/SDL/deps/ -type f -print0 | sort -z | xargs -r0 cat > make/linux/SDL.deps + command: find make/linux64/SDL/deps/ -type f -print0 | sort -z | xargs -r0 cat > make/linux64/SDL.deps - restore_cache: keys: - - v1-SRB2-{{ .Branch }}-{{ checksum "make/linux/SDL.deps" }} + - v1-SRB2-{{ .Branch }}-{{ checksum "make/linux64/SDL.deps" }} - run: name: Compile - command: make -C src LINUX=1 ERRORMODE=1 NOUPNP=1 -k -j4 + command: make -C src LINUX64=1 ERRORMODE=1 -k -j4 - store_artifacts: path: /home/circleci/SRB2/bin/ destination: bin - save_cache: - key: v1-SRB2-{{ .Branch }}-{{ checksum "make/linux/SDL.deps" }} + key: v1-SRB2-{{ .Branch }}-{{ checksum "make/linux64/SDL.deps" }} paths: - /home/circleci/.ccache From 5b6c24281d736305aa126c75f150c25167353f6c Mon Sep 17 00:00:00 2001 From: Alam Ed Arias Date: Sun, 7 Jan 2024 13:07:28 -0500 Subject: [PATCH 218/227] Fixup DEBUGMODE builds doomdef.h: check if the build system already define our DEBUG code version.mk: do not fail on our inline functions in DEBUGMODE features.mk: we do not need the VALGRIND libraies, just the headers mserv.c: start registered with false in Update_parameters() --- src/Makefile.d/features.mk | 1 + src/Makefile.d/versions.mk | 3 +++ src/doomdef.h | 8 ++++++++ src/mserv.c | 2 +- 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Makefile.d/features.mk b/src/Makefile.d/features.mk index b0ff2a71f..7ea07f71c 100644 --- a/src/Makefile.d/features.mk +++ b/src/Makefile.d/features.mk @@ -61,6 +61,7 @@ endif # (Valgrind is a memory debugger.) ifdef VALGRIND VALGRIND_PKGCONFIG?=valgrind +VALGRIND_LDFLAGS= $(eval $(call Use_pkg_config,VALGRIND)) ZDEBUG=1 opts+=-DHAVE_VALGRIND diff --git a/src/Makefile.d/versions.mk b/src/Makefile.d/versions.mk index 2523d7f3c..b639ad9a1 100644 --- a/src/Makefile.d/versions.mk +++ b/src/Makefile.d/versions.mk @@ -84,6 +84,9 @@ endif WFLAGS+=-Wnested-externs #WFLAGS+=-Wunreachable-code WFLAGS+=-Winline +ifdef DEBUGMODE + WFLAGS+=-Wno-error=inline +endif ifdef GCC43 WFLAGS+=-funit-at-a-time WFLAGS+=-Wlogical-op diff --git a/src/doomdef.h b/src/doomdef.h index 45d6645fa..b0610cb82 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -110,11 +110,19 @@ FILE *fopenfile(const char*, const char*); // If you don't disable ALL debug first, you get ALL debug enabled #if !defined (NDEBUG) +#ifndef PACKETDROP #define PACKETDROP +#endif +#ifndef PARANOIA #define PARANOIA +#endif +#ifndef RANGECHECK #define RANGECHECK +#endif +#ifndef ZDEBUG #define ZDEBUG #endif +#endif // Uncheck this to compile debugging code //#define RANGECHECK diff --git a/src/mserv.c b/src/mserv.c index f83b16d07..d60cc09c5 100644 --- a/src/mserv.c +++ b/src/mserv.c @@ -506,7 +506,7 @@ static void Update_parameters (void) { #ifdef MASTERSERVER - int registered; + int registered = 0; int delayed; if (Online()) From caaeaef6cc2760ca36cebd12ac176edfb2e7ec6c Mon Sep 17 00:00:00 2001 From: Alam Ed Arias Date: Sun, 7 Jan 2024 13:09:47 -0500 Subject: [PATCH 219/227] fixup Valgrind warning: Conditional jump or move depends on uninitialised value(s) strncpy (vg_replace_strmem.c:599) MIT_SetCurBackground (m_menu.c:2650) M_IterateMenuTree (m_menu.c:2581) M_SetMenuCurBackground (m_menu.c:2790) F_InitMenuPresValues (f_finale.c:2276) D_StartTitle (d_main.c:998) G_Responder (g_game.c:2117) D_ProcessEvents (d_main.c:282) Local_Maketic (d_clisrv.c:5297) NetUpdate (d_clisrv.c:5701) TryRunTics (d_clisrv.c:5371) D_SRB2Loop (d_main.c:820) --- src/m_menu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/m_menu.c b/src/m_menu.c index 3946803b2..d918c1a2b 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -2785,7 +2785,7 @@ void M_ChangeMenuMusic(const char *defaultmusname, boolean defaultmuslooping) void M_SetMenuCurBackground(const char *defaultname) { - char name[9]; + char name[9] = ""; strncpy(name, defaultname, 8); M_IterateMenuTree(MIT_SetCurBackground, &name); } From a1308d8aa5bd18fc8a1f026d906eec0ecec64805 Mon Sep 17 00:00:00 2001 From: Alam Ed Arias Date: Sun, 7 Jan 2024 19:21:30 +0000 Subject: [PATCH 220/227] Update .gitlab-ci.yml file Drop Debian musl jobs --- .gitlab-ci.yml | 62 -------------------------------------------------- 1 file changed, 62 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 40b418c0f..b99a850a0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -447,49 +447,6 @@ Debian stable Clang: # make echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" -Debian stable musl: - stage: build - - when: manual - - allow_failure: true - - artifacts: - paths: - - "bin/" - - "src/comptime.h" - expose_as: "musl" - name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-musl" - - variables: - CC: musl-gcc - LDD: musl-ldd - - script: - - - | - # apt_toolchain - echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages" - - apt-get install gcc - - | - # apt_toolchain - echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K" - - - - | - # apt_development - echo -e "\e[0Ksection_start:`date +%s`:apt_development[collapsed=true]\r\e[0KInstalling development packages" - - apt-get install musl-tools - - | - # apt_development - echo -e "\e[0Ksection_end:`date +%s`:apt_development\r\e[0K" - - - - | - # make - echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" - - make --directory=src --keep-going CCACHE=1 ERRORMODE=1 NONX86=1 SDL=0 NOHW=1 NOZLIB=1 NOCURL=1 NOGME=1 NOOPENMPT=1 || make --directory=src --keep-going CCACHE=1 ERRORMODE=1 NONX86=1 SDL=0 NOHW=1 NOZLIB=1 NOCURL=1 NOGME=1 NOOPENMPT=1 NOUPNP=1 - - | - # make - echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" - Debian testing Clang: extends: Debian stable Clang @@ -510,25 +467,6 @@ Debian testing Clang: CFLAGS: -Wno-cast-align -Wno-deprecated-non-prototype LDFLAGS: -Wl,-fuse-ld=gold -Debian testing musl: - extends: Debian stable musl - - when: manual - - image: debian:testing-slim - - artifacts: - paths: - - "bin/" - - "src/comptime.h" - expose_as: "testing-musl" - name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-testing-musl" - - variables: - CC: musl-gcc - LDD: musl-ldd - LDFLAGS: -Wl,-fuse-ld=gold - Alpine 3 GCC: stage: build From efb1b491aa6bdae2f1eded559cda300a88c276ee Mon Sep 17 00:00:00 2001 From: Jisk Date: Mon, 8 Jan 2024 19:13:30 +0000 Subject: [PATCH 221/227] Expose chat_on to Lua as chatactive --- src/lua_script.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lua_script.c b/src/lua_script.c index f90db7bc3..d63f885f4 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -38,6 +38,8 @@ #include "doomstat.h" #include "g_state.h" +#include "hu_stuff.h" + lua_State *gL = NULL; // List of internal libraries to load from SRB2 @@ -432,6 +434,9 @@ int LUA_PushGlobals(lua_State *L, const char *word) return 0; LUA_PushUserdata(L, &camera2, META_CAMERA); return 1; + } else if (fastcmp(word, "chatactive")) { + lua_pushboolean(L, chat_on); + return 1; } return 0; } From 159307c552a66266c4a2f533baca192b94b801e7 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Mon, 8 Jan 2024 22:03:31 -0300 Subject: [PATCH 222/227] Fix 3D floor side texture scale when there are multiple on the same line --- src/r_segs.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/r_segs.c b/src/r_segs.c index 62fea352f..16d6a1856 100644 --- a/src/r_segs.c +++ b/src/r_segs.c @@ -525,6 +525,9 @@ static boolean R_IsFFloorTranslucent(visffloor_t *pfloor) // // R_RenderThickSideRange // Renders all the thick sides in the given range. + +static fixed_t ffloortexturecolumn[MAXVIDWIDTH]; + void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) { size_t pindex; @@ -732,10 +735,10 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) wall_scalex = FixedDiv(FRACUNIT, sidedef->scalex_mid); wall_scaley = sidedef->scaley_mid; - thicksidecol = ds->thicksidecol; + thicksidecol = ffloortexturecolumn; for (INT32 x = x1; x <= x2; x++) - thicksidecol[x] = FixedDiv(thicksidecol[x], wall_scalex) + ds->offsetx; + thicksidecol[x] = FixedDiv(ds->thicksidecol[x], wall_scalex) + ds->offsetx; mfloorclip = ds->sprbottomclip; mceilingclip = ds->sprtopclip; From 28c30a9294e8aeadf0ecc685991ced1513ac7227 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Mon, 8 Jan 2024 22:08:50 -0300 Subject: [PATCH 223/227] Optimize for when wall_scalex == 1.0 --- src/r_segs.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/r_segs.c b/src/r_segs.c index 16d6a1856..6ca7bce28 100644 --- a/src/r_segs.c +++ b/src/r_segs.c @@ -737,8 +737,16 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) thicksidecol = ffloortexturecolumn; - for (INT32 x = x1; x <= x2; x++) - thicksidecol[x] = FixedDiv(ds->thicksidecol[x], wall_scalex) + ds->offsetx; + if (wall_scalex == FRACUNIT) + { + for (INT32 x = x1; x <= x2; x++) + thicksidecol[x] = ds->thicksidecol[x] + ds->offsetx; + } + else + { + for (INT32 x = x1; x <= x2; x++) + thicksidecol[x] = FixedDiv(ds->thicksidecol[x], wall_scalex) + ds->offsetx; + } mfloorclip = ds->sprbottomclip; mceilingclip = ds->sprtopclip; From 3e34c961658ba95f8308de12473a237c5be59bdb Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Tue, 9 Jan 2024 14:14:49 -0300 Subject: [PATCH 224/227] Fix 3D floor sides not using the correct set of line flags --- src/hardware/hw_main.c | 9 +++++++-- src/r_segs.c | 14 +++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index d448aa55e..db4fb53c8 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -1625,12 +1625,17 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom side_t *side = &sides[rover->master->sidenum[0]]; + INT16 lineflags; + if (rover->master->flags & ML_TFERLINE) { size_t linenum = gl_curline->linedef-gl_backsector->lines[0]; newline = rover->master->frontsector->lines[0] + linenum; side = &sides[newline->sidenum[0]]; + lineflags = newline->flags; } + else + lineflags = gl_curline->linedef->flags; texnum = R_GetTextureNum(side->midtexture); @@ -1669,13 +1674,13 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom // ...Oh well, anyway, Lower Unpegged now changes pegging of FOFs like in software // -- Monster Iestyn 26/06/18 fixed_t texturevpeg = side->rowoffset + side->offsety_mid; - boolean attachtobottom = !!(rover->master->flags & ML_DONTPEGBOTTOM); + boolean attachtobottom = !!(lineflags & ML_DONTPEGBOTTOM); grTex = HWR_GetTexture(texnum); xscale = FixedToFloat(side->scalex_mid); yscale = FixedToFloat(side->scaley_mid); - if (!(rover->master->flags & ML_SKEWTD)) // no skewing + if (!(lineflags & ML_SKEWTD)) // no skewing { if (attachtobottom) texturevpeg -= (*rover->topheight - *rover->bottomheight) * yscale; diff --git a/src/r_segs.c b/src/r_segs.c index 6ca7bce28..bd4869bdc 100644 --- a/src/r_segs.c +++ b/src/r_segs.c @@ -553,7 +553,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) fixed_t left_top, left_bottom; // needed here for slope skewing pslope_t *skewslope = NULL; boolean do_texture_skew; - UINT32 lineflags; + INT16 lineflags; fixed_t wall_scalex, wall_scaley; void (*colfunc_2s) (column_t *); @@ -578,7 +578,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) lineflags = newline->flags; } else - lineflags = pfloor->master->flags; + lineflags = curline->linedef->flags; texnum = R_GetTextureNum(sidedef->midtexture); @@ -757,10 +757,12 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) left_bottom = P_GetFFloorBottomZAt(pfloor, ds->leftpos.x, ds->leftpos.y) - viewz; do_texture_skew = lineflags & ML_SKEWTD; - skewslope = *pfloor->t_slope; // skew using top slope by default if (do_texture_skew) + { + skewslope = *pfloor->t_slope; // skew using top slope by default dc_texturemid = FixedMul(left_top, wall_scaley); + } else dc_texturemid = FixedMul(*pfloor->topheight - viewz, wall_scaley); @@ -768,14 +770,16 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) if (lineflags & ML_DONTPEGBOTTOM) { - skewslope = *pfloor->b_slope; // skew using bottom slope if (do_texture_skew) + { + skewslope = *pfloor->b_slope; // skew using bottom slope dc_texturemid = FixedMul(left_bottom, wall_scaley); + } else offsetvalue -= FixedMul(*pfloor->topheight - *pfloor->bottomheight, wall_scaley); } - if (do_texture_skew && skewslope) + if (skewslope) { angle_t lineangle = R_PointToAngle2(curline->v1->x, curline->v1->y, curline->v2->x, curline->v2->y); ffloortextureslide = FixedMul(skewslope->zdelta, FINECOSINE((lineangle-skewslope->xydirection)>>ANGLETOFINESHIFT)); From 9d718bc6392c28bf242ca672d81a6630251c9e02 Mon Sep 17 00:00:00 2001 From: Zwip-Zwap Zapony Date: Wed, 10 Jan 2024 21:26:04 +0100 Subject: [PATCH 225/227] Fix single-sided sloped midtextures in OpenGL --- src/hardware/hw_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index db4fb53c8..e0548e50d 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -1532,7 +1532,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom wallVerts[3].t += (gl_frontsector->ceilingheight - worldtop) * yscale * grTex->scaleY; wallVerts[2].t += (gl_frontsector->ceilingheight - worldtopslope) * yscale * grTex->scaleY; wallVerts[0].t += (gl_frontsector->floorheight - worldbottom) * yscale * grTex->scaleY; - wallVerts[1].t += (gl_frontsector->floorheight - worldbottomslope) * yscale * yscale; + wallVerts[1].t += (gl_frontsector->floorheight - worldbottomslope) * yscale * grTex->scaleY; } else if (gl_linedef->flags & ML_DONTPEGBOTTOM) { wallVerts[3].t = wallVerts[0].t + ((worldbottom - worldtop) * yscale) * grTex->scaleY; wallVerts[2].t = wallVerts[1].t + ((worldbottomslope - worldtopslope) * yscale) * grTex->scaleY; From 5a2a24b8688e8e9c91f6e09650e82d7e55b96534 Mon Sep 17 00:00:00 2001 From: Mari0shi Date: Thu, 11 Jan 2024 12:11:54 +0000 Subject: [PATCH 226/227] Draw Character in NiGHTS Attack --- src/lua_skinlib.c | 7 +++++- src/m_menu.c | 55 ++++++++++++++++++++++++++++++++++++++++++----- src/r_skins.c | 6 +++++- src/r_skins.h | 1 + 4 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/lua_skinlib.c b/src/lua_skinlib.c index 3debd3746..ee5be8d9e 100644 --- a/src/lua_skinlib.c +++ b/src/lua_skinlib.c @@ -54,7 +54,8 @@ enum skin { skin_contspeed, skin_contangle, skin_soundsid, - skin_sprites + skin_sprites, + skin_natkcolor }; static const char *const skin_opt[] = { @@ -94,6 +95,7 @@ static const char *const skin_opt[] = { "contangle", "soundsid", "sprites", + "natkcolor", NULL}; #define UNIMPLEMENTED luaL_error(L, LUA_QL("skin_t") " field " LUA_QS " is not implemented for Lua and cannot be accessed.", skin_opt[field]) @@ -218,6 +220,9 @@ static int skin_get(lua_State *L) case skin_sprites: LUA_PushUserdata(L, skin->sprites, META_SKINSPRITES); break; + case skin_natkcolor: + lua_pushinteger(L, skin->natkcolor); + break; } return 1; } diff --git a/src/m_menu.c b/src/m_menu.c index 9aae59445..78da84166 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -5896,14 +5896,14 @@ static void M_DrawNightsAttackBackground(void) } // NiGHTS Attack floating Super Sonic. -static patch_t *ntssupersonic[2]; +/*static patch_t *ntssupersonic[2]; static void M_DrawNightsAttackSuperSonic(void) { const UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_YELLOW, GTC_CACHE); INT32 timer = FixedInt(ntsatkdrawtimer/4) % 2; angle_t fa = (FixedAngle((FixedInt(ntsatkdrawtimer * 4) % 360)<>ANGLETOFINESHIFT) & FINEMASK; V_DrawFixedPatch(235<spriteframes[spritetimer]; //Our animation frame is equal to the number on the timer + + NightsAttackSprite = W_CachePatchNum(sprframe->lumppat[6], PU_PATCH); //Draw the right facing angle + + if (skins[skinnumber].natkcolor) //If you set natkcolor use it + color = skins[skinnumber].natkcolor; + else if ((skins[skinnumber].flags & SF_SUPER) && !(skins[skinnumber].flags & SF_NONIGHTSSUPER)) //If you go super in NiGHTS, use supercolor + color = skins[skinnumber].supercolor+4; + else //If you don't go super in NiGHTS or at all, use prefcolor + color = skins[skinnumber].prefcolor; + + + + colormap = R_GetTranslationColormap(TC_BLINK, color, GTC_CACHE); //Make the sprite color be our prefcolor + + angle_t fa = (FixedAngle(((FixedInt(ntsatkdrawtimer * 4)) % 360)<>ANGLETOFINESHIFT) & FINEMASK; + + if (sprframe->flip & 1<<6) //If our sprite is supposed to be flipped + flags = V_FLIP; //Flip it + else + flags = 0; + + + V_DrawFixedPatch((270<width)*3)/2, 135, 0, ngradeletters[bestoverall]); @@ -10348,8 +10393,8 @@ static void M_NightsAttack(INT32 choice) // This is really just to make sure Sonic is the played character, just in case M_PatchSkinNameTable(); - ntssupersonic[0] = W_CachePatchName("NTSSONC1", PU_PATCH); - ntssupersonic[1] = W_CachePatchName("NTSSONC2", PU_PATCH); + //ntssupersonic[0] = W_CachePatchName("NTSSONC1", PU_PATCH); + //ntssupersonic[1] = W_CachePatchName("NTSSONC2", PU_PATCH); G_SetGamestate(GS_TIMEATTACK); // do this before M_SetupNextMenu so that menu meta state knows that we're switching titlemapinaction = TITLEMAP_OFF; // Nope don't give us HOMs please diff --git a/src/r_skins.c b/src/r_skins.c index fbc2a30e1..06da17be6 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -149,6 +149,8 @@ static void Sk_SetDefaultValue(skin_t *skin) skin->contspeed = 17; skin->contangle = 0; + skin->natkcolor = SKINCOLOR_NONE; + for (i = 0; i < sfx_skinsoundslot0; i++) if (S_sfx[i].skinsound != -1) skin->soundsid[S_sfx[i].skinsound] = i; @@ -603,7 +605,6 @@ static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value) UINT16 color = R_GetSuperColorByName(value); skin->supercolor = (color ? color : SKINCOLOR_SUPERGOLD1); } - #define GETFLOAT(field) else if (!stricmp(stoken, #field)) skin->field = FLOAT_TO_FIXED(atof(value)); GETFLOAT(jumpfactor) GETFLOAT(highresscale) @@ -644,6 +645,9 @@ static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value) GETFLAG(NOSHIELDABILITY) #undef GETFLAG + else if (!stricmp(stoken, "natkcolor")) + skin->natkcolor = R_GetColorByName(value); // SKINCOLOR_NONE is allowed here + else // let's check if it's a sound, otherwise error out { boolean found = false; diff --git a/src/r_skins.h b/src/r_skins.h index bf2275a49..5acb415fe 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -70,6 +70,7 @@ typedef struct UINT16 prefcolor; UINT16 supercolor; UINT16 prefoppositecolor; // if 0 use tables instead + UINT16 natkcolor; //Color for Nights Attack Menu fixed_t highresscale; // scale of highres, default is 0.5 UINT8 contspeed; // continue screen animation speed From 35b6b215623c8f1bb388707202153ce8688152c2 Mon Sep 17 00:00:00 2001 From: spherallic Date: Thu, 11 Jan 2024 13:18:32 +0100 Subject: [PATCH 227/227] Clean up NiGHTS attack character code, fix compile issues --- src/m_menu.c | 62 ++++++++++++++-------------------------------------- 1 file changed, 17 insertions(+), 45 deletions(-) diff --git a/src/m_menu.c b/src/m_menu.c index 6b6fa7ec9..504a5c6cb 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -5979,16 +5979,6 @@ static void M_DrawNightsAttackBackground(void) if (ntsatkdrawtimer < 0) ntsatkdrawtimer = 0; } -// NiGHTS Attack floating Super Sonic. -/*static patch_t *ntssupersonic[2]; -static void M_DrawNightsAttackSuperSonic(void) -{ - const UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_YELLOW, GTC_CACHE); - INT32 timer = FixedInt(ntsatkdrawtimer/4) % 2; - angle_t fa = (FixedAngle((FixedInt(ntsatkdrawtimer * 4) % 360)<>ANGLETOFINESHIFT) & FINEMASK; - V_DrawFixedPatch(235<sprites[SPR2_NFLY].numframes == 0) //If we don't have NiGHTS sprites skinnumber = 0; //Default to Sonic else skinnumber = (cv_chooseskin.value-1); - spritedef_t *sprdef = &skins[skinnumber].sprites[SPR2_NFLY]; //Make our patch the selected character's NFLY sprite - spritetimer = FixedInt(ntsatkdrawtimer/2) % skins[skinnumber].sprites[SPR2_NFLY].numframes; //Make the sprite timer cycle though all the frames at 2 tics per frame + spritedef_t *sprdef = &skins[skinnumber]->sprites[SPR2_NFLY]; //Make our patch the selected character's NFLY sprite + spritetimer = FixedInt(ntsatkdrawtimer/2) % skins[skinnumber]->sprites[SPR2_NFLY].numframes; //Make the sprite timer cycle though all the frames at 2 tics per frame spriteframe_t *sprframe = &sprdef->spriteframes[spritetimer]; //Our animation frame is equal to the number on the timer - NightsAttackSprite = W_CachePatchNum(sprframe->lumppat[6], PU_PATCH); //Draw the right facing angle + natksprite = W_CachePatchNum(sprframe->lumppat[6], PU_PATCH); //Draw the right facing angle - if (skins[skinnumber].natkcolor) //If you set natkcolor use it - color = skins[skinnumber].natkcolor; - else if ((skins[skinnumber].flags & SF_SUPER) && !(skins[skinnumber].flags & SF_NONIGHTSSUPER)) //If you go super in NiGHTS, use supercolor - color = skins[skinnumber].supercolor+4; + if (skins[skinnumber]->natkcolor) //If you set natkcolor use it + color = skins[skinnumber]->natkcolor; + else if ((skins[skinnumber]->flags & SF_SUPER) && !(skins[skinnumber]->flags & SF_NONIGHTSSUPER)) //If you go super in NiGHTS, use supercolor + color = skins[skinnumber]->supercolor+4; else //If you don't go super in NiGHTS or at all, use prefcolor - color = skins[skinnumber].prefcolor; - - - - colormap = R_GetTranslationColormap(TC_BLINK, color, GTC_CACHE); //Make the sprite color be our prefcolor + color = skins[skinnumber]->prefcolor; angle_t fa = (FixedAngle(((FixedInt(ntsatkdrawtimer * 4)) % 360)<>ANGLETOFINESHIFT) & FINEMASK; - if (sprframe->flip & 1<<6) //If our sprite is supposed to be flipped - flags = V_FLIP; //Flip it - else - flags = 0; - - - V_DrawFixedPatch((270<highresscale, skins[skinnumber]->shieldscale), + (sprframe->flip & 1<<6) ? V_FLIP : 0, + natksprite, + R_GetTranslationColormap(TC_BLINK, color, GTC_CACHE)); //if (P_HasGrades(cv_nextmap.value, 0)) // V_DrawScaledPatch(235 - (((ngradeletters[bestoverall])->width)*3)/2, 135, 0, ngradeletters[bestoverall]); @@ -10483,9 +10458,6 @@ static void M_NightsAttack(INT32 choice) // This is really just to make sure Sonic is the played character, just in case M_PatchSkinNameTable(); - //ntssupersonic[0] = W_CachePatchName("NTSSONC1", PU_PATCH); - //ntssupersonic[1] = W_CachePatchName("NTSSONC2", PU_PATCH); - G_SetGamestate(GS_TIMEATTACK); // do this before M_SetupNextMenu so that menu meta state knows that we're switching titlemapinaction = TITLEMAP_OFF; // Nope don't give us HOMs please M_SetupNextMenu(&SP_NightsAttackDef);

7YPGVV!|FDU7{YvC5yRS+6p@| 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 z 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