Skip to content

Commit

Permalink
Merge pull request #46 from Lamby777/dialogue-manager
Browse files Browse the repository at this point in the history
Dialog box management
  • Loading branch information
Lamby777 authored Dec 1, 2023
2 parents 97ebf8c + d362900 commit e1d077d
Show file tree
Hide file tree
Showing 11 changed files with 278 additions and 105 deletions.
3 changes: 1 addition & 2 deletions pets-lib/dg/src/main.dg
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
% Main
###

Import ./rodrick.dg
Import rodrick.dg

###
---
Expand Down
35 changes: 35 additions & 0 deletions pets-lib/src/consts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//!
//! All the "important" constants for configuring
//! how the game works. Tinker all you want. Go nuts. :)
//!
pub mod playercb {
// Movement physics stuff
pub const ACCELERATION: f64 = 3000.0;
pub const FRICTION: f64 = 2500.0;
pub const MAX_SPEED: f64 = 320.0;

// Distance between party members
pub const PERSONAL_SPACE: u16 = 15;
}

pub mod dialogue {
use godot::engine::tween::TransitionType;

pub const NARRATOR_DISPLAYNAME: &str = "";
pub const UNKNOWN_DISPLAYNAME: &str = "???";
pub const DEFAULT_VOX: &str = "_";

pub const UI_LAYER_NAME: &str = "UILayer";
pub const DBOX_NODE_NAME: &str = "Dialog Box";
pub const DBOX_TWEEN_TIME: f64 = 0.5;
pub const DBOX_TWEEN_TRANS: TransitionType = TransitionType::TRANS_QUAD;
}

pub mod main_menu {
use godot::engine::tween::TransitionType;

pub const MENU_TWEEN_TIME: f64 = 0.1;
pub const MENU_TWEEN_TRANS: TransitionType = TransitionType::TRANS_QUAD;
pub const MENU_WAVE_BBCODE: &str = "[wave amp=100 freq=-6]";
}
74 changes: 50 additions & 24 deletions pets-lib/src/dialogue/autoload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
//! Singleton for accessing player stats in GDScript.
//!
use dialogical::Metaline::*;
use dialogical::Speaker::{self, *};
use dialogical::{Interaction, Page};
use godot::engine::Engine;
use godot::prelude::*;

use super::dbox::DialogBox;
use crate::consts::dialogue::*;
use crate::prelude::*;

/// Autoload class for easy management of dialog boxes
Expand All @@ -17,18 +21,6 @@ pub struct DBoxInterface {
dbox_scene: Gd<PackedScene>,
}

/// Show a dialog box with the given speaker and message
/// usage: `show_dialog!("Cherry", "Hello, {}!", name, ...)`
#[macro_export]
macro_rules! show_dialog {
($speaker:expr, $($t:tt)*) => {{
let msg = format!($($t)*);

let dbox = crate::dialogue::autoload::DBoxInterface::singleton();
dbox.bind().show_dialog($speaker.into(), msg.into());
}};
}

