mod resource;
mod skill;
mod shard;
mod command;
mod teleporter;
mod message;
mod uber_state;
mod bonus_item;
mod bonus_upgrade;
mod sysmessage;
mod wheel_command;
mod shop_command;
use std::fmt;
use std::str::FromStr;
use rustc_hash::FxHashMap;
use serde::{Serialize, Deserialize};
use wotw_seedgen_derive::VVariant;
use crate::header::{parser, CodeDisplay};
use crate::header::{VResolve, vdisplay};
use crate::settings::Difficulty;
use crate::util::{Zone, Icon, MapIcon};
use crate::uber_state::UberIdentifier;
pub use self::{
resource::Resource,
skill::Skill,
shard::Shard,
command::{Command, VCommand, ToggleCommand, EquipSlot},
teleporter::Teleporter,
message::{Message, VMessage},
uber_state::{UberStateItem, VUberStateItem, UberStateOperator, VUberStateOperator, UberStateRange, VUberStateRange, UberStateRangeBoundary, VUberStateRangeBoundary, UberStateValue},
bonus_item::BonusItem,
bonus_upgrade::BonusUpgrade,
sysmessage::SysMessage,
wheel_command::{WheelCommand, VWheelCommand, WheelItemPosition, WheelBind},
shop_command::{ShopCommand, VShopCommand},
};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, VVariant, Serialize, Deserialize)]
#[serde(into = "String", try_from = "&str")]
pub enum Item {
Relic(Zone),
Water,
RemoveWater,
Skill(Skill),
RemoveSkill(Skill),
Teleporter(Teleporter),
RemoveTeleporter(Teleporter),
Resource(Resource),
Shard(Shard),
RemoveShard(Shard),
BonusItem(BonusItem),
BonusUpgrade(BonusUpgrade),
SpiritLight(#[VWrap] u32),
RemoveSpiritLight(#[VWrap] u32),
Message(#[VType] Message),
UberState(#[VType] UberStateItem),
Command(#[VType] Command),
WheelCommand(#[VType] WheelCommand),
ShopCommand(#[VType] ShopCommand),
SysMessage(SysMessage),
}
vdisplay! {
VItem,
impl fmt::Display for Item {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SpiritLight(amount) => write!(f, "{amount} Spirit Light"),
Self::RemoveSpiritLight(amount) => write!(f, "Remove {amount} Spirit Light"),
Self::Resource(resource) => write!(f, "{resource}"),
Self::Skill(skill) => write!(f, "{skill}"),
Self::RemoveSkill(skill) => write!(f, "Remove {skill}"),
Self::Shard(shard) => write!(f, "{shard}"),
Self::RemoveShard(shard) => write!(f, "Remove {shard}"),
Self::Command(command) => write!(f, "{command}"),
Self::Teleporter(teleporter) => write!(f, "{teleporter} TP"),
Self::RemoveTeleporter(teleporter) => write!(f, "Remove {teleporter} TP"),
Self::Message(message) => write!(f, "Display \"{message}\""),
Self::UberState(command) => write!(f, "{command}"),
Self::Water => write!(f, "Clean Water"),
Self::RemoveWater => write!(f, "Remove Clean Water"),
Self::BonusItem(bonus_item) => write!(f, "{bonus_item}"),
Self::BonusUpgrade(bonus_upgrade) => write!(f, "{bonus_upgrade}"),
Self::Relic(zone) => write!(f, "{zone} Relic"),
Self::SysMessage(message) => write!(f, "{message}"),
Self::WheelCommand(command) => write!(f, "{command}"),
Self::ShopCommand(command) => write!(f, "{command}"),
}
}
}
}
impl FromStr for Item {
type Err = String;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let mut parser = parser::new(input);
let item = VItem::parse(&mut parser).map_err(|err| err.verbose_display())?
.resolve(&FxHashMap::default())?;
parser.expect_end().map_err(|err| err.verbose_display())?;
Ok(item)
}
}
impl Item {
#[inline]
pub fn is_progression(&self, difficulty: Difficulty) -> bool {
match self {
Item::Resource(resource) => match resource {
Resource::ShardSlot => difficulty >= Difficulty::Unsafe,
Resource::Health | Resource::Energy | Resource::Ore | Resource::Keystone => true,
},
Item::Skill(skill) => match skill {
Skill::AncestralLight1 | Skill::AncestralLight2 => difficulty >= Difficulty::Unsafe,
Skill::Shuriken | Skill::Blaze | Skill::Sentry => difficulty >= Difficulty::Gorlek,
Skill::Seir | Skill::WallJump => false,
Skill::Bash |
Skill::DoubleJump |
Skill::Launch |
Skill::Glide |
Skill::WaterBreath |
Skill::Grenade |
Skill::Grapple |
Skill::Flash |
Skill::Spear |
Skill::Regenerate |
Skill::Bow |
Skill::Hammer |
Skill::Sword |
Skill::Burrow |
Skill::Dash |
Skill::WaterDash |
Skill::Flap => true,
},
Item::Shard(shard) => match shard {
Shard::Overcharge |
Shard::Wingclip |
Shard::Magnet |
Shard::Splinter |
Shard::Reckless |
Shard::LifePact |
Shard::LastStand |
Shard::UltraBash |
Shard::UltraGrapple |
Shard::Overflow |
Shard::Thorn |
Shard::Catalyst |
Shard::Sticky |
Shard::Finesse |
Shard::SpiritSurge |
Shard::Lifeforce |
Shard::Deflector |
Shard::Fracture => difficulty >= Difficulty::Unsafe,
Shard::TripleJump |
Shard::Resilience |
Shard::Vitality |
Shard::Energy => difficulty >= Difficulty::Gorlek,
_ => false,
},
Item::SpiritLight(_) | Item::Teleporter(_) | Item::Water | Item::UberState(_) => true,
_ => false,
}
}
#[inline]
pub fn is_multiworld_spread(&self) -> bool {
!matches!(self, Item::SpiritLight(_))
}
#[inline]
pub fn is_single_instance(&self) -> bool {
!matches!(self,
Item::SpiritLight(_) | Item::RemoveSpiritLight(_) |
Item::Resource(_) |
Item::BonusItem(_) | Item::BonusUpgrade(_) |
Item::UberState(_) | Item::Command(_) | Item::Message(_)
)
}
#[inline]
pub fn cost(&self) -> u32 {
#[allow(clippy::match_same_arms)]
match self {
Item::SpiritLight(amount) => *amount,
Item::Resource(Resource::Ore) => 20,
Item::Resource(Resource::Energy | Resource::Health) => 120,
Item::Resource(Resource::Keystone) => 320,
Item::Resource(Resource::ShardSlot) => 480,
Item::Skill(Skill::Regenerate | Skill::WaterBreath) => 200, Item::Skill(Skill::WallJump | Skill::DoubleJump | Skill::Dash) => 1200, Item::Skill(Skill::Glide | Skill::Grapple) => 1400, Item::Skill(Skill::Sword | Skill::Hammer | Skill::Bow | Skill::Shuriken) => 1600, Item::Skill(Skill::Burrow | Skill::Bash | Skill::Flap | Skill::WaterDash | Skill::Grenade | Skill::Flash | Skill::Seir) | Item::Water => 1800, Item::Skill(Skill::Blaze | Skill::Sentry) => 2800, Item::Skill(Skill::AncestralLight1 | Skill::AncestralLight2) => 3000, Item::Skill(Skill::Spear) => 4000, Item::Skill(Skill::Launch) => 40000, Item::Shard(_) => 1000,
Item::Teleporter(Teleporter::Marsh) => 30000,
Item::Teleporter(_) => 25000,
_ => 400,
}
}
#[inline]
pub fn shop_price(&self) -> u32 {
#[allow(clippy::match_same_arms)]
match self {
Item::Resource(Resource::Health) => 200,
Item::Resource(Resource::Energy) => 150,
Item::Resource(Resource::Ore | Resource::Keystone) => 100,
Item::Resource(Resource::ShardSlot) => 250,
Item::Skill(skill) => match skill {
Skill::WaterBreath | Skill::Regenerate | Skill::Seir => 200,
Skill::AncestralLight1 | Skill::AncestralLight2 => 300,
Skill::Blaze => 420,
Skill::Launch => 800,
_ => 500,
},
Item::Water => 500,
Item::Teleporter(_) | Item::Shard(_) => 250,
Item::BonusItem(_) => 300,
Item::BonusUpgrade(BonusUpgrade::SentryEfficiency | BonusUpgrade::RapidHammer) => 600,
Item::BonusUpgrade(_) => 300,
_ => 200,
}
}
#[inline]
pub fn random_shop_price(&self) -> bool {
!matches!(self, Item::Skill(Skill::Blaze))
}
pub fn attached_state(&self) -> Option<UberIdentifier> {
match self {
Item::Skill(skill) => Some(1000 + *skill as u16),
Item::Water => Some(2000),
Item::Teleporter(teleporter) => return Some(teleporter.attached_state()),
_ => None,
}.map(|uber_id| UberIdentifier::new(6, uber_id))
}
pub fn code(&self) -> CodeDisplay<Item> {
CodeDisplay::new(self, |s, f| {
match s {
Item::SpiritLight(amount) => write!(f, "0|{}", amount),
Item::RemoveSpiritLight(amount) => write!(f, "0|-{}", amount),
Item::Resource(resource) => write!(f, "1|{}", *resource as u8),
Item::Skill(skill) => write!(f, "2|{}", *skill as u8),
Item::RemoveSkill(skill) => write!(f, "2|-{}", *skill as u8),
Item::Shard(shard) => write!(f, "3|{}", *shard as u8),
Item::RemoveShard(shard) => write!(f, "3|-{}", *shard as u8),
Item::Command(command) => write!(f, "4|{}", command.code()),
Item::Teleporter(teleporter) => write!(f, "5|{}", *teleporter as u8),
Item::RemoveTeleporter(teleporter) => write!(f, "5|-{}", *teleporter as u8),
Item::Message(message) => write!(f, "6|{}", message.code()),
Item::UberState(command) => write!(f, "8|{}", command.code()),
Item::Water => write!(f, "9|0"),
Item::RemoveWater => write!(f, "9|-0"),
Item::BonusItem(bonus) => write!(f, "10|{}", *bonus as u8),
Item::BonusUpgrade(bonus) => write!(f, "11|{}", *bonus as u8),
Item::Relic(zone) => write!(f, "14|{}", *zone as u8),
Item::SysMessage(message) =>
if let SysMessage::MapRelicList(zone) = message {
write!(f, "15|{}|{}", *zone as u8, message.to_id())
} else {
write!(f, "15|{}", message.to_id())
},
Item::WheelCommand(command) => write!(f, "16|{}", command.code()),
Item::ShopCommand(command) => write!(f, "17|{}", command.code()),
}
})
}
pub fn description(&self) -> Option<String> {
match self {
Item::BonusItem(bonus_item) => bonus_item.description(),
Item::BonusUpgrade(bonus_upgrade) => bonus_upgrade.description(),
_ => None,
}
}
pub fn icon(&self) -> Option<Icon> {
match self {
Item::SpiritLight(_) => Some(Icon::File(String::from("assets/icons/game/experience.png"))),
Item::Resource(resource) => resource.icon(),
Item::Skill(skill) => skill.icon(),
Item::Shard(shard) => shard.icon(),
Item::Teleporter(_) => Some(Icon::File(String::from("assets/icons/game/teleporter.png"))),
Item::Message(_) => Some(Icon::File(String::from("assets/icons/game/message.png"))),
Item::Water => Some(Icon::File(String::from("assets/icons/game/water.png"))),
Item::BonusItem(bonus_item) => bonus_item.icon(),
Item::BonusUpgrade(bonus_upgrade) => bonus_upgrade.icon(),
Item::Relic(_) => Some(Icon::File(String::from("assets/icons/game/relic.png"))),
_ => None,
}
}
pub fn map_icon(&self) -> MapIcon {
match self {
Item::SpiritLight(_) => MapIcon::SpiritLight,
Item::Resource(resource) => resource.map_icon(),
Item::Skill(_) => MapIcon::Skill,
Item::Shard(_) => MapIcon::Shard,
Item::Teleporter(_) => MapIcon::Teleporter,
Item::Water | Item::Relic(_) => MapIcon::QuestItem,
_ => MapIcon::Other,
}
}
}
impl From<Item> for String {
fn from(item: Item) -> String {
item.code().to_string()
}
}
impl TryFrom<&str> for Item {
type Error = String;
fn try_from<'a>(code: &str) -> Result<Self, Self::Error> {
Item::from_str(code)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn item_display() {
assert_eq!(Item::SpiritLight(45).code().to_string(), "0|45");
assert_eq!(Item::Resource(Resource::Keystone).code().to_string(), "1|3");
assert_eq!(Item::Skill(Skill::Launch).code().to_string(), "2|8");
assert_eq!(Item::Skill(Skill::AncestralLight1).code().to_string(), "2|120");
assert_eq!(Item::Shard(Shard::Magnet).code().to_string(), "3|8");
assert_eq!(Item::Teleporter(Teleporter::Marsh).code().to_string(), "5|16");
assert_eq!(Item::Water.code().to_string(), "9|0");
assert_eq!(Item::BonusItem(BonusItem::Relic).code().to_string(), "10|20");
assert_eq!(Item::BonusUpgrade(BonusUpgrade::ShurikenEfficiency).code().to_string(), "11|4");
assert_eq!(Item::Message(Message::new(String::from("8|0|9|7"))).code().to_string(), "6|8|0|9|7");
}
}