Move passable and opaque into objectdef

This commit is contained in:
2026-05-30 21:22:36 -05:00
parent a0d21bde20
commit 0241cd2a8d
5 changed files with 59 additions and 25 deletions
+27 -19
View File
@@ -72,7 +72,7 @@ pub struct FontSpec {
/// ///
/// For scripted objects (`Archetype::Object`), the behavior will eventually be /// For scripted objects (`Archetype::Object`), the behavior will eventually be
/// determined by the Rhai script rather than this struct. See the Object variant /// determined by the Rhai script rather than this struct. See the Object variant
/// for details. /// for details. (TODO fix this comment)
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct Behavior { pub struct Behavior {
/// Whether an entity can walk through this cell. /// Whether an entity can walk through this cell.
@@ -104,8 +104,6 @@ pub enum Archetype {
Empty, Empty,
/// A solid wall; impassable and opaque. /// A solid wall; impassable and opaque.
Wall, Wall,
/// A scripted object. Behavior defaults to impassable until script-driven.
Object,
/// Sentinel for map files that reference an unknown archetype name. /// Sentinel for map files that reference an unknown archetype name.
/// Renders as a yellow `?` on red to make the error visible in-game. /// Renders as a yellow `?` on red to make the error visible in-game.
ErrorBlock, ErrorBlock,
@@ -125,10 +123,6 @@ impl Archetype {
passable: false, passable: false,
opaque: true, opaque: true,
}, },
Archetype::Object => Behavior {
passable: false,
opaque: false,
},
Archetype::ErrorBlock => Behavior { Archetype::ErrorBlock => Behavior {
passable: false, passable: false,
opaque: true, opaque: true,
@@ -141,7 +135,6 @@ impl Archetype {
match self { match self {
Archetype::Empty => "empty", Archetype::Empty => "empty",
Archetype::Wall => "wall", Archetype::Wall => "wall",
Archetype::Object => "object",
Archetype::ErrorBlock => "error_block", Archetype::ErrorBlock => "error_block",
} }
} }
@@ -162,11 +155,6 @@ impl Archetype {
fg: Rgba8 { r: 0x80, g: 0x80, b: 0x80, a: 255 }, fg: Rgba8 { r: 0x80, g: 0x80, b: 0x80, a: 255 },
bg: Rgba8 { r: 0x60, g: 0x60, b: 0x60, a: 255 }, bg: Rgba8 { r: 0x60, g: 0x60, b: 0x60, a: 255 },
}, },
Archetype::Object => Glyph {
tile: 63,
fg: Rgba8 { r: 255, g: 255, b: 0, a: 255 }, // yellow
bg: Rgba8 { r: 0, g: 0, b: 0, a: 255 },
},
// Visually distinct so malformed map files are immediately obvious. // Visually distinct so malformed map files are immediately obvious.
Archetype::ErrorBlock => Glyph { Archetype::ErrorBlock => Glyph {
tile: 63, tile: 63,
@@ -199,7 +187,7 @@ impl TryFrom<&str> for Archetype {
/// ///
/// `ErrorBlock` is excluded — it is a sentinel for load errors, not a valid /// `ErrorBlock` is excluded — it is a sentinel for load errors, not a valid
/// editing choice. /// editing choice.
pub const ALL_ARCHETYPES: &[Archetype] = &[Archetype::Empty, Archetype::Wall, Archetype::Object]; pub const ALL_ARCHETYPES: &[Archetype] = &[Archetype::Empty, Archetype::Wall];
/// A scripted object placed on the board, loaded from a map file. /// A scripted object placed on the board, loaded from a map file.
/// ///
@@ -228,11 +216,25 @@ pub struct ObjectDef {
/// Visual representation of this object. Owned by the object (not derived /// Visual representation of this object. Owned by the object (not derived
/// from the grid cell), so scripts can change tile, fg, and bg at runtime. /// from the grid cell), so scripts can change tile, fg, and bg at runtime.
pub glyph: Glyph, pub glyph: Glyph,
/// Whether the object blocks movement, of players or other objects
pub passable: bool,
/// Whether the object blocks line of sight / FOV for the player
pub opaque: bool,
/// Name of the Rhai script in [`Board::scripts`] that drives this object. /// Name of the Rhai script in [`Board::scripts`] that drives this object.
/// `None` means this object has no script yet. /// `None` means this object has no script yet.
pub script_name: Option<String>, pub script_name: Option<String>,
} }
impl ObjectDef {
pub fn default_glyph() -> Glyph {
Glyph {
tile: 63,
fg: Rgba8 { r: 255, g: 255, b: 0, a: 255 }, // yellow
bg: Rgba8 { r: 0, g: 0, b: 0, a: 255 },
}
}
}
/// A portal that teleports the player to a named entry point on another board. /// A portal that teleports the player to a named entry point on another board.
/// ///
/// Portals are loaded from `[[portals]]` entries in `.toml` map files and /// Portals are loaded from `[[portals]]` entries in `.toml` map files and
@@ -345,17 +347,23 @@ impl Board {
/// Objects block movement before the grid cell archetype is consulted. /// Objects block movement before the grid cell archetype is consulted.
/// Panics if `x` or `y` are out of bounds. /// Panics if `x` or `y` are out of bounds.
pub fn is_passable(&self, x: usize, y: usize) -> bool { pub fn is_passable(&self, x: usize, y: usize) -> bool {
// An object on this cell always blocks (until scripting overrides this). // An object on this cell blocks if it's unpassable
if self.object_at(x, y).is_some() { if let Some(obj) = self.object_at(x, y) && !obj.passable {
return false; false
} else {
self.get(x, y).1.behavior().passable
} }
self.get(x, y).1.behavior().passable
} }
/// Returns the index into [`Board::objects`] of the object at `(x, y)`, if any. /// Returns the index into [`Board::objects`] of the object at `(x, y)`, if any.
pub fn object_at(&self, x: usize, y: usize) -> Option<usize> { pub fn object_index_at(&self, x: usize, y: usize) -> Option<usize> {
self.objects.iter().position(|o| o.x == x && o.y == y) self.objects.iter().position(|o| o.x == x && o.y == y)
} }
/// Returns a borrow of the actual object at `(x, y)` if any
pub fn object_at(&self, x: usize, y: usize) -> Option<&ObjectDef> {
self.object_index_at(x, y).map(|idx| &self.objects[idx])
}
} }
/// Holds the active game board and provides game-logic operations. /// Holds the active game board and provides game-logic operations.
+12
View File
@@ -138,11 +138,19 @@ pub(crate) struct MapFileObjectEntry {
fg: String, fg: String,
/// Background color as an `"#RRGGBB"` hex string. /// Background color as an `"#RRGGBB"` hex string.
bg: String, bg: String,
/// Whether the object blocks player / object movement
#[serde(default)]
passable: bool,
/// Whether the object blocks player line of sight / FOV
#[serde(default = "default_true")]
opaque: bool,
/// Name of the Rhai script in the `[scripts]` table, if any. /// Name of the Rhai script in the `[scripts]` table, if any.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
script_name: Option<String>, script_name: Option<String>,
} }
fn default_true() -> bool { true }
/// Parses an `"#RRGGBB"` hex color string into an [`Rgba8`]. /// Parses an `"#RRGGBB"` hex color string into an [`Rgba8`].
/// Returns opaque black on any parse failure. /// Returns opaque black on any parse failure.
fn parse_color(hex: &str) -> Rgba8 { fn parse_color(hex: &str) -> Rgba8 {
@@ -256,6 +264,8 @@ impl TryFrom<MapFile> for Board {
fg: parse_color(&e.fg), fg: parse_color(&e.fg),
bg: parse_color(&e.bg), bg: parse_color(&e.bg),
}, },
passable: e.passable,
opaque: e.opaque,
script_name: e.script_name, script_name: e.script_name,
}) })
.collect(); .collect();
@@ -365,6 +375,8 @@ impl From<&Board> for MapFile {
tile: TileIndex::Num(o.glyph.tile), tile: TileIndex::Num(o.glyph.tile),
fg: color_to_hex(o.glyph.fg), fg: color_to_hex(o.glyph.fg),
bg: color_to_hex(o.glyph.bg), bg: color_to_hex(o.glyph.bg),
passable: o.passable,
opaque: o.opaque,
script_name: o.script_name.clone(), script_name: o.script_name.clone(),
}) })
.collect(); .collect();
+4
View File
@@ -180,6 +180,10 @@ pub(crate) fn show_editor_panel(
} }
let obj = &mut board.objects[i]; let obj = &mut board.objects[i];
ui.checkbox(&mut obj.passable, "Passable");
ui.checkbox(&mut obj.opaque, "Opaque");
let current = let current =
obj.script_name.as_deref().unwrap_or("(none)").to_string(); obj.script_name.as_deref().unwrap_or("(none)").to_string();
+8 -6
View File
@@ -10,7 +10,7 @@ mod render;
use editor::{EditorState, EditorTab, show_editor_panel}; use editor::{EditorState, EditorTab, show_editor_panel};
use font::BitmapFont; use font::BitmapFont;
use kiln_core::game::{Archetype, FontSpec, GameState, Glyph, ObjectDef}; use kiln_core::game::{FontSpec, GameState, Glyph, ObjectDef};
use kiln_core::map_file; use kiln_core::map_file;
use render::{ use render::{
DEFAULT_WINDOW_H, DEFAULT_WINDOW_W, MIN_WINDOW_H, MIN_WINDOW_W, board_origin, draw_board, DEFAULT_WINDOW_H, DEFAULT_WINDOW_W, MIN_WINDOW_H, MIN_WINDOW_W, board_origin, draw_board,
@@ -236,22 +236,24 @@ impl eframe::App for App {
if self.editor.tab == EditorTab::Objects { if self.editor.tab == EditorTab::Objects {
if self.editor.placing_object { if self.editor.placing_object {
// Placement mode: create a new object here if none exists. // Placement mode: create a new object here if none exists.
if board.object_at(cx, cy).is_none() { if board.object_index_at(cx, cy).is_none() {
board.objects.push(ObjectDef { board.objects.push(ObjectDef {
x: cx, x: cx,
y: cy, y: cy,
glyph: Glyph { glyph: Glyph {
tile: Archetype::Object.default_glyph().tile, tile: ObjectDef::default_glyph().tile,
fg: Archetype::Object.default_glyph().fg, fg: ObjectDef::default_glyph().fg,
bg: Archetype::Object.default_glyph().bg, bg: ObjectDef::default_glyph().bg,
}, },
passable: false,
opaque: true,
script_name: None, script_name: None,
}); });
} }
self.editor.placing_object = false; self.editor.placing_object = false;
} else { } else {
// Selection mode: click to select an existing object. // Selection mode: click to select an existing object.
let found = board.object_at(cx, cy); let found = board.object_index_at(cx, cy);
self.editor.selected_object = found; self.editor.selected_object = found;
self.editor.object_glyph_picker_open = false; self.editor.object_glyph_picker_open = false;
if let Some(i) = found { if let Some(i) = found {
+8
View File
@@ -37,3 +37,11 @@ content = """
# # # #
############################################################ ############################################################
""" """
[[objects]]
fg = "#aa3333"
bg = "#000000"
x = 10
y = 10
tile = "#"
passable = true