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
|
||||
/// clamped at 0). Zero time cost. Applied to `GameState::player_gems`.
|
||||
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
|
||||
/// things (e.g. gems) to despawn themselves from their `grab()` hook.
|
||||
Die,
|
||||
@@ -191,6 +197,11 @@ pub(crate) fn action_to_map(action: &Action) -> rhai::Map {
|
||||
m.insert("type".into(), ds("AddGems"));
|
||||
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 => {
|
||||
m.insert("type".into(), ds("Die"));
|
||||
}
|
||||
|
||||
@@ -110,6 +110,21 @@ builtins! {
|
||||
bg: Rgba8 { r: 0, g: 0, b: 0, a: 255 },
|
||||
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.
|
||||
|
||||
+82
-8
@@ -18,25 +18,43 @@ pub const SAY_DURATION: f64 = 3.0;
|
||||
/// paired with its [`Rgba8`] color for rendering.
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub struct Keyring {
|
||||
/// Blue key.
|
||||
pub blue: bool,
|
||||
/// Green key.
|
||||
pub green: bool,
|
||||
/// Cyan key.
|
||||
pub cyan: bool,
|
||||
/// Red key.
|
||||
pub red: bool,
|
||||
/// Purple key.
|
||||
pub purple: bool,
|
||||
/// Orange key (custom color, not in the standard EGA palette).
|
||||
pub orange: bool,
|
||||
/// Yellow key.
|
||||
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.
|
||||
pub white: bool,
|
||||
}
|
||||
|
||||
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.
|
||||
///
|
||||
/// 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
|
||||
// `self` after the board borrow drops.
|
||||
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 new_bubbles: Vec<SpeechBubble> = Vec::new();
|
||||
// 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.
|
||||
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.
|
||||
Action::Die => {
|
||||
board.remove_object(ba.source);
|
||||
@@ -383,6 +404,11 @@ impl GameState {
|
||||
if gem_delta != 0 {
|
||||
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 {
|
||||
self.scripts.run_bump(bumped, bumper);
|
||||
}
|
||||
@@ -762,4 +788,52 @@ mod tests {
|
||||
}
|
||||
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)`,
|
||||
//! `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)`,
|
||||
//! `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
|
||||
//!
|
||||
@@ -906,6 +906,15 @@ fn register_write_api(engine: &mut Engine, queues: &QueueMap) {
|
||||
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.
|
||||
let q = queues.clone();
|
||||
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