use crate::{
gdobj::{ColourChannel, GDObjConfig, GDObject, GDValue, MoveEasing},
utils::clamp_to_values,
};
pub const POS_PLAYER1: i32 = 99999;
pub const POS_PLAYER2: i32 = 99998;
pub const MOVE_X_ONLY: i32 = 1;
pub const MOVE_Y_ONLY: i32 = 2;
#[repr(i32)]
pub enum Gamemode {
Cube = 0,
Ship = 1,
Ball = 2,
Ufo = 3,
Wave = 4,
Robot = 5,
Spider = 6,
Swing = 7,
}
#[repr(i32)]
pub enum StopMode {
Stop = 0,
Pause = 1,
Resume = 2,
}
#[repr(i32)]
pub enum ItemAlign {
Center = 0,
Left = 1,
Right = 2,
}
#[repr(i32)]
pub enum CounterMode {
Attempts = -3,
Points = -2,
MainTime = -1,
}
#[repr(i32)]
#[derive(PartialEq)]
pub enum TransitionMode {
Both = 0,
Enter = 1,
Exit = 2,
}
#[repr(i32)]
pub enum TransitionType {
Fade = 22,
FromBottom = 23,
FromTop = 24,
FromLeft = 25,
FromRight = 26,
ScaleIn = 27,
ScaleOut = 28,
Random = 55,
AwayToLeft = 56,
AwayToRight = 57,
AwayFromMiddle = 58,
TowardsMiddle = 59,
None = 1915,
}
#[repr(i32)]
pub enum ItemType {
Counter = 1,
Timer = 2,
Points = 3,
MainTime = 4,
Attempts = 5,
}
#[repr(i32)]
pub enum Op {
Set = 0,
Add = 1,
Sub = 2,
Mul = 3,
Div = 4,
}
#[repr(i32)]
pub enum CompareOp {
Equals = 0,
Greater = 1,
GreaterOrEquals = 2,
Less = 3,
LessOrEquals = 4,
NotEquals = 5,
}
#[repr(i32)]
pub enum RoundMode {
None = 0,
Nearest = 1,
Floor = 2,
Ceiling = 3,
}
#[repr(i32)]
pub enum SignMode {
None = 0,
Absolute = 1,
Negative = 2,
}
#[repr(i32)]
pub enum TargetPlayer {
Player1 = 138,
Player2 = 200,
PlayerTarget = 201,
}
pub enum MoveMode {
Default(DefaultMove),
Targeting(TargetMove),
Directional(DirectionalMove),
}
pub enum MoveLock {
Player,
Camera,
}
pub struct DefaultMove {
pub dx: f64,
pub dy: f64,
pub x_lock: Option<MoveLock>,
pub y_lock: Option<MoveLock>,
}
pub struct TargetMove {
pub target_group_id: i32,
pub center_group_id: Option<i32>,
pub axis_only: Option<i32>,
}
pub struct DirectionalMove {
pub target_group_id: i32,
pub center_group_id: Option<i32>,
pub distance: i32,
}
pub fn move_trigger(
config: GDObjConfig,
move_config: MoveMode,
time: f64,
target_group: i32,
silent: bool,
dynamic: bool,
easing: Option<(MoveEasing, f64)>,
) -> GDObject {
let mut properties = vec![
(51, GDValue::Int(target_group)),
(10, GDValue::Float(time)),
(393, GDValue::Int(1)),
(397, GDValue::Int(dynamic as i32)),
(544, GDValue::Int(silent as i32)),
];
if let Some((easing, rate)) = easing {
properties.push((30, GDValue::Int(easing as i32)));
properties.push((85, GDValue::Float(rate)));
}
match move_config {
MoveMode::Default(config) => {
if let Some(lock) = config.x_lock {
properties.push((
match lock {
MoveLock::Player => 58,
MoveLock::Camera => 141,
},
GDValue::Int(1),
));
properties.push((143, GDValue::Float(config.dx as f64)));
} else {
properties.push((28, GDValue::Int(config.dx as i32)));
}
if let Some(lock) = config.y_lock {
properties.push((
match lock {
MoveLock::Player => 59,
MoveLock::Camera => 142,
},
GDValue::Int(1),
));
properties.push((144, GDValue::Float(config.dy as f64)));
} else {
properties.push((29, GDValue::Int(config.dy as i32)));
}
}
MoveMode::Targeting(config) => {
properties.push((100, GDValue::Int(1)));
if let Some(id) = config.center_group_id {
properties.push((395, GDValue::Int(id)));
}
if let Some(axis) = config.axis_only {
properties.push((101, GDValue::Int(axis)));
}
match config.target_group_id {
POS_PLAYER1 => properties.push((138, GDValue::Int(1))),
POS_PLAYER2 => properties.push((200, GDValue::Int(1))),
id => properties.push((71, GDValue::Int(id))),
};
}
MoveMode::Directional(config) => {
if let Some(id) = config.center_group_id {
properties.push((395, GDValue::Int(id)));
}
match config.target_group_id {
POS_PLAYER1 => properties.push((138, GDValue::Int(1))),
POS_PLAYER2 => properties.push((200, GDValue::Int(1))),
id => properties.push((71, GDValue::Int(id))),
};
properties.push((394, GDValue::Int(1)));
properties.push((396, GDValue::Int(config.distance)));
}
}
GDObject::new(901, config, properties)
}
pub fn start_pos(
config: GDObjConfig,
start_speed: f64,
starting_gamemode: Gamemode,
starting_as_mini: bool,
starting_as_dual: bool,
starting_mirrored: bool,
reset_camera: bool,
rotate_gameplay: bool,
reverse_gameplay: bool,
target_order: i32,
target_channel: i32,
disabled: bool,
) -> GDObject {
let start_speed = clamp_to_values(start_speed, &[0.5, 1.0, 2.0, 3.0, 4.0]);
let properties = vec![
(
10004,
GDValue::Int(match start_speed {
0.5 => 1,
2.0 => 2,
3.0 => 3,
4.0 => 4,
_ => 0,
}),
),
(10002, GDValue::Int(starting_gamemode as i32)),
(10003, GDValue::Int(starting_as_mini as i32)),
(10008, GDValue::Int(starting_as_dual as i32)),
(10021, GDValue::Int(disabled as i32)),
(10028, GDValue::Int(starting_mirrored as i32)),
(10029, GDValue::Int(rotate_gameplay as i32)),
(10020, GDValue::Int(reverse_gameplay as i32)),
(10019, GDValue::Int(target_order)),
(10026, GDValue::Int(target_channel)),
(10035, GDValue::Int(reset_camera as i32)),
(10010, GDValue::Int(0)),
(10011, GDValue::String("".to_string())),
(10020, GDValue::Int(1)),
(10022, GDValue::Int(0)),
(10023, GDValue::Int(0)),
(10024, GDValue::Int(0)),
(10027, GDValue::Int(1)),
(10031, GDValue::Int(1)),
(10032, GDValue::Int(1)),
(10033, GDValue::Int(1)),
(10034, GDValue::Int(1)),
(10036, GDValue::Int(0)),
(10037, GDValue::Int(1)),
(10038, GDValue::Int(1)),
(10039, GDValue::Int(1)),
(10040, GDValue::Int(1)),
(10041, GDValue::Int(1)),
(10042, GDValue::Int(1)),
(10043, GDValue::Int(0)),
(10044, GDValue::Int(0)),
(10045, GDValue::Int(1)),
(10046, GDValue::Int(0)),
(10009, GDValue::Int(1)),
];
GDObject::new(31, config, properties)
}
pub fn colour_trigger(
config: GDObjConfig,
colour: (u8, u8, u8),
channel: ColourChannel,
fade_time: f64,
opacity: f64,
blending: bool,
use_player_col_1: bool,
use_player_col_2: bool,
copy_colour: Option<(ColourChannel, i32, f64, f64, bool, bool, bool, bool)>,
) -> GDObject {
let mut properties = vec![
(7, GDValue::Int(colour.0 as i32)),
(8, GDValue::Int(colour.1 as i32)),
(9, GDValue::Int(colour.2 as i32)),
(10, GDValue::Float(fade_time)),
(15, GDValue::Int(use_player_col_1 as i32)),
(23, GDValue::Int(channel.as_i32())),
(16, GDValue::Int(use_player_col_2 as i32)),
(35, GDValue::Float(opacity)),
];
if blending {
properties.push((17, GDValue::Bool(true)));
}
if let Some((
channel,
hue,
saturation,
lightness,
static_sat_scalar,
static_brightness_scalar,
legacy_hsv,
copy_opacity,
)) = copy_colour
{
let mut cfg_string = format!(
"{hue}a{saturation}a{lightness}a{}a",
static_sat_scalar as i32
);
if !legacy_hsv {
cfg_string += &format!("{}", static_brightness_scalar as i32);
properties.push((210, GDValue::Bool(true)));
}
if copy_opacity {
properties.push((60, GDValue::Bool(true)));
}
properties.push((49, GDValue::String(cfg_string)));
properties.push((50, GDValue::Int(channel.as_i32())));
}
GDObject::new(899, config, properties)
}
pub fn stop_trigger(
config: GDObjConfig,
target_group: i32,
stop_mode: StopMode,
use_control_id: bool,
) -> GDObject {
let properties = vec![
(51, GDValue::Int(target_group)),
(535, GDValue::Int(use_control_id as i32)),
(580, GDValue::Int(stop_mode as i32)),
];
GDObject::new(1616, config, properties)
}
pub fn alpha_trigger(
config: GDObjConfig,
target_group: i32,
opacity: f64,
fade_time: f64,
) -> GDObject {
GDObject::new(
1007,
config,
vec![
(10, GDValue::Float(fade_time)),
(35, GDValue::Float(opacity)),
(51, GDValue::Int(target_group)),
],
)
}
pub fn toggle_trigger(config: GDObjConfig, target_group: i32, activate_group: bool) -> GDObject {
let mut properties = vec![
(51, GDValue::Int(target_group)),
(64, GDValue::Bool(true)),
(67, GDValue::Bool(true)),
];
if activate_group {
properties.push((56, GDValue::Bool(true)));
}
GDObject::new(1049, config, properties)
}
pub fn pulse_trigger(config: GDObjConfig) {}
pub fn transition_object(
config: GDObjConfig,
transition: TransitionType,
mode: TransitionMode,
target_channel: Option<i32>,
) -> GDObject {
let mut properties = vec![];
if mode != TransitionMode::Both {
properties.push((217, GDValue::Int(mode as i32)));
}
if let Some(channel) = target_channel {
properties.push((344, GDValue::Int(channel)));
}
GDObject::new(transition as i32, config, properties)
}
pub fn reverse_gameplay(config: GDObjConfig) -> GDObject {
GDObject::new(1917, config, vec![])
}
pub fn link_visible(config: GDObjConfig, target_group: i32) -> GDObject {
GDObject::new(3662, config, vec![(51, GDValue::Int(target_group))])
}
pub fn timewarp(config: GDObjConfig, time_scale: f64) -> GDObject {
GDObject::new(1935, config, vec![(120, GDValue::Float(time_scale))])
}
pub fn show_player(config: GDObjConfig) -> GDObject {
GDObject::new(1613, config, vec![])
}
pub fn hide_player(config: GDObjConfig) -> GDObject {
GDObject::new(1612, config, vec![])
}
pub fn show_player_trail(config: GDObjConfig) -> GDObject {
GDObject::new(32, config, vec![])
}
pub fn hide_player_trail(config: GDObjConfig) -> GDObject {
GDObject::new(33, config, vec![])
}
pub fn bg_effect_on(config: GDObjConfig) -> GDObject {
GDObject::new(1818, config, vec![])
}
pub fn bg_effect_off(config: GDObjConfig) -> GDObject {
GDObject::new(1819, config, vec![])
}
pub fn group_reset(config: GDObjConfig, target_group: i32) -> GDObject {
GDObject::new(3618, config, vec![(51, GDValue::Int(target_group))])
}
pub fn shake_trigger(config: GDObjConfig, strength: i32, interval: f64, duration: f64) -> GDObject {
GDObject::new(
1520,
config,
vec![
(75, GDValue::Int(strength)),
(84, GDValue::Float(interval)),
(10, GDValue::Float(duration)),
],
)
}
pub fn bg_speed(config: GDObjConfig, mod_x: f64, mod_y: f64) -> GDObject {
GDObject::new(
3606,
config,
vec![(143, GDValue::Float(mod_x)), (144, GDValue::Float(mod_y))],
)
}
pub fn mg_speed(config: GDObjConfig, mod_x: f64, mod_y: f64) -> GDObject {
GDObject::new(
3612,
config,
vec![(143, GDValue::Float(mod_x)), (144, GDValue::Float(mod_y))],
)
}
pub fn player_control(
config: GDObjConfig,
p1: bool,
p2: bool,
stop_jump: bool,
stop_move: bool,
stop_rotation: bool,
stop_slide: bool,
) -> GDObject {
GDObject::new(
1932,
config,
vec![
(138, GDValue::Int(p1 as i32)),
(200, GDValue::Int(p2 as i32)),
(540, GDValue::Int(stop_jump as i32)),
(541, GDValue::Int(stop_move as i32)),
(542, GDValue::Int(stop_rotation as i32)),
(543, GDValue::Int(stop_slide as i32)),
],
)
}
pub fn gravity_trigger(
config: GDObjConfig,
gravity: f64,
target_player: Option<TargetPlayer>,
) -> GDObject {
let mut properties = vec![(148u16, GDValue::Float(gravity))];
if let Some(player) = target_player {
properties.push((player as i32 as u16, GDValue::Bool(true)));
}
GDObject::new(2066, config, properties)
}
pub fn end_trigger(
config: GDObjConfig,
spawn_id: Option<i32>,
target_pos: Option<i32>,
no_effects: bool,
instant: bool,
no_sfx: bool,
) -> GDObject {
let mut properties = vec![
(460, GDValue::Int(no_effects as i32)),
(461, GDValue::Int(instant as i32)),
(467, GDValue::Int(no_sfx as i32)),
];
if let Some(id) = spawn_id {
properties.push((51, GDValue::Int(id)));
}
if let Some(pos) = target_pos {
properties.push((71, GDValue::Int(pos)));
}
GDObject::new(3600, config, properties)
}
pub fn counter_object(
config: GDObjConfig,
item_id: i32,
timer: bool,
align: ItemAlign,
seconds_only: bool,
special_mode: Option<CounterMode>,
) -> GDObject {
let mut properties = vec![
(80, GDValue::Int(item_id)),
(389, GDValue::Int(seconds_only as i32)),
(391, GDValue::Int(align as i32)),
(466, GDValue::Int(timer as i32)),
];
if let Some(mode) = special_mode {
properties.push((390, GDValue::Int(mode as i32)));
}
GDObject::new(1615, config, properties)
}
pub fn item_edit(
config: GDObjConfig,
operand1: Option<(i32, ItemType)>,
operand2: Option<(i32, ItemType)>,
target_id: i32,
target_type: ItemType,
modifier: f64,
assign_op: Op,
mod_op: Option<Op>,
id_op: Option<Op>,
id_rounding: RoundMode,
result_rounding: RoundMode,
id_sign: SignMode,
result_sign: SignMode,
) -> GDObject {
let mod_op = match mod_op {
Some(op) => op,
None => Op::Mul,
};
let id_op = match id_op {
Some(op) => op,
None => Op::Add,
};
let op_1 = match operand1 {
Some(cfg) => cfg,
None => (0, ItemType::Counter),
};
let op_2 = match operand2 {
Some(cfg) => cfg,
None => (0, ItemType::Counter),
};
GDObject::new(
3619,
config,
vec![
(36, GDValue::Int(1)),
(51, GDValue::Int(target_id)),
(80, GDValue::Int(op_1.0)),
(95, GDValue::Int(op_2.0)),
(476, GDValue::Int(op_1.1 as i32)),
(477, GDValue::Int(op_2.1 as i32)),
(478, GDValue::Int(target_type as i32)),
(479, GDValue::Float(modifier)),
(480, GDValue::Int(assign_op as i32)),
(481, GDValue::Int(id_op as i32)),
(482, GDValue::Int(mod_op as i32)),
(485, GDValue::Int(id_rounding as i32)),
(486, GDValue::Int(result_rounding as i32)),
(578, GDValue::Int(id_sign as i32)),
(579, GDValue::Int(result_sign as i32)),
],
)
}
pub fn item_compare(
config: GDObjConfig,
true_id: i32,
false_id: i32,
lhs: (i32, ItemType, f64, Op, RoundMode, SignMode),
rhs: (i32, ItemType, f64, Op, RoundMode, SignMode),
compare_op: CompareOp,
tolerance: f64,
) -> GDObject {
let properties = vec![
(51, GDValue::Int(true_id)),
(71, GDValue::Int(false_id)),
(80, GDValue::Int(lhs.0)),
(95, GDValue::Int(rhs.0)),
(476, GDValue::Int(lhs.1 as i32)),
(477, GDValue::Int(rhs.1 as i32)),
(479, GDValue::Float(lhs.2)),
(483, GDValue::Float(rhs.2)),
(480, GDValue::Int(lhs.3 as i32)),
(481, GDValue::Int(rhs.3 as i32)),
(482, GDValue::Int(compare_op as i32)),
(484, GDValue::Float(tolerance)),
(485, GDValue::Int(lhs.4 as i32)),
(486, GDValue::Int(rhs.4 as i32)),
(578, GDValue::Int(lhs.5 as i32)),
(579, GDValue::Int(rhs.5 as i32)),
];
GDObject::new(3620, config, properties)
}
pub fn persistent_item(
config: GDObjConfig,
item_id: i32,
timer: bool,
persistent: bool,
target_all: bool,
reset: bool,
) -> GDObject {
GDObject::new(
3641,
config,
vec![
(51, GDValue::Int(item_id)),
(491, GDValue::Int(persistent as i32)),
(492, GDValue::Int(target_all as i32)),
(493, GDValue::Int(reset as i32)),
(494, GDValue::Int(timer as i32)),
],
)
}
pub fn random_trigger(
config: GDObjConfig,
chance: f64,
target_group1: i32,
target_group2: i32,
) -> GDObject {
GDObject::new(
1912,
config,
vec![
(51, GDValue::Int(target_group1)),
(71, GDValue::Int(target_group2)),
(10, GDValue::Float(chance)),
],
)
}
pub fn spawn_trigger(
config: GDObjConfig,
spawn_id: i32,
delay: f64,
delay_variation: f64,
reset_remap: bool,
spawn_ordered: bool,
preview_disable: bool,
) -> GDObject {
GDObject::new(
1268,
config,
vec![
(51, GDValue::Int(spawn_id)),
(63, GDValue::Float(delay)),
(102, GDValue::Int(preview_disable as i32)),
(441, GDValue::Int(spawn_ordered as i32)),
(556, GDValue::Float(delay_variation)),
(581, GDValue::Int(reset_remap as i32)),
],
)
}
pub fn on_death(config: GDObjConfig, target_group: i32, activate_group: bool) -> GDObject {
GDObject::new(
1812,
config,
vec![
(51, GDValue::Int(target_group)),
(56, GDValue::Int(activate_group as i32)),
],
)
}
pub fn spawn_particle(
config: GDObjConfig,
particle_group: i32,
position_group: i32,
position_offsets: Option<(i32, i32)>,
position_variation: Option<(i32, i32)>,
rotation_config: Option<(i32, i32)>,
scale_config: Option<(f64, f64)>,
match_rotation: bool,
) -> GDObject {
let mut properties = vec![
(51, GDValue::Int(particle_group)),
(71, GDValue::Int(position_group)),
(551, GDValue::Bool(match_rotation)),
];
if let Some((x, y)) = position_offsets {
properties.push((547, GDValue::Int(x)));
properties.push((548, GDValue::Int(y)));
}
if let Some((x, y)) = position_variation {
properties.push((549, GDValue::Int(x)));
properties.push((550, GDValue::Int(y)));
}
if let Some((rot, var)) = rotation_config {
properties.push((552, GDValue::Int(rot)));
properties.push((553, GDValue::Int(var)));
}
if let Some((scale, var)) = scale_config {
properties.push((554, GDValue::Float(scale)));
properties.push((555, GDValue::Float(var)));
}
GDObject::new(3608, config, properties)
}
pub fn collision_block(config: GDObjConfig, id: i32, dynamic: bool) -> GDObject {
GDObject::new(
1816,
config,
vec![(80, GDValue::Int(id)), (94, GDValue::Int(dynamic as i32))],
)
}
pub fn toggle_block(
config: GDObjConfig,
target_group: i32,
activate_group: bool,
claim_touch: bool,
multi_activate: bool,
spawn_only: bool,
) -> GDObject {
GDObject::new(
3643,
config,
vec![
(51, GDValue::Int(target_group)),
(56, GDValue::Int(activate_group as i32)),
(99, GDValue::Int(multi_activate as i32)),
(445, GDValue::Int(claim_touch as i32)),
(504, GDValue::Int(spawn_only as i32)),
],
)
}
pub fn state_block(config: GDObjConfig, state_on: i32, state_off: i32) -> GDObject {
GDObject::new(
3640,
config,
vec![(51, GDValue::Int(state_on)), (71, GDValue::Int(state_off))],
)
}
pub fn time_control(config: GDObjConfig, id: i32, stop: bool) -> GDObject {
GDObject::new(
3617,
config,
vec![(80, GDValue::Int(id)), (472, GDValue::Int(stop as i32))],
)
}
pub fn time_event(
config: GDObjConfig,
id: i32,
target_group: i32,
target_time: f64,
multi_activate: bool,
) -> GDObject {
GDObject::new(
3615,
config,
vec![
(80, GDValue::Int(id)),
(51, GDValue::Int(target_group)),
(473, GDValue::Float(target_time)),
(475, GDValue::Int(multi_activate as i32)),
],
)
}
pub fn camera_zoom(
config: GDObjConfig,
zoom: f64,
time: f64,
easing: Option<(MoveEasing, f64)>,
) -> GDObject {
let mut properties = vec![(10, GDValue::Float(time)), (371, GDValue::Float(zoom))];
if let Some((easing, rate)) = easing {
properties.push((30, GDValue::Int(easing as i32)));
properties.push((85, GDValue::Float(rate)));
}
GDObject::new(1913, config, properties)
}
pub fn camera_guide(
config: GDObjConfig,
zoom: f64,
offset_x: i32,
offset_y: i32,
opacity: f64,
) -> GDObject {
GDObject::new(
1913,
config,
vec![
(28, GDValue::Int(offset_x)),
(29, GDValue::Int(offset_y)),
(371, GDValue::Float(zoom)),
(506, GDValue::Float(opacity)),
],
)
}