-
Notifications
You must be signed in to change notification settings - Fork 5
Warpaints
This document details some notes on how warpaints and colors work in the game.
In the SDB, colors are often stored as uint32
.
We understand the game to use 2 different ways to store colors in the SDB.
In the SDB table dbvisualrecords::WarpaintPalette
, you find colors stored as uint
in for example the color1_highlight
field.
An example value is 4294967295
.
These fields are understood to be represent a 32bit ARGB value using 1 byte per color, such that the first 8 bits represent the Alpha value, the next 8 bits the Red value, then 8 bits Green, and finally 8 bits Blue, in order high to low bits.
In the SDB table dbcharacter::Monster
, there is a hair_color
field with a uint
value.
An example value is 3461939200
.
This 32 bit value represents two 16 bit colors.
The first 16 bits are "light" and the last 16 bits are "dark", in order of high to low bits.
Each 16 bit color value uses 5 bits for red, 6 bits for green and 5 bits for blue, in order of high to low bits.
The light and dark colors are blended together for the final result.
Warpaints Palettes, in dbvisualrecords::WarpaintPalette
, contain a total of 7 colors in 14 fields.
From reading the previous section, perhaps you can now infer that for example color1_highlight
and color1_shadow
, each contain an argb-color
and together these two values can blend into a single light-dark-color
.
That is how the colors should be sent down to the client from the server.
Here is some sample code to perform the conversion
function ffColorCompressAndCombine(argb_light, argb_dark) {
function compress_8bit_to_5bit(value) {
return Math.floor( ( value * 32 ) / 256 )
}
function compress_8bit_to_6bit(value) {
return Math.floor( ( value * 64 ) / 256 )
}
function compress(rgb) {
const input_red = (rgb & 0xFF0000) >> 16
const input_green = (rgb & 0x00FF00) >> 8
const input_blue = (rgb & 0x0000FF)
const compressed_red = compress_8bit_to_5bit(input_red)
const compressed_green = compress_8bit_to_6bit(input_green)
const compressed_blue = compress_8bit_to_5bit(input_blue)
// RRRRR GGGGGG BBBBB
const result = 0x0000 | (compressed_red << 11) | (compressed_green << 5) | compressed_blue
return result
}
// Skip alpha
const light = 0x0000 | (argb_light & 0x00FFFFFF)
const dark = 0x0000 | (argb_dark & 0x00FFFFFF)
// Compress
const compressed_light = compress(light)
const compressed_dark = compress(dark)
// Combine
const combined = (0x0000 | ( compressed_light << 16 ) | compressed_dark) >>> 0
return combined
}
We have 7 colors for each palette, and we know the Chassis/Battleframe uses an array of 7 colors in the network views.
Digging into the UI we can find the following names for the Chassis/Battleframe, and assume they are in the appropriate order:
ARMOR_COLOR_1
ARMOR_COLOR_2
ARMOR_COLOR_3
BODYSUIT_COLOR_1
BODYSUIT_COLOR_2
GLOW_COLOR_1
GLOW_COLOR_2
In the SDB table dbcharacter::Monster
, we can find the following fields:
fullbody_warpaint_palette_id
armor_warpaint_palette_id
bodysuit_warpaint_palette_id
glow_warpaint_palette_id
We must also note the field chassis_id
(Battleframe), and the associated table dbitems::Battleframe
which has the following fields:
default_fullbody_palette_id
default_armor_palette_id
default_glow_palette_id
default_bodysuit_palette_id
Given what we see, the following approach has thus far given the most satisfying results:
- Retrieve the 4 palettes and all of their colors, prioritising the Monster table if they are set (not 0), and retrieve the default from Battleframe otherwise.
- Select one of the four palettes to use all of its 7 colors as the base palette, in the following priority order: fullbody -> armor -> bodysuit -> glow
- If armor is set, then override the colors 1-3 with those of that palette.
- If bodysuit is set, then override the colors 4-5 with those of that palette.
- If glow is set, then override the colors 6-7 with those of the that palette.
Patterns in dbvisualrecords::CziPattern
have a one byte type_flags
column.
This is likely equivalent to slot_mask
, found in the UI MainUI\Sinvironment\garage\BattleframeGarage.lua
That would mean that the flags indicate which things that the palette can apply to:
2^0: Armor,
2^1: Bodysuit,
2^2: Weapon1,
2^3: Weapon2,
Thus, type_flags
== 15
would imply that the palette can be applied to all of these slots.