- 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.
134 lines
3.8 KiB
Rust
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());
|
|
}
|
|
}
|