use crate::fighter::WiiRDFrameSpeedModifier;
use crate::high_level_fighter;
use crate::high_level_fighter::{CollisionBoxValues, SectionScriptAst};
use crate::sakurai::fighter_data::ArcFighterData;
use crate::script::{Requirement, VariableDataType};
use crate::script_ast::variable_ast::{
InternalConstantInt, LongtermAccessBool, LongtermAccessFloat, LongtermAccessInt,
RandomAccessBool, RandomAccessFloat, RandomAccessInt, VariableAst,
};
use crate::script_ast::{
ArmorType, BinaryExpression, Block, ComparisonOperator, DisableMovement, EdgeSlide, EventAst,
Expression, FloatValue, GrabBoxArguments, HitBoxArguments, HurtBoxState, Interrupt, Iterations,
LedgeGrabEnable, ScriptAst, SpecialHitBoxArguments, SpecifyThrow, ThrowUse,
};
use std::collections::HashMap;
pub struct ScriptRunner<'a> {
pub subaction_name: String,
pub wiird_frame_speed_modifiers: &'a [WiiRDFrameSpeedModifier],
pub call_stacks: Vec<CallStack<'a>>,
pub fighter_scripts: &'a [&'a ScriptAst],
pub common_scripts: &'a [&'a ScriptAst],
pub section_scripts: &'a [SectionScriptAst],
pub call_every_frame: HashMap<i32, CallEveryFrame<'a>>,
pub visited_gotos: Vec<i32>,
pub subaction_index: usize,
pub frame_index: f32, pub animation_index: f32, pub frame_count: usize, pub interruptible: bool,
pub hitboxes: [Option<ScriptCollisionBox>; 7],
pub hurtbox_state_all: HurtBoxState,
pub hurtbox_states: HashMap<i32, HurtBoxState>,
pub ledge_grab_enable: LedgeGrabEnable,
pub frame_speed_modifier: f32,
pub tag_display: bool,
pub x: f32,
pub y: f32,
pub x_vel: f32,
pub y_vel: f32,
pub x_vel_modify: VelModify,
pub y_vel_modify: VelModify,
pub disable_movement: DisableMovement,
pub armor_type: ArmorType,
pub armor_tolerance: f32,
pub damage: f32,
pub airbourne: bool,
pub edge_slide: EdgeSlide, pub reverse_direction: bool,
pub change_subaction: ChangeSubaction,
pub invisible_bones: Vec<i32>,
pub interrupts: Vec<Interrupt>,
pub bad_interrupts: Vec<Interrupt>,
pub hitbox_sets_rehit: [bool; 10],
pub slope_contour_stand: Option<i32>,
pub slope_contour_full: Option<(i32, i32)>,
pub rumble: Option<(i32, i32)>,
pub rumble_loop: Option<(i32, i32)>,
pub grab_interrupt_damage: Option<i32>,
pub throw: Option<SpecifyThrow>,
pub throw_activate: bool,
pub jumps_used: i32,
pub wall_jump_count: i32,
pub wall_jump_interval: i32,
pub footstool_count: i32,
pub fall_time: i32,
pub swim_time: i32,
pub lip_stick_refresh: i32,
pub curry_remaining_time: i32,
pub curry_angle2: i32,
pub star_remaining_time: i32,
pub mushroom_remaining_time: i32,
pub lightning_remaining_time: i32,
pub size_flag: i32,
pub metal_block_remaining_time: i32,
pub combo_count: i32,
pub bubble_time: i32,
pub attacks_performed: i32,
pub costume_id: i32,
pub hitstun_frames_remaining: i32,
pub meteor_cancel_window: i32,
pub missed_techs: i32,
pub tether_count: i32,
pub temp1: i32,
pub temp2: i32,
pub longterm_access_int: Vec<i32>,
pub special_landing_lag: f32,
pub special_fall_mobility_multiplier: f32,
pub shield_charge: f32,
pub curry_angle1: f32,
pub curry_randomness: f32,
pub longterm_access_float: Vec<f32>,
pub is_dead: bool,
pub cannot_die: bool,
pub automatic_footstool: bool,
pub has_final: bool,
pub has_final_aura: bool,
pub has_curry: bool,
pub has_hammer: bool,
pub hit_by_paralyze: bool,
pub has_screw_attack: bool,
pub stamina_dead: bool,
pub has_tag: bool,
pub can_not_ledge_grab: bool,
pub can_not_teeter: bool,
pub velocity_ignore_hitstun: bool,
pub deflection: bool,
pub longterm_access_bool: Vec<bool>,
pub throw_data_param1: i32,
pub throw_data_param2: i32,
pub throw_data_param3: i32,
pub random_access_int: Vec<i32>,
pub enable_turn_when_below_zero: f32,
pub random_access_float: Vec<f32>,
pub character_float: bool,
pub enable_fast_fall: bool,
pub shorthop: bool,
pub enable_action_transition: bool,
pub specials_movement: bool,
pub enable_glide: bool,
pub enable_jab_loop: bool,
pub enable_auto_jab: bool,
pub enable_jab_end: bool,
pub landing_lag: bool,
pub random_access_bool: Vec<bool>,
}
pub struct CallStack<'a> {
pub calls: Vec<Call<'a>>,
pub wait_until: f32,
}
pub struct Call<'a> {
pub block: &'a Block,
pub else_branch: Option<&'a Block>,
pub index: usize,
pub subroutine: bool,
pub external: bool,
pub if_statement: bool,
pub execute: bool,
}
pub enum ChangeSubaction {
Continue,
InfiniteLoop,
ChangeSubaction(i32),
ChangeSubactionRestartFrame(i32),
Interrupt(i32),
}
#[derive(Clone, Debug)]
pub struct ScriptCollisionBox {
pub bone_index: i16,
pub hitbox_id: u8,
pub x_offset: f32,
pub y_offset: f32,
pub z_offset: f32,
pub size: f32,
pub values: CollisionBoxValues,
pub interpolate: bool,
}
impl ScriptCollisionBox {
fn from_hitbox(args: &HitBoxArguments, interpolate: bool, damage: f32) -> ScriptCollisionBox {
ScriptCollisionBox {
bone_index: args.bone_index,
hitbox_id: args.hitbox_id,
x_offset: args.x_offset,
y_offset: args.y_offset,
z_offset: args.z_offset,
size: args.size,
values: CollisionBoxValues::from_hitbox(args, damage),
interpolate,
}
}
fn from_special_hitbox(
args: &SpecialHitBoxArguments,
interpolate: bool,
damage: f32,
) -> ScriptCollisionBox {
ScriptCollisionBox {
bone_index: args.hitbox_args.bone_index,
hitbox_id: args.hitbox_args.hitbox_id,
x_offset: args.hitbox_args.x_offset,
y_offset: args.hitbox_args.y_offset,
z_offset: args.hitbox_args.z_offset,
size: args.hitbox_args.size,
values: CollisionBoxValues::from_special_hitbox(args, damage),
interpolate,
}
}
fn from_grabbox(args: &GrabBoxArguments, interpolate: bool) -> ScriptCollisionBox {
ScriptCollisionBox {
bone_index: args.bone_index as i16,
hitbox_id: args.hitbox_id as u8,
x_offset: args.x_offset,
y_offset: args.y_offset,
z_offset: args.z_offset,
size: args.size,
values: CollisionBoxValues::from_grabbox(args),
interpolate,
}
}
fn is_hit(&self) -> bool {
match self.values {
CollisionBoxValues::Hit(_) => true,
CollisionBoxValues::Grab(_) => false,
}
}
fn is_grab(&self) -> bool {
match self.values {
CollisionBoxValues::Hit(_) => false,
CollisionBoxValues::Grab(_) => true,
}
}
}
impl<'a> ScriptRunner<'a> {
pub fn new(
subaction_index: usize,
wiird_frame_speed_modifiers: &'a [WiiRDFrameSpeedModifier],
subaction_scripts: &[&'a ScriptAst],
fighter_scripts: &'a [&'a ScriptAst],
common_scripts: &'a [&'a ScriptAst],
section_scripts: &'a [SectionScriptAst],
init_hack_script: &Block,
fighter_data: &ArcFighterData,
subaction_name: String,
) -> ScriptRunner<'a> {
let mut call_stacks = vec![];
for script in subaction_scripts {
let calls = vec![Call {
block: &script.block,
else_branch: None,
index: 0,
subroutine: false,
external: false,
if_statement: false,
execute: true,
}];
call_stacks.push(CallStack {
calls,
wait_until: -1.0,
});
}
let ledge_grab_enable = match subaction_name.as_ref() {
"JumpF" | "JumpB" | "JumpAerialF" | "JumpAerialB" | "FallF" | "FallB" | "Fall"
| "FallAerialF" | "FallAerialB" | "FallAerial" | "FallSpecialF" | "FallSpecialB"
| "FallSpecial" => LedgeGrabEnable::EnableInFront,
_ => LedgeGrabEnable::Disable,
};
let mut invisible_bones = vec![];
for reference in &fighter_data.model_visibility.references {
for default in &fighter_data.model_visibility.defaults {
if let Some(bone_switch) =
reference.bone_switches.get(default.switch_index as usize)
{
if let Some(group) = bone_switch.groups.get(default.group_index as usize) {
for bone in &group.bones {
invisible_bones.push(*bone);
}
}
}
}
}
for reference in &fighter_data.model_visibility.references {
for default in &fighter_data.model_visibility.defaults {
if let Some(bone_switch) =
reference.bone_switches.get(default.switch_index as usize)
{
if let Some(group) = bone_switch.groups.get(default.group_index as usize) {
for bone in &group.bones {
invisible_bones.retain(|x| x != bone);
}
}
}
}
}
let mut runner = ScriptRunner {
subaction_name,
wiird_frame_speed_modifiers,
call_stacks,
fighter_scripts,
common_scripts,
section_scripts,
subaction_index,
ledge_grab_enable,
call_every_frame: HashMap::new(),
visited_gotos: vec![],
frame_index: 0.0,
animation_index: 0.0,
frame_count: 0,
interruptible: false,
hitboxes: [None, None, None, None, None, None, None],
hurtbox_state_all: HurtBoxState::Normal,
hurtbox_states: HashMap::new(),
frame_speed_modifier: 1.0,
tag_display: true,
x: 0.0,
y: 0.0,
x_vel: 0.0,
y_vel: 0.0,
x_vel_modify: VelModify::None,
y_vel_modify: VelModify::None,
disable_movement: DisableMovement::Enable,
armor_type: ArmorType::None,
armor_tolerance: 0.0,
damage: 0.0,
airbourne: false,
edge_slide: EdgeSlide::SlideOff,
reverse_direction: false,
change_subaction: ChangeSubaction::Continue,
interrupts: vec![],
bad_interrupts: vec![],
hitbox_sets_rehit: [false; 10],
slope_contour_stand: None,
slope_contour_full: None,
rumble: None,
rumble_loop: None,
grab_interrupt_damage: None,
throw: None,
throw_activate: false,
invisible_bones,
jumps_used: 0,
wall_jump_count: 0,
wall_jump_interval: 0,
footstool_count: 0,
fall_time: 0,
swim_time: 0,
lip_stick_refresh: 0,
curry_remaining_time: 0,
curry_angle2: 0,
star_remaining_time: 0,
mushroom_remaining_time: 0,
lightning_remaining_time: 0,
size_flag: 0,
metal_block_remaining_time: 0,
combo_count: 0,
bubble_time: 0,
attacks_performed: 0,
costume_id: 0,
hitstun_frames_remaining: 0,
meteor_cancel_window: 0,
missed_techs: 0,
tether_count: 0,
temp1: 0,
temp2: 0,
longterm_access_int: vec![0; 0x500],
special_landing_lag: 0.0,
special_fall_mobility_multiplier: 0.0,
shield_charge: 0.0,
curry_angle1: 0.0,
curry_randomness: 0.0,
longterm_access_float: vec![0.0; 0x500],
is_dead: false,
cannot_die: false,
automatic_footstool: false,
has_final: false,
has_final_aura: false,
has_curry: false,
has_hammer: false,
hit_by_paralyze: false,
has_screw_attack: false,
stamina_dead: false,
has_tag: false,
can_not_ledge_grab: false,
can_not_teeter: false,
velocity_ignore_hitstun: false,
deflection: false,
longterm_access_bool: vec![false; 0x500],
throw_data_param1: 0,
throw_data_param2: 0,
throw_data_param3: 0,
random_access_int: vec![0; 0x100],
enable_turn_when_below_zero: 0.0,
random_access_float: vec![0.0; 0x100],
character_float: false,
enable_fast_fall: false,
shorthop: false,
enable_action_transition: false,
specials_movement: false,
enable_glide: false,
enable_jab_loop: false,
enable_auto_jab: false,
enable_jab_end: false,
landing_lag: false,
random_access_bool: vec![false; 0x100], };
for event in &init_hack_script.events {
runner.step_event(event, false, &[], &[], &[]);
}
runner.step_script();
runner
}
pub fn step(&mut self) {
let mut fsms = vec![];
for fsm in self.wiird_frame_speed_modifiers {
if !fsm.action
&& fsm.action_subaction_id as usize == self.subaction_index
&& (self.frame_index as u16) >= fsm.frame as u16
{
fsms.push(fsm);
}
}
fsms.sort_by_key(|x| x.frame);
if let Some(fsm) = fsms.last() {
self.frame_speed_modifier = fsm.frame_speed;
}
self.frame_index += self.frame_speed_modifier;
self.animation_index += self.frame_speed_modifier;
self.frame_count += 1;
self.step_script();
}
fn step_script(&mut self) {
for rehit in self.hitbox_sets_rehit.iter_mut() {
*rehit = false;
}
self.throw_activate = false;
self.rumble = None; self.visited_gotos.clear();
self.x_vel_modify = VelModify::None;
self.y_vel_modify = VelModify::None;
self.reverse_direction = false;
for hitbox in &mut self.hitboxes.iter_mut().flatten() {
hitbox.interpolate = true;
}
for interrupt in self.interrupts.clone() {
if self.evaluate_expression(&interrupt.test).unwrap_bool() {
self.change_subaction = ChangeSubaction::Interrupt(interrupt.action);
}
}
for call_every_frame in self.call_every_frame.values() {
let calls = vec![Call {
block: call_every_frame.block,
else_branch: None,
index: 0,
subroutine: false,
external: call_every_frame.external,
if_statement: false,
execute: true,
}];
self.call_stacks.push(CallStack {
calls,
wait_until: -1.0,
});
}
for i in 0..self.call_stacks.len() {
while !self.call_stacks[i].calls.is_empty() {
if self.frame_index < self.call_stacks[i].wait_until {
break;
}
let call = self.call_stacks[i].calls.last().unwrap();
if let Some(event) = call.block.events.get(call.index) {
self.call_stacks[i].calls.last_mut().unwrap().index += 1;
let external = self.call_stacks[i].calls.last().unwrap().external;
if self.call_stacks[i].calls.last().unwrap().execute {
match self.step_event(
event,
external,
self.fighter_scripts,
self.common_scripts,
self.section_scripts,
) {
StepEventResult::WaitUntil(value) => {
self.call_stacks[i].wait_until = value;
}
StepEventResult::NewForLoop { block, iterations } => {
for _ in 0..iterations {
self.call_stacks[i].calls.push(Call {
block,
else_branch: None,
index: 0,
subroutine: false,
if_statement: false,
execute: true,
external,
});
}
}
StepEventResult::NewCall { block } => {
self.call_stacks[i].calls.push(Call {
block,
else_branch: None,
index: 0,
subroutine: false,
if_statement: false,
execute: true,
external,
});
}
StepEventResult::NewIfStatement {
then_branch,
else_branch,
execute,
} => {
self.call_stacks[i].calls.push(Call {
block: then_branch,
else_branch,
index: 0,
subroutine: false,
if_statement: true,
execute,
external,
});
}
StepEventResult::IfStatementDisableExecution => {
if self.call_stacks[i].calls.last().unwrap().if_statement {
self.call_stacks[i].calls.last_mut().unwrap().execute = false;
}
}
StepEventResult::Subroutine { block, external } => {
self.call_stacks[i].calls.push(Call {
block,
else_branch: None,
index: 0,
subroutine: true,
if_statement: false,
execute: true,
external,
});
}
StepEventResult::CallEveryFrame {
block,
thread_id,
external,
} => {
self.call_every_frame
.insert(thread_id, CallEveryFrame { block, external });
}
StepEventResult::Return => {
let mut run = false;
while run {
run = self.call_stacks[i]
.calls
.pop()
.map(|x| !x.subroutine)
.unwrap_or(false);
}
}
StepEventResult::Goto { block, external } => {
self.call_stacks[i].calls.pop();
self.call_stacks[i].calls.push(Call {
block,
else_branch: None,
index: 0,
subroutine: false,
if_statement: false,
execute: true,
external,
});
}
StepEventResult::None => {}
}
} else {
let new_execution = match event {
EventAst::IfStatementOr(test) => {
self.evaluate_expression(test).unwrap_bool()
&& self.call_stacks[i].calls.last().unwrap().if_statement
}
_ => false,
};
self.call_stacks[i].calls.last_mut().unwrap().execute = new_execution;
}
} else {
let call = self.call_stacks[i].calls.last_mut().unwrap();
if let Some(else_branch) = call.else_branch {
call.block = else_branch;
call.index = 0;
call.else_branch = None;
call.execute = !call.execute;
} else {
self.call_stacks[i].calls.pop();
}
}
}
}
self.call_stacks.truncate(4);
if self.frame_speed_modifier == 0.0 {
self.change_subaction = ChangeSubaction::InfiniteLoop
}
match self.disable_movement {
DisableMovement::Enable => {
self.x += self.x_vel;
self.y += self.y_vel;
}
DisableMovement::DisableVertical => {
self.x += self.x_vel;
}
DisableMovement::DisableHorizontal => {
self.y += self.y_vel;
}
_ => error!("Unknown DisableMovement value"),
}
}
fn step_event<'b>(
&mut self,
event: &'b EventAst,
external: bool,
fighter_scripts: &[&'b ScriptAst],
common_scripts: &[&'b ScriptAst],
section_scripts: &'b [SectionScriptAst],
) -> StepEventResult<'b> {
match event {
EventAst::SyncWait(value) => {
return StepEventResult::WaitUntil(self.frame_index + *value);
}
EventAst::AsyncWait(value) => {
return StepEventResult::WaitUntil(*value);
}
EventAst::ForLoop(for_loop) => {
match for_loop.iterations {
Iterations::Finite(iterations) => {
return StepEventResult::NewForLoop {
block: &for_loop.block,
iterations,
};
}
Iterations::Infinite => {
return StepEventResult::NewCall {
block: &for_loop.block,
};
}
}
}
EventAst::Subroutine(offset) => {
if !external {
if let Some(script) = section_scripts
.iter()
.find(|x| x.callers.contains(&offset.origin))
{
return StepEventResult::Subroutine {
block: &script.script.block,
external: true,
};
}
}
let all_scripts = if external {
common_scripts
} else {
fighter_scripts
};
for script in all_scripts.iter() {
if script.offset == offset.offset {
if !script.block.events.is_empty()
&& std::ptr::eq(&script.block.events[0], event)
{
error!("Avoided hard Subroutine infinite loop (attempted to jump to the same location)");
} else {
return StepEventResult::Subroutine {
block: &script.block,
external,
};
}
}
}
error!("Couldnt find Subroutine offset");
}
EventAst::Return => {
return StepEventResult::Return;
}
EventAst::Goto(offset) => {
if !external {
if let Some(script) = section_scripts
.iter()
.find(|x| x.callers.contains(&offset.origin))
{
return StepEventResult::Goto {
block: &script.script.block,
external: true,
};
}
}
let all_scripts = if external {
common_scripts
} else {
fighter_scripts
};
if !self.visited_gotos.iter().any(|x| *x == offset.offset) {
self.visited_gotos.push(offset.offset);
for script in all_scripts.iter() {
if script.offset == offset.offset {
return StepEventResult::Goto {
block: &script.block,
external,
};
}
}
error!("Couldnt find Goto offset");
}
error!("Avoided Goto infinite loop");
}
EventAst::IfStatement(if_statement) => {
let then_branch = &if_statement.then_branch;
let else_branch = if_statement.else_branch.as_deref();
let execute = self.evaluate_expression(&if_statement.test).unwrap_bool();
return StepEventResult::NewIfStatement {
then_branch,
else_branch,
execute,
};
}
EventAst::IfStatementAnd(test) => {
if !self.evaluate_expression(test).unwrap_bool() {
return StepEventResult::IfStatementDisableExecution;
}
}
EventAst::IfStatementOr(_) => {} EventAst::Switch(_, _) => {} EventAst::EndSwitch => {}
EventAst::Case(_) => {}
EventAst::DefaultCase => {}
EventAst::LoopRest => {
error!("LoopRest: This means the code is expected to infinite loop")
} EventAst::CallEveryFrame { thread_id, offset } => {
if !external {
if let Some(script) = section_scripts
.iter()
.find(|x| x.callers.contains(&offset.origin))
{
return StepEventResult::CallEveryFrame {
thread_id: *thread_id,
block: &script.script.block,
external: true,
};
}
}
let all_scripts = if external {
common_scripts
} else {
fighter_scripts
};
for script in all_scripts.iter() {
if script.offset == offset.offset {
return StepEventResult::CallEveryFrame {
thread_id: *thread_id,
block: &script.block,
external,
};
}
}
}
EventAst::RemoveCallEveryFrame { thread_id } => {
self.call_every_frame.remove(thread_id);
}
EventAst::IndependentSubroutine { .. } => {} EventAst::RemoveIndependentSubroutine { .. } => {} EventAst::SetIndependentSubroutineThreadType { .. } => {} EventAst::DisableInterrupt(_) => {} EventAst::EnableInterrupt(_) => {} EventAst::ToggleInterrupt { .. } => {} EventAst::EnableInterruptGroup(_) => {} EventAst::DisableInterruptGroup(_) => {} EventAst::ClearInterruptGroup(_) => {} EventAst::CreateInterrupt(interrupt) => {
if !(self.frame_index == 0.0
&& self.evaluate_expression(&interrupt.test).unwrap_bool())
{
self.interrupts.push(interrupt.clone());
} else {
self.bad_interrupts.push(interrupt.clone());
}
}
EventAst::PreviousInterruptAddRequirement { test } => {
if let Some(interrupt) = self.interrupts.last_mut() {
let left = Box::new(interrupt.test.clone());
let right = Box::new(test.clone());
let operator = ComparisonOperator::And;
interrupt.test = Expression::Binary(BinaryExpression {
left,
operator,
right,
});
}
}
EventAst::InterruptAddRequirement { .. } => {} EventAst::AllowInterrupts => {
self.interruptible = true;
}
EventAst::DisallowInterrupts => {
self.interruptible = false;
}
EventAst::ChangeSubaction(v0) => {
self.change_subaction = ChangeSubaction::ChangeSubaction(*v0);
}
EventAst::ChangeSubactionRestartFrame(v0) => {
self.change_subaction = ChangeSubaction::ChangeSubactionRestartFrame(*v0);
}
EventAst::SetAnimationFrame(v0) => {
self.animation_index = *v0;
}
EventAst::SetAnimationAndTimerFrame(v0) => {
self.animation_index = *v0;
self.frame_index = *v0;
}
EventAst::FrameSpeedModifier { multiplier, .. } => {
self.frame_speed_modifier = *multiplier;
}
EventAst::TimeManipulation(_, _) => {}
EventAst::SetAirGround(v0) => {
self.airbourne = *v0 == 0; }
EventAst::SetEdgeSlide(v0) => {
self.edge_slide = v0.clone();
}
EventAst::ReverseDirection => {
self.reverse_direction = !self.reverse_direction;
}
EventAst::CreateHitBox(args) => {
if args.hitbox_id < self.hitboxes.len() as u8 {
let interpolate =
if let Some(prev_hitbox) = &self.hitboxes[args.hitbox_id as usize] {
match &prev_hitbox.values {
CollisionBoxValues::Hit(prev_hitbox) => {
args.set_id == prev_hitbox.set_id
}
_ => false,
}
} else {
false
};
let mut empty_set = true;
for hitbox in self.hitboxes.iter().filter_map(|x| x.clone()) {
if let CollisionBoxValues::Hit(hitbox) = hitbox.values {
if hitbox.set_id == args.set_id {
empty_set = false;
}
}
}
if empty_set {
if let Some(rehit) = self.hitbox_sets_rehit.get_mut(args.set_id as usize) {
*rehit = true;
}
}
let damage = self.get_float_value(&args.damage);
self.hitboxes[args.hitbox_id as usize] =
Some(ScriptCollisionBox::from_hitbox(args, interpolate, damage));
} else {
error!(
"invalid hitbox index {} {}",
args.hitbox_id, self.subaction_name
);
}
}
EventAst::ThrownHitBox(_) => {}
EventAst::CreateSpecialHitBox(args) => {
if args.hitbox_args.hitbox_id < self.hitboxes.len() as u8 {
let index = args.hitbox_args.hitbox_id as usize;
let interpolate = if let Some(prev_hitbox) = &self.hitboxes[index] {
match &prev_hitbox.values {
CollisionBoxValues::Hit(prev_hitbox) => {
args.hitbox_args.set_id == prev_hitbox.set_id
}
_ => false,
}
} else {
false
};
let mut empty_set = true;
for hitbox in self.hitboxes.iter().filter_map(|x| x.clone()) {
if let CollisionBoxValues::Hit(hitbox) = hitbox.values {
if hitbox.set_id == args.hitbox_args.set_id {
empty_set = false;
}
}
}
if empty_set {
if let Some(rehit) = self
.hitbox_sets_rehit
.get_mut(args.hitbox_args.set_id as usize)
{
*rehit = true;
}
}
let damage = self.get_float_value(&args.hitbox_args.damage);
self.hitboxes[index] = Some(ScriptCollisionBox::from_special_hitbox(
args,
interpolate,
damage,
));
} else {
error!(
"invalid hitbox index {} {}",
args.hitbox_args.hitbox_id, self.subaction_name
);
}
}
EventAst::DeleteAllHitBoxes => {
for hitbox in self.hitboxes.iter_mut() {
if hitbox.as_ref().map(|x| x.is_hit()).unwrap_or(false) {
*hitbox = None;
}
}
}
EventAst::DefensiveCollision { .. } => {}
EventAst::MoveHitBox(move_hitbox) => {
if let Some(hitbox) = &mut self.hitboxes[move_hitbox.hitbox_id as usize] {
hitbox.bone_index = move_hitbox.new_bone as i16;
hitbox.x_offset = move_hitbox.new_x_offset;
hitbox.y_offset = move_hitbox.new_y_offset;
hitbox.z_offset = move_hitbox.new_z_offset;
}
}
EventAst::ChangeHitBoxDamage {
hitbox_id,
new_damage,
} => {
if let Some(hitbox) = &mut self.hitboxes[*hitbox_id as usize] {
if let CollisionBoxValues::Hit(hitbox) = &mut hitbox.values {
hitbox.damage = *new_damage as f32;
}
}
}
EventAst::ChangeHitBoxSize {
hitbox_id,
new_size,
} => {
if let Some(hitbox) = &mut self.hitboxes[*hitbox_id as usize] {
if let CollisionBoxValues::Hit(hitbox) = &mut hitbox.values {
hitbox.size = *new_size as f32;
}
}
}
EventAst::DeleteHitBox(hitbox_id) => {
if self.hitboxes[*hitbox_id as usize]
.as_ref()
.map(|x| x.is_hit())
.unwrap_or(false)
{
self.hitboxes[*hitbox_id as usize] = None;
}
}
EventAst::CreateGrabBox(args) => {
let mut interpolate = false;
if let Some(prev_hitbox) = &self.hitboxes[args.hitbox_id as usize] {
if let CollisionBoxValues::Grab(_) = prev_hitbox.values {
interpolate = true;
}
}
if args.hitbox_id < self.hitboxes.len() as i32 {
self.hitboxes[args.hitbox_id as usize] =
Some(ScriptCollisionBox::from_grabbox(args, interpolate));
} else {
error!(
"invalid hitbox index {} {}",
args.hitbox_id, self.subaction_name
);
}
}
EventAst::DeleteGrabBox(hitbox_id) => {
if self.hitboxes[*hitbox_id as usize]
.as_ref()
.map(|x| x.is_grab())
.unwrap_or(false)
{
self.hitboxes[*hitbox_id as usize] = None;
}
}
EventAst::DeleteAllGrabBoxes => {
for hitbox in self.hitboxes.iter_mut() {
if hitbox.as_ref().map(|x| x.is_grab()).unwrap_or(false) {
*hitbox = None;
}
}
}
EventAst::SpecifyThrow(throw) => {
match &throw.throw_use {
ThrowUse::Throw => {
self.throw = Some(throw.clone());
}
ThrowUse::GrabInterrupt => {
self.grab_interrupt_damage = Some(throw.damage);
}
ThrowUse::Unknown(_) => {}
}
}
EventAst::ApplyThrow(_) => {
self.throw_activate = true;
}
EventAst::AddHitBoxDamage {
hitbox_id,
add_damage,
} => {
let add_damage = self.get_float_value(add_damage);
if let Some(hitbox) = &mut self.hitboxes[*hitbox_id as usize] {
if let CollisionBoxValues::Hit(hitbox) = &mut hitbox.values {
hitbox.damage += add_damage;
}
}
}
EventAst::ChangeHurtBoxStateAll { state } => {
self.hurtbox_state_all = state.clone();
}
EventAst::ChangeHurtBoxStateSpecific { bone, state } => {
match state {
HurtBoxState::Invincible => {
}
state => {
self.hurtbox_states
.insert(high_level_fighter::get_bone_index(*bone), state.clone());
}
}
}
EventAst::UnchangeHurtBoxStateSpecific => {
self.hurtbox_states.clear();
}
EventAst::ControllerClearBuffer => {}
EventAst::ControllerUnk01 => {}
EventAst::ControllerUnk02 => {}
EventAst::ControllerUnk06(_) => {}
EventAst::ControllerUnk0C => {}
EventAst::Rumble { unk1, unk2 } => self.rumble = Some((*unk1, *unk2)),
EventAst::RumbleLoop { unk1, unk2 } => self.rumble_loop = Some((*unk1, *unk2)),
EventAst::SlopeContourStand { leg_bone_parent } => {
if *leg_bone_parent == 0 {
self.slope_contour_stand = None;
} else {
self.slope_contour_stand = Some(*leg_bone_parent);
}
}
EventAst::SlopeContourFull {
hip_n_or_top_n,
trans_bone,
} => {
if *hip_n_or_top_n == 0 && *trans_bone == 0 {
self.slope_contour_full = None;
} else {
self.slope_contour_full = Some((*hip_n_or_top_n, *trans_bone));
}
}
EventAst::GenerateArticle { .. } => {}
EventAst::ArticleEvent(_) => {}
EventAst::ArticleAnimation(_) => {}
EventAst::ArticleRemove(_) => {}
EventAst::ArticleVisibility { .. } => {}
EventAst::FinalSmashEnter => {}
EventAst::FinalSmashExit => {}
EventAst::TerminateSelf => {}
EventAst::Posture(_) => {}
EventAst::LedgeGrabEnable(enable) => {
self.ledge_grab_enable = enable.clone();
}
EventAst::TagDisplay(display) => {
self.tag_display = *display;
}
EventAst::Armor {
armor_type,
tolerance,
} => {
self.armor_type = armor_type.clone();
self.armor_tolerance = *tolerance;
}
EventAst::AddDamage(damage) => {
self.damage += damage;
}
EventAst::SetOrAddVelocity(values) => {
if values.x_set {
self.x_vel = values.x_vel;
self.x_vel_modify = VelModify::Set(values.x_vel);
} else {
self.x_vel += values.x_vel;
self.x_vel_modify = VelModify::Add(self.x_vel_modify.value() + values.x_vel);
}
if values.y_set {
self.y_vel = values.y_vel;
self.y_vel_modify = VelModify::Set(values.y_vel);
} else {
self.y_vel += values.y_vel;
self.y_vel_modify = VelModify::Add(self.y_vel_modify.value() + values.y_vel);
}
}
EventAst::SetVelocity { x_vel, y_vel } => {
self.x_vel = *x_vel;
self.y_vel = *y_vel;
self.x_vel_modify = VelModify::Set(*x_vel);
self.y_vel_modify = VelModify::Set(*y_vel);
}
EventAst::AddVelocity { x_vel, y_vel } => {
let x_vel = self.get_float_value(x_vel);
let y_vel = self.get_float_value(y_vel);
self.x_vel += x_vel;
self.y_vel += y_vel;
self.x_vel_modify = VelModify::Add(self.x_vel_modify.value() + x_vel);
self.y_vel_modify = VelModify::Add(self.y_vel_modify.value() + y_vel);
}
EventAst::DisableMovement(disable_movement) => {
self.disable_movement = disable_movement.clone();
}
EventAst::DisableMovement2(_) => {} EventAst::ResetVerticalVelocityAndAcceleration(reset) => {
if *reset {
self.y_vel = 0.0;
self.y_vel_modify = VelModify::Set(0.0);
}
}
EventAst::NormalizePhysics => {}
EventAst::SoundEffect1(_) => {}
EventAst::SoundEffect2(_) => {}
EventAst::SoundEffectTransient(_) => {}
EventAst::SoundEffectStop(_) => {}
EventAst::SoundEffectVictory(_) => {}
EventAst::SoundEffectUnk(_) => {}
EventAst::SoundEffectOther1(_) => {}
EventAst::SoundEffectOther2(_) => {}
EventAst::SoundVoiceLow => {}
EventAst::SoundVoiceDamage => {}
EventAst::SoundVoiceOttotto => {}
EventAst::SoundVoiceEating => {}
EventAst::IntVariableSet { value, variable } => {
self.set_variable_int(variable, *value);
}
EventAst::IntVariableAdd { value, variable } => {
let old_value = self.get_variable_int(variable);
self.set_variable_int(variable, old_value + *value);
}
EventAst::IntVariableSubtract { value, variable } => {
let old_value = self.get_variable_int(variable);
self.set_variable_int(variable, old_value - value);
}
EventAst::IntVariableIncrement { variable } => {
let old_value = self.get_variable_int(variable);
self.set_variable_int(variable, old_value + 1);
}
EventAst::IntVariableDecrement { variable } => {
let old_value = self.get_variable_int(variable);
self.set_variable_int(variable, old_value - 1);
}
EventAst::FloatVariableSet { value, variable } => {
let value = self.get_float_value(value);
self.set_variable_float(variable, value);
}
EventAst::FloatVariableAdd { value, variable } => {
let value = self.get_float_value(value);
let old_value = self.get_variable_float(variable);
self.set_variable_float(variable, old_value + value);
}
EventAst::FloatVariableSubtract { value, variable } => {
let value = self.get_float_value(value);
let old_value = self.get_variable_float(variable);
self.set_variable_float(variable, old_value - value);
}
EventAst::FloatVariableMultiply { value, variable } => {
let value = self.get_float_value(value);
let old_value = self.get_variable_float(variable);
self.set_variable_float(variable, old_value * value);
}
EventAst::FloatVariableDivide { value, variable } => {
let value = self.get_float_value(value);
let old_value = self.get_variable_float(variable);
self.set_variable_float(variable, old_value / value);
}
EventAst::BoolVariableSetTrue { variable } => match variable.data_type() {
VariableDataType::Int => self.set_variable_int_inner(variable, 1),
VariableDataType::Float => self.set_variable_float_inner(variable, 1.0),
VariableDataType::Bool => self.set_variable_bool_inner(variable, true),
VariableDataType::Unknown { .. } => {}
},
EventAst::BoolVariableSetFalse { variable } => match variable.data_type() {
VariableDataType::Int => self.set_variable_int_inner(variable, 0),
VariableDataType::Float => self.set_variable_float_inner(variable, 0.0),
VariableDataType::Bool => self.set_variable_bool_inner(variable, false),
VariableDataType::Unknown { .. } => {}
},
EventAst::GraphicEffect(_) => {}
EventAst::ExternalGraphicEffect(_) => {}
EventAst::LimitedScreenTint(_) => {}
EventAst::UnlimitedScreenTint(_) => {}
EventAst::EndUnlimitedScreenTint { .. } => {}
EventAst::SwordGlow(_) => {}
EventAst::DeleteSwordGlow { .. } => {}
EventAst::AestheticWindEffect(_) => {}
EventAst::EndAestheticWindEffect { .. } => {}
EventAst::ScreenShake { .. } => {}
EventAst::ModelChanger { .. } => {
}
EventAst::CameraCloseup(_) => {}
EventAst::CameraNormal => {}
EventAst::RemoveFlashEffect => {}
EventAst::FlashEffectOverlay { .. } => {}
EventAst::SetColorOfFlashEffectOverlay { .. } => {}
EventAst::FlashEffectLight { .. } => {}
EventAst::SetColorOfFlashEffectLight { .. } => {}
EventAst::ItemPickup { .. } => {}
EventAst::ItemThrow { .. } => {}
EventAst::ItemThrow2 { .. } => {}
EventAst::ItemDrop => {}
EventAst::ItemConsume { .. } => {}
EventAst::ItemSetProperty { .. } => {}
EventAst::FireWeapon => {}
EventAst::FireProjectile => {}
EventAst::Item1F { .. } => {}
EventAst::ItemCreate { .. } => {}
EventAst::ItemVisibility(_) => {}
EventAst::ItemDelete => {}
EventAst::BeamSwordTrail { .. } => {}
EventAst::Unknown(event) => {
debug!("unknown event: {:#?}", event);
}
EventAst::Nop => {}
}
StepEventResult::None
}
#[rustfmt::skip]
fn evaluate_expression(&mut self, expression: &Expression) -> ExprResult {
match expression {
Expression::Nullary (requirement) => {
ExprResult::Bool (match requirement {
Requirement::CharacterExists => true,
Requirement::OnGround => true,
Requirement::InAir => false,
Requirement::FacingRight => true,
Requirement::HasntTethered3Times => true,
Requirement::IsNotInDamagingLens => true,
_ => false
})
}
Expression::Unary (unary) => {
ExprResult::Bool (match unary.requirement {
Requirement::CharacterExists => true,
Requirement::OnGround => true,
Requirement::InAir => false,
Requirement::FacingRight => true,
Requirement::HasntTethered3Times => true,
Requirement::IsNotInDamagingLens => true,
Requirement::BoolIsTrue => self.evaluate_expression(&unary.value).unwrap_bool(),
_ => false
})
}
#[allow(clippy::float_cmp)]
Expression::Binary (binary) => {
let result = match &binary.operator {
ComparisonOperator::LessThan => {
match (self.evaluate_expression(&binary.left), self.evaluate_expression(&binary.right)) {
(ExprResult::Float (left), ExprResult::Float (right)) => left < right,
(ExprResult::Int (left), ExprResult::Float (right)) => (left as f32) < right,
(ExprResult::Float (left), ExprResult::Int (right)) => left < right as f32,
(ExprResult::Int (left), ExprResult::Int (right)) => left < right,
_ => panic!("Cannot evaluate expression: {:?}", binary),
}
}
ComparisonOperator::LessThanOrEqual => {
match (self.evaluate_expression(&binary.left), self.evaluate_expression(&binary.right)) {
(ExprResult::Float (left), ExprResult::Float (right)) => left <= right,
(ExprResult::Int (left), ExprResult::Float (right)) => (left as f32) <= right,
(ExprResult::Float (left), ExprResult::Int (right)) => left <= right as f32,
(ExprResult::Int (left), ExprResult::Int (right)) => left <= right,
_ => panic!("Cannot evaluate expression: {:?}", binary),
}
}
ComparisonOperator::Equal => {
match (self.evaluate_expression(&binary.left), self.evaluate_expression(&binary.right)) {
(ExprResult::Float (left), ExprResult::Float (right)) => left == right,
(ExprResult::Int (left), ExprResult::Float (right)) => (left as f32) == right,
(ExprResult::Float (left), ExprResult::Int (right)) => left == right as f32,
(ExprResult::Int (left), ExprResult::Int (right)) => left == right,
_ => panic!("Cannot evaluate expression: {:?}", binary),
}
}
ComparisonOperator::NotEqual => {
match (self.evaluate_expression(&binary.left), self.evaluate_expression(&binary.right)) {
(ExprResult::Float (left), ExprResult::Float (right)) => left != right,
(ExprResult::Int (left), ExprResult::Float (right)) => (left as f32) != right,
(ExprResult::Float (left), ExprResult::Int (right)) => left != right as f32,
(ExprResult::Int (left), ExprResult::Int (right)) => left != right,
_ => panic!("Cannot evaluate expression: {:?}", binary),
}
}
ComparisonOperator::GreaterThanOrEqual => {
match (self.evaluate_expression(&binary.left), self.evaluate_expression(&binary.right)) {
(ExprResult::Float (left), ExprResult::Float (right)) => left >= right,
(ExprResult::Int (left), ExprResult::Float (right)) => (left as f32) >= right,
(ExprResult::Float (left), ExprResult::Int (right)) => left >= right as f32,
(ExprResult::Int (left), ExprResult::Int (right)) => left >= right,
_ => panic!("Cannot evaluate expression: {:?}", binary),
}
}
ComparisonOperator::GreaterThan => {
match (self.evaluate_expression(&binary.left), self.evaluate_expression(&binary.right)) {
(ExprResult::Float (left), ExprResult::Float (right)) => left > right,
(ExprResult::Int (left), ExprResult::Float (right)) => (left as f32) > right,
(ExprResult::Float (left), ExprResult::Int (right)) => left > right as f32,
(ExprResult::Int (left), ExprResult::Int (right)) => left > right,
_ => panic!("Cannot evaluate expression: {:?}", binary),
}
}
ComparisonOperator::Or => self.evaluate_expression(&*binary.left).unwrap_bool() || self.evaluate_expression(&*binary.right).unwrap_bool(),
ComparisonOperator::And => self.evaluate_expression(&*binary.left).unwrap_bool() && self.evaluate_expression(&*binary.right).unwrap_bool(),
ComparisonOperator::UnknownArg (_) => false,
};
ExprResult::Bool (result)
}
Expression::Not (expression) => ExprResult::Bool (!self.evaluate_expression(expression).unwrap_bool()),
Expression::Variable (variable) => {
match variable.data_type() {
VariableDataType::Int => ExprResult::Int (self.get_variable_int_inner(variable)),
VariableDataType::Float => ExprResult::Float (self.get_variable_float_inner(variable)),
VariableDataType::Bool => ExprResult::Bool (self.get_variable_bool_inner(variable)),
VariableDataType::Unknown { .. } => ExprResult::Bool (false), }
}
Expression::Value (int) => ExprResult::Int (*int),
Expression::Scalar (float) => ExprResult::Float (*float),
}
}
fn get_variable_int(&self, variable: &VariableAst) -> i32 {
match variable.data_type() {
VariableDataType::Int => self.get_variable_int_inner(variable),
VariableDataType::Float => self.get_variable_float_inner(variable) as i32,
VariableDataType::Bool => {
if self.get_variable_bool_inner(variable) {
1
} else {
0
}
}
VariableDataType::Unknown { .. } => 0,
}
}
fn get_variable_float(&self, variable: &VariableAst) -> f32 {
match variable.data_type() {
VariableDataType::Int => self.get_variable_int_inner(variable) as f32,
VariableDataType::Float => self.get_variable_float_inner(variable),
VariableDataType::Bool => {
if self.get_variable_bool_inner(variable) {
1.0
} else {
0.0
}
}
VariableDataType::Unknown { .. } => 0.0,
}
}
fn get_float_value(&self, value: &FloatValue) -> f32 {
match value {
FloatValue::Constant(value) => *value,
FloatValue::Variable(variable) => self.get_variable_float(variable),
}
}
fn set_variable_int(&mut self, variable: &VariableAst, value: i32) {
match variable.data_type() {
VariableDataType::Int => self.set_variable_int_inner(variable, value),
VariableDataType::Float => self.set_variable_float_inner(variable, value as f32),
VariableDataType::Bool => self.set_variable_bool_inner(variable, value != 0),
VariableDataType::Unknown { .. } => {}
}
}
fn set_variable_float(&mut self, variable: &VariableAst, value: f32) {
match variable.data_type() {
VariableDataType::Int => self.set_variable_int_inner(variable, value as i32),
VariableDataType::Float => self.set_variable_float_inner(variable, value),
VariableDataType::Bool => self.set_variable_bool_inner(variable, value != 0.0),
VariableDataType::Unknown { .. } => {}
}
}
fn get_variable_int_inner(&self, variable: &VariableAst) -> i32 {
match variable {
VariableAst::InternalConstantInt (InternalConstantInt::CurrentFrame) => self.frame_index as i32,
VariableAst::InternalConstantInt (InternalConstantInt::CharacterDirection) => 1,
VariableAst::InternalConstantInt (InternalConstantInt::CharacterDirectionOpposite) => -1,
VariableAst::InternalConstantInt (InternalConstantInt::CurrentFrameSpeed) => self.frame_speed_modifier as i32,
VariableAst::InternalConstantInt (InternalConstantInt::CurrentSubaction) => 0, VariableAst::InternalConstantInt (InternalConstantInt::CurrentAction) => 0, VariableAst::InternalConstantInt (InternalConstantInt::CrawlControlStickXOffsetMax) => 20, VariableAst::InternalConstantInt (InternalConstantInt::CrawlControlStickXOffsetMin) => -20, VariableAst::InternalConstantInt (_) => 0, VariableAst::LongtermAccessInt (LongtermAccessInt::JumpsUsed) => self.jumps_used,
VariableAst::LongtermAccessInt (LongtermAccessInt::WallJumpCount) => self.wall_jump_count,
VariableAst::LongtermAccessInt (LongtermAccessInt::WallJumpInterval) => self.wall_jump_interval,
VariableAst::LongtermAccessInt (LongtermAccessInt::FootstoolCount) => self.footstool_count,
VariableAst::LongtermAccessInt (LongtermAccessInt::FallTime) => self.fall_time,
VariableAst::LongtermAccessInt (LongtermAccessInt::SwimTime) => self.swim_time,
VariableAst::LongtermAccessInt (LongtermAccessInt::LipStickRefresh) => self.lip_stick_refresh,
VariableAst::LongtermAccessInt (LongtermAccessInt::CurryRemainingTime) => self.curry_remaining_time,
VariableAst::LongtermAccessInt (LongtermAccessInt::CurryAngle2) => self.curry_angle2,
VariableAst::LongtermAccessInt (LongtermAccessInt::StarRemainingTime) => self.star_remaining_time,
VariableAst::LongtermAccessInt (LongtermAccessInt::MushroomRemainingTime) => self.mushroom_remaining_time,
VariableAst::LongtermAccessInt (LongtermAccessInt::LightningRemainingTime) => self.lightning_remaining_time,
VariableAst::LongtermAccessInt (LongtermAccessInt::SizeFlag) => self.size_flag,
VariableAst::LongtermAccessInt (LongtermAccessInt::MetalBlockRemainingTime) => self.metal_block_remaining_time,
VariableAst::LongtermAccessInt (LongtermAccessInt::ComboCount) => self.combo_count,
VariableAst::LongtermAccessInt (LongtermAccessInt::BubbleTime) => self.bubble_time,
VariableAst::LongtermAccessInt (LongtermAccessInt::AttacksPerformed) => self.attacks_performed,
VariableAst::LongtermAccessInt (LongtermAccessInt::CostumeID) => self.costume_id,
VariableAst::LongtermAccessInt (LongtermAccessInt::HitstunFramesRemaining) => self.hitstun_frames_remaining,
VariableAst::LongtermAccessInt (LongtermAccessInt::MeteorCancelWindow) => self.meteor_cancel_window,
VariableAst::LongtermAccessInt (LongtermAccessInt::MissedTechs) => self.missed_techs,
VariableAst::LongtermAccessInt (LongtermAccessInt::TetherCount) => self.tether_count,
VariableAst::LongtermAccessInt (LongtermAccessInt::Temp1) => self.temp1,
VariableAst::LongtermAccessInt (LongtermAccessInt::Temp2) => self.temp2,
VariableAst::LongtermAccessInt (LongtermAccessInt::Address (address)) => self.longterm_access_int.get(*address as usize).cloned().unwrap_or(0),
VariableAst::RandomAccessInt (RandomAccessInt::ThrowDataParam1) => self.throw_data_param1,
VariableAst::RandomAccessInt (RandomAccessInt::ThrowDataParam2) => self.throw_data_param2,
VariableAst::RandomAccessInt (RandomAccessInt::ThrowDataParam3) => self.throw_data_param3,
VariableAst::RandomAccessInt (RandomAccessInt::Address (address)) => self.random_access_int.get(*address as usize).cloned().unwrap_or(0),
VariableAst::Unknown { .. } => 0,
VariableAst::LongtermAccessFloat (_) | VariableAst::LongtermAccessBool (_) |
VariableAst::RandomAccessFloat (_) | VariableAst::RandomAccessBool (_)
=> panic!("Called get_variable_int on a variable that is not an int. '{:?}' It is a brawllib_rs logic error if this is reached", variable),
}
}
fn set_variable_int_inner(&mut self, variable: &VariableAst, value: i32) {
match variable {
VariableAst::InternalConstantInt (_) => {}, VariableAst::LongtermAccessInt (LongtermAccessInt::JumpsUsed) => self.jumps_used = value,
VariableAst::LongtermAccessInt (LongtermAccessInt::WallJumpCount) => self.wall_jump_count = value,
VariableAst::LongtermAccessInt (LongtermAccessInt::WallJumpInterval) => self.wall_jump_interval = value,
VariableAst::LongtermAccessInt (LongtermAccessInt::FootstoolCount) => self.footstool_count = value,
VariableAst::LongtermAccessInt (LongtermAccessInt::FallTime) => self.fall_time = value,
VariableAst::LongtermAccessInt (LongtermAccessInt::SwimTime) => self.swim_time = value,
VariableAst::LongtermAccessInt (LongtermAccessInt::LipStickRefresh) => self.lip_stick_refresh = value,
VariableAst::LongtermAccessInt (LongtermAccessInt::CurryRemainingTime) => self.curry_remaining_time = value,
VariableAst::LongtermAccessInt (LongtermAccessInt::CurryAngle2) => self.curry_angle2 = value,
VariableAst::LongtermAccessInt (LongtermAccessInt::StarRemainingTime) => self.star_remaining_time = value,
VariableAst::LongtermAccessInt (LongtermAccessInt::MushroomRemainingTime) => self.mushroom_remaining_time = value,
VariableAst::LongtermAccessInt (LongtermAccessInt::LightningRemainingTime) => self.lightning_remaining_time = value,
VariableAst::LongtermAccessInt (LongtermAccessInt::SizeFlag) => self.size_flag = value,
VariableAst::LongtermAccessInt (LongtermAccessInt::MetalBlockRemainingTime) => self.metal_block_remaining_time = value,
VariableAst::LongtermAccessInt (LongtermAccessInt::ComboCount) => self.combo_count = value,
VariableAst::LongtermAccessInt (LongtermAccessInt::BubbleTime) => self.bubble_time = value,
VariableAst::LongtermAccessInt (LongtermAccessInt::AttacksPerformed) => self.attacks_performed = value,
VariableAst::LongtermAccessInt (LongtermAccessInt::CostumeID) => self.costume_id = value,
VariableAst::LongtermAccessInt (LongtermAccessInt::HitstunFramesRemaining) => self.hitstun_frames_remaining = value,
VariableAst::LongtermAccessInt (LongtermAccessInt::MeteorCancelWindow) => self.meteor_cancel_window = value,
VariableAst::LongtermAccessInt (LongtermAccessInt::MissedTechs) => self.missed_techs = value,
VariableAst::LongtermAccessInt (LongtermAccessInt::TetherCount) => self.tether_count = value,
VariableAst::LongtermAccessInt (LongtermAccessInt::Temp1) => self.temp1 = value,
VariableAst::LongtermAccessInt (LongtermAccessInt::Temp2) => self.temp2 = value,
VariableAst::LongtermAccessInt (LongtermAccessInt::Address (address)) =>
if (*address as usize) < self.longterm_access_int.len() {
self.longterm_access_int[*address as usize] = value;
}
VariableAst::RandomAccessInt (RandomAccessInt::ThrowDataParam1) => self.throw_data_param1 = value,
VariableAst::RandomAccessInt (RandomAccessInt::ThrowDataParam2) => self.throw_data_param2 = value,
VariableAst::RandomAccessInt (RandomAccessInt::ThrowDataParam3) => self.throw_data_param3 = value,
VariableAst::RandomAccessInt (RandomAccessInt::Address (address)) =>
if (*address as usize) < self.random_access_int.len() {
self.random_access_int[*address as usize] = value;
}
VariableAst::Unknown { .. } => {},
VariableAst::LongtermAccessFloat (_) | VariableAst::LongtermAccessBool (_) |
VariableAst::RandomAccessFloat (_) | VariableAst::RandomAccessBool (_)
=> panic!("Called set_variable_int_inner on a variable that is not an int. '{:?}' It is a brawllib_rs logic error if this is reached.", variable),
}
}
fn get_variable_float_inner(&self, variable: &VariableAst) -> f32 {
match variable {
VariableAst::LongtermAccessFloat (LongtermAccessFloat::SpecialLandingLag) => self.special_landing_lag,
VariableAst::LongtermAccessFloat (LongtermAccessFloat::SpecialFallMobilityMultiplier) => self.special_fall_mobility_multiplier,
VariableAst::LongtermAccessFloat (LongtermAccessFloat::ShieldCharge) => self.shield_charge,
VariableAst::LongtermAccessFloat (LongtermAccessFloat::CurryAngle1) => self.curry_angle1,
VariableAst::LongtermAccessFloat (LongtermAccessFloat::CurryRandomness) => self.curry_randomness,
VariableAst::LongtermAccessFloat (LongtermAccessFloat::Address (address)) => self.longterm_access_float.get(*address as usize).cloned().unwrap_or(0.0),
VariableAst::RandomAccessFloat (RandomAccessFloat::EnableTurnWhenBelowZero) => self.enable_turn_when_below_zero,
VariableAst::RandomAccessFloat (RandomAccessFloat::Address (address)) => self.random_access_float.get(*address as usize).cloned().unwrap_or(0.0),
VariableAst::Unknown { .. } => 0.0,
VariableAst::LongtermAccessInt (_) | VariableAst::LongtermAccessBool (_) |
VariableAst::RandomAccessInt (_) | VariableAst::RandomAccessBool (_) |
VariableAst::InternalConstantInt (_) => panic!("Called get_variable_float on a variable that is not a float. '{:?}' It is a brawllib_rs logic error if this is reached.", variable),
}
}
fn set_variable_float_inner(&mut self, variable: &VariableAst, value: f32) {
match variable {
VariableAst::LongtermAccessFloat (LongtermAccessFloat::SpecialLandingLag) => self.special_landing_lag = value,
VariableAst::LongtermAccessFloat (LongtermAccessFloat::SpecialFallMobilityMultiplier) => self.special_fall_mobility_multiplier = value,
VariableAst::LongtermAccessFloat (LongtermAccessFloat::ShieldCharge) => self.shield_charge = value,
VariableAst::LongtermAccessFloat (LongtermAccessFloat::CurryAngle1) => self.curry_angle1 = value,
VariableAst::LongtermAccessFloat (LongtermAccessFloat::CurryRandomness) => self.curry_randomness = value,
VariableAst::LongtermAccessFloat (LongtermAccessFloat::Address (address)) =>
if (*address as usize) < self.longterm_access_float.len() {
self.longterm_access_float[*address as usize] = value;
}
VariableAst::RandomAccessFloat (RandomAccessFloat::EnableTurnWhenBelowZero) => self.enable_turn_when_below_zero = value,
VariableAst::RandomAccessFloat (RandomAccessFloat::Address (address)) =>
if (*address as usize) < self.random_access_float.len() {
self.random_access_float[*address as usize] = value;
}
VariableAst::Unknown { .. } => { },
VariableAst::LongtermAccessInt (_) | VariableAst::LongtermAccessBool (_) |
VariableAst::RandomAccessInt (_) | VariableAst::RandomAccessBool (_) |
VariableAst::InternalConstantInt (_) => panic!("Called set_variable_float_inner on a variable that is not a float. '{:?}' It is a brawllib_rs logic error if this is reached.", variable),
}
}
fn get_variable_bool_inner(&self, variable: &VariableAst) -> bool {
match variable {
VariableAst::LongtermAccessBool (LongtermAccessBool::IsDead) => self.is_dead,
VariableAst::LongtermAccessBool (LongtermAccessBool::CannotDie) => self.cannot_die,
VariableAst::LongtermAccessBool (LongtermAccessBool::AutomaticFootstool) => self.automatic_footstool,
VariableAst::LongtermAccessBool (LongtermAccessBool::HasFinal) => self.has_final,
VariableAst::LongtermAccessBool (LongtermAccessBool::HasFinalAura) => self.has_final_aura,
VariableAst::LongtermAccessBool (LongtermAccessBool::HasCurry) => self.has_curry,
VariableAst::LongtermAccessBool (LongtermAccessBool::HasHammer) => self.has_hammer,
VariableAst::LongtermAccessBool (LongtermAccessBool::HitByParalyze) => self.hit_by_paralyze,
VariableAst::LongtermAccessBool (LongtermAccessBool::HasScrewAttack) => self.has_screw_attack,
VariableAst::LongtermAccessBool (LongtermAccessBool::StaminaDead) => self.stamina_dead,
VariableAst::LongtermAccessBool (LongtermAccessBool::HasTag) => self.has_tag,
VariableAst::LongtermAccessBool (LongtermAccessBool::CanNotLedgeGrab) => self.can_not_ledge_grab,
VariableAst::LongtermAccessBool (LongtermAccessBool::CanNotTeeter) => self.can_not_teeter,
VariableAst::LongtermAccessBool (LongtermAccessBool::VelocityIgnoreHitstun) => self.velocity_ignore_hitstun,
VariableAst::LongtermAccessBool (LongtermAccessBool::Deflection) => self.deflection,
VariableAst::LongtermAccessBool (LongtermAccessBool::Address (address)) => self.longterm_access_bool.get(*address as usize).cloned().unwrap_or(false),
VariableAst::RandomAccessBool (RandomAccessBool::CharacterFloat) => self.character_float,
VariableAst::RandomAccessBool (RandomAccessBool::EnableFastFall) => self.enable_fast_fall,
VariableAst::RandomAccessBool (RandomAccessBool::Shorthop) => self.shorthop,
VariableAst::RandomAccessBool (RandomAccessBool::EnableActionTransition) => self.enable_action_transition,
VariableAst::RandomAccessBool (RandomAccessBool::SpecialsMovement) => self.specials_movement,
VariableAst::RandomAccessBool (RandomAccessBool::EnableGlide) => self.enable_glide,
VariableAst::RandomAccessBool (RandomAccessBool::EnableJabLoop) => self.enable_jab_loop,
VariableAst::RandomAccessBool (RandomAccessBool::EnableAutoJab) => self.enable_auto_jab,
VariableAst::RandomAccessBool (RandomAccessBool::EnableJabEnd) => self.enable_jab_end,
VariableAst::RandomAccessBool (RandomAccessBool::EnableLandingLag) => self.landing_lag,
VariableAst::RandomAccessBool (RandomAccessBool::Address (address)) => self.random_access_bool.get(*address as usize).cloned().unwrap_or(false),
VariableAst::Unknown { .. } => false,
VariableAst::LongtermAccessInt (_) | VariableAst::LongtermAccessFloat (_) |
VariableAst::RandomAccessInt (_) | VariableAst::RandomAccessFloat (_) |
VariableAst::InternalConstantInt (_) => panic!("Called get_variable_bool on a variable that is not a bool. '{:?}' It is a brawllib_rs logic error if this is reached.", variable),
}
}
fn set_variable_bool_inner(&mut self, variable: &VariableAst, value: bool) {
match variable {
VariableAst::LongtermAccessBool (LongtermAccessBool::IsDead) => self.is_dead = value,
VariableAst::LongtermAccessBool (LongtermAccessBool::CannotDie) => self.cannot_die = value,
VariableAst::LongtermAccessBool (LongtermAccessBool::AutomaticFootstool) => self.automatic_footstool = value,
VariableAst::LongtermAccessBool (LongtermAccessBool::HasFinal) => self.has_final = value,
VariableAst::LongtermAccessBool (LongtermAccessBool::HasFinalAura) => self.has_final_aura = value,
VariableAst::LongtermAccessBool (LongtermAccessBool::HasCurry) => self.has_curry = value,
VariableAst::LongtermAccessBool (LongtermAccessBool::HasHammer) => self.has_hammer = value,
VariableAst::LongtermAccessBool (LongtermAccessBool::HitByParalyze) => self.hit_by_paralyze = value,
VariableAst::LongtermAccessBool (LongtermAccessBool::HasScrewAttack) => self.has_screw_attack = value,
VariableAst::LongtermAccessBool (LongtermAccessBool::StaminaDead) => self.stamina_dead = value,
VariableAst::LongtermAccessBool (LongtermAccessBool::HasTag) => self.has_tag = value,
VariableAst::LongtermAccessBool (LongtermAccessBool::CanNotLedgeGrab) => self.can_not_ledge_grab = value,
VariableAst::LongtermAccessBool (LongtermAccessBool::CanNotTeeter) => self.can_not_teeter = value,
VariableAst::LongtermAccessBool (LongtermAccessBool::VelocityIgnoreHitstun) => self.velocity_ignore_hitstun = value,
VariableAst::LongtermAccessBool (LongtermAccessBool::Deflection) => self.deflection = value,
VariableAst::LongtermAccessBool (LongtermAccessBool::Address (address)) =>
if (*address as usize) < self.longterm_access_bool.len() {
self.longterm_access_bool[*address as usize] = value;
}
VariableAst::RandomAccessBool (RandomAccessBool::CharacterFloat) => self.character_float = value,
VariableAst::RandomAccessBool (RandomAccessBool::EnableFastFall) => self.enable_fast_fall = value,
VariableAst::RandomAccessBool (RandomAccessBool::Shorthop) => self.shorthop = value,
VariableAst::RandomAccessBool (RandomAccessBool::EnableActionTransition) => self.enable_action_transition = value,
VariableAst::RandomAccessBool (RandomAccessBool::SpecialsMovement) => self.specials_movement = value,
VariableAst::RandomAccessBool (RandomAccessBool::EnableGlide) => self.enable_glide = value,
VariableAst::RandomAccessBool (RandomAccessBool::EnableJabLoop) => self.enable_jab_loop = value,
VariableAst::RandomAccessBool (RandomAccessBool::EnableAutoJab) => self.enable_auto_jab = value,
VariableAst::RandomAccessBool (RandomAccessBool::EnableJabEnd) => self.enable_jab_end = value,
VariableAst::RandomAccessBool (RandomAccessBool::EnableLandingLag) => self.landing_lag = value,
VariableAst::RandomAccessBool (RandomAccessBool::Address (address)) =>
if (*address as usize) < self.random_access_bool.len() {
self.random_access_bool[*address as usize] = value;
}
VariableAst::Unknown { .. } => {},
VariableAst::LongtermAccessInt (_) | VariableAst::LongtermAccessFloat (_) |
VariableAst::RandomAccessInt (_) | VariableAst::RandomAccessFloat (_) |
VariableAst::InternalConstantInt (_) => panic!("Called set_variable_bool_inner on a variable that is not a bool. '{:?}' It is a brawllib_rs logic error if this is reached.", variable),
}
}
}
enum StepEventResult<'a> {
WaitUntil(f32),
NewForLoop {
block: &'a Block,
iterations: i32,
},
NewCall {
block: &'a Block,
},
NewIfStatement {
then_branch: &'a Block,
else_branch: Option<&'a Block>,
execute: bool,
},
IfStatementDisableExecution,
Goto {
block: &'a Block,
external: bool,
},
Subroutine {
block: &'a Block,
external: bool,
},
CallEveryFrame {
block: &'a Block,
external: bool,
thread_id: i32,
},
Return,
None,
}
pub struct CallEveryFrame<'a> {
pub block: &'a Block,
pub external: bool,
}
#[derive(Debug)]
enum ExprResult {
Int(i32),
Float(f32),
Bool(bool),
}
impl ExprResult {
fn unwrap_bool(&self) -> bool {
match self {
ExprResult::Bool(result) => *result,
ExprResult::Int(value) => *value != 0,
ExprResult::Float(value) => *value != 0.0,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum VelModify {
Set(f32),
Add(f32),
None,
}
impl VelModify {
pub fn value(&self) -> f32 {
match self {
VelModify::Set(a) => *a,
VelModify::Add(a) => *a,
VelModify::None => 0.0,
}
}
}