diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj
index 7c2b509a44..024f9c9287 100644
--- a/OpenRA.Game/OpenRA.Game.csproj
+++ b/OpenRA.Game/OpenRA.Game.csproj
@@ -252,6 +252,7 @@
+
diff --git a/OpenRA.Game/Traits/Player/IndexedPlayerPalette.cs b/OpenRA.Game/Traits/Player/IndexedPlayerPalette.cs
new file mode 100644
index 0000000000..ee273adcc8
--- /dev/null
+++ b/OpenRA.Game/Traits/Player/IndexedPlayerPalette.cs
@@ -0,0 +1,91 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
+ * This file is part of OpenRA, which is free software. It is made
+ * available to you under the terms of the GNU General Public License
+ * as published by the Free Software Foundation. For more information,
+ * see COPYING.
+ */
+#endregion
+
+using System.Collections.Generic;
+using System.Drawing;
+using System.IO;
+using OpenRA.Graphics;
+
+namespace OpenRA.Traits
+{
+ [Desc("Define a player palette by swapping palette indices.")]
+ public class IndexedPlayerPaletteInfo : ITraitInfo, IRulesetLoaded
+ {
+ [Desc("The name of the palette to base off.")]
+ [PaletteReference] public readonly string BasePalette = null;
+
+ [Desc("The prefix for the resulting player palettes")]
+ [PaletteDefinition(true)] public readonly string BaseName = "player";
+
+ [Desc("Remap these indices to player colors.")]
+ public readonly int[] RemapIndex = { };
+
+ [Desc("Allow palette modifiers to change the palette.")]
+ public readonly bool AllowModifiers = true;
+
+ public readonly Dictionary PlayerIndex;
+
+ public object Create(ActorInitializer init) { return new IndexedPlayerPalette(this); }
+
+ public void RulesetLoaded(Ruleset rules, ActorInfo ai)
+ {
+ foreach (var p in PlayerIndex)
+ if (p.Value.Length != RemapIndex.Length)
+ throw new YamlException("PlayerIndex for player `{0}` length does not match RemapIndex!".F(p.Key));
+ }
+ }
+
+ public class IndexedPlayerPalette : ILoadsPlayerPalettes
+ {
+ readonly IndexedPlayerPaletteInfo info;
+
+ public IndexedPlayerPalette(IndexedPlayerPaletteInfo info)
+ {
+ this.info = info;
+ }
+
+ public void LoadPlayerPalettes(WorldRenderer wr, string playerName, HSLColor color, bool replaceExisting)
+ {
+ var basePalette = wr.Palette(info.BasePalette).Palette;
+ ImmutablePalette pal;
+ int[] remap;
+
+ if (info.PlayerIndex.TryGetValue(playerName, out remap))
+ pal = new ImmutablePalette(basePalette, new IndexedColorRemap(basePalette, info.RemapIndex, remap));
+ else
+ pal = new ImmutablePalette(basePalette);
+
+ wr.AddPalette(info.BaseName + playerName, pal, info.AllowModifiers, replaceExisting);
+ }
+ }
+
+ public class IndexedColorRemap : IPaletteRemap
+ {
+ Dictionary replacements = new Dictionary();
+ IPalette basePalette;
+
+ public IndexedColorRemap(IPalette basePalette, int[] ramp, int[] remap)
+ {
+ this.basePalette = basePalette;
+ if (ramp.Length != remap.Length)
+ throw new InvalidDataException("ramp and remap lengths do no match.");
+
+ for (var i = 0; i < ramp.Length; i++)
+ replacements[ramp[i]] = remap[i];
+ }
+
+ public Color GetRemappedColor(Color original, int index)
+ {
+ int c;
+ return replacements.TryGetValue(index, out c)
+ ? basePalette.GetColor(c) : original;
+ }
+ }
+}