use std::ptr::NonNull;
use vtable_rs::VPtr;
use crate::cs::{CSEzTask, CSEzVoidTask};
use crate::position::HavokPosition;
use crate::{ChainingMap, DLList, DLMap, UnkDLTree};
use crate::{DLVector, cs::ChrIns};
use shared::{F32Vector4, OwnedPtr, Subclass, Superclass};
use super::{BlockId, ChrCam, FieldInsHandle, NetChrSync, PlayerIns};
#[repr(C)]
#[shared::singleton("WorldChrMan")]
pub struct WorldChrMan {
vftable: usize,
unk8: usize,
pub world_area_chr: [WorldAreaChr<ChrIns>; 28],
pub world_block_chr: [WorldBlockChr<ChrIns>; 192],
pub world_grid_area_chr: [WorldGridAreaChr; 6],
pub world_area_info_owner: usize,
pub world_area_chr_list_count: u32,
unk10d9c: u32,
pub world_area_chr_ptr: usize,
pub world_block_chr_list_count: u32,
unk10dac: u32,
pub world_block_chr_ptr: usize,
pub world_grid_area_chr_list_count: u32,
unk10dbc: u32,
pub world_grid_area_chr_ptr: usize,
pub world_area_list: [OwnedPtr<WorldAreaChrBase>; 34],
pub world_area_list_count: u32,
unk10edc: u32,
pub player_chr_set: ChrSet<PlayerIns>,
pub ghost_chr_set: ChrSet<ChrIns>,
pub summon_buddy_chr_set: ChrSet<ChrIns>,
pub debug_chr_set: ChrSet<ChrIns>,
pub open_field_chr_set: OpenFieldChrSet,
pub chr_set_holder_count: u32,
pub chr_set_holders: [ChrSetHolder<ChrIns>; 196],
pub null_chr_set_holder: ChrSetHolder<ChrIns>,
pub chr_sets: [Option<OwnedPtr<ChrSet<ChrIns>>>; 196],
pub null_chr_set: Option<OwnedPtr<ChrSet<ChrIns>>>,
pub player_grid_area: Option<NonNull<WorldGridAreaChr>>,
pub main_player: Option<OwnedPtr<PlayerIns>>,
pub sinner_hunter: Option<OwnedPtr<PlayerIns>>,
unk_block_id_1: BlockId,
unk_block_id_2: BlockId,
unk1e520: [u8; 0x18],
pub summon_buddy_manager: OwnedPtr<SummonBuddyManager>,
unk1e540: usize,
unk1e548: usize,
unk1e550: usize,
unk1e558: u32,
unk1e55c: f32,
unk1e560: [u8; 0x80],
pub net_chr_sync: OwnedPtr<NetChrSync>,
unk1e5e8: usize,
unk1e5f0: usize,
unk1e5f8: usize,
unk1e600: usize,
unk1e608: [u8; 0x40],
pub debug_chr_creator: OwnedPtr<CSDebugChrCreator>,
unk1e650: usize,
unk1e658: usize,
unk1e660: usize,
unk1e668: usize,
unk1e670: [u8; 0x18],
unk1e688: usize,
unk1e690: usize,
unk1e698: usize,
unk1e6a0: usize,
unk1e6a8: usize,
unk1e6b0: usize,
unk1e6b8: [u8; 0x628],
pub chr_cam: Option<NonNull<ChrCam>>,
unk1ece8: [u8; 0x4e8],
pub chr_inses_by_distance: DLVector<ChrInsDistanceEntry>,
unk1f1f0: [u8; 0x10],
pub chr_inses_by_update_priority: DLVector<NonNull<ChrIns>>,
pub omission_update_budget_near: u32,
pub omission_update_budget_far: u32,
unk1f228: [u8; 0x28],
chr_ins_calc_update_info_perf_begin_task: CSEzVoidTask<CSEzTask, Self>,
chr_ins_calc_update_info_perf_end_task: CSEzVoidTask<CSEzTask, Self>,
chr_ins_ailogic_perf_begin_task: CSEzVoidTask<CSEzTask, Self>,
chr_ins_ailogic_perf_end_task: CSEzVoidTask<CSEzTask, Self>,
chr_ins_pre_behavior_task: CSEzVoidTask<CSEzTask, Self>,
chr_ins_pre_behavior_safe_task2: CSEzVoidTask<CSEzTask, Self>,
chr_ins_pre_cloth_task: CSEzVoidTask<CSEzTask, Self>,
chr_ins_pre_cloth_safe_task: CSEzVoidTask<CSEzTask, Self>,
chr_ins_post_physics_task: CSEzVoidTask<CSEzTask, Self>,
chr_ins_post_physics_safe_task: CSEzVoidTask<CSEzTask, Self>,
}
impl WorldChrMan {
pub fn chr_ins_by_handle(&self, handle: &FieldInsHandle) -> Option<&ChrIns> {
let chr_set_index = handle.selector.container() as usize;
let chr_set = self.chr_sets.get(chr_set_index)?.as_ref()?;
chr_set.chr_ins_by_handle(handle)
}
pub fn chr_ins_by_handle_mut(&mut self, handle: &FieldInsHandle) -> Option<&mut ChrIns> {
let chr_set_index = handle.selector.container() as usize;
let chr_set = self.chr_sets.get_mut(chr_set_index)?.as_mut()?;
chr_set.chr_ins_by_handle_mut(handle)
}
pub fn spawn_debug_character(&mut self, request: &ChrDebugSpawnRequest) {
let mut name_bytes = format!("c{:0>4}", request.chr_id)
.encode_utf16()
.collect::<Vec<_>>();
name_bytes.resize(0x20, 0x0);
self.debug_chr_creator
.init_data
.name
.clone_from_slice(name_bytes.as_mut());
self.debug_chr_creator.init_data.chara_init_param_id = request.chara_init_param_id;
self.debug_chr_creator.init_data.npc_param_id = request.npc_param_id;
self.debug_chr_creator.init_data.npc_think_param_id = request.npc_think_param_id;
self.debug_chr_creator.init_data.event_entity_id = request.event_entity_id;
self.debug_chr_creator.init_data.talk_id = request.talk_id;
self.debug_chr_creator.init_data.spawn_position =
F32Vector4(request.pos_x, request.pos_y, request.pos_z, 0.0);
self.debug_chr_creator.spawn = true;
}
}
pub struct ChrDebugSpawnRequest {
pub chr_id: i32,
pub chara_init_param_id: i32,
pub npc_param_id: i32,
pub npc_think_param_id: i32,
pub event_entity_id: i32,
pub talk_id: i32,
pub pos_x: f32,
pub pos_y: f32,
pub pos_z: f32,
}
#[repr(C)]
pub struct ChrInsDistanceEntry {
pub chr_ins: NonNull<ChrIns>,
pub distance: f32,
_unkc: u32,
}
#[repr(C)]
pub struct CSDebugChrCreator {
vftable: usize,
stepper_fns: usize,
unk10: usize,
unk18_tree: UnkDLTree<()>,
unk30: [u8; 0x14],
pub spawn: bool,
unk45: [u8; 0x3],
unk48: [u8; 0x68],
pub init_data: CSDebugChrCreatorInitData,
pub last_created_chr: Option<NonNull<ChrIns>>,
unk1b8: usize,
}
#[repr(C)]
pub struct CSDebugChrCreatorInitData {
pub spawn_position: F32Vector4,
spawn_rotation: F32Vector4,
unk20: F32Vector4,
spawn_scale: F32Vector4,
pub npc_param_id: i32,
pub npc_think_param_id: i32,
pub event_entity_id: i32,
pub talk_id: i32,
pub name: [u16; 0x20],
unk90: usize,
name_pointer: usize,
unka0: usize,
unka8: usize,
name_capacity: usize,
unkb8: usize,
unkc0: usize,
enemy_type: u8,
hamari_simulate: bool,
unkca: [u8; 0x2],
pub chara_init_param_id: i32,
spawn_manipulator_type: u32,
unkd4: [u8; 0x18],
spawn_count: u32,
unkf0: [u8; 0x10],
}
#[repr(C)]
pub struct ChrSetHolder<T>
where
T: Subclass<ChrIns> + 'static,
{
pub chr_set: NonNull<ChrSet<T>>,
pub chr_set_index: u32,
_padc: u32,
pub world_block_chr: NonNull<WorldBlockChr<T>>,
}
#[repr(C)]
#[derive(Subclass)]
pub struct WorldAreaChr<T>
where
T: Subclass<ChrIns> + 'static,
{
pub base: WorldAreaChrBase,
pub world_area_info: usize,
unk18: u32,
unk1c: u32,
pub world_block_chr: NonNull<WorldBlockChr<T>>,
}
#[repr(C)]
#[derive(Superclass)]
pub struct WorldAreaChrBase {
vftable: usize,
pub world_area_info: usize,
}
#[repr(C)]
pub struct WorldBlockChr<T>
where
T: Subclass<ChrIns> + 'static,
{
vftable: usize,
pub world_block_info1: usize,
unk10: [u8; 0x68],
pub chr_set: ChrSet<T>,
unkd0: [u8; 0x40],
pub world_block_info2: usize,
pub chr_set_ptr: NonNull<ChrSet<T>>,
allocator: usize,
unk128: [u8; 0x30],
pub block_id: BlockId,
unk15c: u32,
}
#[vtable_rs::vtable]
trait ChrSetVmt {
fn get_capacity(&self) -> u32;
fn safe_get_chr_ins_by_index(&self, index: u32) -> Option<NonNull<ChrIns>>;
fn get_chr_ins_by_index(&self, index: u32) -> Option<NonNull<ChrIns>>;
fn get_chr_ins_by_handle(&self, handle: FieldInsHandle) -> Option<NonNull<ChrIns>>;
fn safe_get_chr_set_entry_by_index(&self, index: u32) -> Option<NonNull<ChrSetEntry<ChrIns>>>;
fn get_chr_set_entry_by_index(&self, index: u32) -> Option<NonNull<ChrSetEntry<ChrIns>>>;
fn get_chr_set_entry_by_handle(
&self,
handle: FieldInsHandle,
) -> Option<NonNull<ChrSetEntry<ChrIns>>>;
fn get_index_by_handle(&self, handle: FieldInsHandle) -> u32;
fn free_chr_list(&mut self);
fn unk48(&mut self);
fn unk50(&mut self);
fn unk58(&mut self, param_2: usize);
fn unk60(&mut self, param_2: usize);
fn unk68(&mut self, param_2: usize, param_3: usize, param_4: u8, param_5: u8);
}
#[repr(C)]
#[derive(Superclass)]
pub struct ChrSet<T>
where
T: Subclass<ChrIns> + 'static,
{
vftable: VPtr<dyn ChrSetVmt, Self>,
pub index: i32,
unkc: i32,
pub capacity: u32,
_pad14: u32,
pub entries: NonNull<ChrSetEntry<T>>,
unk20: i32,
_pad24: u32,
pub entity_id_mapping: DLMap<u32, NonNull<ChrSetEntry<T>>>,
pub group_id_mapping: DLMap<u32, NonNull<ChrSetEntry<T>>>,
}
impl<T> ChrSet<T>
where
T: Subclass<ChrIns> + 'static,
{
pub fn get_capacity(&self) -> u32 {
(self.vftable.get_capacity)(self)
}
pub fn chr_ins_by_handle(&self, field_ins_handle: &FieldInsHandle) -> Option<&T> {
(self.vftable.get_chr_ins_by_handle)(self, field_ins_handle.to_owned())
.and_then(|chr_ins| unsafe { chr_ins.as_ref() }.as_subclass())
}
pub fn chr_ins_by_handle_mut(&mut self, field_ins_handle: &FieldInsHandle) -> Option<&mut T> {
(self.vftable.get_chr_ins_by_handle)(self, field_ins_handle.to_owned())
.and_then(|mut chr_ins| unsafe { chr_ins.as_mut() }.as_subclass_mut())
}
pub fn characters(&self) -> impl Iterator<Item = &mut T> {
let mut current = self.entries;
let end = unsafe { current.add(self.capacity as usize) };
std::iter::from_fn(move || {
while current != end {
let chr_ins = unsafe { current.as_mut().chr_ins };
current = unsafe { current.add(1) };
let Some(mut chr_ins) = chr_ins else {
continue;
};
return Some(unsafe { chr_ins.as_mut() });
}
None
})
}
}
#[repr(C)]
pub struct ChrSetEntry<T>
where
T: Subclass<ChrIns>,
{
pub chr_ins: Option<NonNull<T>>,
pub chr_load_status: ChrLoadStatus,
pub chr_update_type: ChrUpdateType,
pub entry_flags: u8,
_padb: [u8; 5],
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ChrLoadStatus {
Unloaded = 0,
Initializing = 1,
Active = 2,
NetworkInitializing = 3,
ReadyForActivation = 4,
Unloading = 5,
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ChrUpdateType {
Local = 0,
Unknown1 = 1,
Unknown2 = 2,
Unknown3 = 3,
Remote = 4,
}
#[repr(C)]
#[derive(Subclass)]
pub struct OpenFieldChrSet {
pub base: ChrSet<ChrIns>,
unk58: UnkDLTree<()>,
unk70: f32,
pad74: u32,
list1: [OpenFieldChrSetList1Entry; 1500],
unk5e38: u32,
unk5e3c: u32,
unk5e40: u32,
unk5e44: u32,
list2: [OpenFieldChrSetList2Entry; 1500],
unkbc08: u64,
unkbc10: u64,
}
#[repr(C)]
pub struct OpenFieldChrSetList1Entry {
unk0: u64,
pub chr_ins: NonNull<ChrIns>,
}
#[repr(C)]
pub struct OpenFieldChrSetList2Entry {
unk0: u64,
unk8: u32,
unkc: u32,
}
#[repr(C)]
#[derive(Subclass)]
pub struct WorldGridAreaChr {
pub base: WorldAreaChrBase,
pub world_grid_area_info: usize,
unk_tree: UnkDLTree<()>,
}
#[repr(C)]
pub struct SummonBuddyManager {
pub trigger_speffect_to_buddy_map: ChainingMap<i32, i32>,
pub request_summon_speffect_id: i32,
pub active_summon_speffect_id: u32,
pub disappear_requested: bool,
pub chr_set: NonNull<ChrSet<ChrIns>>,
pub buddy_stone_entity_id: u32,
pub active_summmon_buddy_stone_entity_id: u32,
unk40: usize,
unk48: usize,
unk50: usize,
unk58: usize,
unk60: usize,
unk68: usize,
pub groups: DLMap<i32, DLList<SummonBuddyGroup>>,
unk88: i32,
pub buddy_disappear_delay_sec: f32,
pub item_use_cooldown_timer: f32,
unk94: u32,
unk98: u32,
unk9c: f32,
pub spawn_origin: HavokPosition,
pub spawn_rotation: f32,
pub player_has_alive_summon: bool,
pub is_within_activation_range: bool,
pub prev_is_within_activation_range: bool,
pub is_within_warn_range: bool,
pub prev_is_within_warn_range: bool,
pub last_buddy_slot: u32,
unkc0: f32,
unkc4: f32,
pub eliminate_target_entries: DLMap<FieldInsHandle, CSBuddyStoneEliminateTargetCalc>,
pub requested_summon_goods_id: i32,
pub active_summon_goods_id: i32,
pub warp_manager: OwnedPtr<SummonBuddyWarpManager>,
unkf0: usize,
pub debug_buddy_stone_param_id: u32,
unk100: usize,
unk108: usize,
}
#[repr(C)]
pub struct SummonBuddyWarpManager {
pub entries: DLMap<FieldInsHandle, SummonBuddyWarpEntry>,
pub trigger_time_ray_block: f32,
pub trigger_dist_to_player: f32,
pub trigger_threshold_time_path_stacked: f32,
pub trigger_threshold_range_path_stacked: f32,
unk28: u32,
unk2c: i32,
unk30: bool,
}
#[repr(C)]
pub struct SummonBuddyWarpEntry {
unk0: usize,
pub warp_stage: SummonBuddyWarpStage,
unk10: usize,
pub target_position: HavokPosition,
pub q_target_rotation: F32Vector4,
pub flags: u32,
pub time_ray_blocked: f32,
unk48: f32,
unk50: F32Vector4,
unk60: f32,
pub time_path_stacked: f32,
unk68: usize,
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum SummonBuddyWarpStage {
None = 0,
RequestWarp = 1,
Warping = 2,
FadeIn = 3,
}
#[repr(C)]
pub struct SummonBuddyGroup {
pub chr_ins: NonNull<ChrIns>,
unk8: bool,
pub has_mount: bool,
pub buddy_param_id: i32,
pub buddy_stone_param_id: i32,
pub doping_sp_effect_id: i32,
pub dopping_level_sp_effect_id: u32,
pub spawn_animation: u32,
pub warp_requested: bool,
pub disappear_requested: bool,
pub disappear_delay_sec: f32,
pub has_spawn_point: bool,
pub disable_pc_target_share: bool,
pub follow_type: u8,
pub is_remote: bool,
pub has_mogh_great_rune_buff: bool,
unk2d: bool,
}
#[repr(C)]
pub struct CSBuddyStoneEliminateTargetCalc {
vftable: u64,
pub owner_field_ins_handle: FieldInsHandle,
pub buddy_stone_param_id: i32,
pub target_event_entity_id: i32,
pub target_in_range: bool,
pub range_check_counter: u32,
}
#[repr(C)]
#[derive(Superclass)]
#[superclass(children(NearEnemyFinder))]
pub struct IChrFinder {
vtable: VPtr<dyn IChrFinderVmt, Self>,
pub found: NonNull<ChrIns>,
pub distance: f32,
}
#[vtable_rs::vtable]
trait IChrFinderVmt {
fn destructor(&mut self);
fn reached_end(&mut self, chr_set_index: u32) -> bool;
fn run(&mut self, chr_ins: &ChrIns);
}
#[repr(C)]
#[derive(Subclass)]
pub struct NearEnemyFinder {
pub chr_finder: IChrFinder,
pub search_origin: HavokPosition,
pub team_type: u8,
unk34: f32,
unk38: f32,
}
#[cfg(test)]
mod test {
use std::mem::size_of;
use crate::cs::*;
#[test]
fn proper_sizes() {
assert_eq!(0x20, size_of::<CSBuddyStoneEliminateTargetCalc>());
assert_eq!(0x30, size_of::<SummonBuddyGroup>());
assert_eq!(0x70, size_of::<SummonBuddyWarpEntry>());
assert_eq!(0x38, size_of::<SummonBuddyWarpManager>());
assert_eq!(0x110, size_of::<SummonBuddyManager>());
assert_eq!(0x18, size_of::<IChrFinder>());
assert_eq!(0x40, size_of::<NearEnemyFinder>());
}
}