Files
skids/src/data/palette.rs
T
andy 9d82d99c15 Add the contents of disk 1
- LHA -lh5/-lh6/-lh7 decoding with CRC checks (disk 1 is mostly lh5)
- Real track palettes and set names, read from each track file's header
- Camaro, plus the caravan as a towable trailer (Caravans option)
- Drivers' names, menu pictures and the original .set values per car
- Original sound effects (IFF 8SVX) through a new sample mixer: engine
  pitched with revs, tyre screech, crashes, backfire, horn, start beeps
- The original tune (OctaMED MMD1) through a new player; loudness tracks
  OpenMPT's render through the whole song. Music option in the menu
- Title picture (IFF ILBM) at the top of the menu
- skids unpack and skids music commands
- Gate crossings now judged on each car's whole movement per frame,
  including collision knocks; stalled computer cars are put back on the
  line after 8 seconds

The executable's game logic (handling, AI) is still not decoded.
2026-10-19 20:24:33 +00:00

134 lines
3.8 KiB
Rust

//! Colour palettes.
//!
//! Each original track file carries the 16-colour palette its six tracks
//! share (see `track.rs`). `TRACK_DEFAULT` is only a fallback for track
//! pictures that arrive without one: indices 0-7 scenery, 8-15 road.
//!
//! Each track asset's PNG carries its own palette, so the usual way to
//! recolour a track is to edit that. Palettes can also be dropped in as text
//! files (16 lines of `RRGGBB`, `#` comments allowed), which win over both:
//! palettes/track1-3.pal one track
//! palettes/track1.pal all six tracks in TRACK1
//! palettes/default.pal every track
use super::FileSet;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Rgb(pub u8, pub u8, pub u8);
impl Rgb {
pub const fn to_u32(self) -> u32 {
((self.0 as u32) << 16) | ((self.1 as u32) << 8) | self.2 as u32
}
pub fn scale(self, f: f32) -> Rgb {
let s = |c: u8| ((c as f32 * f).round().clamp(0.0, 255.0)) as u8;
Rgb(s(self.0), s(self.1), s(self.2))
}
pub fn mix(self, o: Rgb, t: f32) -> Rgb {
let m = |a: u8, b: u8| (a as f32 + (b as f32 - a as f32) * t).round() as u8;
Rgb(m(self.0, o.0), m(self.1, o.1), m(self.2, o.2))
}
}
pub const TRACK_DEFAULT: [Rgb; 16] = [
// scenery: deep shadow through sunlit grass
Rgb(0x0c, 0x1a, 0x0e),
Rgb(0x18, 0x2c, 0x14),
Rgb(0x24, 0x40, 0x1a),
Rgb(0x34, 0x56, 0x20),
Rgb(0x48, 0x6c, 0x28),
Rgb(0x60, 0x84, 0x34),
Rgb(0x84, 0x9c, 0x4c),
Rgb(0xb0, 0xb8, 0x7c),
// driving surface: tarmac, kerbs and markings
Rgb(0x3a, 0x36, 0x34),
Rgb(0x50, 0x4c, 0x48),
Rgb(0x66, 0x62, 0x5c),
Rgb(0x7c, 0x78, 0x70),
Rgb(0x94, 0x90, 0x86),
Rgb(0xae, 0xaa, 0x9e),
Rgb(0xcc, 0xc8, 0xbc),
Rgb(0xf0, 0xee, 0xe4),
];
/// Per-player body colours for the three-colour vehicle sprites
/// (index 1 = tyres and shadow, 2 = trim, 3 = bodywork).
pub const TEAM_COLOURS: [Rgb; 8] = [
Rgb(0xe0, 0x30, 0x28),
Rgb(0x30, 0x60, 0xe8),
Rgb(0xf0, 0xc8, 0x20),
Rgb(0x30, 0xb0, 0x48),
Rgb(0xe0, 0x60, 0xd0),
Rgb(0x20, 0xc0, 0xc8),
Rgb(0xf0, 0x80, 0x20),
Rgb(0xe8, 0xe8, 0xe8),
];
pub fn car_palette(team: usize) -> [u32; 4] {
let c = TEAM_COLOURS[team % TEAM_COLOURS.len()];
[
0,
Rgb(0x10, 0x10, 0x14).to_u32(),
c.scale(0.7).to_u32(),
c.mix(Rgb(0xff, 0xff, 0xff), 0.35).to_u32(),
]
}
pub fn parse(text: &str) -> Option<[Rgb; 16]> {
let mut out = [Rgb(0, 0, 0); 16];
let mut n = 0;
for line in text.lines() {
let line = line
.split('#')
.next()
.unwrap_or("")
.trim()
.trim_start_matches("0x");
if line.is_empty() {
continue;
}
if n == 16 || line.len() != 6 {
return None;
}
let v = u32::from_str_radix(line, 16).ok()?;
out[n] = Rgb((v >> 16) as u8, (v >> 8) as u8, v as u8);
n += 1;
}
(n == 16).then_some(out)
}
/// A palette file for a track named like `TRACK1-3`, most specific first.
pub fn override_for(files: &FileSet, track: &str) -> Option<[Rgb; 16]> {
let name = track.to_lowercase();
let group = name.split('-').next().unwrap_or(&name).to_string();
[
format!("palettes/{name}.pal"),
format!("palettes/{group}.pal"),
"palettes/default.pal".into(),
]
.iter()
.find_map(|k| {
files
.get(k)
.and_then(|d| parse(&String::from_utf8_lossy(d)))
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_palette_file() {
let mut text = String::from("# test\n");
for i in 0..16 {
text.push_str(&format!("{:02x}{:02x}{:02x}\n", i, i * 2, i * 3));
}
let p = parse(&text).unwrap();
assert_eq!(p[15], Rgb(15, 30, 45));
assert!(parse("ffffff\n").is_none());
}
}