From 4f873d5f8f1e5fd64e096d2ae4278f5b0e867a1a Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Sun, 29 Oct 2023 20:33:49 -0300 Subject: [PATCH] WIP ZDoom translations --- src/CMakeLists.txt | 1 + src/Sourcefile | 1 + src/doomdef.h | 1 + src/m_misc.c | 94 ++++ src/p_setup.c | 3 + src/r_data.c | 7 + src/r_translation.c | 697 +++++++++++++++++++++++++++ src/r_translation.h | 48 ++ src/sdl/Srb2SDL-vc10.vcxproj | 2 + src/sdl/Srb2SDL-vc10.vcxproj.filters | 6 + 10 files changed, 860 insertions(+) create mode 100644 src/r_translation.c create mode 100644 src/r_translation.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 22c1def27..7987e94e8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -68,6 +68,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 r_things.c r_bbox.c r_textures.c + r_translation.c r_patch.c r_patchrotation.c r_picformats.c diff --git a/src/Sourcefile b/src/Sourcefile index 6ed1f3b4c..a37b63b1a 100644 --- a/src/Sourcefile +++ b/src/Sourcefile @@ -62,6 +62,7 @@ r_splats.c r_things.c r_bbox.c r_textures.c +r_translation.c r_patch.c r_patchrotation.c r_picformats.c diff --git a/src/doomdef.h b/src/doomdef.h index b382d0ecb..f36c6f286 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -540,6 +540,7 @@ void M_UnGetToken(void); void M_TokenizerOpen(const char *inputString); void M_TokenizerClose(void); const char *M_TokenizerRead(UINT32 i); +const char *M_TokenizerReadZDoom(UINT32 i); UINT32 M_TokenizerGetEndPos(void); void M_TokenizerSetEndPos(UINT32 newPos); char *sizeu1(size_t num); diff --git a/src/m_misc.c b/src/m_misc.c index f547f5c41..9232737c7 100644 --- a/src/m_misc.c +++ b/src/m_misc.c @@ -2129,6 +2129,100 @@ const char *M_TokenizerRead(UINT32 i) return tokenizerToken[i]; } +const char *M_TokenizerReadZDoom(UINT32 i) +{ + if (!tokenizerInput) + return NULL; + + tokenizerStartPos = tokenizerEndPos; + + // Try to detect comments now, in case we're pointing right at one + M_DetectComment(&tokenizerStartPos); + + // Find the first non-whitespace char, or else the end of the string trying + while ((tokenizerInput[tokenizerStartPos] == ' ' + || tokenizerInput[tokenizerStartPos] == '\t' + || tokenizerInput[tokenizerStartPos] == '\r' + || tokenizerInput[tokenizerStartPos] == '\n' + || tokenizerInput[tokenizerStartPos] == '\0' + || tokenizerInComment != 0) + && tokenizerStartPos < tokenizerInputLength) + { + // Try to detect comment endings now + if (tokenizerInComment == 1 && tokenizerInput[tokenizerStartPos] == '\n') + tokenizerInComment = 0; // End of line for a single-line comment + else if (tokenizerInComment == 2 + && tokenizerStartPos < tokenizerInputLength - 1 + && tokenizerInput[tokenizerStartPos] == '*' + && tokenizerInput[tokenizerStartPos+1] == '/') + { + // End of multi-line comment + tokenizerInComment = 0; + tokenizerStartPos++; // Make damn well sure we're out of the comment ending at the end of it all + } + + tokenizerStartPos++; + M_DetectComment(&tokenizerStartPos); + } + + // If the end of the string is reached, no token is to be read + if (tokenizerStartPos == tokenizerInputLength) { + tokenizerEndPos = tokenizerInputLength; + return NULL; + } + // Else, if it's one of these three symbols, capture only this one character + else if (tokenizerInput[tokenizerStartPos] == ',' + || tokenizerInput[tokenizerStartPos] == '{' + || tokenizerInput[tokenizerStartPos] == '}' + || tokenizerInput[tokenizerStartPos] == '[' + || tokenizerInput[tokenizerStartPos] == ']' + || tokenizerInput[tokenizerStartPos] == '=' + || tokenizerInput[tokenizerStartPos] == ':' + || tokenizerInput[tokenizerStartPos] == '%') + { + tokenizerEndPos = tokenizerStartPos + 1; + tokenizerToken[i][0] = tokenizerInput[tokenizerStartPos]; + tokenizerToken[i][1] = '\0'; + return tokenizerToken[i]; + } + // Return entire string within quotes, except without the quotes. + else if (tokenizerInput[tokenizerStartPos] == '"') + { + tokenizerEndPos = ++tokenizerStartPos; + while (tokenizerInput[tokenizerEndPos] != '"' && tokenizerEndPos < tokenizerInputLength) + tokenizerEndPos++; + + M_ReadTokenString(i); + tokenizerEndPos++; + return tokenizerToken[i]; + } + + // Now find the end of the token. This includes several additional characters that are okay to capture as one character, but not trailing at the end of another token. + tokenizerEndPos = tokenizerStartPos + 1; + while ((tokenizerInput[tokenizerEndPos] != ' ' + && tokenizerInput[tokenizerEndPos] != '\t' + && tokenizerInput[tokenizerEndPos] != '\r' + && tokenizerInput[tokenizerEndPos] != '\n' + && tokenizerInput[tokenizerEndPos] != ',' + && tokenizerInput[tokenizerEndPos] != '{' + && tokenizerInput[tokenizerEndPos] != '}' + && tokenizerInput[tokenizerEndPos] != '[' + && tokenizerInput[tokenizerEndPos] != ']' + && tokenizerInput[tokenizerEndPos] != '=' + && tokenizerInput[tokenizerEndPos] != ':' + && tokenizerInput[tokenizerEndPos] != '%' + && tokenizerInComment == 0) + && tokenizerEndPos < tokenizerInputLength) + { + tokenizerEndPos++; + // Try to detect comment starts now; if it's in a comment, we don't want it in this token + M_DetectComment(&tokenizerEndPos); + } + + M_ReadTokenString(i); + return tokenizerToken[i]; +} + UINT32 M_TokenizerGetEndPos(void) { return tokenizerEndPos; diff --git a/src/p_setup.c b/src/p_setup.c index 0390761b6..e0b6e902d 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -30,6 +30,7 @@ #include "r_data.h" #include "r_things.h" // for R_AddSpriteDefs #include "r_textures.h" +#include "r_translation.h" #include "r_patch.h" #include "r_picformats.h" #include "r_sky.h" @@ -8164,6 +8165,8 @@ static boolean P_LoadAddon(UINT16 numlumps) HWR_ClearAllTextures(); #endif + R_LoadTrnslateLumps(); + // // search for sprite replacements // diff --git a/src/r_data.c b/src/r_data.c index 4b7492f90..8a7a4dd6f 100644 --- a/src/r_data.c +++ b/src/r_data.c @@ -20,6 +20,7 @@ #include "m_misc.h" #include "r_data.h" #include "r_textures.h" +#include "r_translation.h" #include "r_patch.h" #include "r_picformats.h" #include "w_wad.h" @@ -1208,6 +1209,12 @@ void R_InitData(void) R_Init8to16(); } + CONS_Printf("PaletteRemap_Init()...\n"); + PaletteRemap_Init(); + + CONS_Printf("R_LoadTrnslateLumps()...\n"); + R_LoadTrnslateLumps(); + CONS_Printf("R_LoadTextures()...\n"); R_LoadTextures(); diff --git a/src/r_translation.c b/src/r_translation.c new file mode 100644 index 000000000..7e98fd084 --- /dev/null +++ b/src/r_translation.c @@ -0,0 +1,697 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 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 r_translation.c +/// \brief Translations + +#include "r_translation.h" +#include "r_data.h" +#include "v_video.h" // pMasterPalette +#include "z_zone.h" +#include "w_wad.h" + +#include + +remaptable_t **paletteremaps = NULL; +unsigned numpaletteremaps = 0; + +void PaletteRemap_Init(void) +{ + remaptable_t *base = PaletteRemap_New(); + PaletteRemap_SetIdentity(base); + PaletteRemap_Add(base); +} + +remaptable_t *PaletteRemap_New(void) +{ + remaptable_t *tr = Z_Calloc(sizeof(remaptable_t), PU_STATIC, NULL); + tr->num_entries = 256; + return tr; +} + +remaptable_t *PaletteRemap_Copy(remaptable_t *tr) +{ + remaptable_t *copy = Z_Malloc(sizeof(remaptable_t), PU_STATIC, NULL); + memcpy(copy, tr, sizeof(remaptable_t)); + return copy; +} + +boolean PaletteRemap_Equal(remaptable_t *a, remaptable_t *b) +{ + if (a->num_entries != b->num_entries) + return false; + + return memcmp(a->remap, b->remap, a->num_entries) == 0; +} + +void PaletteRemap_SetIdentity(remaptable_t *tr) +{ + for (unsigned i = 0; i < tr->num_entries; i++) + { + tr->remap[i] = i; + } +} + +boolean PaletteRemap_IsIdentity(remaptable_t *tr) +{ + for (unsigned i = 0; i < 256; i++) + { + if (tr->remap[i] != i) + return false; + } + + return true; +} + +unsigned PaletteRemap_Add(remaptable_t *tr) +{ +#if 0 + for (unsigned i = 0; i < numpaletteremaps; i++) + { + if (PaletteRemap_Equal(tr, paletteremaps[i])) + return i; + } +#endif + + numpaletteremaps++; + paletteremaps = Z_Realloc(paletteremaps, sizeof(remaptable_t *) * numpaletteremaps, PU_STATIC, NULL); + paletteremaps[numpaletteremaps - 1] = tr; + + return numpaletteremaps - 1; +} + +static boolean PalIndexOutOfRange(int color) +{ + return color < 0 || color > 255; +} + +static boolean IndicesOutOfRange(int start, int end) +{ + return PalIndexOutOfRange(start) || PalIndexOutOfRange(end); +} + +static boolean IndicesOutOfRange2(int start1, int end1, int start2, int end2) +{ + return IndicesOutOfRange(start1, end1) || IndicesOutOfRange(start2, end2); +} + +#define SWAP(a, b, t) { \ + t swap = a; \ + a = b; \ + b = swap; \ +} + +boolean PaletteRemap_AddIndexRange(remaptable_t *tr, int start, int end, int pal1, int pal2) +{ + if (IndicesOutOfRange2(start, end, pal1, pal2)) + return false; + + if (start > end) + { + SWAP(start, end, int); + SWAP(pal1, pal2, int); + } + else if (start == end) + { + tr->remap[start] = pal1; + return true; + } + + double palcol = pal1; + double palstep = (pal2 - palcol) / (end - start); + + for (int i = start; i <= end; palcol += palstep, ++i) + { + double idx = round(palcol); + tr->remap[i] = (int)idx; + } + + return true; +} + +boolean PaletteRemap_AddColorRange(remaptable_t *tr, int start, int end, int _r1,int _g1, int _b1, int _r2, int _g2, int _b2) +{ + if (IndicesOutOfRange(start, end)) + return false; + + double r1 = _r1; + double g1 = _g1; + double b1 = _b1; + double r2 = _r2; + double g2 = _g2; + double b2 = _b2; + double r, g, b; + double rs, gs, bs; + + if (start > end) + { + SWAP(start, end, int); + + r = r2; + g = g2; + b = b2; + rs = r1 - r2; + gs = g1 - g2; + bs = b1 - b2; + } + else + { + r = r1; + g = g1; + b = b1; + rs = r2 - r1; + gs = g2 - g1; + bs = b2 - b1; + } + + if (start == end) + { + tr->remap[start] = NearestColor(r, g, b); + } + else + { + rs /= (end - start); + gs /= (end - start); + bs /= (end - start); + + for (int i = start; i <= end; ++i) + { + tr->remap[i] = NearestColor(r, g, b); + r += rs; + g += gs; + b += bs; + } + } + + return true; +} + +#define clamp(val, minval, maxval) max(min(val, maxval), minval) + +boolean PaletteRemap_AddDesaturation(remaptable_t *tr, int start, int end, double r1, double g1, double b1, double r2, double g2, double b2) +{ + if (IndicesOutOfRange(start, end)) + return false; + + r1 = clamp(r1, 0.0, 2.0); + g1 = clamp(g1, 0.0, 2.0); + b1 = clamp(b1, 0.0, 2.0); + r2 = clamp(r2, 0.0, 2.0); + g2 = clamp(g2, 0.0, 2.0); + b2 = clamp(b2, 0.0, 2.0); + + if (start > end) + { + SWAP(start, end, int); + SWAP(r1, r2, double); + SWAP(g1, g2, double); + SWAP(b1, b2, double); + } + + r2 -= r1; + g2 -= g1; + b2 -= b1; + r1 *= 255; + g1 *= 255; + b1 *= 255; + + for (int c = start; c <= end; c++) + { + double intensity = (pMasterPalette[c].s.red * 77 + pMasterPalette[c].s.green * 143 + pMasterPalette[c].s.blue * 37) / 255.0; + + tr->remap[c] = NearestColor( + min(255, (int)(r1 + intensity*r2)), + min(255, (int)(g1 + intensity*g2)), + min(255, (int)(b1 + intensity*b2)) + ); + } + + return true; +} + +boolean PaletteRemap_AddColourisation(remaptable_t *tr, int start, int end, int r, int g, int b) +{ + if (IndicesOutOfRange(start, end)) + return false; + + for (int i = start; i < end; ++i) + { + double br = pMasterPalette[i].s.red; + double bg = pMasterPalette[i].s.green; + double bb = pMasterPalette[i].s.blue; + double grey = (br * 0.299 + bg * 0.587 + bb * 0.114) / 255.0f; + if (grey > 1.0) + grey = 1.0; + + br = r * grey; + bg = g * grey; + bb = b * grey; + + tr->remap[i] = NearestColor( + (int)br, + (int)bg, + (int)bb + ); + } + + return true; +} + +boolean PaletteRemap_AddTint(remaptable_t *tr, int start, int end, int r, int g, int b, int amount) +{ + if (IndicesOutOfRange(start, end)) + return false; + + for (int i = start; i < end; ++i) + { + float br = pMasterPalette[i].s.red; + float bg = pMasterPalette[i].s.green; + float bb = pMasterPalette[i].s.blue; + float a = amount * 0.01f; + float ia = 1.0f - a; + + br = br * ia + r * a; + bg = bg * ia + g * a; + bb = bb * ia + b * a; + + tr->remap[i] = NearestColor( + (int)br, + (int)bg, + (int)bb + ); + } + + return true; +} + +static boolean ExpectToken(const char *expect) +{ + return strcmp(M_TokenizerReadZDoom(0), expect) == 0; +} + +static boolean StringToNumber(const char *tkn, int *out) +{ + char *endPos = NULL; + +#ifndef AVOID_ERRNO + errno = 0; +#endif + + int result = strtol(tkn, &endPos, 10); + if (endPos == tkn || *endPos != '\0') + return false; + +#ifndef AVOID_ERRNO + if (errno == ERANGE) + return false; +#endif + + *out = result; + + return true; +} + +static boolean ParseNumber(int *out) +{ + return StringToNumber(M_TokenizerReadZDoom(0), out); +} + +static boolean ParseDecimal(double *out) +{ + const char *tkn = M_TokenizerReadZDoom(0); + + char *endPos = NULL; + +#ifndef AVOID_ERRNO + errno = 0; +#endif + + double result = strtod(tkn, &endPos); + if (endPos == tkn || *endPos != '\0') + return false; + +#ifndef AVOID_ERRNO + if (errno == ERANGE) + return false; +#endif + + *out = result; + + return true; +} + +static struct PaletteRemapParseResult *ThrowError(const char *format, ...) +{ + struct PaletteRemapParseResult *err = Z_Calloc(sizeof(struct PaletteRemapParseResult), PU_STATIC, NULL); + + va_list argptr; + va_start(argptr, format); + vsprintf(err->error, format, argptr); + va_end(argptr); + + return err; +} + +struct PaletteRemapParseResult *PaletteRemap_ParseString(remaptable_t *tr, char *translation) +{ + int start, end; + + M_TokenizerOpen(translation); + + if (!ParseNumber(&start)) + return ThrowError("expected a number for start range"); + if (!ExpectToken(":")) + return ThrowError("expected ':'"); + if (!ParseNumber(&end)) + return ThrowError("expected a number for end range"); + + if (start < 0 || start > 255 || end < 0 || end > 255) + return ThrowError("palette indices out of range"); + + if (!ExpectToken("=")) + return ThrowError("expected '='"); + + const char *tkn = M_TokenizerReadZDoom(0); + if (strcmp(tkn, "[") == 0) + { + // translation using RGB values + int r1, g1, b1; + int r2, g2, b2; + + // start + if (!ParseNumber(&r1)) + return ThrowError("expected a number for starting red"); + if (!ExpectToken(",")) + return ThrowError("expected ','"); + + if (!ParseNumber(&g1)) + return ThrowError("expected a number for starting green"); + if (!ExpectToken(",")) + return ThrowError("expected ','"); + + if (!ParseNumber(&b1)) + return ThrowError("expected a number for starting blue"); + if (!ExpectToken(",")) + return ThrowError("expected ','"); + + if (!ExpectToken("]")) + return ThrowError("expected ']'"); + if (!ExpectToken(":")) + return ThrowError("expected ':'"); + if (!ExpectToken("[")) + return ThrowError("expected '["); + + // end + if (!ParseNumber(&r2)) + return ThrowError("expected a number for ending red"); + if (!ExpectToken(",")) + return ThrowError("expected ','"); + + if (!ParseNumber(&g2)) + return ThrowError("expected a number for ending green"); + if (!ExpectToken(",")) + return ThrowError("expected ','"); + + if (!ParseNumber(&b2)) + return ThrowError("expected a number for ending blue"); + if (!ExpectToken("]")) + return ThrowError("expected ']'"); + + PaletteRemap_AddColorRange(tr, start, end, r1, g1, b1, r2, g2, b2); + } + else if (strcmp(tkn, "%") == 0) + { + // translation using RGB values (desaturation) + double r1, g1, b1; + double r2, g2, b2; + + if (!ExpectToken("[")) + return ThrowError("expected '["); + + // start + if (!ParseDecimal(&r1)) + return ThrowError("expected a number for starting red"); + if (!ExpectToken(",")) + return ThrowError("expected ','"); + + if (!ParseDecimal(&g1)) + return ThrowError("expected a number for starting green"); + if (!ExpectToken(",")) + return ThrowError("expected ','"); + + if (!ParseDecimal(&b1)) + return ThrowError("expected a number for starting blue"); + if (!ExpectToken("]")) + return ThrowError("expected ']'"); + + if (!ExpectToken(":")) + return ThrowError("expected ':'"); + + if (!ExpectToken("[")) + return ThrowError("expected '["); + + // end + if (!ParseDecimal(&r2)) + return ThrowError("expected a number for ending red"); + if (!ExpectToken(",")) + return ThrowError("expected ','"); + + if (!ParseDecimal(&g2)) + return ThrowError("expected a number for ending green"); + if (!ExpectToken(",")) + return ThrowError("expected ','"); + + if (!ParseDecimal(&b2)) + return ThrowError("expected a number for ending blue"); + if (!ExpectToken("]")) + return ThrowError("expected ']'"); + + PaletteRemap_AddDesaturation(tr, start, end, r1, g1, b1, r2, g2, b2); + } + else if (strcmp(tkn, "#") == 0) + { + // Colourise translation + int r, g, b; + + if (!ExpectToken("[")) + return ThrowError("expected '["); + if (!ParseNumber(&r)) + return ThrowError("expected a number for red"); + if (!ExpectToken(",")) + return ThrowError("expected ','"); + if (!ParseNumber(&g)) + return ThrowError("expected a number for green"); + if (!ExpectToken(",")) + return ThrowError("expected ','"); + if (!ParseNumber(&b)) + return ThrowError("expected a number for blue"); + if (!ExpectToken("]")) + return ThrowError("expected ']'"); + + PaletteRemap_AddColourisation(tr, start, end, r, g, b); + } + else if (strcmp(tkn, "@") == 0) + { + // Tint translation + int a, r, g, b; + + if (!ExpectToken("[")) + return ThrowError("expected '["); + if (!ParseNumber(&a)) + return ThrowError("expected a number for amount"); + if (!ExpectToken(",")) + return ThrowError("expected ','"); + if (!ParseNumber(&r)) + return ThrowError("expected a number for red"); + if (!ExpectToken(",")) + return ThrowError("expected ','"); + if (!ParseNumber(&g)) + return ThrowError("expected a number for green"); + if (!ExpectToken(",")) + return ThrowError("expected ','"); + if (!ParseNumber(&b)) + return ThrowError("expected a number for blue"); + if (!ExpectToken("]")) + return ThrowError("expected ']'"); + + PaletteRemap_AddTint(tr, start, end, r, g, b, a); + } + else + { + int pal1, pal2; + + if (!StringToNumber(tkn, &pal1)) + return ThrowError("expected a number for starting index"); + if (!ExpectToken(":")) + return ThrowError("expected ':'"); + if (!ParseNumber(&pal2)) + return ThrowError("expected a number for ending index"); + + PaletteRemap_AddIndexRange(tr, start, end, pal1, pal2); + } + + M_TokenizerClose(); + + return NULL; +} + +static void P_ParseTrnslate(INT32 wadNum, UINT16 lumpnum) +{ + char *lumpData = (char *)W_CacheLumpNumPwad(wadNum, lumpnum, PU_STATIC); + size_t lumpLength = W_LumpLengthPwad(wadNum, lumpnum); + char *text = (char *)Z_Malloc((lumpLength + 1), PU_STATIC, NULL); + memmove(text, lumpData, lumpLength); + text[lumpLength] = '\0'; + Z_Free(lumpData); + + char *p = text; + char *tkn = M_GetToken(p); + while (tkn != NULL) + { + remaptable_t *tr = NULL; + + char *name = tkn; + + tkn = M_GetToken(NULL); + if (strcmp(tkn, ":") == 0) + { + Z_Free(tkn); + tkn = M_GetToken(NULL); + + remaptable_t *tbl = R_GetTranslationByID(R_FindCustomTranslation(tkn)); + if (tbl) + tr = PaletteRemap_Copy(tbl); + else + { + CONS_Alert(CONS_ERROR, "Error parsing translation '%s': No translation named '%s'\n", name, tkn); + goto fail; + } + + Z_Free(tkn); + tkn = M_GetToken(NULL); + } + else + { + tr = PaletteRemap_New(); + PaletteRemap_SetIdentity(tr); + } + +#if 0 + tkn = M_GetToken(NULL); + if (strcmp(tkn, "=") != 0) + { + CONS_Alert(CONS_ERROR, "Error parsing translation '%s': Expected '=', got '%s'\n", name, tkn); + goto fail; + } + Z_Free(tkn); +#endif + + do { + struct PaletteRemapParseResult *error = PaletteRemap_ParseString(tr, tkn); + if (error) + { + CONS_Alert(CONS_ERROR, "Error parsing translation '%s': %s\n", name, error->error); + Z_Free(error); + goto fail; + } + + Z_Free(tkn); + tkn = M_GetToken(NULL); + if (!tkn) + break; + + if (strcmp(tkn, ",") != 0) + break; + + Z_Free(tkn); + tkn = M_GetToken(NULL); + } while (true); + + // add it + unsigned id = PaletteRemap_Add(tr); + R_AddCustomTranslation(name, id); + Z_Free(name); + } + +fail: + Z_Free(tkn); + Z_Free((void *)text); +} + +void R_LoadTrnslateLumps(void) +{ + for (INT32 w = numwadfiles-1; w >= 0; w--) + { + UINT16 lump = W_CheckNumForNamePwad("TRNSLATE", w, 0); + + while (lump != INT16_MAX) + { + P_ParseTrnslate(w, lump); + lump = W_CheckNumForNamePwad("TRNSLATE", (UINT16)w, lump + 1); + } + } +} + +struct CustomTranslation +{ + char *name; + unsigned id; + UINT32 hash; +}; + +static struct CustomTranslation *customtranslations = NULL; +static unsigned numcustomtranslations = 0; + +int R_FindCustomTranslation(const char *name) +{ + UINT32 hash = quickncasehash(name, strlen(name)); + + for (unsigned i = 0; i < numcustomtranslations; i++) + { + if (hash == customtranslations[i].hash + && strcmp(name, customtranslations[i].name) == 0) + return (int)customtranslations[i].id; + } + + return -1; +} + +void R_AddCustomTranslation(const char *name, int trnum) +{ + struct CustomTranslation *tr = NULL; + UINT32 hash = quickncasehash(name, strlen(name)); + + for (unsigned i = 0; i < numcustomtranslations; i++) + { + tr = &customtranslations[i]; + if (hash == tr->hash + && strcmp(name, tr->name) == 0) + { + break; + } + } + + if (tr == NULL) + { + numcustomtranslations++; + customtranslations = Z_Realloc(customtranslations, sizeof(struct CustomTranslation) * numcustomtranslations, PU_STATIC, NULL); + tr = &customtranslations[numcustomtranslations - 1]; + } + + tr->id = trnum; + tr->name = Z_StrDup(name); + tr->hash = quickncasehash(name, strlen(name)); +} + +remaptable_t *R_GetTranslationByID(int id) +{ + if (id < 0 || id >= (signed)numpaletteremaps) + return NULL; + + return paletteremaps[id]; +} diff --git a/src/r_translation.h b/src/r_translation.h new file mode 100644 index 000000000..2a02a0c50 --- /dev/null +++ b/src/r_translation.h @@ -0,0 +1,48 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 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 r_translation.h +/// \brief Translations + +#include "doomdef.h" + +typedef struct +{ + UINT8 remap[256]; + unsigned num_entries; +} remaptable_t; + +extern remaptable_t **paletteremaps; +extern unsigned numpaletteremaps; + +void PaletteRemap_Init(void); +remaptable_t *PaletteRemap_New(void); +remaptable_t *PaletteRemap_Copy(remaptable_t *tr); +boolean PaletteRemap_Equal(remaptable_t *a, remaptable_t *b); +void PaletteRemap_SetIdentity(remaptable_t *tr); +boolean PaletteRemap_IsIdentity(remaptable_t *tr); +unsigned PaletteRemap_Add(remaptable_t *tr); + +boolean PaletteRemap_AddIndexRange(remaptable_t *tr, int start, int end, int pal1, int pal2); +boolean PaletteRemap_AddColorRange(remaptable_t *tr, int start, int end, int _r1,int _g1, int _b1, int _r2, int _g2, int _b2); +boolean PaletteRemap_AddDesaturation(remaptable_t *tr, int start, int end, double r1, double g1, double b1, double r2, double g2, double b2); +boolean PaletteRemap_AddColourisation(remaptable_t *tr, int start, int end, int r, int g, int b); +boolean PaletteRemap_AddTint(remaptable_t *tr, int start, int end, int r, int g, int b, int amount); + +struct PaletteRemapParseResult +{ + char error[4096]; +}; + +struct PaletteRemapParseResult *PaletteRemap_ParseString(remaptable_t *tr, char *translation); + +int R_FindCustomTranslation(const char *name); +void R_AddCustomTranslation(const char *name, int trnum); +remaptable_t *R_GetTranslationByID(int id); + +void R_LoadTrnslateLumps(void); diff --git a/src/sdl/Srb2SDL-vc10.vcxproj b/src/sdl/Srb2SDL-vc10.vcxproj index 9b51cfb80..a397e16e9 100644 --- a/src/sdl/Srb2SDL-vc10.vcxproj +++ b/src/sdl/Srb2SDL-vc10.vcxproj @@ -342,6 +342,7 @@ + @@ -530,6 +531,7 @@ + diff --git a/src/sdl/Srb2SDL-vc10.vcxproj.filters b/src/sdl/Srb2SDL-vc10.vcxproj.filters index 96501b216..231f03497 100644 --- a/src/sdl/Srb2SDL-vc10.vcxproj.filters +++ b/src/sdl/Srb2SDL-vc10.vcxproj.filters @@ -537,6 +537,9 @@ R_Rend + + R_Rend + R_Rend @@ -1081,6 +1084,9 @@ R_Rend + + R_Rend + R_Rend