Skip to content

Commit

Permalink
Choices; Jump and End actions; And more
Browse files Browse the repository at this point in the history
  • Loading branch information
QueenOfSquiggles committed Jan 10, 2024
1 parent 1775e3a commit f3a3e11
Show file tree
Hide file tree
Showing 8 changed files with 312 additions and 70 deletions.
1 change: 1 addition & 0 deletions scenes/testing_scene.gd
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ extends Node

func _ready() -> void:
CoreDialog.load_track("res://Dialogic/example.json")
CoreDialog.blackboard_action("set shotgun true")
CoreDialog.event_bus.track_ended.connect(_on_track_end)

func _on_track_end() -> void:
Expand Down
59 changes: 52 additions & 7 deletions src/scene/dialog/core_dialog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use godot::{
use crate::util::SquigglesUtil;

use super::{
dialog_blackboard::Blackboard,
dialog_blackboard::{Blackboard, Entry},
dialog_events::DialogEvents,
dialog_gui::{DialogGUI, DialogSettings},
dialog_track::{DialogError, DialogTrack, Line},
Expand All @@ -19,12 +19,10 @@ use super::{
pub struct CoreDialog {
current_track: Option<DialogTrack>,
#[var]
current_line_index: i32,
#[var]
override_settings: Option<Gd<DialogSettings>>,
#[var]
event_bus: Option<Gd<DialogEvents>>,
gui: Option<Gd<DialogGUI>>,
pub event_bus: Option<Gd<DialogEvents>>,
pub gui: Option<Gd<DialogGUI>>,
pub blackboard: Blackboard,
#[base]
base: Base<Object>,
Expand Down Expand Up @@ -87,20 +85,30 @@ impl CoreDialog {
// create and add GUI
let mut gui = DialogGUI::new_alloc();

gui.bind_mut().track = Some(VecDeque::from_iter(track.lines.clone().into_iter()));
gui.bind_mut().track = Some(VecDeque::from_iter(track.lines.clone()));
SquigglesUtil::add_child_deferred(&mut root.upcast(), &gui.clone().upcast());
self.gui = Some(gui);
}

fn handle_dialog_error(err: DialogError) {
godot_error!("DialogError: {:#?}", err);
}
#[func]
pub fn make_choice_selection(&mut self, selection: i32) -> bool {
let Some(gui) = &mut self.gui else {
return false;
};
gui.bind_mut().make_dialog_choice(selection)
}

pub fn handle_line_action(&mut self, line: &Line) {
if self.event_bus.is_none() {
self.init_event_bus();
}
match line {
Line::Action { action } => self.blackboard.parse_action(action.clone()),
Line::Action { action } => {
self.blackboard_action(action.to_godot());
}
Line::Signal { name, args } => {
let Some(bus) = &mut self.event_bus else {
return;
Expand All @@ -125,6 +133,35 @@ impl CoreDialog {
#[func]
pub fn blackboard_action(&mut self, action: GString) {
self.blackboard.parse_action(action.to_string());
let Some((event_name, event_arg)) = self.blackboard.get_event() else {
return;
};
match event_name.as_str() {
// TODO handle events with pub const value
"end" => {
let Some(gui) = &mut self.gui else {
return;
};
gui.bind_mut().update_track(VecDeque::new());
}
"jump" => {
let Entry::Number(index) = event_arg else {
return;
};
let index = index.floor() as usize;
let Some(gui) = &mut self.gui else {
return;
};
let n_track = self.current_track.clone().unwrap();
let mut lines = VecDeque::from_iter(n_track.lines.iter().cloned());
for _ in 0..index {
let _ = lines.pop_front();
}
gui.bind_mut().update_track(lines);
}
_ => godot_error!("Unhandled internal event! event: \"{}\"", event_name),
}
self.blackboard.mark_event_handled();
}

#[func]
Expand All @@ -138,6 +175,14 @@ impl CoreDialog {
// self.blackboard.debug_print();
}

pub fn jump(&mut self, index: usize) {
godot_print!("Jumping to {}", index);
}

pub fn end(&mut self) {
godot_print!("Ending dialog");
}

pub fn singleton() -> Gd<CoreDialog> {
let Some(vol) = Engine::singleton().get_singleton(StringName::from(Self::SINGLETON_NAME))
else {
Expand Down
140 changes: 122 additions & 18 deletions src/scene/dialog/dialog_blackboard.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
use core::fmt;
use std::{
collections::{hash_map::DefaultHasher, HashMap},
collections::{hash_map::DefaultHasher, HashMap, VecDeque},
fmt::Display,
hash::{Hash, Hasher},
rc::Rc,
};

use godot::{engine::Json, prelude::*};

#[derive(Debug, PartialEq, Clone)]
enum Entry {
pub enum Entry {
Number(f32),
String(String),
Bool(bool),
None,
}
impl Default for Entry {
fn default() -> Self {
Self::None
}
}
impl Display for Entry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Expand All @@ -25,37 +31,81 @@ impl Display for Entry {
}
}

#[derive(Default)]
pub struct Blackboard {
entries: HashMap<String, Entry>,
commands: Vec<Command>,
}

impl Default for Blackboard {
fn default() -> Self {
let mut zelf = Self {
entries: HashMap::new(),
commands: Vec::new(),
};
zelf.commands.push(Command {
name: "set".into(),
args: 2,
callback: Rc::new(|bb, args| bb.set(args[0].as_str(), args[1].as_str())),
});
zelf.commands.push(Command {
name: "add".into(),
args: 2,
callback: Rc::new(|bb, args| bb.add(args[0].as_str(), args[1].as_str())),
});
zelf.commands.push(Command {
name: "sub".into(),
args: 2,
callback: Rc::new(|bb, args| bb.sub(args[0].as_str(), args[1].as_str())),
});
zelf.commands.push(Command {
name: "jump".into(),
args: 1,
callback: Rc::new(|bb, args| bb.jump(args[0].as_str())),
});
zelf.commands.push(Command {
name: "end".into(),
args: 0,
callback: Rc::new(|bb, _| bb.end()),
});
zelf
}
}

impl Blackboard {
const RECOGNIZED_COMMANDS: [&'static str; 3] = ["set", "add", "sub"];
/// Parses the action string
pub fn parse_action(&mut self, code: String) {
// godot_print!("Running action(s): {}", code);
for action in code.split(';') {
// godot_print!("Running sub-action: {}", action);
let parts = Vec::from_iter(action.trim().splitn(3, ' '));
if parts.len() != 3 {
godot_warn!("Improperly formed dialog code \"{}\". Ignoring", action);
continue;
let mut parts = VecDeque::from_iter(action.trim().split(' ').map(|dirty| dirty.trim()));
let command = parts.pop_front().unwrap_or("");
let mut callback: Option<_> = None;
for cmd in self.commands.clone().iter() {
if cmd.name == command {
if parts.len() == cmd.args {
callback = Some(cmd.callback.clone());
break;
} else {
godot_warn!(
"Command \"{}\" requires \"{}\" arguments. Found {}. Code: {}",
command,
cmd.args,
parts.len(),
action
);
}
}
}
let (command, key, value) = (parts[0].trim(), parts[1].trim(), parts[2].trim());
if !Self::RECOGNIZED_COMMANDS.contains(&command) {

if let Some(callable) = callback {
let parts = parts.iter().map(|v| v.to_string()).collect();
(callable)(self, parts);
} else {
godot_warn!(
"Unrecognized command! \"{}\" in line \"{}\"",
command,
action
);
continue;
}
match command {
"set" => self.set(key, value),
"add" => self.add(key, value),
"sub" => self.sub(key, value),
_ => unreachable!("If you're seeing this, you forgot to add a new command to the match statement. But I still love you XOXO"),
}
}
}
Expand Down Expand Up @@ -118,6 +168,10 @@ impl Blackboard {
// godot_print!("Set value: {} = {}. Enum value: {}", key, value, entry);
}

pub fn unset(&mut self, key: &str) {
let _ = self.entries.remove(&key.to_string());
}

pub fn add(&mut self, key: &str, value: &str) {
if !self.entries.contains_key(&key.to_string()) {
godot_warn!("Cannot add to \"\"! Does not exist yet!");
Expand Down Expand Up @@ -187,6 +241,44 @@ impl Blackboard {
self.entries.insert(key.to_string(), nval);
}

pub const EVENT_KEY: &'static str = "__event__";
pub const EVENT_ARG_KEY: &'static str = "__event_arg__";
fn set_event(&mut self, event_name: &str, arg: Option<&str>) {
self.entries.insert(
Self::EVENT_KEY.to_string(),
Entry::String(event_name.to_string()),
);
if let Some(arg) = arg {
self.entries
.insert(Self::EVENT_ARG_KEY.to_string(), Self::get_entry_for(arg));
}
}

pub fn jump(&mut self, target: &str) {
self.set_event("jump", Some(target));
}

pub fn end(&mut self) {
self.set_event("end", None);
}

pub fn get_event(&self) -> Option<(String, Entry)> {
if let Some(Entry::String(name)) = self.get(Self::EVENT_KEY) {
let arg = self.get(Self::EVENT_ARG_KEY);
return Some((name.clone(), arg.unwrap_or_default().clone()));
}
None
}

pub fn mark_event_handled(&mut self) {
if self.entries.contains_key(&Self::EVENT_KEY.to_string()) {
self.unset(Self::EVENT_KEY);
}
if self.entries.contains_key(&Self::EVENT_ARG_KEY.to_string()) {
self.unset(Self::EVENT_ARG_KEY);
}
}

fn get_entry_for(value: &str) -> Entry {
let var = Json::parse_string(value.to_godot());
match var.get_type() {
Expand Down Expand Up @@ -221,7 +313,7 @@ impl Blackboard {
}
};
match entry {
Entry::Number(val) => f32::floor(val * 100f32) as i32, // this does force accuracy to only 0.01, but then again, this system is not designed for
Entry::Number(val) => f32::floor(val) as i32, // this does force integer accuracy only, but then again, this system is not designed for complex maths
Entry::String(val) => {
let mut s = DefaultHasher::new();
val.hash(&mut s);
Expand All @@ -248,6 +340,10 @@ impl Blackboard {
}
}

pub fn get(&self, key: &str) -> Option<Entry> {
Some(self.entries.get(key)?.clone())
}

pub fn debug_print(&self) {
let mappings: Vec<String> = self
.entries
Expand All @@ -269,3 +365,11 @@ impl fmt::Debug for Blackboard {
f.debug_map().entries(self.entries.iter()).finish()
}
}
type CommandFunction = Rc<dyn Fn(&mut Blackboard, VecDeque<String>)>;

#[derive(Clone)]
struct Command {
name: String,
args: usize,
callback: CommandFunction,
}
Loading

0 comments on commit f3a3e11

Please sign in to comment.