feat: asymmetric characters that flip when turning

- Player template is now asymmetric:
  - Weapon arm extends forward (right side, x=3..4)
  - Hair flows backward (left side, x=-3..-4)
  - Cape drapes to the left (x=-3..-4)
  - Front hand colored as 'weapon' (bright steel)
- Goblin template asymmetric: weapon arm extends forward (x=4)
- Entity.flip_facing() mirrors all rest_offsets X and body positions
- Entity.facing_right field (default true)
- Game loop detects mouse crossing player center X and calls flip_facing()
- Verlet constraints adapt smoothly to mirrored positions
- 'weapon' label added to preview_ascii

All 171 tests + 14 scenarios pass.
This commit is contained in:
Emil
2026-06-21 18:14:34 +03:00
parent e50df7b51e
commit 586487e61c
3 changed files with 40 additions and 12 deletions
+15 -11
View File
@@ -46,13 +46,13 @@ impl BodyTemplate {
p!(0, -5, 40, 30, 50, "eye"),
p!(-1, -5, 60, 40, 70, "eye"),
p!(1, -5, 60, 40, 70, "eye"),
p!(-2, -6, 120, 80, 40, "hair"),
p!(-1, -7, 140, 90, 50, "hair"),
p!(-3, -6, 120, 80, 40, "hair"),
p!(-2, -7, 140, 90, 50, "hair"),
p!(-1, -7, 150, 100, 55, "hair"),
p!(0, -7, 150, 100, 55, "hair"),
p!(1, -7, 140, 90, 50, "hair"),
p!(2, -6, 120, 80, 40, "hair"),
p!(-3, -7, 110, 70, 35, "hair"),
p!(3, -7, 110, 70, 35, "hair"),
p!(-3, -5, 110, 70, 35, "hair"),
p!(-4, -5, 100, 60, 30, "hair"),
p!(-2, -4, 80, 50, 130, "hat"),
p!(2, -4, 80, 50, 130, "hat"),
p!(-1, -5, 90, 55, 140, "hat"),
@@ -70,23 +70,25 @@ impl BodyTemplate {
p!(1, -3, 150, 90, 210, "shirt"),
p!(-2, -3, 210, 185, 240, "hand"),
p!(2, -3, 210, 185, 240, "hand"),
p!(3, -3, 220, 195, 245, "weapon"),
p!(4, -3, 230, 205, 250, "weapon"),
p!(-1, -2, 150, 90, 210, "shirt"),
p!(0, -2, 160, 100, 220, "shirt"),
p!(1, -2, 150, 90, 210, "shirt"),
p!(-2, -2, 200, 170, 230, "hand"),
p!(2, -2, 200, 170, 230, "hand"),
p!(-3, -2, 80, 50, 130, "cape"),
p!(3, -2, 80, 50, 130, "cape"),
p!(-4, -2, 80, 50, 130, "cape"),
p!(-3, -2, 75, 45, 120, "cape"),
p!(-1, -1, 150, 90, 210, "shirt"),
p!(0, -1, 160, 100, 220, "shirt"),
p!(1, -1, 150, 90, 210, "shirt"),
p!(-3, -1, 75, 45, 120, "cape"),
p!(3, -1, 75, 45, 120, "cape"),
p!(-4, -1, 70, 40, 110, "cape"),
p!(-3, -1, 70, 40, 110, "cape"),
p!(-1, 0, 230, 190, 110, "belt"),
p!(0, 0, 240, 200, 120, "belt"),
p!(1, 0, 230, 190, 110, "belt"),
p!(-3, 0, 70, 40, 110, "cape"),
p!(3, 0, 70, 40, 110, "cape"),
p!(-4, 0, 60, 35, 100, "cape"),
p!(-3, 0, 65, 38, 105, "cape"),
p!(-1, 1, 95, 55, 155, "pants"),
p!(0, 1, 95, 55, 155, "pants"),
p!(1, 1, 95, 55, 155, "pants"),
@@ -145,6 +147,7 @@ impl BodyTemplate {
p!(2, -3, 110, 180, 85, "hand"),
p!(-3, -3, 100, 170, 75, "hand"),
p!(3, -3, 100, 170, 75, "hand"),
p!(4, -3, 180, 160, 60, "weapon"),
p!(-1, -2, 55, 130, 45, "shirt"),
p!(0, -2, 65, 140, 50, "shirt"),
p!(1, -2, 55, 130, 45, "shirt"),
@@ -357,6 +360,7 @@ impl BodyTemplate {
("cape", '>'),
("tooth", '.'),
("ear", '<'),
("weapon", '/'),
]);
for part in &self.parts {
+18
View File
@@ -41,6 +41,7 @@ pub struct Entity {
pub toughness: u32,
pub willpower: u32,
pub counted_for_score: bool,
pub facing_right: bool,
}
impl Clone for Entity {
@@ -76,6 +77,7 @@ impl Clone for Entity {
toughness: self.toughness,
willpower: self.willpower,
counted_for_score: self.counted_for_score,
facing_right: self.facing_right,
}
}
}
@@ -111,6 +113,7 @@ impl Entity {
toughness: 10,
willpower: 10,
counted_for_score: false,
facing_right: true,
half_w: 3.5,
half_h: 3.0,
}
@@ -195,6 +198,21 @@ impl Entity {
}
}
pub fn flip_facing(&mut self) {
self.facing_right = !self.facing_right;
for off in &mut self.rest_offsets {
off.0 = -off.0;
}
for b in &mut self.bodies {
if b.alive {
let dx = b.x - self.cx;
b.x = self.cx - dx;
let old_dx = b.old_x - self.cx;
b.old_x = self.cx - old_dx;
}
}
}
pub fn set_horizontal_vel(&mut self, vx: f32) {
if self.rigid {
self.cvx = vx;
+7 -1
View File
@@ -329,7 +329,13 @@ fn run_gpu_mode<R: GpuRenderer>(title: &str) {
{
let (px, _py) = game.player.center(&game.entities);
let player_screen_x = ((px as i32 - game.cam_x) as f64) * 8.0 + 4.0;
game.player.facing_right = input.mouse_x >= player_screen_x;
let should_face_right = input.mouse_x >= player_screen_x;
if should_face_right != game.player.facing_right {
if let Some(e) = game.player.entity_mut(&mut game.entities) {
e.flip_facing();
}
game.player.facing_right = should_face_right;
}
}
if input.cam_left {