Skip to content

Commit 8e12d6b

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 4cee4f7 commit 8e12d6b

7 files changed

Lines changed: 186 additions & 39 deletions

File tree

CrossPlatformUI/Lang/Resources.resx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,9 @@ amount.</value>
236236
immune in each group is the same as the number in that group that are immune in vanilla.</value>
237237
</data>
238238
<data name="ShuffleSpritePalettesToolTip" xml:space="preserve">
239-
<value>Randomizes the colors each of the enemies are. This could cause which enemies are visible
240-
in the dark to change.</value>
239+
<value>Randomizes the color palettes of enemies and NPCs. Health bar color is also randomized.
240+
241+
Does *not* change what is visible in caves without a candle.</value>
241242
</data>
242243
<data name="EnemyExperienceDropsToolTip" xml:space="preserve">
243244
<value>Controls how much experience enemies give.

CrossPlatformUI/Views/Tabs/CustomizeView.axaml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,14 @@
5252
<ToolTip.Tip><TextBlock Text="{x:Static lang:Resources.BeepFrequencyToolTip}"/></ToolTip.Tip>
5353
</ComboBox>
5454

55-
<CheckBox
56-
IsChecked="{Binding UseCommunityText}"
57-
Content="Use Community Text"
58-
>
55+
<CheckBox IsChecked="{Binding UseCommunityText}"
56+
Content="Use Community Text">
5957
<ToolTip.Tip><TextBlock Text="{x:Static lang:Resources.UseCommunityTextToolTip}"/></ToolTip.Tip>
6058
</CheckBox>
59+
<CheckBox IsChecked="{Binding ShuffleSpritePalettes}"
60+
Content="Shuffle Sprite Palettes">
61+
<ToolTip.Tip><TextBlock Text="{x:Static lang:Resources.ShuffleSpritePalettesToolTip}"/></ToolTip.Tip>
62+
</CheckBox>
6163

6264
<Separator/>
6365

CrossPlatformUI/Views/Tabs/EnemiesView.axaml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,6 @@
119119
<ToolTip.Tip><TextBlock Text="{x:Static lang:Resources.EnemyExperienceDropsToolTip}"/></ToolTip.Tip>
120120
</ComboBox>
121121

122-
<CheckBox IsChecked="{Binding Config.ShuffleSpritePalettes}"
123-
Content="Shuffle Sprite Palettes">
124-
<ToolTip.Tip><TextBlock Text="{x:Static lang:Resources.ShuffleSpritePalettesToolTip}"/></ToolTip.Tip>
125-
</CheckBox>
126122
<CheckBox IsChecked="{Binding Config.RandomizeKnockback}"
127123
Content="Randomize Knockback">
128124
<ToolTip.Tip><TextBlock Text="{x:Static lang:Resources.RandomizeKnockbackToolTip}"/></ToolTip.Tip>

RandomizerCore/Hyrule.cs

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

25322532
if (props.ShuffleEnemyPalettes)
25332533
{
2534-
List<int> doubleLocs = [0x40b4, 0x80b4, 0x100b4, 0x100b8, 0x100bc, 0x140b4, 0x140b8, 0x140bc];
2535-
List<int> singleLocs = [0x40b8, 0x40bc, 0x80b8, 0x80bc];
2534+
Random customizationRng = new Random(SeedHash);
2535+
RerollPaletteTable(RomMap.WEST_PALETTE_TABLE, customizationRng);
2536+
RerollPaletteTable(RomMap.EAST_PALETTE_TABLE, customizationRng);
2537+
RerollPaletteTable(RomMap.TOWN_PALETTE_TABLE, customizationRng);
2538+
RerollPaletteTable(RomMap.PALACE_PALETTE_TABLE_MAJOR, customizationRng);
2539+
RerollPaletteTable(RomMap.GP_PALETTE_TABLE_MAJOR, customizationRng);
25362540

2537-
foreach (int i in doubleLocs)
2538-
{
2539-
int low = r.Next(12) + 1;
2540-
int high = (r.Next(2) + 1) * 16;
2541-
int color = high + low;
2542-
ROMData.Put(i, (byte)color);
2543-
ROMData.Put(i + 16, (byte)color);
2544-
ROMData.Put(i - 1, (byte)(color - 15));
2545-
ROMData.Put(i + 16 - 1, (byte)(color - 15));
2546-
}
2547-
foreach (int i in singleLocs)
2548-
{
2549-
int low = r.Next(13);
2550-
int high = (r.Next(3)) * 16;
2551-
int color = high + low;
2552-
ROMData.Put(i, (byte)color);
2553-
ROMData.Put(i + 16, (byte)color);
2554-
ROMData.Put(i + 16 - 1, (byte)(color - 15));
2555-
}
2541+
/*
2542+
lets no longer shuffle orange/red/blue bits for enemies, it changes
2543+
what is visible in the dark and also doesn't add enough to be worth it imo.
2544+
I also prefer if we can have the palette shuffle as a customize (non-flag) option.
25562545
2557-
for (int i = 0x54e8; i < 0x5508; i++)
2546+
for (int i = RomMap.WEST_ENEMY_STATS_TABLE + 0x3; i < RomMap.WEST_ENEMY_STATS_TABLE + 0x23; i++)
25582547
{
2559-
if (i != 0x54f8)
2548+
if (i != RomMap.WEST_ENEMY_STATS_TABLE + 0x13) // skip elevator
25602549
{
25612550
int b = ROMData.GetByte(i);
25622551
int p = b & 0x3F;
@@ -2565,7 +2554,6 @@ private void UpdateRom()
25652554
ROMData.Put(i, (byte)(n + p));
25662555
}
25672556
}
2568-
25692557
for (int i = 0x94e8; i < 0x9508; i++)
25702558
{
25712559
if (i != 0x94f8)
@@ -2610,6 +2598,7 @@ private void UpdateRom()
26102598
ROMData.Put(i, (byte)(n + p));
26112599
}
26122600
}
2601+
*/
26132602
}
26142603

