#![warn(missing_docs)]
use std::fmt;
use assembly_core::buffer::CastError;
use assembly_fdb::{
mem::{Field, Row, Table, Tables},
value::Value,
};
use latin1str::Latin1Str;
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
pub mod ext;
use columns::{IconsColumn, MissionTasksColumn, MissionsColumn};
use tables::{
ActivitiesTable, ActivityRewardsTable, ActivityTextTable, BehaviorParameterTable,
BehaviorTemplateTable, CollectibleComponentTable, ComponentsRegistryTable,
CurrencyDenominationsTable, DeletionRestrictionsTable, DestructibleComponentTable, EmotesTable,
IconsTable, InventoryComponentTable, ItemComponentTable, ItemSetSkillsTable, ItemSetsTable,
JetPackPadComponentTable, LootMatrixTable, LootTableTable, MissionEmailTable,
MissionNpcComponentTable, MissionTasksTable, MissionTextTable, MissionsTable, NpcIconsTable,
ObjectSkillsTable, ObjectsTable, PackageComponentTable, PlayerStatisticsTable,
PreconditionsTable, PropertyTemplateTable, RebuildComponentTable, RebuildSectionsTable,
RenderComponentTable, RewardCodesTable, RewardsTable, SkillBehaviorTable,
SmashableComponentTable, SpeedchatMenuTable, TamingBuildPuzzlesTable, UgBehaviorSoundsTable,
VendorComponentTable, WhatsCoolItemSpotlightTable, WhatsCoolNewsAndTipsTable,
ZoneLoadingTipsTable, ZoneTableTable,
};
use self::ext::{Components, Mission, MissionTask};
pub trait TypedTable<'de>: Sized {
type Column: TypedColumn;
const NAME: &'static str;
fn as_raw(&self) -> Table<'de>;
fn new(inner: Table<'de>) -> Self;
fn of(tables: Tables<'de>) -> Option<Result<Self, CastError>> {
let table = tables.by_name(Self::NAME)?;
Some(table.map(Self::new))
}
}
pub trait TypedRow<'a, 'b>
where
'a: 'b,
{
type Table: TypedTable<'a> + 'a;
fn new(inner: Row<'a>, table: &'b Self::Table) -> Self;
fn get(table: &'b Self::Table, index_key: i32, key: i32, id_col: usize) -> Option<Self>
where
Self: Sized,
{
let hash = index_key as usize % table.as_raw().bucket_count();
if let Some(b) = table.as_raw().bucket_at(hash) {
for r in b.row_iter() {
if r.field_at(id_col).and_then(|x| x.into_opt_integer()) == Some(key) {
return Some(Self::new(r, table));
}
}
}
None
}
}
pub trait TypedColumn: fmt::Debug + Copy + Clone + Eq {
fn to_static_str(&self) -> &'static str;
fn serialize_struct_field<S: ::serde::ser::SerializeStruct>(
&self,
s: &mut S,
value: Field,
) -> Result<(), S::Error>;
}
pub struct RowIter<'a, 'b, R>
where
R: TypedRow<'a, 'b>,
{
inner: assembly_fdb::mem::iter::TableRowIter<'a>,
table: &'b R::Table,
}
impl<'a, 'b, R> RowIter<'a, 'b, R>
where
R: TypedRow<'a, 'b>,
{
pub fn new(table: &'b R::Table) -> Self {
Self {
inner: table.as_raw().row_iter(),
table,
}
}
}
impl<'a, 'b, R> Iterator for RowIter<'a, 'b, R>
where
R: TypedRow<'a, 'b>,
{
type Item = R;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|row| R::new(row, self.table))
}
}
#[derive(Clone)]
pub struct TypedDatabase<'db> {
pub activities: ActivitiesTable<'db>,
pub activity_text: ActivityTextTable<'db>,
pub activity_rewards: Option<ActivityRewardsTable<'db>>,
pub behavior_parameters: BehaviorParameterTable<'db>,
pub behavior_templates: BehaviorTemplateTable<'db>,
pub collectible_component: CollectibleComponentTable<'db>,
pub comp_reg: ComponentsRegistryTable<'db>,
pub currency_denominations: CurrencyDenominationsTable<'db>,
pub deletion_restrictions: DeletionRestrictionsTable<'db>,
pub destructible_component: DestructibleComponentTable<'db>,
pub emotes: EmotesTable<'db>,
pub icons: IconsTable<'db>,
pub inventory_component: InventoryComponentTable<'db>,
pub item_component: ItemComponentTable<'db>,
pub item_sets: ItemSetsTable<'db>,
pub item_set_skills: ItemSetSkillsTable<'db>,
pub jet_pack_pad_component: Option<JetPackPadComponentTable<'db>>,
pub loot_table: LootTableTable<'db>,
pub loot_matrix: LootMatrixTable<'db>,
pub mission_email: Option<MissionEmailTable<'db>>,
pub mission_npc_component: Option<MissionNpcComponentTable<'db>>,
pub mission_tasks: MissionTasksTable<'db>,
pub mission_text: MissionTextTable<'db>,
pub missions: MissionsTable<'db>,
pub npc_icons: NpcIconsTable<'db>,
pub objects: ObjectsTable<'db>,
pub object_skills: ObjectSkillsTable<'db>,
pub package_component: Option<PackageComponentTable<'db>>,
pub player_statistics: Option<PlayerStatisticsTable<'db>>,
pub preconditions: PreconditionsTable<'db>,
pub property_template: PropertyTemplateTable<'db>,
pub rebuild_component: RebuildComponentTable<'db>,
pub rebuild_sections: Option<RebuildSectionsTable<'db>>,
pub rewards: Option<RewardsTable<'db>>,
pub reward_codes: Option<RewardCodesTable<'db>>,
pub render_comp: RenderComponentTable<'db>,
pub skills: SkillBehaviorTable<'db>,
pub smashable_component: Option<SmashableComponentTable<'db>>,
pub speedchat_menu: SpeedchatMenuTable<'db>,
pub taming_build_puzzles: TamingBuildPuzzlesTable<'db>,
pub ug_behavior_sounds: Option<UgBehaviorSoundsTable<'db>>,
pub vendor_component: Option<VendorComponentTable<'db>>,
pub whats_cool_item_spotlight: Option<WhatsCoolItemSpotlightTable<'db>>,
pub whats_cool_news_and_tips: Option<WhatsCoolNewsAndTipsTable<'db>>,
pub zone_loading_tips: Option<ZoneLoadingTipsTable<'db>>,
pub zone_table: ZoneTableTable<'db>,
}
fn is_not_empty(s: &&Latin1Str) -> bool {
!s.is_empty()
}
impl<'a> TypedDatabase<'a> {
pub fn new(tables: Tables<'a>) -> Result<Self, CastError> {
Ok(TypedDatabase {
activities: ActivitiesTable::of(tables).expect("Missing Table 'Activities'")?,
activity_text: ActivityTextTable::of(tables).expect("Missing Table 'ActivityText'")?,
activity_rewards: ActivityRewardsTable::of(tables).transpose()?,
behavior_parameters: BehaviorParameterTable::of(tables)
.expect("Missing Table 'BehaviorParameter'")?,
behavior_templates: BehaviorTemplateTable::of(tables)
.expect("Missing Table 'BehaviorTemplate'")?,
collectible_component: CollectibleComponentTable::of(tables)
.expect("Missing Table 'CollectibleComponent'")?,
comp_reg: ComponentsRegistryTable::of(tables)
.expect("Missing Table 'ComponentsRegistry'")?,
currency_denominations: CurrencyDenominationsTable::of(tables)
.expect("Missing Table 'CurrencyDenominations'")?,
deletion_restrictions: DeletionRestrictionsTable::of(tables)
.expect("Missing Table 'DeletionRestrictions'")?,
destructible_component: DestructibleComponentTable::of(tables)
.expect("Missing Table 'DestructibleComponent'")?,
emotes: EmotesTable::of(tables).expect("Missing Table 'Emotes'")?,
icons: IconsTable::of(tables).expect("Missing Table 'Icons'")?,
inventory_component: InventoryComponentTable::of(tables)
.expect("Missing Table 'InventoryComponent'")?,
item_component: ItemComponentTable::of(tables)
.expect("Missing Table 'ItemComponent'")?,
item_sets: ItemSetsTable::of(tables).expect("Missing Table 'ItemSets'")?,
item_set_skills: ItemSetSkillsTable::of(tables)
.expect("Missing Table 'ItemSetSkills'")?,
jet_pack_pad_component: JetPackPadComponentTable::of(tables).transpose()?,
loot_matrix: LootMatrixTable::of(tables).expect("Missing Table 'LootMatrix'")?,
loot_table: LootTableTable::of(tables).expect("Missing Table 'LootTable'")?,
mission_email: MissionEmailTable::of(tables).transpose()?,
mission_npc_component: MissionNpcComponentTable::of(tables).transpose()?,
mission_tasks: MissionTasksTable::of(tables).expect("Missing Table 'MissionTasks'")?,
mission_text: MissionTextTable::of(tables).expect("Missing Table 'MissionText'")?,
missions: MissionsTable::of(tables).expect("Missing Table 'Missions'")?,
npc_icons: NpcIconsTable::of(tables).expect("Missing Table 'NpcIcons'")?,
objects: ObjectsTable::of(tables).expect("Missing Table 'Objects'")?,
object_skills: ObjectSkillsTable::of(tables).expect("Missing Table 'ObjectSkills'")?,
package_component: PackageComponentTable::of(tables).transpose()?,
player_statistics: PlayerStatisticsTable::of(tables).transpose()?,
preconditions: PreconditionsTable::of(tables)
.expect("Missing Table 'Preconditions'")?,
property_template: PropertyTemplateTable::of(tables)
.expect("Missing Table 'PropertyTemplate'")?,
rewards: RewardsTable::of(tables).transpose()?,
reward_codes: RewardCodesTable::of(tables).transpose()?,
rebuild_component: RebuildComponentTable::of(tables)
.expect("Missing Table 'RebuildComponent'")?,
rebuild_sections: RebuildSectionsTable::of(tables).transpose()?,
render_comp: RenderComponentTable::of(tables)
.expect("Missing Table 'RenderComponent'")?,
skills: SkillBehaviorTable::of(tables).expect("Missing Table 'SkillBehavior'")?,
smashable_component: SmashableComponentTable::of(tables).transpose()?,
speedchat_menu: SpeedchatMenuTable::of(tables)
.expect("Missing Table 'SpeedchatMenu'")?,
taming_build_puzzles: TamingBuildPuzzlesTable::of(tables)
.expect("Missing Table 'TamingBuildPuzzles'")?,
ug_behavior_sounds: UgBehaviorSoundsTable::of(tables).transpose()?,
vendor_component: VendorComponentTable::of(tables).transpose()?,
whats_cool_item_spotlight: WhatsCoolItemSpotlightTable::of(tables).transpose()?,
whats_cool_news_and_tips: WhatsCoolNewsAndTipsTable::of(tables).transpose()?,
zone_loading_tips: ZoneLoadingTipsTable::of(tables).transpose()?,
zone_table: ZoneTableTable::of(tables).expect("Missing Table 'ZoneTable'")?,
})
}
pub fn get_icon_path(&self, id: i32) -> Option<&Latin1Str> {
let hash = u32::from_ne_bytes(id.to_ne_bytes());
let bucket = self.icons.as_raw().bucket_for_hash(hash);
let col_icon_path = self
.icons
.get_col(IconsColumn::IconPath)
.expect("Missing column 'Icons::IconPath'");
for row in bucket.row_iter() {
let id_field = row.field_at(0).unwrap();
if id_field == Field::Integer(id) {
return row.field_at(col_icon_path).unwrap().into_opt_text();
}
}
None
}
pub fn get_mission_data(&self, id: i32) -> Option<Mission> {
let hash = u32::from_ne_bytes(id.to_ne_bytes());
let bucket = self.missions.as_raw().bucket_for_hash(hash);
let col_mission_icon_id = self
.missions
.get_col(MissionsColumn::MissionIconId)
.expect("Missing column 'Missions::mission_icon_id'");
let col_is_mission = self
.missions
.get_col(MissionsColumn::IsMission)
.expect("Missing column 'Missions::is_mission'");
for row in bucket.row_iter() {
let id_field = row.field_at(0).unwrap();
if id_field == Field::Integer(id) {
let mission_icon_id = row
.field_at(col_mission_icon_id)
.unwrap()
.into_opt_integer();
let is_mission = row
.field_at(col_is_mission)
.unwrap()
.into_opt_boolean()
.unwrap_or(true);
return Some(Mission {
mission_icon_id,
is_mission,
});
}
}
None
}
pub fn get_mission_tasks(&self, id: i32) -> Vec<MissionTask> {
let hash = u32::from_ne_bytes(id.to_ne_bytes());
let bucket = self.mission_tasks.as_raw().bucket_for_hash(hash);
let mut tasks = Vec::with_capacity(4);
let col_icon_id = self
.mission_tasks
.get_col(MissionTasksColumn::IconId)
.expect("Missing column 'MissionTasks::icon_id'");
let col_uid = self
.mission_tasks
.get_col(MissionTasksColumn::Uid)
.expect("Missing column 'MissionTasks::uid'");
for row in bucket.row_iter() {
let id_field = row.field_at(0).unwrap();
if id_field == Field::Integer(id) {
let icon_id = row.field_at(col_icon_id).unwrap().into_opt_integer();
let uid = row.field_at(col_uid).unwrap().into_opt_integer().unwrap();
tasks.push(MissionTask { icon_id, uid })
}
}
tasks
}
pub fn get_object_name_desc(&self, id: i32) -> Option<(String, String)> {
let hash = u32::from_ne_bytes(id.to_ne_bytes());
let table = self.objects.as_raw();
let bucket = table
.bucket_at(hash as usize % table.bucket_count())
.unwrap();
for row in bucket.row_iter() {
let mut fields = row.field_iter();
let id_field = fields.next().unwrap();
if id_field == Field::Integer(id) {
let name = fields.next().unwrap(); let description = fields.nth(2).unwrap(); let display_name = fields.nth(2).unwrap(); let internal_notes = fields.nth(2).unwrap(); let title = match (
name.into_opt_text().filter(is_not_empty),
display_name.into_opt_text().filter(is_not_empty),
) {
(Some(name), Some(display)) if display != name => {
format!("{} ({}) | Object #{}", display.decode(), name.decode(), id)
}
(Some(name), _) => {
format!("{} | Object #{}", name.decode(), id)
}
(None, Some(display)) => {
format!("{} | Object #{}", display.decode(), id)
}
(None, None) => {
format!("Object #{}", id)
}
};
let desc = match (
description.into_opt_text().filter(is_not_empty),
internal_notes.into_opt_text().filter(is_not_empty),
) {
(Some(description), Some(internal_notes)) if description != internal_notes => {
format!("{} ({})", description.decode(), internal_notes.decode(),)
}
(Some(description), _) => {
format!("{}", description.decode())
}
(None, Some(internal_notes)) => {
format!("{}", internal_notes.decode())
}
(None, None) => String::new(),
};
return Some((title, desc));
}
}
None
}
pub fn get_render_image(&self, id: i32) -> Option<&Latin1Str> {
let hash = u32::from_ne_bytes(id.to_ne_bytes());
let table = self.render_comp.as_raw();
let bucket = table
.bucket_at(hash as usize % table.bucket_count())
.unwrap();
for row in bucket.row_iter() {
let mut fields = row.field_iter();
let id_field = fields.next().unwrap();
if id_field == Field::Integer(id) {
let _render_asset = fields.next().unwrap();
let icon_asset = fields.next().unwrap();
if let Field::Text(url) = icon_asset {
return Some(url);
}
}
}
None
}
pub fn get_components(&self, id: i32) -> Components {
let hash = u32::from_ne_bytes(id.to_ne_bytes());
let table = self.comp_reg.as_raw();
let bucket = table
.bucket_at(hash as usize % table.bucket_count())
.unwrap();
let mut comp = Components::default();
for row in bucket.row_iter() {
let mut fields = row.field_iter();
let id_field = fields.next().unwrap();
if id_field == Field::Integer(id) {
let component_type = fields.next().unwrap();
let component_id = fields.next().unwrap();
if let Value::Integer(2) = component_type {
comp.render = component_id.into_opt_integer();
}
}
}
comp
}
}