keys 1
This commit is contained in:
@@ -79,6 +79,12 @@ pub(crate) enum Action {
|
|||||||
/// Add `n` to the player's gem count (negative subtracts; the count is
|
/// Add `n` to the player's gem count (negative subtracts; the count is
|
||||||
/// clamped at 0). Zero time cost. Applied to `GameState::player_gems`.
|
/// clamped at 0). Zero time cost. Applied to `GameState::player_gems`.
|
||||||
AddGems(i64),
|
AddGems(i64),
|
||||||
|
/// Give (`true`) or take (`false`) the named key color from the player.
|
||||||
|
///
|
||||||
|
/// Color must be one of `"blue"`, `"green"`, `"cyan"`, `"red"`, `"purple"`,
|
||||||
|
/// `"orange"`, `"yellow"`, `"white"`. An unrecognized name is logged and
|
||||||
|
/// ignored. Zero time cost. Applied to `GameState::player_keys`.
|
||||||
|
SetKey(String, bool),
|
||||||
/// Remove the source object from the board. Zero time cost. Used by grab
|
/// Remove the source object from the board. Zero time cost. Used by grab
|
||||||
/// things (e.g. gems) to despawn themselves from their `grab()` hook.
|
/// things (e.g. gems) to despawn themselves from their `grab()` hook.
|
||||||
Die,
|
Die,
|
||||||
@@ -191,6 +197,11 @@ pub(crate) fn action_to_map(action: &Action) -> rhai::Map {
|
|||||||
m.insert("type".into(), ds("AddGems"));
|
m.insert("type".into(), ds("AddGems"));
|
||||||
m.insert("n".into(), Dynamic::from(*n));
|
m.insert("n".into(), Dynamic::from(*n));
|
||||||
}
|
}
|
||||||
|
Action::SetKey(color, present) => {
|
||||||
|
m.insert("type".into(), ds("SetKey"));
|
||||||
|
m.insert("color".into(), Dynamic::from(color.clone()));
|
||||||
|
m.insert("present".into(), Dynamic::from(*present));
|
||||||
|
}
|
||||||
Action::Die => {
|
Action::Die => {
|
||||||
m.insert("type".into(), ds("Die"));
|
m.insert("type".into(), ds("Die"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,6 +110,21 @@ builtins! {
|
|||||||
bg: Rgba8 { r: 0, g: 0, b: 0, a: 255 },
|
bg: Rgba8 { r: 0, g: 0, b: 0, a: 255 },
|
||||||
script: include_str!("scripts/spinner.rhai"),
|
script: include_str!("scripts/spinner.rhai"),
|
||||||
},
|
},
|
||||||
|
Key => [
|
||||||
|
"key_red" => 12,
|
||||||
|
"key_orange" => 12,
|
||||||
|
"key_yellow" => 12,
|
||||||
|
"key_green" => 12,
|
||||||
|
"key_blue" => 12,
|
||||||
|
"key_cyan" => 12,
|
||||||
|
"key_purple" => 12,
|
||||||
|
"key_white" => 12,
|
||||||
|
] {
|
||||||
|
behavior: Behavior { solid: true, opaque: false, pushable: Pushable::Any, grab: true },
|
||||||
|
fg: Rgba8 { r: 0xAA, g: 0xAA, b: 0xAA, a: 255 },
|
||||||
|
bg: Rgba8 { r: 0, g: 0, b: 0, a: 255 },
|
||||||
|
script: include_str!("scripts/key.rhai"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A class of board cell, encoding its default behavior and appearance.
|
/// A class of board cell, encoding its default behavior and appearance.
|
||||||
|
|||||||
+82
-8
@@ -18,25 +18,43 @@ pub const SAY_DURATION: f64 = 3.0;
|
|||||||
/// paired with its [`Rgba8`] color for rendering.
|
/// paired with its [`Rgba8`] color for rendering.
|
||||||
#[derive(Copy, Clone, Default)]
|
#[derive(Copy, Clone, Default)]
|
||||||
pub struct Keyring {
|
pub struct Keyring {
|
||||||
/// Blue key.
|
|
||||||
pub blue: bool,
|
|
||||||
/// Green key.
|
|
||||||
pub green: bool,
|
|
||||||
/// Cyan key.
|
|
||||||
pub cyan: bool,
|
|
||||||
/// Red key.
|
/// Red key.
|
||||||
pub red: bool,
|
pub red: bool,
|
||||||
/// Purple key.
|
|
||||||
pub purple: bool,
|
|
||||||
/// Orange key (custom color, not in the standard EGA palette).
|
/// Orange key (custom color, not in the standard EGA palette).
|
||||||
pub orange: bool,
|
pub orange: bool,
|
||||||
/// Yellow key.
|
/// Yellow key.
|
||||||
pub yellow: bool,
|
pub yellow: bool,
|
||||||
|
/// Green key.
|
||||||
|
pub green: bool,
|
||||||
|
/// Blue key.
|
||||||
|
pub blue: bool,
|
||||||
|
/// Cyan key.
|
||||||
|
pub cyan: bool,
|
||||||
|
/// Purple key.
|
||||||
|
pub purple: bool,
|
||||||
/// White key.
|
/// White key.
|
||||||
pub white: bool,
|
pub white: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Keyring {
|
impl Keyring {
|
||||||
|
/// Sets the named key slot to `value`. Returns `false` if `name` is not a
|
||||||
|
/// recognized color (`"blue"`, `"green"`, `"cyan"`, `"red"`, `"purple"`,
|
||||||
|
/// `"orange"`, `"yellow"`, `"white"`).
|
||||||
|
pub fn set_by_name(&mut self, name: &str, value: bool) -> bool {
|
||||||
|
match name {
|
||||||
|
"blue" => self.blue = value,
|
||||||
|
"green" => self.green = value,
|
||||||
|
"cyan" => self.cyan = value,
|
||||||
|
"red" => self.red = value,
|
||||||
|
"purple" => self.purple = value,
|
||||||
|
"orange" => self.orange = value,
|
||||||
|
"yellow" => self.yellow = value,
|
||||||
|
"white" => self.white = value,
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns all eight keys in display order, each paired with its color.
|
/// Returns all eight keys in display order, each paired with its color.
|
||||||
///
|
///
|
||||||
/// Order: blue, green, cyan, red, purple, orange, yellow, white.
|
/// Order: blue, green, cyan, red, purple, orange, yellow, white.
|
||||||
@@ -261,6 +279,7 @@ impl GameState {
|
|||||||
// Net change to the player's gem count from AddGems actions; applied to
|
// Net change to the player's gem count from AddGems actions; applied to
|
||||||
// `self` after the board borrow drops.
|
// `self` after the board borrow drops.
|
||||||
let mut gem_delta: i64 = 0;
|
let mut gem_delta: i64 = 0;
|
||||||
|
let mut key_changes: Vec<(String, bool)> = Vec::new();
|
||||||
let mut sends: Vec<(ObjectId, String, Option<ScriptArg>)> = Vec::new();
|
let mut sends: Vec<(ObjectId, String, Option<ScriptArg>)> = Vec::new();
|
||||||
let mut new_bubbles: Vec<SpeechBubble> = Vec::new();
|
let mut new_bubbles: Vec<SpeechBubble> = Vec::new();
|
||||||
// Collected outside the board borrow so we can assign to self.active_scroll.
|
// Collected outside the board borrow so we can assign to self.active_scroll.
|
||||||
@@ -362,6 +381,8 @@ impl GameState {
|
|||||||
}
|
}
|
||||||
// Accumulated and applied to `self.player_gems` after the borrow drops.
|
// Accumulated and applied to `self.player_gems` after the borrow drops.
|
||||||
Action::AddGems(n) => gem_delta += n,
|
Action::AddGems(n) => gem_delta += n,
|
||||||
|
// Collected and applied to `self.player_keys` after the borrow drops.
|
||||||
|
Action::SetKey(color, present) => key_changes.push((color, present)),
|
||||||
// A grab thing despawns itself from its grab() hook.
|
// A grab thing despawns itself from its grab() hook.
|
||||||
Action::Die => {
|
Action::Die => {
|
||||||
board.remove_object(ba.source);
|
board.remove_object(ba.source);
|
||||||
@@ -383,6 +404,11 @@ impl GameState {
|
|||||||
if gem_delta != 0 {
|
if gem_delta != 0 {
|
||||||
self.player_gems = (self.player_gems as i64 + gem_delta).max(0) as u32;
|
self.player_gems = (self.player_gems as i64 + gem_delta).max(0) as u32;
|
||||||
}
|
}
|
||||||
|
for (color, present) in key_changes {
|
||||||
|
if !self.player_keys.set_by_name(&color, present) {
|
||||||
|
self.log.push(LogLine::error(format!("set_key: unknown color {color:?}")));
|
||||||
|
}
|
||||||
|
}
|
||||||
for (bumped, bumper) in bumps {
|
for (bumped, bumper) in bumps {
|
||||||
self.scripts.run_bump(bumped, bumper);
|
self.scripts.run_bump(bumped, bumper);
|
||||||
}
|
}
|
||||||
@@ -762,4 +788,52 @@ mod tests {
|
|||||||
}
|
}
|
||||||
assert_eq!(seq, vec![47, 0xC4, 92, 0xB3, 47]);
|
assert_eq!(seq, vec![47, 0xC4, 92, 0xB3, 47]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_key_gives_and_takes_keys() {
|
||||||
|
let mut sobj = ObjectDef::new(0, 0);
|
||||||
|
sobj.solid = false;
|
||||||
|
sobj.script_name = Some("s".to_string());
|
||||||
|
let board = open_board(2, 1, (1, 0), vec![sobj]);
|
||||||
|
let scripts = HashMap::from([(
|
||||||
|
"s".to_string(),
|
||||||
|
"fn init() { set_key(\"blue\", true); set_key(\"red\", true); }".to_string(),
|
||||||
|
)]);
|
||||||
|
let mut game = GameState::with_scripts(board, scripts);
|
||||||
|
game.run_init();
|
||||||
|
|
||||||
|
assert!(game.player_keys.blue);
|
||||||
|
assert!(game.player_keys.red);
|
||||||
|
assert!(!game.player_keys.cyan); // cyan was not set by the script
|
||||||
|
|
||||||
|
// A second script can take a key.
|
||||||
|
let mut sobj2 = ObjectDef::new(0, 0);
|
||||||
|
sobj2.solid = false;
|
||||||
|
sobj2.script_name = Some("t".to_string());
|
||||||
|
let board2 = open_board(2, 1, (1, 0), vec![sobj2]);
|
||||||
|
let scripts2 = HashMap::from([(
|
||||||
|
"t".to_string(),
|
||||||
|
"fn init() { set_key(\"blue\", true); set_key(\"blue\", false); }".to_string(),
|
||||||
|
)]);
|
||||||
|
let mut game2 = GameState::with_scripts(board2, scripts2);
|
||||||
|
game2.run_init();
|
||||||
|
|
||||||
|
assert!(!game2.player_keys.blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_key_unknown_color_logs_error() {
|
||||||
|
let mut sobj = ObjectDef::new(0, 0);
|
||||||
|
sobj.solid = false;
|
||||||
|
sobj.script_name = Some("s".to_string());
|
||||||
|
let board = open_board(2, 1, (1, 0), vec![sobj]);
|
||||||
|
let scripts = HashMap::from([(
|
||||||
|
"s".to_string(),
|
||||||
|
r#"fn init() { set_key("chartreuse", true); }"#.to_string(),
|
||||||
|
)]);
|
||||||
|
let mut game = GameState::with_scripts(board, scripts);
|
||||||
|
game.run_init();
|
||||||
|
|
||||||
|
assert!(game.log.iter().any(|l| l.spans.iter().any(|s| s.text.contains("set_key"))));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-1
@@ -59,7 +59,7 @@
|
|||||||
//! `move(dir)`, `delay(secs)`, `now()`, `set_tile(n)`, `log(msg)`, `say(msg)`,
|
//! `move(dir)`, `delay(secs)`, `now()`, `set_tile(n)`, `log(msg)`, `say(msg)`,
|
||||||
//! `set_fg(fg)`, `set_bg(bg)`, `set_color(fg, bg)`, `set_tag(target, tag, present)`,
|
//! `set_fg(fg)`, `set_bg(bg)`, `set_color(fg, bg)`, `set_tag(target, tag, present)`,
|
||||||
//! `send(target_id, fn_name [, arg])`, `teleport(x, y)`, `push(x, y, dir)`,
|
//! `send(target_id, fn_name [, arg])`, `teleport(x, y)`, `push(x, y, dir)`,
|
||||||
//! `swap([[src_x, src_y, dst_x, dst_y], …])`, `add_gems(n)`, `die()`
|
//! `swap([[src_x, src_y, dst_x, dst_y], …])`, `add_gems(n)`, `set_key(color, present)`, `die()`
|
||||||
//!
|
//!
|
||||||
//! ## Queue API
|
//! ## Queue API
|
||||||
//!
|
//!
|
||||||
@@ -906,6 +906,15 @@ fn register_write_api(engine: &mut Engine, queues: &QueueMap) {
|
|||||||
emit(&q, source_of(&ctx), Action::AddGems(n));
|
emit(&q, source_of(&ctx), Action::AddGems(n));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// set_key(color, present): give (true) or take (false) the named key color.
|
||||||
|
let q = queues.clone();
|
||||||
|
engine.register_fn(
|
||||||
|
"set_key",
|
||||||
|
move |ctx: NativeCallContext, color: ImmutableString, present: bool| {
|
||||||
|
emit(&q, source_of(&ctx), Action::SetKey(color.into(), present));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// die(): remove the calling object from the board.
|
// die(): remove the calling object from the board.
|
||||||
let q = queues.clone();
|
let q = queues.clone();
|
||||||
engine.register_fn("die", move |ctx: NativeCallContext| {
|
engine.register_fn("die", move |ctx: NativeCallContext| {
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Built-in script for the `gem` archetype (see kiln-core/src/builtin_scripts.rs).
|
||||||
|
//
|
||||||
|
// A gem is a grabbable collectible: walking onto it (or pushing it into the
|
||||||
|
// player) fires this `grab()` hook instead of blocking. We bump the player's gem
|
||||||
|
// count and remove ourselves from the board.
|
||||||
|
fn grab() {
|
||||||
|
let colors = [
|
||||||
|
"red",
|
||||||
|
"orange",
|
||||||
|
"yellow",
|
||||||
|
"green",
|
||||||
|
"blue",
|
||||||
|
"cyan",
|
||||||
|
"purple",
|
||||||
|
"white"
|
||||||
|
];
|
||||||
|
|
||||||
|
for c in colors {
|
||||||
|
if Me.has_tag(`BUILTIN_key_${c}`) {
|
||||||
|
set_key(c, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
die();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user