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
//! Data packs: items, recipes, workshops, loot and texts described in
//! TOML files instead of code, and changes of vanilla values.
//!
//! A pack is a set of files: `items.toml`, `recipes.toml`, `workshops.toml`,
//! `loot.toml` and `texts.toml`, each optional, and the PNG icons the items
//! name. [`Pack::parse`] reads and checks them against the types below and
//! reports every problem with its file, line and column; [`Load::add_pack`]
//! adds what they describe through the same calls code uses, beside vanilla
//! content, never in its place. A mod embeds a pack with
//! [`PackFiles::with`]; a folder `mods/<id>/` with a `pack.toml`
//! ([`PackManifest`]) and no DLL is a pack the loader runs on its own.
//!
//! Every name a pack defines lives in its namespace: `lantern` in the pack
//! `you.lanterns` is the content id `you.lanterns:lantern`. A reference is a name of
//! the pack, a full content id of another mod (`you.lockers:locker`), or a vanilla
//! GUID (`6943053a-32c1-4718-ae22-e61427ddf54d`).
//!
//! # Examples
//!
//! ```
//! use enshwick::packs::{Pack, PackFiles};
//!
//! let files = PackFiles::new()
//! .with("items.toml", r#"
//! [[item]]
//! id = "lantern"
//! like = "6943053a-32c1-4718-ae22-e61427ddf54d"
//! name = "lantern.name"
//! "#)
//! .with("texts.toml", "[en]\n\"lantern.name\" = \"Lantern\"\n");
//! let pack = Pack::parse("you.lanterns", &files)?;
//! assert_eq!(pack.ids(), ["you.lanterns:lantern"]);
//! # Ok::<(), enshwick::packs::ParsePackError>(())
//! ```
use crate::content::Guid;
use crate::content::icons::{Icon, Pixels};
use crate::content::items::{FuelSetup, ImpactUiValue, NewItem};
use crate::content::recipes::NewRecipe;
use crate::content::workshops::{NewWorkshop, StationRecipe, WorkshopId};
use crate::{Load, Off};
/// The manifest file of a pack folder.
pub const MANIFEST_FILE: &str = "pack.toml";
/// The items file.
pub const ITEMS_FILE: &str = "items.toml";
/// The recipes file.
pub const RECIPES_FILE: &str = "recipes.toml";
/// The workshops file.
pub const WORKSHOPS_FILE: &str = "workshops.toml";
/// The loot file.
pub const LOOT_FILE: &str = "loot.toml";
/// The texts file, in the form of a mod's [texts](crate::i18n).
pub const TEXTS_FILE: &str = "texts.toml";
/// Every file a pack folder may hold besides its icons, in the order the
/// loader reads them.
pub const FILES: [&str; 6] = [
MANIFEST_FILE,
ITEMS_FILE,
RECIPES_FILE,
WORKSHOPS_FILE,
LOOT_FILE,
TEXTS_FILE,
];
/// The largest file a pack may hold: 4 MiB.
pub const MAX_FILE: u64 = 4 << 20;
impl Load<'_> {
/// Adds what `pack` describes, on the server and the client alike:
/// its workshops, items, recipes, texts and loot, each through the call
/// code uses, beside vanilla content, and declares its changes of
/// vanilla values with [`Load::change`].
///
/// # Errors
///
/// [`Off`] as the calls it makes return it, such as for a content id
/// the loader refuses.
///
/// # Examples
///
/// ```
/// use enshwick::packs::{Pack, PackFiles};
///
/// const ITEMS: &str = r#"
/// [[item]]
/// id = "lantern"
/// like = "6943053a-32c1-4718-ae22-e61427ddf54d"
/// "#;
///
/// fn init(load: &mut enshwick::Load<'_>) -> Result<(), enshwick::Off> {
/// let files = PackFiles::new().with("items.toml", ITEMS);
/// load.add_pack(&Pack::parse("you.my-mod", &files)?)
/// }
/// ```
pub fn add_pack(&mut self, pack: &Pack) -> Result<(), Off> {
let texts = self.texts(&pack.texts)?;
let tag = |load: &mut Self, key: &Option<String>| -> Result<Option<Guid>, Off> {
key.as_ref()
.map(|key| {
let tag = id(&format!("{}:text/{key}", pack.namespace))?;
load.add_texts(&texts, &[(key, tag)])?;
Ok(tag.guid())
})
.transpose()
};
for workshop in &pack.workshops {
let base = &workshop.id;
let name = tag(self, &workshop.name)?;
let description = tag(self, &workshop.description)?;
let station_recipe = workshop
.cost
.map(|cost| -> Result<StationRecipe, Off> {
Ok(StationRecipe {
id: id(&format!("{base}.recipe"))?,
cost,
})
})
.transpose()?;
self.add_workshop(NewWorkshop {
like: workshop.like,
item: id(base)?,
template: id(&format!("{base}.template"))?,
workshop: id(&format!("{base}.workshop"))?,
name,
description,
station_recipe,
debug_name: workshop.debug_name.clone(),
})?;
}
for item in &pack.items {
let own = id(&item.id)?;
let name = tag(self, &item.name)?;
let description = tag(self, &item.description)?;
let mut stats = Vec::new();
for (label, value, format) in &item.stats {
let label = tag(self, &Some(label.clone()))?.unwrap_or(Guid([0; 16]));
stats.push(ImpactUiValue {
loca_id: label,
value: *value,
value_format: *format,
});
}
let meter = item
.meter
.map(|(max, start)| -> Result<FuelSetup, Off> {
Ok(FuelSetup {
template: id(&format!("{}.meter", item.id))?,
max_fuel_charges: max,
start,
})
})
.transpose()?;
let icon = item
.icon
.as_ref()
.map(|icon| -> Result<Icon, Off> {
let (like, pixels) = match icon {
PackIcon::Tint { like, tint } => (*like, Pixels::Tint(*tint)),
PackIcon::Png(bytes) => {
let bytes: &'static [u8] = Box::leak(bytes.clone().into_boxed_slice());
(item.like, Pixels::Png(bytes))
}
};
Ok(Icon {
id: id(&format!("{}.icon", item.id))?,
like,
pixels,
})
})
.transpose()?;
self.add_item(NewItem {
name,
description,
debug_name: item.debug_name.clone(),
meter,
stats,
icon,
..NewItem::new(own, item.like)
})?;
}
self.add_pack_rest(pack)
}
/// Adds `pack`'s recipes, changes of values and loot.
fn add_pack_rest(&mut self, pack: &Pack) -> Result<(), Off> {
for recipe in &pack.recipes {
self.add_recipe(NewRecipe {
id: id(&recipe.id)?,
like: recipe.like,
output: recipe.output,
output_count: recipe.count,
cost: recipe.cost,
workshop: recipe.workshop.map(WorkshopId::new),
})?;
}
for (change, source) in &pack.changes {
self.change(change, source)?;
}
let group = group(&pack.namespace);
for drop in &pack.drops {
self.add_loot_in(group, drop.clone())?;
}
Ok(())
}
}
mod changes;
mod entries;
mod files;
mod manifest;
mod pack;
#[cfg(test)]
mod tests;
#[cfg(feature = "schema")]
pub use entries::schemas;
pub use files::*;
pub use manifest::*;
pub use pack::*;