Skip to content

Commit 1769020

Browse files
committed
Rework Sprite Palette Shuffle to be a customize (non-flag) setting
- Improve the picked color options a bit - Don't shuffle what's visible in the dark (we don't touch the enemy attribute tables and the color bit there)
1 parent e51f111 commit 1769020

3 files changed

Lines changed: 173 additions & 26 deletions

File tree

RandomizerCore/Hyrule.cs

Lines changed: 111 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2634,32 +2634,21 @@ private void UpdateRom()
26342634

26352635
if (props.ShuffleEnemyPalettes)
26362636
{
2637-
List<int> doubleLocs = [0x40b4, 0x80b4, 0x100b4, 0x100b8, 0x100bc, 0x140b4, 0x140b8, 0x140bc];
2638-
List<int> singleLocs = [0x40b8, 0x40bc, 0x80b8, 0x80bc];
2637+
Random customizationRng = new Random(SeedHash);
2638+
RerollPaletteTable(RomMap.WEST_PALETTE_TABLE, customizationRng);
2639+
RerollPaletteTable(RomMap.EAST_PALETTE_TABLE, customizationRng);
2640+
RerollPaletteTable(RomMap.TOWN_PALETTE_TABLE, customizationRng);
2641+
RerollPaletteTable(RomMap.PALACE_PALETTE_TABLE_MAJOR, customizationRng);
2642+
RerollPaletteTable(RomMap.GP_PALETTE_TABLE_MAJOR, customizationRng);
26392643

2640-
foreach (int i in doubleLocs)
2641-
{
2642-
int low = r.Next(12) + 1;
2643-
int high = (r.Next(2) + 1) * 16;
2644-
int color = high + low;
2645-
ROMData.Put(i, (byte)color);
2646-
ROMData.Put(i + 16, (byte)color);
2647-
ROMData.Put(i - 1, (byte)(color - 15));
2648-
ROMData.Put(i + 16 - 1, (byte)(color - 15));
2649-
}
2650-
foreach (int i in singleLocs)
2651-
{
2652-
int low = r.Next(13);
2653-
int high = (r.Next(3)) * 16;
2654-
int color = high + low;
2655-
ROMData.Put(i, (byte)color);
2656-
ROMData.Put(i + 16, (byte)color);
2657-
ROMData.Put(i + 16 - 1, (byte)(color - 15));
2658-
}
2644+
/*
2645+
lets no longer shuffle orange/red/blue bits for enemies, it changes
2646+
what is visible in the dark and also doesn't add enough to be worth it imo.
2647+
I also prefer if we can have the palette shuffle as a customize (non-flag) option.
26592648
2660-
for (int i = 0x54e8; i < 0x5508; i++)
2649+
for (int i = RomMap.WEST_ENEMY_STATS_TABLE + 0x3; i < RomMap.WEST_ENEMY_STATS_TABLE + 0x23; i++)
26612650
{
2662-
if (i != 0x54f8)
2651+
if (i != RomMap.WEST_ENEMY_STATS_TABLE + 0x13) // skip elevator
26632652
{
26642653
int b = ROMData.GetByte(i);
26652654
int p = b & 0x3F;
@@ -2668,7 +2657,6 @@ private void UpdateRom()
26682657
ROMData.Put(i, (byte)(n + p));
26692658
}
26702659
}
2671-
26722660
for (int i = 0x94e8; i < 0x9508; i++)
26732661
{
26742662
if (i != 0x94f8)
@@ -2713,6 +2701,7 @@ private void UpdateRom()
27132701
ROMData.Put(i, (byte)(n + p));
27142702
}
27152703
}
2704+
*/
27162705
}
27172706

27182707
//WRITE UPDATES TO WIZARD/QUEST COLLECTABLES HERE
@@ -2930,6 +2919,104 @@ private void UpdateRom()
29302919
}
29312920
}
29322921

