/* ** r_translate.cpp ** Translatioo table handling ** **--------------------------------------------------------------------------- ** Copyright 1998-2006 Randy Heit ** 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. The name of the author may not be used to endorse or promote products ** derived from this software without specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. **--------------------------------------------------------------------------- ** */ #include #include "templates.h" #include "r_data/r_translate.h" #include "v_video.h" #include "g_game.h" #include "d_netinf.h" #include "sc_man.h" #include "engineerrors.h" #include "w_wad.h" #include "serializer.h" #include "d_player.h" #include "r_data/sprites.h" #include "r_state.h" #include "vm.h" #include "v_text.h" #include "m_crc32.h" #include "g_levellocals.h" #include "palutil.h" #include "gi.h" PaletteContainer palMgr; const uint8_t IcePalette[16][3] = { { 10, 8, 18 }, { 15, 15, 26 }, { 20, 16, 36 }, { 30, 26, 46 }, { 40, 36, 57 }, { 50, 46, 67 }, { 59, 57, 78 }, { 69, 67, 88 }, { 79, 77, 99 }, { 89, 87,109 }, { 99, 97,120 }, { 109,107,130 }, { 118,118,141 }, { 128,128,151 }, { 138,138,162 }, { 148,148,172 } }; //---------------------------------------------------------------------------- // // // //---------------------------------------------------------------------------- bool FRemapTable::operator==(const FRemapTable &o) { // Two translations are identical when they have the same amount of colors // and the palette values for both are identical. if (&o == this) return true; if (o.NumEntries != NumEntries) return false; return !memcmp(o.Palette, Palette, NumEntries * sizeof(*Palette)); } //---------------------------------------------------------------------------- // // // //---------------------------------------------------------------------------- static void SerializeRemap(FSerializer &arc, FRemapTable &remap) { arc("numentries", remap.NumEntries); arc.Array("remap", remap.Remap, remap.NumEntries); arc.Array("palette", remap.Palette, remap.NumEntries); } void StaticSerializeTranslations(FSerializer &arc) { if (arc.BeginArray("translations")) { // Does this level have custom translations? FRemapTable *trans; int w; if (arc.isWriting()) { auto size = palMgr.NumTranslations(TRANSLATION_LevelScripted); for (unsigned int i = 0; i < size; ++i) { trans = palMgr.TranslationToTable(TRANSLATION(TRANSLATION_LevelScripted, i)); if (trans != NULL && !trans->IsIdentity()) { if (arc.BeginObject(nullptr)) { arc("index", i); SerializeRemap(arc, *trans); arc.EndObject(); } } } } else { while (arc.BeginObject(nullptr)) { arc("index", w); FRemapTable remap; SerializeRemap(arc, remap); palMgr.UpdateTranslation(TRANSLATION(TRANSLATION_LevelScripted, w), &remap); arc.EndObject(); } } arc.EndArray(); } } //---------------------------------------------------------------------------- // // // //---------------------------------------------------------------------------- static TArray BloodTranslationColors; int CreateBloodTranslation(PalEntry color) { unsigned int i; if (BloodTranslationColors.Size() == 0) { // Don't use the first slot. palMgr.PushIdentityTable(TRANSLATION_Blood); BloodTranslationColors.Push(0); } for (i = 1; i < BloodTranslationColors.Size(); i++) { if (color.r == BloodTranslationColors[i].r && color.g == BloodTranslationColors[i].g && color.b == BloodTranslationColors[i].b) { // A duplicate of this translation already exists return i; } } if (BloodTranslationColors.Size() >= MAX_DECORATE_TRANSLATIONS) { I_Error("Too many blood colors"); } FRemapTable trans; trans.Palette[0] = 0; trans.Remap[0] = 0; for (i = 1; i < 256; i++) { int bright = std::max(std::max(GPalette.BaseColors[i].r, GPalette.BaseColors[i].g), GPalette.BaseColors[i].b); PalEntry pe = PalEntry(255, color.r*bright/255, color.g*bright/255, color.b*bright/255); int entry = ColorMatcher.Pick(pe.r, pe.g, pe.b); trans.Palette[i] = pe; trans.Remap[i] = entry; } palMgr.AddTranslation(TRANSLATION_Blood, &trans); return BloodTranslationColors.Push(color); } //---------------------------------------------------------------------------- // // R_InitTranslationTables // Creates the translation tables to map the green color ramp to gray, // brown, red. Assumes a given structure of the PLAYPAL. // //---------------------------------------------------------------------------- void R_InitTranslationTables () { int i; // Each player gets two translations. Doom and Strife don't use the // extra ones, but Heretic and Hexen do. These are set up during // netgame arbitration and as-needed, so they just get to be identity // maps until then so they won't be invalid. for (i = 0; i < MAXPLAYERS; ++i) { palMgr.PushIdentityTable(TRANSLATION_Players); palMgr.PushIdentityTable(TRANSLATION_PlayersExtra); palMgr.PushIdentityTable(TRANSLATION_RainPillar); } // The menu player also gets a separate translation table palMgr.PushIdentityTable(TRANSLATION_Players); // The three standard translations from Doom or Heretic (seven for Strife), // plus the generic ice translation. FRemapTable stdremaps[10]; for (i = 0; i < 8; ++i) { stdremaps[i].MakeIdentity(); } // Each player corpse has its own translation so they won't change // color if the player who created them changes theirs. for (i = 0; i < FLevelLocals::BODYQUESIZE; ++i) { palMgr.PushIdentityTable(TRANSLATION_PlayerCorpses); } // Create the standard translation tables if (gameinfo.gametype & GAME_DoomChex) { for (i = 0x70; i < 0x80; i++) { // map green ramp to gray, brown, red stdremaps[0].Remap[i] = 0x60 + (i&0xf); stdremaps[1].Remap[i] = 0x40 + (i&0xf); stdremaps[2].Remap[i] = 0x20 + (i&0xf); stdremaps[0].Palette[i] = GPalette.BaseColors[0x60 + (i&0xf)] | MAKEARGB(255,0,0,0); stdremaps[1].Palette[i] = GPalette.BaseColors[0x40 + (i&0xf)] | MAKEARGB(255,0,0,0); stdremaps[2].Palette[i] = GPalette.BaseColors[0x20 + (i&0xf)] | MAKEARGB(255,0,0,0); } } else if (gameinfo.gametype == GAME_Heretic) { for (i = 225; i <= 240; i++) { stdremaps[0].Remap[i] = 114+(i-225); // yellow stdremaps[1].Remap[i] = 145+(i-225); // red stdremaps[2].Remap[i] = 190+(i-225); // blue stdremaps[0].Palette[i] = GPalette.BaseColors[114+(i-225)] | MAKEARGB(255,0,0,0); stdremaps[1].Palette[i] = GPalette.BaseColors[145+(i-225)] | MAKEARGB(255,0,0,0); stdremaps[2].Palette[i] = GPalette.BaseColors[190+(i-225)] | MAKEARGB(255,0,0,0); } } else if (gameinfo.gametype == GAME_Strife) { for (i = 0x20; i <= 0x3F; ++i) { stdremaps[0].Remap[i] = i - 0x20; stdremaps[1].Remap[i] = i - 0x20; stdremaps[2].Remap[i] = 0xD0 + (i&0xf); stdremaps[3].Remap[i] = 0xD0 + (i&0xf); stdremaps[4].Remap[i] = i - 0x20; stdremaps[5].Remap[i] = i - 0x20; stdremaps[6].Remap[i] = i - 0x20; } for (i = 0x50; i <= 0x5F; ++i) { // Merchant hair stdremaps[4].Remap[i] = 0x80 + (i&0xf); stdremaps[5].Remap[i] = 0x10 + (i&0xf); stdremaps[6].Remap[i] = 0x40 + (i&0xf); } for (i = 0x80; i <= 0x8F; ++i) { stdremaps[0].Remap[i] = 0x40 + (i&0xf); // red stdremaps[1].Remap[i] = 0xB0 + (i&0xf); // rust stdremaps[2].Remap[i] = 0x10 + (i&0xf); // gray stdremaps[3].Remap[i] = 0x30 + (i&0xf); // dark green stdremaps[4].Remap[i] = 0x50 + (i&0xf); // gold stdremaps[5].Remap[i] = 0x60 + (i&0xf); // bright green stdremaps[6].Remap[i] = 0x90 + (i&0xf); // blue } for (i = 0xC0; i <= 0xCF; ++i) { stdremaps[4].Remap[i] = 0xA0 + (i&0xf); stdremaps[5].Remap[i] = 0x20 + (i&0xf); stdremaps[6].Remap[i] = (i&0xf); } stdremaps[6].Remap[0xC0] = 1; for (i = 0xD0; i <= 0xDF; ++i) { stdremaps[4].Remap[i] = 0xB0 + (i&0xf); stdremaps[5].Remap[i] = 0x30 + (i&0xf); stdremaps[6].Remap[i] = 0x10 + (i&0xf); } for (i = 0xF1; i <= 0xF6; ++i) { stdremaps[0].Remap[i] = 0xDF + (i&0xf); } for (i = 0xF7; i <= 0xFB; ++i) { stdremaps[0].Remap[i] = i - 6; } for (i = 0; i < 7; ++i) { for (int j = 0x20; j <= 0xFB; ++j) { stdremaps[i].Palette[j] = GPalette.BaseColors[stdremaps[i].Remap[j]] | MAKEARGB(255,0,0,0); } } } // Create the ice translation table, based on Hexen's. Alas, the standard // Doom palette has no good substitutes for these bluish-tinted grays, so // they will just look gray unless you use a different PLAYPAL with Doom. uint8_t IcePaletteRemap[16]; for (i = 0; i < 16; ++i) { IcePaletteRemap[i] = ColorMatcher.Pick (IcePalette[i][0], IcePalette[i][1], IcePalette[i][2]); } FRemapTable *remap = &stdremaps[STD_Ice]; remap->Remap[0] = 0; remap->Palette[0] = 0; for (i = 1; i < 256; ++i) { int r = GPalette.BaseColors[i].r; int g = GPalette.BaseColors[i].g; int b = GPalette.BaseColors[i].b; int v = (r*77 + g*143 + b*37) >> 12; remap->Remap[i] = IcePaletteRemap[v]; remap->Palette[i] = PalEntry(255, IcePalette[v][0], IcePalette[v][1], IcePalette[v][2]); } // The alphatexture translation. This is just a standard index as gray mapping. remap = &stdremaps[STD_Gray]; remap->Remap[0] = 0; remap->Palette[0] = 0; for (i = 1; i < 256; i++) { remap->Remap[i] = i; remap->Palette[i] = PalEntry(255, i, i, i); } // Palette to grayscale ramp. For internal use only, because the remap does not map to the palette. remap = &stdremaps[STD_Grayscale]; remap->Remap[0] = 0; remap->Palette[0] = 0; for (i = 1; i < 256; i++) { int r = GPalette.BaseColors[i].r; int g = GPalette.BaseColors[i].g; int b = GPalette.BaseColors[i].b; int v = (r * 77 + g * 143 + b * 37) >> 8; remap->Remap[i] = v; remap->Palette[i] = PalEntry(255, v, v, v); } palMgr.AddTranslation(TRANSLATION_Standard, stdremaps, 10); } //---------------------------------------------------------------------------- // // [RH] Create a player's translation table based on a given mid-range color. // [GRB] Split to 2 functions (because of player setup menu) // //---------------------------------------------------------------------------- static void SetRemap(FRemapTable *table, int i, float r, float g, float b) { int ir = clamp (int(r * 255.f), 0, 255); int ig = clamp (int(g * 255.f), 0, 255); int ib = clamp (int(b * 255.f), 0, 255); table->Remap[i] = ColorMatcher.Pick (ir, ig, ib); table->Palette[i] = PalEntry(255, ir, ig, ib); } //---------------------------------------------------------------------------- // // Sets the translation for Heretic's rain pillar // This tries to create a translation that preserves the brightness of // the rain projectiles so that their effect isn't ruined. // //---------------------------------------------------------------------------- static void SetPillarRemap(FRemapTable *table, int i, float h, float s, float v) { float ph, ps, pv; float fr = GPalette.BaseColors[i].r / 255.f; float fg = GPalette.BaseColors[i].g / 255.f; float fb = GPalette.BaseColors[i].b / 255.f; RGBtoHSV(fr, fg, fb, &ph, &ps, &pv); HSVtoRGB(&fr, &fg, &fb, h, s, (v*0.2f + pv*0.8f)); int ir = clamp (int(fr * 255.f), 0, 255); int ig = clamp (int(fg * 255.f), 0, 255); int ib = clamp (int(fb * 255.f), 0, 255); table->Remap[i] = ColorMatcher.Pick (ir, ig, ib); table->Palette[i] = PalEntry(255, ir, ig, ib); } //---------------------------------------------------------------------------- static bool SetRange(FRemapTable *table, int start, int end, int first, int last) { bool identity = true; if (start == end) { int pi = (first + last) / 2; table->Remap[start] = GPalette.Remap[pi]; identity &= (pi == start); table->Palette[start] = GPalette.BaseColors[table->Remap[start]]; table->Palette[start].a = 255; } else { int palrange = last - first; for (int i = start; i <= end; ++i) { int pi = first + palrange * (i - start) / (end - start); table->Remap[i] = GPalette.Remap[pi]; identity &= (pi == i); table->Palette[i] = GPalette.BaseColors[table->Remap[i]]; table->Palette[i].a = 255; } } return identity; } //---------------------------------------------------------------------------- // // // //---------------------------------------------------------------------------- static void R_CreatePlayerTranslation (float h, float s, float v, const FPlayerColorSet *colorset, FPlayerSkin *skin, FRemapTable *table, FRemapTable *alttable, FRemapTable *pillartable) { int i; uint8_t start = skin->range0start; uint8_t end = skin->range0end; float r, g, b; float bases, basev; float sdelta, vdelta; float range; // Set up the base translation for this skin. If the skin was created // for the current game, then this is just an identity translation. // Otherwise, it remaps the colors from the skin's original palette to // the current one. if (skin->othergame) { memcpy (table->Remap, OtherGameSkinRemap, table->NumEntries); memcpy (table->Palette, OtherGameSkinPalette, sizeof(*table->Palette) * table->NumEntries); } else { for (i = 0; i < table->NumEntries; ++i) { table->Remap[i] = i; } memcpy(table->Palette, GPalette.BaseColors, sizeof(*table->Palette) * table->NumEntries); } for (i = 1; i < table->NumEntries; ++i) { table->Palette[i].a = 255; } // [GRB] Don't translate skins with color range 0-0 (PlayerPawn default) if (start == 0 && end == 0) { table->Inactive = true; return; } table->Inactive = false; range = (float)(end-start+1); bases = s; basev = v; if (colorset != NULL && colorset->Lump >= 0 && Wads.LumpLength(colorset->Lump) < 256) { // Bad table length. Ignore it. colorset = NULL; } if (colorset != NULL) { bool identity = true; // Use the pre-defined range instead of a custom one. if (colorset->Lump < 0) { identity &= SetRange(table, start, end, colorset->FirstColor, colorset->LastColor); for (i = 0; i < colorset->NumExtraRanges; ++i) { identity &= SetRange(table, colorset->Extra[i].RangeStart, colorset->Extra[i].RangeEnd, colorset->Extra[i].FirstColor, colorset->Extra[i].LastColor); } } else { FMemLump translump = Wads.ReadLump(colorset->Lump); const uint8_t *trans = (const uint8_t *)translump.GetMem(); for (i = start; i <= end; ++i) { table->Remap[i] = GPalette.Remap[trans[i]]; identity &= (trans[i] == i); table->Palette[i] = GPalette.BaseColors[table->Remap[i]]; table->Palette[i].a = 255; } } // If the colorset created an identity translation mark it as inactive table->Inactive = identity; } else if (gameinfo.gametype & GAME_DoomStrifeChex) { // Build player sprite translation s -= 0.23f; v += 0.1f; sdelta = 0.23f / range; vdelta = -0.94112f / range; for (i = start; i <= end; i++) { float uses, usev; uses = clamp (s, 0.f, 1.f); usev = clamp (v, 0.f, 1.f); HSVtoRGB (&r, &g, &b, h, uses, usev); SetRemap(table, i, r, g, b); s += sdelta; v += vdelta; } } else if (gameinfo.gametype == GAME_Heretic) { float vdelta = 0.418916f / range; // Build player sprite translation for (i = start; i <= end; i++) { v = vdelta * (float)(i - start) + basev - 0.2352937f; v = clamp (v, 0.f, 1.f); HSVtoRGB (&r, &g, &b, h, s, v); SetRemap(table, i, r, g, b); } // Build rain/lifegem translation if (alttable) { bases = MIN (bases*1.3f, 1.f); basev = MIN (basev*1.3f, 1.f); for (i = 145; i <= 168; i++) { s = MIN (bases, 0.8965f - 0.0962f*(float)(i - 161)); v = MIN (1.f, (0.2102f + 0.0489f*(float)(i - 144)) * basev); HSVtoRGB (&r, &g, &b, h, s, v); SetRemap(alttable, i, r, g, b); SetPillarRemap(pillartable, i, h, s, v); } } } else if (gameinfo.gametype == GAME_Hexen) { if (memcmp (sprites[skin->sprite].name, "PLAY", 4) == 0) { // The fighter is different! He gets a brown hairy loincloth, but the other // two have blue capes. float vs[9] = { .28f, .32f, .38f, .42f, .47f, .5f, .58f, .71f, .83f }; // Build player sprite translation //h = 45.f; v = MAX (0.1f, v); for (i = start; i <= end; i++) { HSVtoRGB (&r, &g, &b, h, s, vs[(i-start)*9/(int)range]*basev); SetRemap(table, i, r, g, b); } } else { float ms[18] = { .95f, .96f, .89f, .97f, .97f, 1.f, 1.f, 1.f, .97f, .99f, .87f, .77f, .69f, .62f, .57f, .47f, .43f }; float mv[18] = { .16f, .19f, .22f, .25f, .31f, .35f, .38f, .41f, .47f, .54f, .60f, .65f, .71f, .77f, .83f, .89f, .94f, 1.f }; // Build player sprite translation v = MAX (0.1f, v); for (i = start; i <= end; i++) { HSVtoRGB (&r, &g, &b, h, ms[(i-start)*18/(int)range]*bases, mv[(i-start)*18/(int)range]*basev); SetRemap(table, i, r, g, b); } } } if (gameinfo.gametype == GAME_Hexen && alttable != NULL) { // Build Hexen's lifegem translation. // Is the player's translation range the same as the gem's and we are using a // predefined translation? If so, then use the same one for the gem. Otherwise, // build one as per usual. if (colorset != NULL && start == 164 && end == 185) { *alttable = *table; } else { for (i = 164; i <= 185; ++i) { const PalEntry *base = &GPalette.BaseColors[i]; float dummy; RGBtoHSV (base->r/255.f, base->g/255.f, base->b/255.f, &dummy, &s, &v); HSVtoRGB (&r, &g, &b, h, s*bases, v*basev); SetRemap(alttable, i, r, g, b); } } } } //---------------------------------------------------------------------------- // // // //---------------------------------------------------------------------------- void R_BuildPlayerTranslation (int player) { float h, s, v; FPlayerColorSet *colorset; D_GetPlayerColor (player, &h, &s, &v, &colorset); FRemapTable remaps[3]; R_CreatePlayerTranslation (h, s, v, colorset, &Skins[players[player].userinfo.GetSkin()], &remaps[0], &remaps[1], &remaps[2]); palMgr.UpdateTranslation(TRANSLATION(TRANSLATION_Players, player), &remaps[0]); palMgr.UpdateTranslation(TRANSLATION(TRANSLATION_PlayersExtra, player), &remaps[1]); palMgr.UpdateTranslation(TRANSLATION(TRANSLATION_RainPillar, player), &remaps[2]); } //---------------------------------------------------------------------------- // // // //---------------------------------------------------------------------------- void R_GetPlayerTranslation (int color, const FPlayerColorSet *colorset, FPlayerSkin *skin, FRemapTable *table) { float h, s, v; if (colorset != NULL) { color = colorset->RepresentativeColor; } RGBtoHSV (RPART(color)/255.f, GPART(color)/255.f, BPART(color)/255.f, &h, &s, &v); R_CreatePlayerTranslation (h, s, v, colorset, skin, table, NULL, NULL); } DEFINE_ACTION_FUNCTION(_Translation, SetPlayerTranslation) { PARAM_PROLOGUE; PARAM_UINT(tgroup); PARAM_UINT(tnum); PARAM_UINT(pnum); PARAM_POINTER(cls, FPlayerClass); if (pnum >= MAXPLAYERS || tgroup >= NUM_TRANSLATION_TABLES) { ACTION_RETURN_BOOL(false); } auto self = &players[pnum]; int PlayerColor = self->userinfo.GetColor(); int PlayerSkin = self->userinfo.GetSkin(); int PlayerColorset = self->userinfo.GetColorSet(); if (cls != nullptr) { PlayerSkin = R_FindSkin(Skins[PlayerSkin].Name, int(cls - &PlayerClasses[0])); FRemapTable remap; R_GetPlayerTranslation(PlayerColor, GetColorSet(cls->Type, PlayerColorset), &Skins[PlayerSkin], &remap); palMgr.UpdateTranslation(TRANSLATION(tgroup, tnum), &remap); } ACTION_RETURN_BOOL(true); } //---------------------------------------------------------------------------- // // // //---------------------------------------------------------------------------- static TMap customTranslationMap; int R_FindCustomTranslation(FName name) { switch (name) { case NAME_Ice: // Ice is a special case which will remain in its original slot. return TRANSLATION(TRANSLATION_Standard, 7); case NAME_None: return 0; case NAME_RainPillar1: case NAME_RainPillar2: case NAME_RainPillar3: case NAME_RainPillar4: case NAME_RainPillar5: case NAME_RainPillar6: case NAME_RainPillar7: case NAME_RainPillar8: return TRANSLATION(TRANSLATION_RainPillar, name.GetIndex() - NAME_RainPillar1); case NAME_Player1: case NAME_Player2: case NAME_Player3: case NAME_Player4: case NAME_Player5: case NAME_Player6: case NAME_Player7: case NAME_Player8: return TRANSLATION(TRANSLATION_Players, name.GetIndex() - NAME_Player1); } int *t = customTranslationMap.CheckKey(FName(name, true)); return (t != nullptr)? *t : -1; } DEFINE_ACTION_FUNCTION(_Translation, GetID) { PARAM_PROLOGUE; PARAM_NAME(t); ACTION_RETURN_INT(R_FindCustomTranslation(t)); } //---------------------------------------------------------------------------- // // // //---------------------------------------------------------------------------- void R_ParseTrnslate() { customTranslationMap.Clear(); palMgr.ClearTranslationSlot(TRANSLATION_Custom); int lump; int lastlump = 0; while (-1 != (lump = Wads.FindLump("TRNSLATE", &lastlump))) { FScanner sc(lump); while (sc.GetToken()) { sc.TokenMustBe(TK_Identifier); FName newtrans = sc.String; FRemapTable NewTranslation; if (sc.CheckToken(':')) { sc.MustGetAnyToken(); if (sc.TokenType == TK_IntConst) { int max = 6; if (sc.Number < 0 || sc.Number > max) { sc.ScriptError("Translation must be in the range [0,%d]", max); } NewTranslation = *palMgr.TranslationToTable(TRANSLATION(TRANSLATION_Standard, sc.Number)); } else if (sc.TokenType == TK_Identifier) { int tnum = R_FindCustomTranslation(sc.String); if (tnum == -1) { sc.ScriptError("Base translation '%s' not found in '%s'", sc.String, newtrans.GetChars()); } NewTranslation = *palMgr.TranslationToTable(tnum); } else { // error out. sc.TokenMustBe(TK_Identifier); } } else NewTranslation.MakeIdentity(); sc.MustGetToken('='); do { sc.MustGetToken(TK_StringConst); int pallump = Wads.CheckNumForFullName(sc.String, true, ns_global); if (pallump >= 0) // { int start = 0; if (sc.CheckToken(',')) { sc.MustGetValue(false); start = sc.Number; } uint8_t palette[768]; int numcolors = ReadPalette(pallump, palette); NewTranslation.AddColors(start, numcolors, palette); } else { try { NewTranslation.AddToTranslation(sc.String); } catch (CRecoverableError & err) { sc.ScriptMessage("Error in translation '%s':\n" TEXTCOLOR_YELLOW "%s\n", sc.String, err.GetMessage()); } } } while (sc.CheckToken(',')); int trans = palMgr.StoreTranslation(TRANSLATION_Custom, &NewTranslation); customTranslationMap[newtrans] = trans; } } } //---------------------------------------------------------------------------- // // // //---------------------------------------------------------------------------- struct FTranslation { PalEntry colors[256]; }; DEFINE_ACTION_FUNCTION(_Translation, AddTranslation) { PARAM_SELF_STRUCT_PROLOGUE(FTranslation); FRemapTable NewTranslation; memcpy(&NewTranslation.Palette[0], self->colors, 256 * sizeof(PalEntry)); for (int i = 0; i < 256; i++) { NewTranslation.Remap[i] = ColorMatcher.Pick(self->colors[i]); } int trans = palMgr.StoreTranslation(TRANSLATION_Custom, &NewTranslation); ACTION_RETURN_INT(trans); }