use std::{mem::MaybeUninit, ptr::NonNull};
use crate::{
cs::{
BlockId, CSEzStateTalkEnv, CSEzStateTalkEvent, CSMenuManImp, FieldInsHandle, MenuJobBase,
MenuType, WorldChrMan,
},
ez_state::{
EzStateEnvironmentQuery, EzStateEvent, EzStateMachineImpl, EzStateRawValue, EzStateValue,
},
};
use shared::{FromStatic, InstanceError};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum EzStateInvokeError {
#[error("Failed to get WorldChrMan instance")]
WorldChrManError(InstanceError),
#[error("Failed to get MenuMan instance")]
MenuManError(InstanceError),
#[error("NPC associated with this talk script does not exist")]
ChrError,
}
#[repr(C)]
pub struct TalkScript {
pub machine_holder: Option<NonNull<TalkScriptMachineHolder>>,
unk8: usize,
pub env: Box<CSEzStateTalkEnv>,
pub event: Box<CSEzStateTalkEvent>,
pub talk_id: i32,
pub npc_talk: Box<CSNpcTalkIns>,
unk30: i32,
unk34: i32,
unk38: bool,
unk3c: i32,
unk40: i32,
pub elapsed_frames: i32,
pub elapsed_time: f32,
unk50: usize,
pub map_id: BlockId,
}
impl TalkScript {
pub fn new(map_id: BlockId, talk_id: i32, field_ins_handle: FieldInsHandle) -> Self {
let mut npc_talk = Box::new(CSNpcTalkIns::new(talk_id, field_ins_handle));
npc_talk.menu_state.owner = Some(NonNull::from_ref(&*npc_talk));
let env = Box::new(CSEzStateTalkEnv::new(talk_id, npc_talk.as_ref()));
let event = Box::new(CSEzStateTalkEvent::new(talk_id, npc_talk.as_ref()));
Self {
machine_holder: None,
unk8: 0,
env,
event,
talk_id,
npc_talk,
unk30: -1,
unk34: -1,
unk38: true,
unk3c: 0,
unk40: 0,
elapsed_frames: 0,
elapsed_time: 0.0,
unk50: 0,
map_id,
}
}
pub unsafe fn event_unchecked(&mut self, args: impl Into<EzStateEvent>) {
let event_args = args.into();
(self.event.vftable.invoke)(&mut self.event, &event_args);
}
pub unsafe fn env_unchecked(
&mut self,
args: impl Into<EzStateEnvironmentQuery>,
) -> EzStateValue {
let env_args = args.into();
let mut env_result = MaybeUninit::<EzStateRawValue>::uninit();
(self.env.vftable.invoke)(&mut self.env, &mut env_result, &env_args);
(&(unsafe { env_result.assume_init() })).into()
}
pub fn event(&mut self, args: impl Into<EzStateEvent>) -> Result<(), EzStateInvokeError> {
self.check_invoke_preconditions()?;
unsafe { self.event_unchecked(args) }
Ok(())
}
pub fn env(
&mut self,
args: impl Into<EzStateEnvironmentQuery>,
) -> Result<EzStateValue, EzStateInvokeError> {
self.check_invoke_preconditions()?;
Ok(unsafe { self.env_unchecked(args) })
}
fn check_invoke_preconditions(&self) -> Result<(), EzStateInvokeError> {
let _ = unsafe { CSMenuManImp::instance() }.map_err(EzStateInvokeError::MenuManError)?;
let world_chr_man =
unsafe { WorldChrMan::instance() }.map_err(EzStateInvokeError::WorldChrManError)?;
let _ = world_chr_man
.chr_ins_by_handle(&self.npc_talk.base.field_ins_handle)
.ok_or(EzStateInvokeError::ChrError)?;
Ok(())
}
}
#[repr(C)]
pub struct TalkScriptMachineHolder {
pub machine: NonNull<EzStateMachineImpl>,
unk8: usize,
unk10: usize,
pub talk_id: i32,
pub field_ins_handle: FieldInsHandle,
unk24: bool,
pub owner: NonNull<TalkScript>,
}
#[repr(C)]
#[derive(Default)]
pub struct OpenMenuJob {
pub finalize_callback_job: Option<NonNull<MenuJobBase>>,
pub input_data_count: u64,
}
#[repr(C)]
#[derive(Default)]
pub struct NpcMenuState {
pub open_menu_job: OpenMenuJob,
pub current_open_menu: MenuType,
pub owner: Option<NonNull<CSNpcTalkIns>>,
}
#[repr(C)]
pub struct CSTalkIns {
pub talk_id: i32,
unk4: i32,
unk8: i32,
unkc: i32,
unk10: i32,
unk14: u8,
pub talk_interrupt_reason: i32,
pub talk_param_id: i32,
unk20: i32,
unk24: f32,
unk28: i32,
unk2c: bool,
unk2d: bool,
unk2e: bool,
unk2f: bool,
unk30: bool,
unk34: i32,
unk38: bool,
unk39: bool,
unk3a: bool,
unk3b: bool,
pub field_ins_handle: FieldInsHandle,
unk48: usize,
unk50: f32,
unk54: i32,
unk58: f32,
pub event_flag_id: i32,
unk60: u8,
unk64: i32,
unk68: u8,
unk6c: i32,
}
impl CSTalkIns {
pub fn new(talk_id: i32, field_ins_handle: FieldInsHandle) -> Self {
CSTalkIns {
talk_id,
unk4: -1,
unk8: -1,
unkc: -1,
unk10: 0,
unk14: 0u8,
talk_interrupt_reason: -1,
talk_param_id: -1,
unk20: -1,
unk24: -1.0,
unk28: -1,
unk2c: false,
unk2d: false,
unk2e: false,
unk2f: false,
unk30: false,
unk34: -1,
unk38: true,
unk39: false,
unk3a: false,
unk3b: false,
field_ins_handle,
unk48: 0,
unk50: -1.0,
unk54: 0,
unk58: 0.0,
event_flag_id: -1,
unk60: 0u8,
unk64: 0,
unk68: 0u8,
unk6c: 0,
}
}
}
#[repr(C)]
pub struct CSNpcTalkIns {
pub base: CSTalkIns,
unk70: usize,
unk78: i32,
unk7c: f32,
unk80: i32,
unk84: f32,
unk88: usize,
unk90: usize,
pub menu_state: Box<NpcMenuState>,
unka0: usize,
talk_dynamic_chr_ctrl: usize,
unkb0: i16,
unkb4: i32,
unkb8: i32,
unkbc: f32,
}
impl CSNpcTalkIns {
pub fn new(talk_id: i32, field_ins_handle: FieldInsHandle) -> Self {
Self {
base: CSTalkIns::new(talk_id, field_ins_handle),
unk70: 0,
unk78: 0,
unk7c: -1.0,
unk80: 0,
unk84: -1.0,
unk88: 0,
unk90: 0,
menu_state: Box::default(),
unka0: 0,
talk_dynamic_chr_ctrl: 0,
unkb0: 0,
unkb4: 0,
unkb8: 0,
unkbc: 5.0,
}
}
}