use std::{
collections::HashMap,
ops::{Index, IndexMut},
};
use thiserror::Error;
use crate::{
game_state::{GameMode, GameOstrich, GameState},
Level, Spatula,
};
use self::game_var::{GameVar, GameVarMut, InterfaceBackend};
pub mod dolphin;
pub mod game_var;
pub mod mock;
#[non_exhaustive]
pub struct GameInterface<F: InterfaceBackend> {
pub is_loading: F::Var<bool>,
pub game_state: F::Mut<GameState>,
pub game_mode: F::Mut<GameMode>,
pub game_ostrich: F::Mut<GameOstrich>,
pub hans: Hans<F>,
pub powers: PowerUps<F>,
pub scene_id: F::Var<[u8; 4]>,
pub spatula_count: F::Mut<u32>,
pub tasks: Tasks<F>,
lab_door_cost: F::Mut<u32>,
}
pub struct Tasks<F: InterfaceBackend> {
pub(crate) arr: HashMap<Spatula, Task<F>>,
}
#[non_exhaustive]
pub struct Task<F: InterfaceBackend> {
pub menu_count: F::Mut<i16>,
pub flags: Option<F::Mut<u8>>,
pub state: Option<F::Mut<u32>>,
}
#[non_exhaustive]
pub struct PowerUps<F: InterfaceBackend> {
pub bubble_bowl: F::Mut<bool>,
pub cruise_bubble: F::Mut<bool>,
pub initial_bubble_bowl: F::Mut<bool>,
pub initial_cruise_bubble: F::Mut<bool>,
}
#[non_exhaustive]
pub struct Hans<F: InterfaceBackend> {
flags: F::Mut<u8>,
}
impl<F: InterfaceBackend> GameInterface<F> {
pub fn start_new_game(&mut self) -> InterfaceResult<()> {
self.game_mode.set(GameMode::Game)
}
pub fn get_current_level(&self) -> InterfaceResult<Level> {
self.scene_id
.get()?
.try_into()
.map_err(|_| InterfaceError::DataUnavailable)
}
pub fn unlock_task(&mut self, spatula: Spatula) -> InterfaceResult<()> {
let task = &mut self.tasks[spatula];
let curr = task.menu_count.get()?;
if curr == 0 {
task.menu_count.set(1)?;
}
Ok(())
}
pub fn mark_task_complete(&mut self, spatula: Spatula) -> InterfaceResult<()> {
self.tasks[spatula].menu_count.set(2)
}
pub fn is_task_complete(&self, spatula: Spatula) -> InterfaceResult<bool> {
Ok(self.tasks[spatula].menu_count.get()? == 2)
}
pub fn collect_spatula(
&mut self,
spatula: Spatula,
current_level: Option<Level>,
) -> InterfaceResult<()> {
if current_level != Some(spatula.get_level()) {
return Ok(());
}
let task = &mut self.tasks[spatula];
let (flags, state) = match (&mut task.flags, &mut task.state) {
(Some(flags), Some(state)) => (flags, state),
_ => return Ok(()),
};
let mut new_flags = flags.get()?;
new_flags &= !1;
let mut new_state = state.get()?;
new_state |= 8;
new_state &= !4;
new_state &= !2;
flags.set(new_flags)?;
state.set(new_state)?;
Ok(())
}
pub fn is_spatula_being_collected(
&self,
spatula: Spatula,
current_level: Option<Level>,
) -> InterfaceResult<bool> {
if current_level != Some(spatula.get_level()) {
return Ok(false);
}
let state = match &self.tasks[spatula].state {
Some(x) => x,
None => return Ok(false),
};
Ok(state.get()? & 4 != 0)
}
pub fn set_lab_door(
&mut self,
value: u32,
current_level: Option<Level>,
) -> InterfaceResult<()> {
if current_level != Some(Level::ChumBucket) {
return Ok(());
}
let cost = value - 1;
self.lab_door_cost.set(cost)?;
Ok(())
}
}
impl<V: InterfaceBackend> Index<Spatula> for Tasks<V> {
type Output = Task<V>;
fn index(&self, index: Spatula) -> &Self::Output {
&self.arr[&index]
}
}
impl<T: InterfaceBackend> IndexMut<Spatula> for Tasks<T> {
fn index_mut(&mut self, index: Spatula) -> &mut Self::Output {
self.arr.get_mut(&index).unwrap()
}
}
impl<F: InterfaceBackend> PowerUps<F> {
pub fn start_with_powers(&mut self, value: bool) -> InterfaceResult<()> {
self.initial_bubble_bowl.set(value)?;
self.initial_cruise_bubble.set(value)
}
pub fn unlock_powers(&mut self) -> InterfaceResult<()> {
self.bubble_bowl.set(true)?;
self.cruise_bubble.set(true)
}
}
impl<F: InterfaceBackend> Hans<F> {
pub fn is_enabled(&mut self) -> InterfaceResult<bool> {
Ok(self.flags.get()? & 4 == 0)
}
pub fn set_enabled(&mut self, value: bool) -> InterfaceResult<()> {
let new = match value {
true => self.flags.get()? & !4,
false => self.flags.get()? | 4,
};
self.flags.set(new)
}
pub fn toggle_enabled(&mut self) -> InterfaceResult<bool> {
let new = self.flags.get()? ^ 4;
self.flags.set(new)?;
Ok(new & 4 == 0)
}
}
pub trait InterfaceProvider: Default {
type Backend: InterfaceBackend;
fn do_with_interface<T>(
&mut self,
fun: impl FnOnce(&mut GameInterface<Self::Backend>) -> InterfaceResult<T>,
) -> InterfaceResult<T>;
fn is_available(&mut self) -> bool;
}
pub type InterfaceResult<T> = std::result::Result<T, InterfaceError>;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum InterfaceError {
#[error("Data temporarily unavailable")]
DataUnavailable,
#[error("Interface became unhooked")]
Unhooked,
#[error("Target emulator process could not be found")]
ProcessNotFound,
#[error("Target emulator is found but no emulation is started")]
EmulationNotRunning,
#[error("A game other than SpongeBob SquarePants: Battle for Bikini Bottom is running.")]
IncorrectGame,
#[error("Unexpected I/O error")]
Io(std::io::Error),
}
impl From<std::io::Error> for InterfaceError {
fn from(e: std::io::Error) -> Self {
if e.kind() == std::io::ErrorKind::InvalidData {
return Self::Io(e);
}
Self::Unhooked
}
}