use crate::Off;
use crate::engine_ids as ids;
use super::super::workshops::WorkshopId;
use super::super::{Blob, EngineFacts, Guid, Id};
use super::derive::{makes, scaled_inputs};
pub const WORKSHOP_RECIPES: usize = 512;
pub const RESERVED_FOR_SINGLE: usize = 16;
#[must_use]
pub fn workshop_limit() -> usize {
let Some(host) = crate::host() else {
return WORKSHOP_RECIPES;
};
let api = host.api();
match (api.workshop_recipes)() {
u32::MAX => usize::MAX,
limit => usize::try_from(limit).unwrap_or(WORKSHOP_RECIPES),
}
}
pub const RECIPE_REGISTRY: Guid = Guid::parse("5e57de0f-280c-4559-8191-f02c1ba656e2");
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct NewRecipe {
pub id: Id,
pub like: Guid,
pub output: Guid,
pub output_count: u32,
pub cost: u32,
pub workshop: Option<WorkshopId>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RecipeLayout {
pub recipes: usize,
pub recipe: usize,
pub guid: usize,
pub id: usize,
pub workshop_guid: usize,
pub workshop: usize,
pub input: usize,
pub output: usize,
pub completion: usize,
pub relative: [usize; 4],
pub input_size: usize,
pub stack_size: usize,
pub stack_item: usize,
pub stack_count: usize,
}
impl RecipeLayout {
pub fn resolve(facts: &(impl EngineFacts + ?Sized)) -> Result<Self, Off> {
let at = |id: &str| facts.offset(id);
Ok(Self {
recipes: at(ids::RECIPE_REGISTRY_RECIPES)?,
recipe: facts.size(ids::RECIPE)?,
guid: at(ids::RECIPE_GUID)?,
id: at(ids::RECIPE_ID)?,
workshop_guid: at(ids::RECIPE_WORKSHOP_GUID)?,
workshop: at(ids::RECIPE_WORKSHOP)?,
input: at(ids::RECIPE_INPUT)?,
output: at(ids::RECIPE_OUTPUT)?,
completion: at(ids::RECIPE_COMPLETION)?,
relative: [
at(ids::RECIPE_INPUT)?,
at(ids::RECIPE_OUTPUT)?,
at(ids::RECIPE_REQUIRED_PROPS)?,
at(ids::RECIPE_DEBUG_NAME)?,
],
input_size: facts.size(ids::RECIPE_INGREDIENT)?,
stack_size: facts.size(ids::RECIPE_STACK)?,
stack_item: at(ids::RECIPE_STACK_ITEM)?,
stack_count: at(ids::RECIPE_STACK_COUNT)?,
})
}
}
#[cfg(feature = "unstable")]
pub(super) fn fnv1a(bytes: &[u8]) -> u32 {
bytes.iter().fold(0x811c_9dc5_u32, |hash, &b| {
(hash ^ u32::from(b)).wrapping_mul(0x0100_0193)
})
}
#[must_use]
pub fn add_recipe_to(
layout: &RecipeLayout,
registry: &[u8],
recipe: &NewRecipe,
limit: usize,
) -> Option<Vec<u8>> {
let mut registry = Blob::new(registry);
let (start, count) = registry.array(layout.recipes)?;
let like = (0..usize::try_from(count).ok()?)
.map(|i| start + i * layout.recipe)
.find(|&at| makes(layout, ®istry, at, recipe.like))?;
let workshop = match recipe.workshop {
Some(target) => target.id(),
None => registry.read_u32(like + layout.workshop)?,
};
if workshop_counts(layout, ®istry)?
.get(&workshop)
.copied()
.unwrap_or(0)
>= limit
{
crate::warn!("recipe skipped: workshop {workshop} holds {limit} recipes, the game's most");
return Some(registry.into_bytes());
}
let template = registry.bytes().get(like..like + layout.recipe)?.to_vec();
let inputs = scaled_inputs(layout, ®istry, like, recipe.cost)?;
let index = registry.grow_array(
layout.recipes,
layout.recipe,
8,
&layout.relative,
&[(template, Some(like))],
)?;
let (start, _) = registry.array(layout.recipes)?;
let at = start + usize::try_from(index).ok()? * layout.recipe;
let id = crate::RecipeId::from_guid(recipe.id.guid())
.get()
.to_le_bytes();
let mut output = vec![0u8; layout.stack_size];
output.get_mut(..16)?.copy_from_slice(&recipe.output.0);
output
.get_mut(layout.stack_item..layout.stack_item + 4)?
.copy_from_slice(&recipe.output.fnv1a().to_le_bytes());
output
.get_mut(layout.stack_count..layout.stack_count + 4)?
.copy_from_slice(&recipe.output_count.to_le_bytes());
let input_count = u32::try_from(inputs.len() / layout.input_size.max(1)).ok()?;
let inputs_at = registry.append(&inputs, 8);
let output_at = registry.append(&output, 8);
let written = registry.write(at + layout.guid, &recipe.id.guid().0)
&& registry.write(at + layout.id, &id)
&& registry.write(at + layout.completion, &id)
&& registry.set_array(at + layout.input, inputs_at, input_count)
&& registry.set_array(at + layout.output, output_at, 1)
&& recipe
.workshop
.is_none_or(|target| retarget(layout, &mut registry, at, target));
written.then(|| registry.into_bytes())
}
pub(super) fn retarget(
layout: &RecipeLayout,
blob: &mut Blob,
at: usize,
workshop: WorkshopId,
) -> bool {
blob.write(at + layout.workshop_guid, &workshop.guid().0)
&& blob.write(at + layout.workshop, &workshop.id().to_le_bytes())
}
pub(super) fn workshop_counts(
layout: &RecipeLayout,
blob: &Blob,
) -> Option<std::collections::HashMap<u32, usize>> {
let (start, count) = blob.array(layout.recipes)?;
let mut counts = std::collections::HashMap::new();
for i in 0..usize::try_from(count).ok()? {
let workshop = blob.read_u32(start + i * layout.recipe + layout.workshop)?;
*counts.entry(workshop).or_insert(0) += 1;
}
Some(counts)
}