use std::sync::LazyLock;
use std::{collections::HashMap, fmt};
use ustr::{Ustr, UstrMap};
use super::{AsItemId, AsLocationId, Item, Location};
use crate::{Error, Iter, ProtocolError, protocol::GameData};
pub(crate) static ARCHIPELAGO_NAME: LazyLock<Ustr> = LazyLock::new(|| Ustr::from("Archipelago"));
static ARCHIPELAGO: LazyLock<Game> = LazyLock::new(|| {
Game::new(
*ARCHIPELAGO_NAME,
vec![],
vec![Location::cheat_console(), Location::server()],
)
});
pub struct Game(GameType);
#[allow(clippy::large_enum_variant)]
enum GameType {
NoDataPackage(Ustr),
DataPackage(DataPackageGame),
}
struct DataPackageGame {
name: Ustr,
items: Vec<Item>,
locations: Vec<Location>,
items_by_id: HashMap<i64, usize>,
items_by_name: UstrMap<usize>,
locations_by_id: HashMap<i64, usize>,
locations_by_name: UstrMap<usize>,
}
impl Game {
pub(crate) fn new(name: Ustr, items: Vec<Item>, locations: Vec<Location>) -> Game {
let mut items_by_id = HashMap::with_capacity(items.len());
let mut items_by_name = UstrMap::with_capacity_and_hasher(items.len(), Default::default());
for (i, item) in items.iter().enumerate() {
items_by_id.insert(item.id(), i);
items_by_name.insert(item.name(), i);
}
let mut locations_by_id = HashMap::with_capacity(locations.len());
let mut locations_by_name =
UstrMap::with_capacity_and_hasher(locations.len(), Default::default());
for (i, location) in locations.iter().enumerate() {
locations_by_id.insert(location.id(), i);
locations_by_name.insert(location.name(), i);
}
Game(GameType::DataPackage(DataPackageGame {
name,
items,
locations,
items_by_id,
items_by_name,
locations_by_id,
locations_by_name,
}))
}
pub(crate) fn no_data_package(name: Ustr) -> Game {
Game(GameType::NoDataPackage(name))
}
pub(crate) fn hydrate(name: Ustr, network: GameData) -> Game {
let mut items = Vec::with_capacity(network.item_name_to_id.len());
let mut items_by_id = HashMap::with_capacity(network.item_name_to_id.len());
let mut items_by_name =
UstrMap::with_capacity_and_hasher(network.item_name_to_id.len(), Default::default());
for (item_name, id) in network.item_name_to_id {
items_by_id.insert(id, items.len());
items_by_name.insert(item_name, items.len());
items.push(Item::new(id, item_name, name));
}
let mut locations = Vec::with_capacity(network.location_name_to_id.len());
let mut locations_by_id = HashMap::with_capacity(network.location_name_to_id.len());
let mut locations_by_name = UstrMap::with_capacity_and_hasher(
network.location_name_to_id.len(),
Default::default(),
);
for (location_name, id) in network.location_name_to_id {
locations_by_id.insert(id, locations.len());
locations_by_name.insert(location_name, locations.len());
locations.push(Location::new(id, location_name, name));
}
Game(GameType::DataPackage(DataPackageGame {
name,
items,
locations,
items_by_id,
items_by_name,
locations_by_id,
locations_by_name,
}))
}
pub fn archipelago() -> &'static Game {
&ARCHIPELAGO
}
pub fn has_data_package(&self) -> bool {
matches!(self.0, GameType::DataPackage(_))
}
pub fn name(&self) -> Ustr {
match &self.0 {
GameType::NoDataPackage(name) => *name,
GameType::DataPackage(game) => game.name,
}
}
pub fn items(&self) -> impl Iter<Item> {
match &self.0 {
GameType::NoDataPackage(_) => Default::default(),
GameType::DataPackage(game) => game.items.iter().copied(),
}
}
pub fn locations(&self) -> impl Iter<Location> {
match &self.0 {
GameType::NoDataPackage(_) => Default::default(),
GameType::DataPackage(game) => game.locations.iter().copied(),
}
}
pub fn has_item(&self, id: impl AsItemId) -> bool {
match &self.0 {
GameType::NoDataPackage(_) => true,
GameType::DataPackage(game) => game.items_by_id.contains_key(&id.as_item_id()),
}
}
pub fn item(&self, id: impl AsItemId) -> Option<Item> {
let id = id.as_item_id();
match &self.0 {
GameType::NoDataPackage(name) => {
Some(Item::new(id, format!("<item #{}>", id).into(), *name))
}
GameType::DataPackage(game) => game.items_by_id.get(&id).map(|i| game.items[*i]),
}
}
pub(crate) fn item_or_err(&self, id: impl AsItemId) -> Result<Item, Error> {
let id = id.as_item_id();
self.item(id).ok_or(
ProtocolError::MissingItem {
id,
game: self.name(),
}
.into(),
)
}
pub fn assert_item(&self, id: impl AsItemId) -> Item {
let id = id.as_item_id();
self.item(id)
.unwrap_or_else(|| panic!("{} doesn't contain an item with ID {}", self.name(), id))
}
pub fn item_by_name(&self, name: impl Into<Ustr>) -> Option<Item> {
match &self.0 {
GameType::NoDataPackage(_) => None,
GameType::DataPackage(game) => game
.items_by_name
.get(&name.into())
.map(|i| game.items[*i]),
}
}
pub fn assert_item_by_name(&self, name: impl Into<Ustr>) -> Item {
let name = name.into();
self.item_by_name(name)
.unwrap_or_else(|| panic!("{} doesn't contain an item named \"{}\"", self.name(), name))
}
pub fn has_location(&self, id: impl AsLocationId) -> bool {
match &self.0 {
GameType::NoDataPackage(_) => true,
GameType::DataPackage(game) => game.locations_by_id.contains_key(&id.as_location_id()),
}
}
pub fn location(&self, id: impl AsLocationId) -> Option<Location> {
let id = id.as_location_id();
match &self.0 {
GameType::NoDataPackage(name) => Some(Location::new(
id,
format!("<location #{}>", id).into(),
*name,
)),
GameType::DataPackage(game) => {
game.locations_by_id.get(&id).map(|i| game.locations[*i])
}
}
}
pub(crate) fn location_or_err(&self, id: impl AsLocationId) -> Result<Location, Error> {
let id = id.as_location_id();
self.location(id).ok_or(
ProtocolError::MissingLocation {
id,
game: self.name(),
}
.into(),
)
}
pub fn assert_location(&self, id: impl AsLocationId) -> Location {
let id = id.as_location_id();
self.location(id)
.unwrap_or_else(|| panic!("{} doesn't contain an location with ID {}", self.name(), id))
}
pub fn location_by_name(&self, name: impl Into<Ustr>) -> Option<Location> {
match &self.0 {
GameType::NoDataPackage(_) => None,
GameType::DataPackage(game) => game
.locations_by_name
.get(&name.into())
.map(|i| game.locations[*i]),
}
}
pub fn assert_location_by_name(&self, name: impl Into<Ustr>) -> Location {
let name = name.into();
self.location_by_name(name).unwrap_or_else(|| {
panic!(
"{} doesn't contain an location named \"{}\"",
self.name(),
name
)
})
}
pub(crate) fn verify_location(&self, id: impl AsLocationId) -> Result<(), Error> {
match &self.0 {
GameType::NoDataPackage(_) => Ok(()),
GameType::DataPackage(game) => {
let id = id.as_location_id();
game.locations_by_id.get(&id).map(|_| ()).ok_or(
ProtocolError::MissingLocation {
id,
game: game.name,
}
.into(),
)
}
}
}
}
impl fmt::Debug for Game {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match &self.0 {
GameType::NoDataPackage(name) => name.fmt(f),
GameType::DataPackage(game) => write!(
f,
"{} ({} items, {} locations)",
game.name,
game.items.len(),
game.locations.len()
),
}
}
}