2020-04-24 17:06:17 +00:00
|
|
|
extern crate loe;
|
|
|
|
|
|
|
|
use std::io::Cursor;
|
2020-04-13 12:30:08 +00:00
|
|
|
use radix_fmt::radix_36;
|
2020-04-24 17:06:17 +00:00
|
|
|
use loe::{process, Config, TransformMode};
|
2020-04-13 12:30:08 +00:00
|
|
|
|
2020-04-12 11:22:20 +00:00
|
|
|
pub mod colour;
|
2020-04-12 12:26:33 +00:00
|
|
|
pub mod dialogue;
|
2020-04-12 12:52:36 +00:00
|
|
|
pub mod ending;
|
2020-04-12 12:49:19 +00:00
|
|
|
pub mod exit;
|
2020-04-12 13:38:07 +00:00
|
|
|
pub mod game;
|
2020-04-12 11:48:07 +00:00
|
|
|
pub mod image;
|
2020-04-12 12:46:55 +00:00
|
|
|
pub mod item;
|
2020-04-12 12:50:07 +00:00
|
|
|
pub mod mock;
|
2020-04-18 15:58:30 +00:00
|
|
|
pub mod palette;
|
2020-04-12 12:21:27 +00:00
|
|
|
pub mod position;
|
2020-04-12 13:28:11 +00:00
|
|
|
pub mod room;
|
2020-04-12 12:35:17 +00:00
|
|
|
pub mod sprite;
|
2020-04-18 15:58:30 +00:00
|
|
|
pub mod text;
|
2020-04-12 11:53:11 +00:00
|
|
|
pub mod tile;
|
2020-04-12 13:20:53 +00:00
|
|
|
pub mod variable;
|
2020-04-12 11:22:20 +00:00
|
|
|
|
2020-06-23 12:11:20 +00:00
|
|
|
// pub mod test_omnibus;
|
2020-06-18 19:32:11 +00:00
|
|
|
|
2020-04-12 11:35:39 +00:00
|
|
|
use colour::Colour;
|
2020-04-12 12:26:33 +00:00
|
|
|
use dialogue::Dialogue;
|
2020-04-12 12:52:36 +00:00
|
|
|
use ending::Ending;
|
2020-04-18 15:58:30 +00:00
|
|
|
use exit::{Exit, Transition};
|
2020-04-18 10:03:24 +00:00
|
|
|
use game::{Game, Version};
|
2020-04-12 11:48:07 +00:00
|
|
|
use image::Image;
|
2020-04-12 12:46:55 +00:00
|
|
|
use item::Item;
|
2020-04-18 15:58:30 +00:00
|
|
|
use palette::Palette;
|
2020-04-12 12:21:27 +00:00
|
|
|
use position::Position;
|
2020-04-12 13:28:11 +00:00
|
|
|
use room::Room;
|
2020-04-12 12:35:17 +00:00
|
|
|
use sprite::Sprite;
|
2020-04-18 15:58:30 +00:00
|
|
|
use std::fmt::Display;
|
|
|
|
use text::{Font, TextDirection};
|
2020-04-12 11:53:11 +00:00
|
|
|
use tile::Tile;
|
2020-04-12 13:20:53 +00:00
|
|
|
use variable::Variable;
|
2020-06-27 18:28:17 +00:00
|
|
|
use std::num::ParseIntError;
|
2020-04-05 17:58:04 +00:00
|
|
|
|
2020-05-31 15:12:23 +00:00
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
2020-04-12 11:53:11 +00:00
|
|
|
pub struct Instance {
|
2020-04-10 15:27:23 +00:00
|
|
|
position: Position,
|
2020-06-27 18:28:17 +00:00
|
|
|
id: String, // item / ending id
|
2020-04-10 15:27:23 +00:00
|
|
|
}
|
|
|
|
|
2020-05-31 15:12:23 +00:00
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
2020-04-12 11:53:11 +00:00
|
|
|
pub struct ExitInstance {
|
2020-04-10 15:27:23 +00:00
|
|
|
position: Position,
|
|
|
|
exit: Exit,
|
2020-04-28 07:34:35 +00:00
|
|
|
transition: Option<Transition>,
|
|
|
|
dialogue_id: Option<String>,
|
2020-04-10 15:27:23 +00:00
|
|
|
}
|
|
|
|
|
2020-04-12 10:22:14 +00:00
|
|
|
pub trait AnimationFrames {
|
|
|
|
fn to_string(&self) -> String;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl AnimationFrames for Vec<Image> {
|
|
|
|
#[inline]
|
|
|
|
fn to_string(&self) -> String {
|
|
|
|
let mut string = String::new();
|
|
|
|
let last_frame = self.len() - 1;
|
2020-04-06 06:23:20 +00:00
|
|
|
|
2020-04-12 10:22:14 +00:00
|
|
|
for (i, frame) in self.into_iter().enumerate() {
|
|
|
|
string.push_str(&frame.to_string());
|
2020-04-06 06:23:20 +00:00
|
|
|
|
2020-04-12 10:22:14 +00:00
|
|
|
if i < last_frame {
|
|
|
|
string.push_str(&"\n>\n".to_string());
|
|
|
|
}
|
2020-04-06 06:23:20 +00:00
|
|
|
}
|
|
|
|
|
2020-04-12 10:22:14 +00:00
|
|
|
string
|
|
|
|
}
|
2020-04-06 06:23:20 +00:00
|
|
|
}
|
2020-04-13 12:30:08 +00:00
|
|
|
|
2020-04-28 17:00:31 +00:00
|
|
|
#[inline]
|
2020-06-27 18:28:17 +00:00
|
|
|
fn from_base36(str: &str) -> Result<u64, ParseIntError> {
|
|
|
|
u64::from_str_radix(str, 36)
|
2020-04-13 12:30:08 +00:00
|
|
|
}
|
|
|
|
|
2020-04-13 13:43:23 +00:00
|
|
|
/// this doesn't work inside ToBase36 for some reason
|
2020-04-28 17:00:31 +00:00
|
|
|
#[inline]
|
2020-04-13 13:43:23 +00:00
|
|
|
fn to_base36(int: u64) -> String {
|
|
|
|
format!("{}", radix_36(int))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub trait ToBase36 {
|
|
|
|
fn to_base36(&self) -> String;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ToBase36 for u64 {
|
2020-04-28 17:00:31 +00:00
|
|
|
#[inline]
|
2020-04-13 13:43:23 +00:00
|
|
|
fn to_base36(&self) -> String {
|
|
|
|
to_base36(*self)
|
|
|
|
}
|
2020-04-13 12:30:08 +00:00
|
|
|
}
|
|
|
|
|
2020-04-13 18:08:21 +00:00
|
|
|
/// e.g. `\nNAME DLG_0`
|
2020-04-28 17:00:31 +00:00
|
|
|
#[inline]
|
2020-04-13 18:08:21 +00:00
|
|
|
fn optional_data_line<T: Display>(label: &str, item: Option<T>) -> String {
|
|
|
|
if item.is_some() {
|
|
|
|
format!("\n{} {}", label, item.unwrap())
|
|
|
|
} else {
|
|
|
|
"".to_string()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-28 17:00:31 +00:00
|
|
|
#[inline]
|
2020-04-24 17:06:17 +00:00
|
|
|
fn transform_line_endings(input: String, mode: TransformMode) -> String {
|
|
|
|
let mut input = Cursor::new(input);
|
|
|
|
let mut output = Cursor::new(Vec::new());
|
|
|
|
|
|
|
|
process(&mut input, &mut output, Config::default().transform(mode)).unwrap();
|
|
|
|
String::from_utf8(output.into_inner()).unwrap()
|
|
|
|
}
|
|
|
|
|
2020-04-28 17:00:31 +00:00
|
|
|
#[inline]
|
2020-04-24 17:52:27 +00:00
|
|
|
fn segments_from_string(string: String) -> Vec<String> {
|
2020-04-30 19:18:15 +00:00
|
|
|
// this is pretty weird but a dialogue can just have an empty line followed by a name
|
|
|
|
// however, on entering two empty lines, dialogue will be wrapped in triple quotation marks
|
|
|
|
// so, handle this here
|
|
|
|
let string = string.replace("\n\nNAME", "\n\"\"\"\n\"\"\"\nNAME");
|
|
|
|
|
2020-04-24 17:52:27 +00:00
|
|
|
let mut output:Vec<String> = Vec::new();
|
|
|
|
// are we inside `"""\n...\n"""`? if so, ignore empty lines
|
|
|
|
let mut inside_escaped_block = false;
|
|
|
|
let mut current_segment : Vec<String> = Vec::new();
|
|
|
|
|
2020-04-30 19:18:37 +00:00
|
|
|
for line in string.lines() {
|
2020-04-24 17:52:27 +00:00
|
|
|
if line == "\"\"\"" {
|
|
|
|
inside_escaped_block = ! inside_escaped_block;
|
|
|
|
}
|
|
|
|
|
|
|
|
if line == "" && !inside_escaped_block {
|
|
|
|
output.push(current_segment.join("\n"));
|
|
|
|
current_segment = Vec::new();
|
|
|
|
} else {
|
|
|
|
current_segment.push(line.to_string());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
output.push(current_segment.join("\n"));
|
|
|
|
|
|
|
|
output
|
|
|
|
}
|
|
|
|
|
2020-06-24 17:36:28 +00:00
|
|
|
/// tries to use an existing ID - if it is already in use, generate a new one
|
|
|
|
/// then return the ID (either original or new)
|
|
|
|
/// todo refactor (unnecessary clones etc.)
|
|
|
|
fn try_id(ids: Vec<String>, id: String) -> String {
|
|
|
|
let id = id.clone();
|
|
|
|
let ids = ids.clone();
|
|
|
|
if is_id_available(&ids, &id) {
|
|
|
|
id
|
|
|
|
} else {
|
|
|
|
new_unique_id(ids)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_id_available(ids: &Vec<String>, id: &String) -> bool {
|
|
|
|
! ids.contains(id)
|
|
|
|
}
|
|
|
|
|
2020-06-18 12:56:50 +00:00
|
|
|
/// e.g. pass all tile IDs into this to get a new non-conflicting tile ID
|
|
|
|
#[inline]
|
2020-06-28 08:19:55 +00:00
|
|
|
fn new_unique_id(ids: Vec<String>) -> String {
|
2020-06-23 15:14:22 +00:00
|
|
|
let mut new_id: u64 = 0;
|
|
|
|
|
2020-06-27 19:22:15 +00:00
|
|
|
while ids.contains(&new_id.to_base36()) {
|
|
|
|
new_id += 1;
|
2020-06-23 15:14:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return to_base36(new_id);
|
2020-06-18 12:56:50 +00:00
|
|
|
}
|
|
|
|
|
2020-04-28 18:30:18 +00:00
|
|
|
pub trait Quote {
|
|
|
|
fn quote(&self) -> String;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Quote for String {
|
|
|
|
#[inline]
|
|
|
|
fn quote(&self) -> String {
|
|
|
|
format!("\"\"\"\n{}\n\"\"\"", self).to_string()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub trait Unquote {
|
|
|
|
fn unquote(&self) -> String;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Unquote for String {
|
|
|
|
#[inline]
|
|
|
|
fn unquote(&self) -> String {
|
|
|
|
self.trim_matches('\"').trim_matches('\n').to_string()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-19 07:13:55 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
2020-06-24 17:36:28 +00:00
|
|
|
use crate::{from_base36, ToBase36, optional_data_line, mock, segments_from_string, Quote, Unquote, new_unique_id, try_id};
|
2020-04-19 07:13:55 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_from_base36() {
|
2020-06-27 18:28:17 +00:00
|
|
|
assert_eq!(from_base36("0").unwrap(), 0);
|
|
|
|
assert_eq!(from_base36("0z").unwrap(), 35);
|
|
|
|
assert_eq!(from_base36("11").unwrap(), 37);
|
2020-04-19 07:13:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_to_base36() {
|
|
|
|
assert_eq!((37 as u64).to_base36(), "11");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_optional_data_line() {
|
|
|
|
let output = optional_data_line("NAME", mock::item().name);
|
|
|
|
assert_eq!(output, "\nNAME door".to_string());
|
|
|
|
}
|
2020-04-24 17:52:27 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_string_to_segments() {
|
|
|
|
let output = segments_from_string(
|
|
|
|
include_str!("./test-resources/segments").to_string()
|
|
|
|
);
|
|
|
|
|
|
|
|
let expected = vec![
|
|
|
|
"\"\"\"\nthe first segment is a long bit of text\n\n\nit contains empty lines\n\n\"\"\"".to_string(),
|
|
|
|
"this is a new segment\nthis is still the second segment\nblah\nblah".to_string(),
|
|
|
|
"DLG SEGMENT_3\n\"\"\"\nthis is a short \"long\" bit of text\n\"\"\"".to_string(),
|
|
|
|
"this is the last segment".to_string(),
|
|
|
|
];
|
|
|
|
|
|
|
|
assert_eq!(output, expected);
|
|
|
|
}
|
2020-04-30 17:54:28 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_quote() {
|
|
|
|
let output = "this is a string.\nIt has 2 lines".to_string().quote();
|
|
|
|
let expected = "\"\"\"\nthis is a string.\nIt has 2 lines\n\"\"\"".to_string();
|
|
|
|
assert_eq!(output, expected);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_unquote() {
|
|
|
|
let output = "\"\"\"\nwho the fuck is scraeming \"LOG OFF\" at my house.\nshow yourself, coward.\ni will never log off\n\"\"\"".to_string().unquote();
|
|
|
|
let expected = "who the fuck is scraeming \"LOG OFF\" at my house.\nshow yourself, coward.\ni will never log off".to_string();
|
|
|
|
assert_eq!(output, expected);
|
|
|
|
}
|
2020-06-23 15:23:24 +00:00
|
|
|
|
2020-06-24 17:36:28 +00:00
|
|
|
#[test]
|
|
|
|
fn test_try_id() {
|
|
|
|
// does a conflict generate a new ID?
|
|
|
|
assert_eq!(
|
|
|
|
try_id(vec!["0".to_string(), "1".to_string()], "1".to_string()),
|
|
|
|
"2".to_string()
|
|
|
|
);
|
|
|
|
// with no conflict, does the ID remain the same?
|
|
|
|
assert_eq!(
|
|
|
|
try_id(vec!["0".to_string(), "1".to_string()], "3".to_string()),
|
|
|
|
"3".to_string()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-06-23 15:23:24 +00:00
|
|
|
#[test]
|
|
|
|
fn test_new_unique_id() {
|
2020-06-23 15:36:33 +00:00
|
|
|
// start
|
|
|
|
assert_eq!(new_unique_id(vec!["1".to_string(), "z".to_string()]), "0".to_string());
|
|
|
|
// middle
|
2020-06-23 15:23:24 +00:00
|
|
|
assert_eq!(new_unique_id(vec!["0".to_string(), "2".to_string()]), "1".to_string());
|
2020-06-23 15:36:33 +00:00
|
|
|
// end
|
2020-06-23 15:23:24 +00:00
|
|
|
assert_eq!(new_unique_id(vec!["0".to_string(), "1".to_string()]), "2".to_string());
|
2020-06-24 14:36:39 +00:00
|
|
|
// check sorting
|
|
|
|
assert_eq!(new_unique_id(vec!["1".to_string(), "0".to_string()]), "2".to_string());
|
2020-06-23 15:36:33 +00:00
|
|
|
// check deduplication
|
2020-06-24 14:15:13 +00:00
|
|
|
assert_eq!(
|
|
|
|
new_unique_id(vec!["0".to_string(), "0".to_string(), "1".to_string()]),
|
|
|
|
"2".to_string()
|
|
|
|
);
|
2020-06-23 15:23:24 +00:00
|
|
|
}
|
2020-04-13 18:08:21 +00:00
|
|
|
}
|