2922+
private void RerollPaletteTable(int paletteTableAddr, Random r)
2923+
{
2924+
byte dark, middle, light;
2925+
2926+
// we are NOT rolling the white color for magic/interface that should match the orange sprite light
2927+
// (white looks fine with all 2 other sprite colors anyway)
2928+
// int[] magicBgColorAddr = [paletteTableAddr + 0x01, paletteTableAddr + 0x11];
2929+
// we ARE rolling the red sprite and matching the red tile color for the life bars to it
2930+
// (it would limit the palette a lot if the red color has to stay red)
2931+
List<int> lifeBgColorAddr = [.. Enumerable.Range(0, 9).Select(i => paletteTableAddr + 0x10 * i + 0x03)];
2932+
List<int> orangeSpriteColorAddr = [paletteTableAddr + 0x94];
2933+
List<int> redSpriteColorAddr = [paletteTableAddr + 0x98];
2934+
List<int> blueSpriteColorAddr = [paletteTableAddr + 0x9c];
2935+
2936+
List<int> darkRangeFull = [.. Enumerable.Range(0x01, 12), .. Enumerable.Range(0x11, 13), 0x2d];
2937+
// we make the life color range slightly narrower, to not make the HUD look too awful
2938+
List<int> darkRangeLife = [.. Enumerable.Range(0x04, 3), .. Enumerable.Range(0x13, 5), .. Enumerable.Range(0x19, 4)];
2939+
// brighter dark colors do not look good in towns
2940+
List<int> darkRangeTown = [.. Enumerable.Range(0x01, 12), 0x1d];
2941+
2942+
if (paletteTableAddr == RomMap.PALACE_PALETTE_TABLE_MAJOR)
2943+
{
2944+
orangeSpriteColorAddr.AddRange(Enumerable.Range(0, 3).Select(i => paletteTableAddr + 0xa4 + 0x10 * i));
2945+
// additional per-palace palettes
2946+
lifeBgColorAddr.AddRange(Enumerable.Range(0, 6).Select(i => RomMap.PALACE_PALETTE_TABLE_ENTRANCES + 0x10 * i + 0x03));
2947+
lifeBgColorAddr.AddRange(Enumerable.Range(0, 6).Select(i => RomMap.PALACE_PALETTE_TABLE_PER_PALACE + 0x10 * i + 0x03));
2948+
}
2949+
if (paletteTableAddr == RomMap.GP_PALETTE_TABLE_MAJOR)
2950+
{
2951+
lifeBgColorAddr.Add(0x1c48f + 0x03); // palette PPU cmd when fading to Dark Link
2952+
lifeBgColorAddr.Add(0x1c4a3 + 0x03); // palette PPU cmd when Dark Link has been defeated
2953+
orangeSpriteColorAddr.Add(paletteTableAddr + 0xa4);
2954+
orangeSpriteColorAddr.Add(paletteTableAddr + 0xc4);
2955+
redSpriteColorAddr.Add(paletteTableAddr + 0xa8);
2956+
blueSpriteColorAddr.Add(paletteTableAddr + 0xac);
2957+
}
2958+
if (paletteTableAddr == RomMap.TOWN_PALETTE_TABLE)
2959+
{
2960+
var stabguy = paletteTableAddr + 0xac;
2961+
List<int> wizardAddr = [.. Enumerable.Range(0, 4).Select(i => paletteTableAddr + 0xa4 + 0x10 * i)];
2962+
orangeSpriteColorAddr.AddRange(wizardAddr);
2963+
(dark, middle, light) = NES.RollMatchingColorTriple(r, darkRangeFull);
2964+
ROMData.Put(stabguy + 1, dark);
2965+
ROMData.Put(stabguy + 2, middle);
2966+
ROMData.Put(stabguy + 3, light);
2967+
}
2968+
if (paletteTableAddr == RomMap.WEST_PALETTE_TABLE || paletteTableAddr == RomMap.EAST_PALETTE_TABLE)
2969+
{
2970+
orangeSpriteColorAddr.AddRange(Enumerable.Range(0, 4).Select(i => paletteTableAddr + 0xa4 + 0x10 * i));
2971+
redSpriteColorAddr.Add(paletteTableAddr + 0xa8);
2972+
blueSpriteColorAddr.Add(paletteTableAddr + 0xac);
2973+
}
2974+
2975+
List<List<int>> tripples = [orangeSpriteColorAddr, redSpriteColorAddr, blueSpriteColorAddr];
2976+
List<int> usedColors = [];
2977+
if (paletteTableAddr == RomMap.TOWN_PALETTE_TABLE)
2978+
{
2979+
usedColors.Add(0x22); // blue sky
2980+
}
2981+
2982+
foreach (List<int> list in tripples)
2983+
{
2984+
bool isOrange = list == orangeSpriteColorAddr;
2985+
2986+
List<int> darkRange = (paletteTableAddr, isOrange) switch
2987+
{
2988+
(RomMap.TOWN_PALETTE_TABLE, true) => darkRangeTown.Intersect(darkRangeLife).ToList(),
2989+
(RomMap.TOWN_PALETTE_TABLE, false) => darkRangeTown,
2990+
(RomMap.GP_PALETTE_TABLE_MAJOR, true) => darkRangeLife.Where(x => x != 0x14 && x != 0x15).ToList(),
2991+
(_, true) => darkRangeLife,
2992+
(_, false) => darkRangeFull,
2993+
};
2994+
2995+
do
2996+
{
2997+
(dark, middle, light) = NES.RollMatchingColorTriple(r, darkRange);
2998+
} while ((dark != 0x2d && usedColors.Contains(dark)) ||
2999+
usedColors.Contains(middle) ||
3000+
(light != 0x30 && usedColors.Contains(light)));
3001+
3002+
// prevent adjacent colors from being picked again
3003+
usedColors.AddRange(Enumerable.Range(-1, 3).Select(i => dark + i));
3004+
usedColors.AddRange(Enumerable.Range(-1, 3).Select(i => middle + i));
3005+
usedColors.AddRange(Enumerable.Range(-1, 3).Select(i => light + i));
3006+
3007+
foreach (var i in list)
3008+
{
3009+
ROMData.Put(i + 1, dark);
3010+
ROMData.Put(i + 2, middle);
3011+
if (list != orangeSpriteColorAddr) { ROMData.Put(i + 3, light); }
3012+
}
3013+
if (list == orangeSpriteColorAddr)
3014+
{
3015+
foreach (var j in lifeBgColorAddr) { ROMData.Put(j, dark); }
3016+
}
3017+
}
3018+
}
3019+
29333020
public void RandomizeSmallItems(int world, bool first)
29343021
{
29353022
logger.Debug("World: " + world);

RandomizerCore/NES.cs

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
using System.Diagnostics;
44
using System.Drawing;
55
using System.Linq;
6-
using System.Text;
7-
using System.Threading.Tasks;
86

97
namespace Z2Randomizer.RandomizerCore;
108

@@ -32,6 +30,60 @@ public class NES
3230
Color.FromArgb(237, 234, 164), Color.FromArgb(214, 244, 164), Color.FromArgb(197, 248, 184), Color.FromArgb(190, 246, 211),
3331
Color.FromArgb(191, 241, 241), Color.FromArgb(185, 185, 185), Color.FromArgb(0, 0, 0), Color.FromArgb(0, 0, 0),
3432
];
33+
34+
/// this should roll three colors that go well together
35+
/// darkRange argument may be passed if you want to specify
36+
/// a base range, to avoid conflicts with other colors and such.
37+
///
38+
/// (there is definitely room for improvements,
39+
/// allowing complementary colors, for example.)
40+
public static (byte dark, byte middle, byte light) RollMatchingColorTriple(Random r, List<int> darkRange)
41+
{
42+
float hueDistance(float h1, float h2)
43+
{
44+
float diff = Math.Abs(h1 - h2);
45+
return Math.Min(diff, 360f - diff);
46+
}
47+
48+
int dark, middle = 0, light = 0;
49+
Color darkColor, middleColor, lightColor;
50+
float darkHue, middleHue = 0f, lightHue;
51+
52+
dark = darkRange.Sample(r);
53+
darkColor = NesColors[dark];
54+
darkHue = darkColor.GetHue();
55+
56+
List<int> middleList = new(26);
57+
if (dark < 0x0d) { middleList.AddRange(Enumerable.Range(0x11, 0x0c)); }
58+
middleList.AddRange(Enumerable.Range(0x21, 0x0c));
59+
bool darkIsGrayscale = dark == 0x00 || dark == 0x10 || dark == 0x1d || dark == 0x2d;
60+
while (middleList.Count > 0)
61+
{
62+
middle = middleList.Sample(r);
63+
middleColor = NesColors[middle];
64+
middleHue = middleColor.GetHue();
65+
if (darkIsGrayscale) { break; }
66+
if (hueDistance(middleHue, darkHue) < 80f) { break; }
67+
middleList.Remove(middle);
68+
}
69+
70+
List<int> lightList = new(26);
71+
if (middle < 0x1d) { lightList.AddRange(Enumerable.Range(0x21, 0x0c)); }
72+
lightList.AddRange(Enumerable.Range(0x31, 0x0c));
73+
while (lightList.Count > 0)
74+
{
75+
light = lightList.Sample(r);
76+
lightColor = NesColors[light];
77+
lightHue = lightColor.GetHue();
78+
if (light == (int)NesColor.White) { break; }
79+
float hueDiffDark = darkIsGrayscale ? 0f : hueDistance(lightHue, darkHue);
80+
float hueDiffMiddle = hueDistance(lightHue, middleHue);
81+
if (hueDiffDark < 80f && hueDiffMiddle < 80f) { break; }
82+
lightList.Remove(middle);
83+
}
84+
85+
return ((byte)dark, (byte)middle, (byte)light);
86+
}
3587
}
3688

3789
/// <summary>

RandomizerCore/RomMap.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,4 +248,12 @@ class RomMap
248248
// (0x15453, 0x16406), // Thunderbird
249249
// (0x15454, 0x158aa), // Dark Link
250250
];
251+
252+
public const int WEST_PALETTE_TABLE = 0x401e;
253+
public const int EAST_PALETTE_TABLE = 0x801e;
254+
public const int TOWN_PALETTE_TABLE = 0xc01e;
255+
public const int PALACE_PALETTE_TABLE_MAJOR = 0x1001e;
256+
public const int PALACE_PALETTE_TABLE_ENTRANCES = 0x10480;
257+
public const int PALACE_PALETTE_TABLE_PER_PALACE = 0x13f10;
258+
public const int GP_PALETTE_TABLE_MAJOR = 0x1401e;
251259
}

0 commit comments

Comments
 (0)