#[godot_api]
impl DBoxInterface {
/// Get a shared ref to the singleton to store in other node structs
Expand All @@ -40,21 +32,55 @@ impl DBoxInterface {
}

#[func]
pub fn show_dialog(&self, spk: GString, msg: GString) {
let mut dbox_gd = self.dbox_scene.instantiate_as::<DialogBox>();

dbox_gd.set_name("Dialog".into());
current_scene!()
.get_node("UILayer".into())
.expect("scene should have a UILayer")
.add_child(dbox_gd.clone().upcast());
pub fn start_ix(&mut self, ix_id: String) {
let ix = ix_map().get(&ix_id).unwrap_or_else(|| {
panic!(
"Could not find interaction \"{}\" in the interaction map",
ix_id
)
});

// simple stuff like this is why I love this language
let mut dbox = self.instantiate_dbox();
{
let mut dbox = dbox_gd.bind_mut();
dbox.set_txts(spk, msg);
let mut dbox = dbox.bind_mut();
dbox.set_ix(ix.clone());
dbox.do_draw();
dbox.pop_up()
dbox.tween_into_view(true);
}
}

#[func]
pub fn scene_has_active_dbox(&self) -> bool {
let ui_layer = current_scene!().get_node(UI_LAYER_NAME.into()).unwrap();
if let Some(dbox) = ui_layer.get_node(DBOX_NODE_NAME.into()) {
let dbox = dbox.cast::<DialogBox>();
let dbox = dbox.bind();
dbox.is_active()
} else {
false
}
}

#[func]
pub fn instantiate_dbox(&self) -> Gd<DialogBox> {
let mut ui_layer = current_scene!()
.get_node(UI_LAYER_NAME.into())
.expect("scene should have a UILayer");

// check if a box already exists
if ui_layer.has_node(DBOX_NODE_NAME.into()) {
let mut dbox = ui_layer
.get_node(DBOX_NODE_NAME.into())
.unwrap()
.cast::<DialogBox>();

dbox.bind_mut().cancel_tween();
dbox
} else {
let mut dbox = self.dbox_scene.instantiate_as::<DialogBox>();
dbox.set_name(DBOX_NODE_NAME.into());
ui_layer.add_child(dbox.clone().upcast());
dbox
}
}
}
Expand Down
168 changes: 147 additions & 21 deletions pets-lib/src/dialogue/dbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,67 @@
//! Dialog box class for menus and dialogue text
//!
use dialogical::Speaker::{self, *};
use dialogical::{Interaction, Page};
use dialogical::{Metaline, Metaline::*, PageMeta};

use godot::engine::tween::TransitionType;
use godot::engine::{IPanelContainer, PanelContainer, RichTextLabel};
use godot::engine::{IPanelContainer, InputEvent, PanelContainer, RichTextLabel, Tween, Viewport};
use godot::prelude::*;

const DBOX_TWEEN_TIME: f64 = 0.5;
const DBOX_TWEEN_TRANS: TransitionType = TransitionType::TRANS_QUAD;
use crate::consts::dialogue::*;
use crate::prelude::DBoxInterface;

/// Turn a Speaker into a displayable name
///
/// Either the name of the speaker or a special name
/// if it's a narrator or unknown speaker
pub fn spk_display(spk: &Speaker) -> String {
match spk {
Named(ref v) => v,
Narrator => NARRATOR_DISPLAYNAME,
Unknown => UNKNOWN_DISPLAYNAME,
}
.to_owned()
}

#[derive(Clone)]
pub struct MetaPair<T> {
pub temporary: T,
pub permanent: T,
}

impl<T> MetaPair<T> {
pub fn clone(v: T) -> Self
where
T: Clone,
{
Self {
temporary: v.clone(),
permanent: v.clone(),
}
}
}

#[derive(GodotClass)]
#[class(base=PanelContainer)]
pub struct DialogBox {
#[base]
node: Base<PanelContainer>,

// state for the current interaction
current_ix: Option<Interaction>,
current_page_number: usize,
speaker: MetaPair<Speaker>,
vox: MetaPair<String>,
tween: Option<Gd<Tween>>,
active: bool,

// independent from any interaction-related stuff,
// these are the actual strings that are displayed
//
// you can set these directly if you're doing something
// that's not part of an interaction
spk_txt: GString,
msg_txt: GString,
}
Expand All @@ -25,15 +74,61 @@ impl DialogBox {
self.node.get_node_as("VSplit/SpeakerName")
}

pub fn is_active(&self) -> bool {
self.active
}

/// Get the message text label
fn msg_txt(&self) -> Gd<RichTextLabel> {
self.node.get_node_as("VSplit/Content")
}

/// Sets the speaker and message text from strings
///
/// DON'T USE THIS FOR INTERACTIONS!!
/// That's what `goto_page` is for.
#[func]
pub fn set_txts(&mut self, speaker: GString, content: GString) {
self.spk_txt = speaker;
self.msg_txt = content;
pub fn set_txts(&mut self, speaker: String, content: String) {
self.spk_txt = speaker.into();
self.msg_txt = content.into();
}

pub fn set_ix(&mut self, ix: Interaction) {
self.current_ix = Some(ix);
self.goto_page(0);
}

/// basically set_txts but for an interaction page
pub fn goto_page(&mut self, pageno: usize) {
let ix = self.current_ix.as_ref().unwrap().clone();
let page = ix.pages.get(pageno).unwrap();

self.update_meta(&page.metadata);
let msg = page.content.clone();
let spk = spk_display(&self.speaker.temporary);
self.set_txts(spk, msg);
}

/// Updates the speaker and vox based on the given page metadata
pub fn update_meta(&mut self, meta: &PageMeta) {
Self::match_meta(&mut self.speaker, &meta.speaker);
Self::match_meta(&mut self.vox, &meta.vox);
}

/// helper method for `update_meta`
///
/// matches over a `Metaline` to update a field depending on
/// whether it's pageonly, permanent, or nochange
fn match_meta<'a, T: Clone>(field: &'a mut MetaPair<T>, meta_field: &'a Metaline<T>) {
field.temporary = match meta_field {
PageOnly(ref v) => v,
Permanent(ref v) => {
field.permanent = v.clone();
v
}
NoChange => &field.permanent,
}
.clone();
}

#[func]
Expand All @@ -43,25 +138,21 @@ impl DialogBox {
self.msg_txt().set_text(self.msg_txt.clone());
}

#[func]
pub fn pop_up(&mut self) {
self.tween_into_view(true);
}

#[func]
pub fn pop_down(&mut self) {
self.tween_into_view(false);
pub fn cancel_tween(&mut self) {
if let Some(tween) = &mut self.tween {
tween.stop()
}
}

fn tween_into_view(&mut self, up: bool) {
pub fn tween_into_view(&mut self, up: bool) -> Gd<Tween> {
let node = &mut self.node;
let viewport_y = node.get_viewport_rect().size.y;
let visible_y = viewport_y - node.get_size().y;

let (tw_start, tw_end) = if up {
(viewport_y, visible_y)
let tw_end = if up {
// visible y
viewport_y - node.get_size().y
} else {
(visible_y, viewport_y)
viewport_y
};

let mut y_tween = node.create_tween().unwrap();
Expand All @@ -73,9 +164,14 @@ impl DialogBox {
DBOX_TWEEN_TIME,
)
.unwrap()
.from(Variant::from(tw_start))
.from(Variant::from(self.node.get_position().y))
.unwrap()
.set_trans(DBOX_TWEEN_TRANS);
.set_trans(DBOX_TWEEN_TRANS)
.unwrap();

self.active = up;
self.tween = Some(y_tween.clone());
y_tween
}
}

Expand All @@ -86,10 +182,40 @@ impl IPanelContainer for DialogBox {
node,
spk_txt: "Cherry".into(),
msg_txt: "[wave amp=50 freq=6]Hello, World![/wave]".into(),

active: false,
tween: None,
current_ix: None,
current_page_number: 0,
speaker: MetaPair::clone(Speaker::Narrator),
vox: MetaPair::clone(DEFAULT_VOX.to_owned()),
}
}

fn ready(&mut self) {
self.do_draw();
}

fn input(&mut self, event: Gd<InputEvent>) {
if event.is_action_pressed("ui_accept".into()) {
if !self.active {
return;
}

// go to next page
let ix = self.current_ix.as_ref().unwrap();
self.current_page_number += 1;

if self.current_page_number >= ix.pages.len() {
self.tween_into_view(false);
self.current_page_number = 0;
} else {
self.goto_page(self.current_page_number);
self.do_draw();
}

// mark the input as handled
self.node.get_viewport().unwrap().set_input_as_handled();
}
}
}
2 changes: 1 addition & 1 deletion pets-lib/src/dialogue/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ macro_rules! packed_dialogue {
}

/// Load every interaction in the game from `packed.dgc`
pub fn ix_list() -> &'static InteractionMap {
pub fn ix_map() -> &'static InteractionMap {
INTERACTIONS.get_or_init(|| {
packed_dialogue!().expect(indoc! {"
Failed to load dialogues. If you are a player,
Expand Down
Loading

0 comments on commit e1d077d

Please sign in to comment.