reimplement avatar as a plain sprite; handle sprite items as these are mostly needed for avatar

This commit is contained in:
Max Bradbury 2020-04-26 13:31:20 +01:00
parent ed6552f39c
commit cf1ca6f1b5
6 changed files with 40 additions and 135 deletions

View File

@ -66,8 +66,7 @@ some more practical uses would be things like:
* "unquote" function for dialogues, game title, anything else that supports """
* add Bitsy HD and Bitsy 7.0 test data
* fix variables/endings becoming "DLG DLG"?
* ITM in avatars? are these starting items?
* make the avatar a sprite - they're not fundamentally different according to Bitsy
* dedupe "animation frames from string" functionality
### tidy up

View File

@ -1,118 +0,0 @@
use crate::{from_base36, optional_data_line, AnimationFrames, Image, Position, ToBase36, is_string_numeric};
use std::error::Error;
/// avatar is a "sprite" in the game data but with a specific id
#[derive(Debug, Eq, PartialEq)]
pub struct Avatar {
pub animation_frames: Vec<Image>,
pub name: Option<String>,
pub room_id: u64,
pub position: Position,
pub colour_id: Option<u64>,
}
impl Avatar {
fn room_position_line(&self) -> String {
format!(
"\nPOS {} {}",
self.room_id.to_base36(),
self.position.to_string()
)
}
fn colour_line(&self) -> String {
optional_data_line("COL", self.colour_id.as_ref())
}
}
impl Avatar {
pub(crate) fn from(string: String) -> Result<Avatar, &'static dyn Error> {
let string = string.replace("SPR A\n", "");
let mut lines: Vec<&str> = string.lines().collect();
let mut name: Option<String> = None;
let mut room_id: Option<u64> = None;
let mut position: Option<Position> = None;
let mut colour_id: Option<u64> = None;
loop {
let last_line = lines.pop().unwrap();
if last_line.starts_with("POS") {
let room_pos = last_line.replace("POS ", "");
let room_pos: Vec<&str> = room_pos.split_whitespace().collect();
room_id = Some(from_base36(room_pos[0]));
if room_pos.len() < 2 {
panic!("Bad room/position for avatar: {}", string);
}
position = Some(Position::from(room_pos[1].to_string()).unwrap());
} else if last_line.starts_with("NAME") {
name = Some(last_line.replace("NAME ", ""));
} else if last_line.starts_with("COL") {
let last_line = last_line.replace("COL ", "");
let result = last_line.parse();
if result.is_ok() {
colour_id = Some(result.unwrap());
} else {
println!("Bad colour id: {}", last_line)
}
} else if is_string_numeric(last_line.to_string()) {
lines.push(last_line);
break;
}
}
let room_id = room_id.unwrap();
let position = position.unwrap();
let animation_frames: String = lines.join("\n");
let animation_frames: Vec<&str> = animation_frames.split(">").collect();
let animation_frames: Vec<Image> = animation_frames
.iter()
.map(|&frame| Image::from(frame.to_string()))
.collect();
Ok(Avatar {
animation_frames,
name,
room_id,
position,
colour_id,
})
}
}
impl ToString for Avatar {
#[inline]
fn to_string(&self) -> String {
format!(
"SPR A\n{}{}{}{}",
self.animation_frames.to_string(),
optional_data_line("NAME", self.name.as_ref()),
self.room_position_line(),
self.colour_line(),
)
}
}
#[cfg(test)]
mod test {
use crate::avatar::Avatar;
use crate::mock;
#[test]
fn avatar_from_string() {
let output = Avatar::from(
include_str!("test-resources/avatar").to_string()
).unwrap();
assert_eq!(output, mock::avatar());
}
#[test]
fn avatar_to_string() {
assert_eq!(mock::avatar().to_string(), include_str!("test-resources/avatar"));
}
}

View File

@ -1,4 +1,4 @@
use crate::{Avatar, Dialogue, Ending, Font, Item, Palette, Room, Sprite, TextDirection, Tile, ToBase36, Variable, transform_line_endings, segments_from_string, is_string_numeric};
use crate::{Dialogue, Ending, Font, Item, Palette, Room, Sprite, TextDirection, Tile, ToBase36, Variable, transform_line_endings, segments_from_string, is_string_numeric};
use std::error::Error;
use loe::TransformMode;
@ -76,7 +76,7 @@ pub struct Game {
pub palettes: Vec<Palette>,
pub rooms: Vec<Room>,
pub tiles: Vec<Tile>,
pub avatar: Avatar,
pub avatar: Sprite,
pub sprites: Vec<Sprite>,
pub items: Vec<Item>,
pub dialogues: Vec<Dialogue>,
@ -127,7 +127,7 @@ impl Game {
let mut palettes: Vec<Palette> = Vec::new();
let mut rooms: Vec<Room> = Vec::new();
let mut tiles: Vec<Tile> = Vec::new();
let mut avatar: Option<Avatar> = None; // unwrap this later
let mut avatar: Option<Sprite> = None; // unwrap this later
let mut sprites: Vec<Sprite> = Vec::new();
let mut items: Vec<Item> = Vec::new();
@ -158,8 +158,8 @@ impl Game {
} else if segment.starts_with("TIL") {
tiles.push(Tile::from(segment));
} else if segment.starts_with("SPR A") {
avatar = Some(Avatar::from(segment).unwrap());
} else if segment.starts_with("SPR") {
avatar = Some(Sprite::from(segment));
} else if segment.starts_with("SPR ") {
sprites.push(Sprite::from(segment));
} else if segment.starts_with("ITM") {
items.push(Item::from(segment));
@ -227,7 +227,7 @@ impl ToString for Game {
}
}
segments.push(self.avatar.to_string());
segments.push(self.avatar.to_string().replace("SPR a", "SPR A"));
for sprite in &self.sprites {
if !is_string_numeric(sprite.id.to_base36()) {

View File

@ -4,7 +4,6 @@ use std::io::Cursor;
use radix_fmt::radix_36;
use loe::{process, Config, TransformMode};
pub mod avatar;
pub mod colour;
pub mod dialogue;
pub mod ending;

View File

@ -25,8 +25,9 @@ pub mod image {
}
}
pub fn avatar() -> Avatar {
Avatar {
pub fn avatar() -> Sprite {
Sprite {
id: 0,
animation_frames: vec![
Image {
pixels: vec![
@ -44,9 +45,11 @@ pub fn avatar() -> Avatar {
},
],
name: None,
room_id: 0,
position: Position { x: 2, y: 5 },
room_id: Some(0),
position: Some(Position { x: 2, y: 5 }),
colour_id: None,
dialogue_id: None,
items: vec![]
}
}
@ -81,6 +84,7 @@ pub fn sprite() -> Sprite {
room_id: Some(4),
position: Some(Position { x: 9, y: 7 }),
colour_id: None,
items: vec![]
}
}
@ -699,7 +703,8 @@ pub fn game_default() -> Game {
walls: vec![],
}],
tiles: vec![self::tile_default()],
avatar: Avatar {
avatar: Sprite {
id: 0,
animation_frames: vec![Image {
pixels: vec![
0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
@ -708,9 +713,11 @@ pub fn game_default() -> Game {
],
}],
name: None,
room_id: 0,
position: Position { x: 4, y: 4 },
room_id: Some(0),
position: Option::from(Position { x: 4, y: 4 }),
colour_id: None,
dialogue_id: None,
items: vec![]
},
sprites: vec![Sprite {
id: 10,
@ -726,6 +733,7 @@ pub fn game_default() -> Game {
room_id: Some(0),
position: Some(Position { x: 8, y: 12 }),
colour_id: None,
items: vec![]
}],
items: vec![Item {
id: 0,

View File

@ -9,6 +9,7 @@ pub struct Sprite {
pub room_id: Option<u64>,
pub position: Option<Position>,
pub colour_id: Option<u64>,
pub items: Vec<String>,
}
impl Sprite {
@ -35,6 +36,15 @@ impl Sprite {
fn colour_line(&self) -> String {
optional_data_line("COL", self.colour_id.as_ref())
}
fn item_lines(&self) -> String {
if self.items.len() == 0 {
"".to_string()
} else {
let lines: Vec<String> = self.items.iter().map(|item| format!("ITM {}", item)).collect();
format!("\n{}", lines.join("\n"))
}
}
}
impl From<String> for Sprite {
@ -47,6 +57,7 @@ impl From<String> for Sprite {
let mut room_id: Option<u64> = None;
let mut position: Option<Position> = None;
let mut colour_id: Option<u64> = None;
let mut items: Vec<String> = Vec::new();
loop {
let last_line = lines.pop().unwrap();
@ -67,12 +78,16 @@ impl From<String> for Sprite {
position = Some(Position::from(room_position[1].to_string()).unwrap());
} else if last_line.starts_with("COL") {
colour_id = Some(last_line.replace("COL ", "").parse().unwrap());
} else if last_line.starts_with("ITM") {
items.push(last_line.replace("ITM ", ""));
} else {
lines.push(last_line);
break;
}
}
items.reverse();
// todo dedupe
let animation_frames = lines[1..].join("");
let animation_frames: Vec<&str> = animation_frames.split(">").collect();
@ -89,6 +104,7 @@ impl From<String> for Sprite {
room_id,
position,
colour_id,
items
}
}
}
@ -97,13 +113,14 @@ impl ToString for Sprite {
#[inline]
fn to_string(&self) -> String {
format!(
"SPR {}\n{}{}{}{}{}",
"SPR {}\n{}{}{}{}{}{}",
self.id.to_base36(),
self.animation_frames.to_string(),
self.name_line(),
self.dialogue_line(),
self.room_position_line(),
self.colour_line(),
self.item_lines(),
)
}
}