diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 67799fd62..e581a8dfb 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -16,6 +16,8 @@ #include //for unlink #endif +#include + #include "i_time.h" #include "i_net.h" #include "i_system.h" @@ -87,6 +89,17 @@ boolean serverrunning = false; INT32 serverplayer = 0; char motd[254], server_context[8]; // Message of the Day, Unique Context (even without Mumble support) +#define MAP_ICON_REQUEST_FREQUENCY 8 + +static UINT8 map_icon_index; // amount of fragments we have retrieved +static UINT8 map_icon_needed; // amount of fragments we need to get the full data set +static INT32 map_icon_read; // amount of bytes we have read +static UINT32 map_icon_missing; // bitmask of fragments we haven't received yet +static tic_t map_icon_last_request; // last time we sent an icon request, to avoid flood +static int map_icon_request_count; // current count of icon requests sent +static UINT8 *map_icon_data; +static patch_t *map_icon; + plrinfo playerinfo[MAXPLAYERS]; SINT8 joinnode = 0; // used for CL_VIEWSERVER @@ -1211,6 +1224,10 @@ static inline void CL_DrawConnectionStatus(void) if (!menuactive) // menu already draws its own fade V_DrawFadeScreen(0xFF00, 16); // force default + // reset the counter for mapicon + if (cl_mode != CL_VIEWSERVER) + map_icon_request_count = 0; + if (cl_mode != CL_DOWNLOADFILES && cl_mode != CL_LOADFILES && cl_mode != CL_CHECKFILES && cl_mode != CL_VIEWSERVER #ifdef HAVE_CURL && cl_mode != CL_DOWNLOADHTTPFILES @@ -1337,9 +1354,42 @@ static inline void CL_DrawConnectionStatus(void) V_DrawThinString(12 + 80, 18, V_ALLOWLOWERCASE, va("%s", serverlist[joinnode].info.servername)); - const char *map = va("%sP", serverlist[joinnode].info.mapname); - patch_t *current_map = W_LumpExists(map) ? W_CachePatchName(map, PU_CACHE) : W_CachePatchName("BLANKLVL", PU_CACHE); - V_DrawSmallScaledPatch(10, 18, 0, current_map); + if (map_icon == NULL) + { + const char *map = va("%sP", serverlist[joinnode].info.mapname); + patch_t *current_map = W_LumpExists(map) ? W_CachePatchName(map, PU_CACHE) : NULL; + + // check first if we may have the map icon loaded already + if (current_map != NULL) + V_DrawSmallScaledPatch(10, 18, 0, current_map); + else if (map_icon_data != NULL && map_icon_request_count <= 5) + { + if (I_GetTime() - map_icon_last_request > MAP_ICON_REQUEST_FREQUENCY) + { + doomdata_t *netbuffer = DOOMCOM_DATA(doomcom); + netbuffer->packettype = PT_NEEDMAPICON; + + // failed to retrieve map icon, request it again + HSendPacket(servernode, false, 0, 0); + map_icon_last_request = I_GetTime(); + map_icon_request_count++; + } + + V_DrawFill(10, 18, MAP_ICON_WIDTH / 2, MAP_ICON_HEIGHT / 2, 31); + char dots[4] = "..."; + if ((I_GetTime() & 0xf) < 4) + dots[0] = '\0'; + else if ((I_GetTime() & 0xf) < 8) + dots[1] = '\0'; + else if ((I_GetTime() & 0xf) < 12) + dots[2] = '\0'; + V_DrawString(10 + MAP_ICON_WIDTH / 4 - V_StringWidth("...", 0) / 2, 18 + MAP_ICON_HEIGHT / 4 - 3, 0, dots); + } + else + V_DrawSmallScaledPatch(10, 18, 0, W_CachePatchName("BLANKLVL", PU_CACHE)); + } + else + V_DrawSmallScaledPatch(10, 18, 0, map_icon); if (serverlist[joinnode].info.iszone) V_DrawThinString(12 + 80, 38, V_ALLOWLOWERCASE, va("%s Zone", serverlist[joinnode].info.maptitle)); @@ -1705,6 +1755,83 @@ static void SV_SendPlayerInfo(INT32 node) HSendPacket(node, false, 0, sizeof(plrinfo) * MSCOMPAT_MAXPLAYERS); } +static void SV_SendMapIcon(INT32 node) +{ + doomdata_t *netbuffer = DOOMCOM_DATA(doomcom); + + const char *map = va("%sP", G_BuildMapName(gamemap)); + if (!W_LumpExists(map) || (mapheaderinfo[gamemap-1]->menuflags & LF2_HIDEINMENU)) + { + // send an empty icon to tell the client there is no map icon + netbuffer->packettype = PT_MAPICON; + UINT8 *icondata = netbuffer->u.mapicondata; + WRITEUINT8(icondata, 0x80); + HSendPacket(node, false, 0, 1); + return; + } + + patch_t *current_map = W_CachePatchName(map, PU_CACHE); + if (current_map->width != MAP_ICON_WIDTH || current_map->height != MAP_ICON_HEIGHT) + { + // we only support 160x100 for now + netbuffer->packettype = PT_MAPICON; + UINT8 *icondata = netbuffer->u.mapicondata; + WRITEUINT8(icondata, 0x80); + HSendPacket(node, false, 0, 1); + return; + } + + UINT8 *data = Z_Malloc(2 * current_map->width * current_map->height, PU_STATIC, NULL); + UINT8 *pixeldata = data + current_map->width * current_map->height; + + R_PatchToPixels(current_map, pixeldata); + + z_stream stream; + stream.zalloc = NULL; + stream.zfree = NULL; + stream.opaque = NULL; + if (deflateInit(&stream, Z_DEFAULT_COMPRESSION) != Z_OK) + { + CONS_Alert(CONS_WARNING, "Cannot compress map icon, not enough memory\n"); + Z_Free(data); + return; + } + + stream.next_in = pixeldata; + stream.avail_in = current_map->width * current_map->height; + stream.next_out = data; + stream.avail_out = current_map->width * current_map->height; + if (deflate(&stream, Z_FINISH) != Z_STREAM_END) + { + CONS_Alert(CONS_WARNING, "Cannot compress map icon, error occured during compression\n"); + deflateEnd(&stream); + Z_Free(data); + return; + } + size_t count = stream.total_out; + deflateEnd(&stream); + + size_t index = 0; + + for (size_t i = 0; i < count; i += MAXPACKETLENGTH-BASEPACKETSIZE-1) + { + size_t size = min(MAXPACKETLENGTH-BASEPACKETSIZE-1, count-i); + + UINT8 *icondata = netbuffer->u.mapicondata; + netbuffer->packettype = PT_MAPICON; + + if (count-i <= MAXPACKETLENGTH-BASEPACKETSIZE-1) + WRITEUINT8(icondata, 0x80 | index); + else + WRITEUINT8(icondata, index); + index++; + + WRITEMEM(icondata, &data[i], size); + HSendPacket(node, false, 0, size + 1); + } + Z_Free(data); +} + /** Sends a PT_SERVERCFG packet * * \param node The destination @@ -2687,9 +2814,26 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic if (cl_mode == CL_VIEWSERVER) { if (key == KEY_ENTER || key == KEY_JOY1) + { cl_mode = CL_CHECKFILES; + if (map_icon != NULL) + Patch_Free(map_icon); + map_icon = NULL; + if (map_icon_data != NULL) + Z_Free(map_icon_data); + map_icon_data = NULL; + } else if (key == KEY_ESCAPE || key == KEY_JOY1+1) + { cl_mode = CL_ABORTED; + if (map_icon != NULL) + Patch_Free(map_icon); + map_icon = NULL; + if (map_icon_data != NULL) + Z_Free(map_icon_data); + map_icon_data = NULL; + map_icon_request_count = 0; + } } // Only ESC and non-keyboard keys abort connection @@ -2805,6 +2949,17 @@ static void CL_ConnectToServer(void) serverlist[i].info.version, serverlist[i].info.subversion); } SL_ClearServerList(servernode); + + if (map_icon_data != NULL) + Z_Free(map_icon_data); + if (map_icon != NULL) + Patch_Free(map_icon); + map_icon_data = Z_Malloc(MAP_ICON_WIDTH * MAP_ICON_HEIGHT, PU_CACHE, NULL); + map_icon_index = 0; + map_icon_needed = 0xff; + map_icon_read = 0; + map_icon_missing = 0xffffffff; + map_icon_last_request = I_GetTime(); #endif do @@ -4690,7 +4845,7 @@ static void HandleConnect(SINT8 node) connectedplayers++; #ifdef SATURNJOIN - const boolean issaturn = (((doomcom->datalength-BASEPACKETSIZE) == sizeof(clientconfig_pak)) && netbuffer->u.clientcfg.issaturn == ISSATURN); // Check the packet lenght to skip potential garbo data! + const boolean issaturn = (((doomcom->datalength) == sizeof(clientconfig_pak)) && netbuffer->u.clientcfg.issaturn == ISSATURN); // Check the packet lenght to skip potential garbo data! #endif if (bannednode && bannednode[node].banid != SIZE_MAX) @@ -4860,17 +5015,6 @@ static void HandleTimeout(SINT8 node) M_StartMessage(M_GetText("Server Timeout\n\nPress Esc\n"), NULL, MM_NOTHING); } -// Helper function for packets that should only be sent by the server -// If it is NOT from the server, bail out and close the connection! -static boolean ServerOnly(SINT8 node) -{ - if (node == servernode) - return false; - - Net_CloseConnection(node); - return true; -} - #ifndef NONET /** Called when a PT_SERVERINFO packet is received * @@ -4910,6 +5054,115 @@ static void HandlePlayerInfo(void) playerinfo[i] = netbuffer->u.playerinfo[i]; } +// Helper function for packets that should only be sent by the server +// If it is NOT from the server, bail out and close the connection! +static boolean ServerOnly(SINT8 node) +{ + if (node == servernode) + return false; + + Net_CloseConnection(node); + return true; +} + +static void PT_MapIcon(void) +{ + if (map_icon_data == NULL) + return; + + if (ServerOnly(doomcom->remotenode)) + return; + + doomdata_t *netbuffer = DOOMCOM_DATA(doomcom); + + UINT8 *icondata = netbuffer->u.mapicondata; + + map_icon_last_request = I_GetTime(); + map_icon_request_count = 0; + + UINT8 index = READUINT8(icondata); + if (index & 0x80) + map_icon_needed = (index & 0x7f) + 1; + index &= 0x7f; + + // check if we already got this fragment + if (map_icon_missing & ((UINT32)1 << index)) + { + map_icon_missing &= ~((UINT32)1 << index); + INT32 offset = index * (MAXPACKETLENGTH-BASEPACKETSIZE-1); + if (offset + doomcom->datalength-1 > MAP_ICON_WIDTH * MAP_ICON_HEIGHT) + { + CONS_Alert(CONS_WARNING, "Excess amount of data for map icon (expected %d, got %d)\n", MAP_ICON_WIDTH * MAP_ICON_HEIGHT, offset + doomcom->datalength-1); + Z_Free(map_icon_data); + map_icon_data = NULL; + return; + } + + READMEM(icondata, &map_icon_data[offset], doomcom->datalength-1); + map_icon_read += doomcom->datalength-1; + map_icon_index++; + } + + if (map_icon_index >= map_icon_needed) + { + if (map_icon_read == 0) + { + // map has no icon + Z_Free(map_icon_data); + map_icon_data = NULL; + return; + } + + UINT8 *data = Z_Malloc(MAP_ICON_WIDTH * MAP_ICON_HEIGHT, PU_STATIC, NULL); + z_stream stream; + stream.zalloc = NULL; + stream.zfree = NULL; + stream.opaque = NULL; + stream.next_in = map_icon_data; + stream.avail_in = map_icon_read; + if (inflateInit(&stream) != Z_OK) + { + CONS_Alert(CONS_WARNING, "Cannot decompress map icon, not enough memory\n"); + Z_Free(data); + Z_Free(map_icon_data); + map_icon_data = NULL; + return; + } + stream.next_out = data; + stream.avail_out = MAP_ICON_WIDTH * MAP_ICON_HEIGHT; + if (inflate(&stream, Z_FINISH) != Z_STREAM_END) + { + CONS_Alert(CONS_WARNING, "Failed to decompress map icon, map icon is invalid or corrupted\n"); + deflateEnd(&stream); + Z_Free(data); + Z_Free(map_icon_data); + map_icon_data = NULL; + return; + } + inflateEnd(&stream); + Z_Free(map_icon_data); + map_icon_data = NULL; + + if (stream.total_out > MAP_ICON_WIDTH * MAP_ICON_HEIGHT) + { + CONS_Alert(CONS_WARNING, "Excess amount of data for map icon (expected %d, got %ld)\n", MAP_ICON_WIDTH * MAP_ICON_HEIGHT, stream.total_out); + Z_Free(data); + return; + } + else if (stream.total_out < MAP_ICON_WIDTH * MAP_ICON_HEIGHT) + { + CONS_Alert(CONS_WARNING, "Insufficient amount of data for map icon (expected %d, got %ld)\n", MAP_ICON_WIDTH * MAP_ICON_HEIGHT, stream.total_out); + Z_Free(data); + return; + } + + //map_icon = Patch_CreateFromData(MAP_ICON_WIDTH, MAP_ICON_HEIGHT, data); + map_icon = (patch_t *)R_PixelsToPatch(data, MAP_ICON_WIDTH, MAP_ICON_HEIGHT, 0, 0, NULL); + + Z_Free(data); + } +} + static void PT_TellFilesNeeded(SINT8 node) { if (server && serverrunning) @@ -4958,11 +5211,18 @@ static void PT_AskInfo(SINT8 node) doomdata_t *netbuffer = DOOMCOM_DATA(doomcom); SV_SendServerInfo(node, (tic_t)LONG(netbuffer->u.askinfo.time)); SV_SendPlayerInfo(node); // Send extra info + //SV_SendMapIcon(node); // idk i dont think we need this here } Net_CloseConnection(node); } +static void PT_NeedMapIcon(SINT8 node) +{ + SV_SendMapIcon(node); + Net_CloseConnection(node); +} + // Negative response of client join request static void PT_ServerRefuse(SINT8 node) { @@ -5223,6 +5483,12 @@ static void HandlePacketFromAwayNode(SINT8 node) case PT_PLAYERINFO: HandlePlayerInfo(); break; + case PT_MAPICON: + PT_MapIcon(); + break; + case PT_NEEDMAPICON: + PT_NeedMapIcon(node); + break; case PT_SERVERTICS: // Do not remove my own server (we have just get a out of order packet) if (node == servernode) @@ -5511,10 +5777,10 @@ static void PT_TextCmd(INT32 netconsole, SINT8 node) // ignore if the textcmd size var is actually larger than it should be // BASEPACKETSIZE + 1 (for size) + textcmd[0] should == datalength - if (netbuffer->u.textcmd[0] > (size_t)doomcom->datalength-BASEPACKETSIZE-1) + if (netbuffer->u.textcmd[0] > (size_t)doomcom->datalength-1) { DEBFILE(va("GetPacket: Bad Textcmd packet size! (expected %d, actual %s, node %u, player %d)\n", - netbuffer->u.textcmd[0], sizeu1((size_t)doomcom->datalength-BASEPACKETSIZE-1), + netbuffer->u.textcmd[0], sizeu1((size_t)doomcom->datalength-1), node, netconsole)); Net_UnAcknowledgePacket(node); return; diff --git a/src/d_clisrv.h b/src/d_clisrv.h index 4b967e670..7582fdc2c 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -120,6 +120,8 @@ typedef enum PT_ISSATURN, // Saturn specific identifier packet #endif + PT_MAPICON, // Send map image icon (gamespy). + PT_NEEDMAPICON, // Request missed map icon (gamespy). NUMPACKETTYPE } packettype_t; @@ -513,6 +515,7 @@ typedef struct INT32 filesneedednum; // 4 bytes filesneededconfig_pak filesneededcfg; // ??? bytes UINT32 pingtable[MAXPLAYERS+1]; // 68 bytes + UINT8 mapicondata[0]; } u; // This is needed to pack diff packet types data together } ATTRPACK doomdata_t; @@ -564,7 +567,6 @@ typedef enum KR_TIMEOUT, //Connection Timeout KR_BAN, //Banned by server KR_LEAVE, //Quit the game - } kickreason_t; /* the max number of name changes in some time period */ diff --git a/src/d_net.c b/src/d_net.c index 3a75a29a4..af1d1a777 100644 --- a/src/d_net.c +++ b/src/d_net.c @@ -1201,6 +1201,7 @@ boolean HGetPacket(void) GotAcks(); continue; } + doomcom->datalength -= BASEPACKETSIZE; break; } #endif // ifndef NONET diff --git a/src/r_defs.h b/src/r_defs.h index e1fc574bc..37a06c03d 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -25,6 +25,9 @@ #include "screen.h" // MAXVIDWIDTH, MAXVIDHEIGHT +#define MAP_ICON_WIDTH 160 +#define MAP_ICON_HEIGHT 100 + // // ClipWallSegment // Clips the given range of columns diff --git a/src/r_patch.c b/src/r_patch.c index f48996ea4..d8e0b94f3 100644 --- a/src/r_patch.c +++ b/src/r_patch.c @@ -451,6 +451,124 @@ void *Patch_CreateGL(patch_t *patch) } #endif // HWRENDER +// +// R_MaskedFlatToPatch +// +// Convert raw pixels to a patch. +// +void *R_PixelsToPatch(UINT8 *raw, INT16 width, INT16 height, INT16 leftoffset, INT16 topoffset, size_t *destsize) +{ + INT16 x, y; + UINT8 *img; + UINT8 *imgptr = imgbuf; + UINT8 *colpointers, *startofspan; + size_t size = 0; + + if (!raw) + return NULL; + + // Write image size and offset + WRITEINT16(imgptr, width); + WRITEINT16(imgptr, height); + WRITEINT16(imgptr, leftoffset); + WRITEINT16(imgptr, topoffset); + + // Leave placeholder to column pointers + colpointers = imgptr; + imgptr += width*4; + + // Write columns + for (x = 0; x < width; x++) + { + int lastStartY = 0; + int spanSize = 0; + startofspan = NULL; + + // Write column pointer + WRITEINT32(colpointers, imgptr - imgbuf); + + // Write pixels + for (y = 0; y < height; y++) + { + UINT8 pixel = raw[((y * width) + x)]; + UINT8 opaque = (pixel != 0xf7); // If not 247 (0xf7), we have a pixel + + // End span if we have a transparent pixel + if (!opaque) + { + if (startofspan) + WRITEUINT8(imgptr, 0); + startofspan = NULL; + continue; + } + + // Start new column if we need to + if (!startofspan || spanSize == 255) + { + int writeY = y; + + // If we reached the span size limit, finish the previous span + if (startofspan) + WRITEUINT8(imgptr, 0); + + if (y > 254) + { + // Make sure we're aligned to 254 + if (lastStartY < 254) + { + WRITEUINT8(imgptr, 254); + WRITEUINT8(imgptr, 0); + imgptr += 2; + lastStartY = 254; + } + + // Write stopgap empty spans if needed + writeY = y - lastStartY; + + while (writeY > 254) + { + WRITEUINT8(imgptr, 254); + WRITEUINT8(imgptr, 0); + imgptr += 2; + writeY -= 254; + } + } + + startofspan = imgptr; + WRITEUINT8(imgptr, writeY); + imgptr += 2; + spanSize = 0; + + lastStartY = y; + } + + // Write the pixel + WRITEUINT8(imgptr, pixel); + spanSize++; + startofspan[1] = spanSize; + } + + if (startofspan) + WRITEUINT8(imgptr, 0); + + WRITEUINT8(imgptr, 0xFF); + } + + size = imgptr-imgbuf; + img = Z_Malloc(size, PU_STATIC, NULL); + memcpy(img, imgbuf, size); + + if (destsize != NULL) + *destsize = size; + + patch_t *converted = Patch_Create((softwarepatch_t *)img, size, NULL); +#ifdef HWRENDER + Patch_CreateGL(converted); +#endif + Z_Free(img); + + return converted; +} // // R_MaskedFlatToPatch @@ -615,3 +733,44 @@ UINT16 R_GetPatchPixel(patch_t *patch, INT32 x, INT32 y, boolean flip) return 0xFF00; } + +void R_PatchToPixels(patch_t *patch, UINT8 *dst) +{ + const INT16 width = patch->width; + + // init out destination to cyan so we can easily check for it when we convert it back to a patch + memset(dst, 0xf7, (width * patch->height)); // should this be here or should this be taken care of before passing it? + + for (INT32 x = 0; x < width; ++x) + { + INT32 y = 0; + INT32 prevdelta = -1; + + column_t *column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[x])); + + while (column->topdelta != 0xff) + { + INT32 topdelta = column->topdelta; + + if (topdelta <= prevdelta) + topdelta += prevdelta; + + prevdelta = topdelta; + + for (; y-topdelta < column->length; ++y) + { + UINT8 pixel = 0; + + if (y >= topdelta) + { + UINT8 *source = (UINT8*)(column) + 3; + pixel = source[y-topdelta]; + } + + dst[(y*width) + x] = pixel; + } + + column = (column_t *)((UINT8 *)column + column->length + 4); + } + } +} diff --git a/src/r_patch.h b/src/r_patch.h index 2978ff10c..2dc9c2f4e 100644 --- a/src/r_patch.h +++ b/src/r_patch.h @@ -1,62 +1,64 @@ -// SONIC ROBO BLAST 2 -//----------------------------------------------------------------------------- -// Copyright (C) 1993-1996 by id Software, Inc. -// Copyright (C) 2018-2019 by Jaime "Lactozilla" Passos. -// Copyright (C) 2019 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 r_patch.h -/// \brief Patch generation. - -#ifndef __R_PATCH__ -#define __R_PATCH__ - -#include "r_defs.h" - -// Structs -typedef enum -{ - ROTAXIS_X, // Roll (the default) - ROTAXIS_Y, // Pitch - ROTAXIS_Z // Yaw -} rotaxis_t; - -typedef struct -{ - INT32 x, y; - rotaxis_t rotaxis; -} spriteframepivot_t; - -typedef struct -{ - spriteframepivot_t pivot[64]; - boolean available; -} spriteinfo_t; - -// Patch functions -patch_t *Patch_Create(softwarepatch_t *source, size_t srcsize, void *dest); -void Patch_Free(patch_t *patch); - -#define Patch_FreeTag(tagnum) Patch_FreeTags(tagnum, tagnum) -void Patch_FreeTags(INT32 lowtag, INT32 hightag); - -#ifdef HWRENDER -void *Patch_AllocateHardwarePatch(patch_t *patch); -void *Patch_CreateGL(patch_t *patch); -#endif - -// Conversions between patches / flats / textures... -boolean R_CheckIfPatch(lumpnum_t lump); - -// SpriteInfo -extern spriteinfo_t spriteinfo[NUMSPRITES]; -void R_LoadSpriteInfoLumps(UINT16 wadnum, UINT16 numlumps); -void R_ParseSPRTINFOLump(UINT16 wadNum, UINT16 lumpNum); - -void *R_MaskedFlatToPatch(UINT16 *raw, INT16 width, INT16 height, INT16 leftoffset, INT16 topoffset, size_t *destsize); -UINT16 R_GetPatchPixel(patch_t *patch, INT32 x, INT32 y, boolean flip); - -#endif // __R_PATCH__ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1993-1996 by id Software, Inc. +// Copyright (C) 2018-2019 by Jaime "Lactozilla" Passos. +// Copyright (C) 2019 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 r_patch.h +/// \brief Patch generation. + +#ifndef __R_PATCH__ +#define __R_PATCH__ + +#include "r_defs.h" + +// Structs +typedef enum +{ + ROTAXIS_X, // Roll (the default) + ROTAXIS_Y, // Pitch + ROTAXIS_Z // Yaw +} rotaxis_t; + +typedef struct +{ + INT32 x, y; + rotaxis_t rotaxis; +} spriteframepivot_t; + +typedef struct +{ + spriteframepivot_t pivot[64]; + boolean available; +} spriteinfo_t; + +// Patch functions +patch_t *Patch_Create(softwarepatch_t *source, size_t srcsize, void *dest); +void Patch_Free(patch_t *patch); + +#define Patch_FreeTag(tagnum) Patch_FreeTags(tagnum, tagnum) +void Patch_FreeTags(INT32 lowtag, INT32 hightag); + +#ifdef HWRENDER +void *Patch_AllocateHardwarePatch(patch_t *patch); +void *Patch_CreateGL(patch_t *patch); +#endif + +// Conversions between patches / flats / textures... +boolean R_CheckIfPatch(lumpnum_t lump); + +// SpriteInfo +extern spriteinfo_t spriteinfo[NUMSPRITES]; +void R_LoadSpriteInfoLumps(UINT16 wadnum, UINT16 numlumps); +void R_ParseSPRTINFOLump(UINT16 wadNum, UINT16 lumpNum); + +void *R_MaskedFlatToPatch(UINT16 *raw, INT16 width, INT16 height, INT16 leftoffset, INT16 topoffset, size_t *destsize); +void *R_PixelsToPatch(UINT8 *raw, INT16 width, INT16 height, INT16 leftoffset, INT16 topoffset, size_t *destsize); +UINT16 R_GetPatchPixel(patch_t *patch, INT32 x, INT32 y, boolean flip); +void R_PatchToPixels(patch_t *patch, UINT8 *dst); // Important: dst must have enough space for patch->width*patch->height pixels! + +#endif // __R_PATCH__