1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
extern crate loe;

use std::io::Cursor;
use radix_fmt::radix_36;
use loe::{process, Config, TransformMode};

pub mod colour;
pub mod dialogue;
pub mod ending;
pub mod exit;
pub mod game;
pub mod image;
pub mod item;
pub mod mock;
pub mod palette;
pub mod position;
pub mod room;
pub mod sprite;
pub mod text;
pub mod tile;
pub mod variable;

use colour::Colour;
use dialogue::Dialogue;
use ending::Ending;
use exit::{Exit, Transition};
use game::{Game, Version};
use image::Image;
use item::Item;
use palette::Palette;
use position::Position;
use room::Room;
use sprite::Sprite;
use std::fmt::Display;
use text::{Font, TextDirection};
use tile::Tile;
use variable::Variable;

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Instance {
    position: Position,
    id: String, // item / ending.rs id
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ExitInstance {
    position: Position,
    exit: Exit,
    transition: Option<Transition>,
    dialogue_id: Option<String>,
}

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;

        for (i, frame) in self.into_iter().enumerate() {
            string.push_str(&frame.to_string());

            if i < last_frame {
                string.push_str(&"\n>\n".to_string());
            }
        }

        string
    }
}

#[inline]
fn from_base36(str: &str) -> u64 {
    u64::from_str_radix(str, 36).expect(&format!("Invalid base36 string: {}", str))
}

/// this doesn't work inside ToBase36 for some reason
#[inline]
fn to_base36(int: u64) -> String {
    format!("{}", radix_36(int))
}

pub trait ToBase36 {
    fn to_base36(&self) -> String;
}

impl ToBase36 for u64 {
    #[inline]
    fn to_base36(&self) -> String {
        to_base36(*self)
    }
}

/// e.g. `\nNAME DLG_0`
#[inline]
fn optional_data_line<T: Display>(label: &str, item: Option<T>) -> String {
    if item.is_some() {
        format!("\n{} {}", label, item.unwrap())
    } else {
        "".to_string()
    }
}

#[inline]
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()
}

#[inline]
fn segments_from_string(string: String) -> Vec<String> {
    // 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");

    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();

    for line in string.lines() {
        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
}

/// for some reason the sprites with numeric IDs go first,
/// then SPR A (avatar), then all the non-numeric IDs
#[inline]
fn is_string_numeric(str: String) -> bool {
    for c in str.chars() {
        if !c.is_numeric() {
            return false;
        }
    }

    return true;
}

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()
    }
}

#[cfg(test)]
mod test {
    use crate::{from_base36, ToBase36, optional_data_line, mock, segments_from_string, Quote, Unquote};

    #[test]
    fn test_from_base36() {
        assert_eq!(from_base36("0"), 0);
        assert_eq!(from_base36("0z"), 35);
        assert_eq!(from_base36("11"), 37);
    }

    #[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());
    }

    #[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);
    }

    #[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);
    }
}