26152604
//WRITE UPDATES TO WIZARD/QUEST COLLECTABLES HERE
@@ -2827,6 +2816,104 @@ private void UpdateRom()
28272816
}
28282817
}
28292818

2819+
private void RerollPaletteTable(int paletteTableAddr, Random r)
2820+
{
2821+
byte dark, middle, light;
2822+
2823+
// we are NOT rolling the white color for magic/interface that should match the orange sprite light
2824+
// (white looks fine with all 2 other sprite colors anyway)
2825+
// int[] magicBgColorAddr = [paletteTableAddr + 0x01, paletteTableAddr + 0x11];
2826+
// we ARE rolling the red sprite and matching the red tile color for the life bars to it
2827+
// (it would limit the palette a lot if the red color has to stay red)
2828+
List<int> lifeBgColorAddr = [.. Enumerable.Range(0, 9).Select(i => paletteTableAddr + 0x10 * i + 0x03)];
2829+
List<int> orangeSpriteColorAddr = [paletteTableAddr + 0x94];
2830+
List<int> redSpriteColorAddr = [paletteTableAddr + 0x98];
2831+
List<int> blueSpriteColorAddr = [paletteTableAddr + 0x9c];
2832+
2833+
List<int> darkRangeFull = [.. Enumerable.Range(0x01, 12), .. Enumerable.Range(0x11, 13), 0x2d];
2834+
// we make the life color range slightly narrower, to not make the HUD look too awful
2835+
List<int> darkRangeLife = [.. Enumerable.Range(0x04, 3), .. Enumerable.Range(0x13, 5), .. Enumerable.Range(0x19, 4)];
2836+
// brighter dark colors do not look good in towns
2837+
List<int> darkRangeTown = [.. Enumerable.Range(0x01, 12), 0x1d];
2838+
2839+
if (paletteTableAddr == RomMap.PALACE_PALETTE_TABLE_MAJOR)
2840+
{
2841+
orangeSpriteColorAddr.AddRange(Enumerable.Range(0, 3).Select(i => paletteTableAddr + 0xa4 + 0x10 * i));
2842+
// additional per-palace palettes
2843+
lifeBgColorAddr.AddRange(Enumerable.Range(0, 6).Select(i => RomMap.PALACE_PALETTE_TABLE_ENTRANCES + 0x10 * i + 0x03));
2844+
lifeBgColorAddr.AddRange(Enumerable.Range(0, 6).Select(i => RomMap.PALACE_PALETTE_TABLE_PER_PALACE + 0x10 * i + 0x03));
2845+
}
2846+
if (paletteTableAddr == RomMap.GP_PALETTE_TABLE_MAJOR)
2847+
{
2848+
lifeBgColorAddr.Add(0x1c48f + 0x03); // palette PPU cmd when fading to Dark Link
2849+
lifeBgColorAddr.Add(0x1c4a3 + 0x03); // palette PPU cmd when Dark Link has been defeated
2850+
orangeSpriteColorAddr.Add(paletteTableAddr + 0xa4);
2851+
orangeSpriteColorAddr.Add(paletteTableAddr + 0xc4);
2852+
redSpriteColorAddr.Add(paletteTableAddr + 0xa8);
2853+
blueSpriteColorAddr.Add(paletteTableAddr + 0xac);
2854+
}
2855+
if (paletteTableAddr == RomMap.TOWN_PALETTE_TABLE)
2856+
{
2857+
var stabguy = paletteTableAddr + 0xac;
2858+
List<int> wizardAddr = [.. Enumerable.Range(0, 4).Select(i => paletteTableAddr + 0xa4 + 0x10 * i)];
2859+
orangeSpriteColorAddr.AddRange(wizardAddr);
2860+
(dark, middle, light) = NES.RollMatchingColorTriple(r, darkRangeFull);
2861+
ROMData.Put(stabguy + 1, dark);
2862+
ROMData.Put(stabguy + 2, middle);
2863+
ROMData.Put(stabguy + 3, light);
2864+
}
2865+
if (paletteTableAddr == RomMap.WEST_PALETTE_TABLE || paletteTableAddr == RomMap.EAST_PALETTE_TABLE)
2866+
{
2867+
orangeSpriteColorAddr.AddRange(Enumerable.Range(0, 4).Select(i => paletteTableAddr + 0xa4 + 0x10 * i));
2868+
redSpriteColorAddr.Add(paletteTableAddr + 0xa8);
2869+
blueSpriteColorAddr.Add(paletteTableAddr + 0xac);
2870+
}
2871+
2872+
List<List<int>> tripples = [orangeSpriteColorAddr, redSpriteColorAddr, blueSpriteColorAddr];
2873+
List<int> usedColors = [];
2874+
if (paletteTableAddr == RomMap.TOWN_PALETTE_TABLE)
2875+
{
2876+
usedColors.Add(0x22); // blue sky
2877+
}
2878+
2879+
foreach (List<int> list in tripples)
2880+
{
2881+
bool isOrange = list == orangeSpriteColorAddr;
2882+
2883+
List<int> darkRange = (paletteTableAddr, isOrange) switch
2884+
{
2885+
(RomMap.TOWN_PALETTE_TABLE, true) => darkRangeTown.Intersect(darkRangeLife).ToList(),
2886+
(RomMap.TOWN_PALETTE_TABLE, false) => darkRangeTown,
2887+
(RomMap.GP_PALETTE_TABLE_MAJOR, true) => darkRangeLife.Where(x => x != 0x14 && x != 0x15).ToList(),
2888+
(_, true) => darkRangeLife,
2889+
(_, false) => darkRangeFull,
2890+
};
2891+
2892+
do
2893+
{
2894+
(dark, middle, light) = NES.RollMatchingColorTriple(r, darkRange);
2895+
} while ((dark != 0x2d && usedColors.Contains(dark)) ||
2896+
usedColors.Contains(middle) ||
2897+
(light != 0x30 && usedColors.Contains(light)));
2898+
2899+
// prevent adjacent colors from being picked again
2900+
usedColors.AddRange(Enumerable.Range(-1, 3).Select(i => dark + i));
2901+
usedColors.AddRange(Enumerable.Range(-1, 3).Select(i => middle + i));
2902+
usedColors.AddRange(Enumerable.Range(-1, 3).Select(i => light + i));
2903+
2904+
foreach (var i in list)
2905+
{
2906+
ROMData.Put(i + 1, dark);
2907+
ROMData.Put(i + 2, middle);
2908+
if (list != orangeSpriteColorAddr) { ROMData.Put(i + 3, light); }
2909+
}
2910+
if (list == orangeSpriteColorAddr)
2911+
{
2912+
foreach (var j in lifeBgColorAddr) { ROMData.Put(j, dark); }
2913+
}
2914+
}
2915+
}
2916+
28302917
public void RandomizeSmallItems(int world, bool first)
28312918
{
28322919
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/RandomizerConfiguration.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -645,9 +645,6 @@ private bool palaceStylesAnyMetastyleSelected()
645645
[Reactive]
646646
private bool dashAlwaysOn;
647647

648-
[Reactive]
649-
private bool shuffleSpritePalettes;
650-
651648
[Reactive]
652649
private bool permanentBeamSword;
653650

@@ -725,6 +722,10 @@ private bool palaceStylesAnyMetastyleSelected()
725722
[IgnoreInFlags]
726723
private NesColor shieldTunic;
727724

725+
[Reactive]
726+
[IgnoreInFlags]
727+
private bool shuffleSpritePalettes;
728+
728729
[Reactive]
729730
[IgnoreInFlags]
730731
private BeamSprites beamSprite;

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)