#[derive(Debug, Clone, PartialEq, yog_data::Schema)]
pub struct SlotLayout {
pub x: f32,
pub y: f32,
#[data(default)]
pub accepts: Vec<String>,
}
impl SlotLayout {
pub fn at(x: f32, y: f32) -> Self {
Self {
x,
y,
accepts: Vec::new(),
}
}
}
pub const SLOT_SIZE: f32 = 18.0;
pub const DEFAULT_PLAYER_INV_OFFSET: (f32, f32) = (8.0, 84.0);
pub const PLAYER_INV_TO_HOTBAR_GAP: f32 = 4.0;
const DEFAULT_GRID_COLS: usize = 9;
const DEFAULT_GRID_ORIGIN: (f32, f32) = (8.0, 18.0);
#[derive(Debug, Clone, yog_data::Schema)]
pub struct InventoryDef {
pub id: String,
pub slot_count: usize,
pub layout: Vec<SlotLayout>,
pub include_player_inventory: bool,
pub player_inv_offset: (f32, f32),
pub background_texture: Option<String>,
pub title: String,
pub background_size: Option<(f32, f32)>,
pub machine: Option<Machine>,
}
#[derive(Debug, Clone, PartialEq, yog_data::Schema)]
pub struct Machine {
pub kind: String,
pub places: Vec<MachinePlace>,
pub result: usize,
}
#[derive(Debug, Clone, PartialEq, yog_data::Schema)]
pub struct MachinePlace {
pub slot: String,
pub index: usize,
pub inv: usize,
}
impl InventoryDef {
pub fn new(id: impl Into<String>, slot_count: usize) -> Self {
Self {
id: id.into(),
slot_count,
layout: Vec::new(),
include_player_inventory: true,
player_inv_offset: DEFAULT_PLAYER_INV_OFFSET,
background_texture: None,
title: String::new(),
background_size: None,
machine: None,
}
}
pub fn machine(mut self, machine: Machine) -> Self {
self.machine = Some(machine);
self
}
pub fn layout(mut self, layout: Vec<SlotLayout>) -> Self {
self.layout = layout;
self
}
pub fn layout_from_text(mut self, text: &str) -> Self {
let parsed = yog_data::text::parse_value(text).ok();
if let Some(layout) = parsed.and_then(|v| yog_data::read_value(&v).ok()) {
self.layout = layout;
}
self
}
pub fn include_player_inventory(mut self, v: bool) -> Self {
self.include_player_inventory = v;
self
}
pub fn player_inv_offset(mut self, x: f32, y: f32) -> Self {
self.player_inv_offset = (x, y);
self
}
pub fn background_texture(mut self, path: impl Into<String>) -> Self {
self.background_texture = Some(path.into());
self
}
pub fn background_size(mut self, w: f32, h: f32) -> Self {
self.background_size = Some((w, h));
self
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = title.into();
self
}
pub fn resolved_layout(&self) -> Vec<SlotLayout> {
if !self.layout.is_empty() {
return self.layout.clone();
}
Self::default_grid(self.slot_count)
}
pub fn default_grid(slot_count: usize) -> Vec<SlotLayout> {
let (x0, y0) = DEFAULT_GRID_ORIGIN;
(0..slot_count)
.map(|i| {
let col = (i % DEFAULT_GRID_COLS) as f32;
let row = (i / DEFAULT_GRID_COLS) as f32;
SlotLayout::at(x0 + col * SLOT_SIZE, y0 + row * SLOT_SIZE)
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_grid_wraps_rows() {
let g = InventoryDef::new("t", DEFAULT_GRID_COLS + 1).resolved_layout();
assert_eq!(g[0], SlotLayout::at(8.0, 18.0));
assert_eq!(g[DEFAULT_GRID_COLS], SlotLayout::at(8.0, 36.0));
}
#[test]
fn layout_reads_from_text() {
let def = InventoryDef::new("t", 2)
.layout_from_text(r##"[{"x": 1.5, "y": 2}, {x: 30, y: 4, accepts: ["#c:dusts"]}]"##);
assert_eq!(def.layout[0], SlotLayout::at(1.5, 2.0));
assert_eq!(def.layout[1].accepts, ["#c:dusts"]);
let kept = def.clone().layout_from_text("not a layout");
assert_eq!(kept.layout, def.layout);
}
}