use super::components::{CameraAnchored, CameraAnchoredDynamic};
use crate::app_state::overworld::OverworldState;
use bevy::prelude::*;
use evalexpr::{
ContextWithMutableFunctions, ContextWithMutableVariables, DefaultNumericTypes, EvalexprError,
Function, HashMapContext, Value,
};
pub(crate) fn update_camera_anchored_ui_on_camera_move_system(
app_state: Res<State<crate::app_state::AppState>>,
overworld_state: Option<Res<State<OverworldState>>>,
camera_query: Query<&Transform, (With<Camera2d>, Changed<Transform>)>,
mut anchored_ui_query: Query<(&CameraAnchored, &mut Transform), Without<Camera2d>>,
) {
let should_run = match app_state.get() {
crate::app_state::AppState::Battle => true,
crate::app_state::AppState::Overworld => overworld_state
.map(|s| matches!(s.get(), &OverworldState::Backpack | &OverworldState::Chase))
.unwrap_or(false),
_ => false,
};
if !should_run {
return;
}
let Ok(camera_transform) = camera_query.single() else {
return;
};
for (anchor, mut transform) in anchored_ui_query.iter_mut() {
let new_translation = camera_transform.translation + anchor.offset;
if transform.translation != new_translation {
transform.translation = new_translation;
}
}
}
pub(crate) fn update_dynamic_camera_anchors_system(
mut anchored_query: Query<
(&mut CameraAnchored, &CameraAnchoredDynamic, &mut Transform),
Without<Camera2d>,
>,
player_query: Query<
&Transform,
(
With<crate::app_state::overworld::character::components::PlayerControlled>,
Without<CameraAnchored>,
Without<Camera2d>,
),
>,
camera_query: Query<
&Transform,
(
With<Camera2d>,
Without<CameraAnchored>,
Without<crate::app_state::overworld::character::components::PlayerControlled>,
),
>,
) {
let Ok(player_transform) = player_query.single() else {
return;
};
let Ok(camera_transform) = camera_query.single() else {
return;
};
let mut context: HashMapContext<DefaultNumericTypes> = HashMapContext::new();
let _ = context.set_function(
"if".to_string(),
Function::new(|argument| {
if let Value::Tuple(args) = argument {
if args.len() != 3 {
return Err(EvalexprError::wrong_function_argument_amount(3, args.len()));
}
if args[0].as_boolean()? {
Ok(args[1].clone())
} else {
Ok(args[2].clone())
}
} else {
Err(EvalexprError::wrong_function_argument_amount(3, 1))
}
}),
);
let _ = context.set_value(
"player.x".to_string(),
evalexpr::Value::Float(player_transform.translation.x as f64),
);
let _ = context.set_value(
"player.y".to_string(),
evalexpr::Value::Float(player_transform.translation.y as f64),
);
let _ = context.set_value(
"camera.x".to_string(),
evalexpr::Value::Float(camera_transform.translation.x as f64),
);
let _ = context.set_value(
"camera.y".to_string(),
evalexpr::Value::Float(camera_transform.translation.y as f64),
);
for (mut anchor, dynamic, mut transform) in anchored_query.iter_mut() {
if let Some(expr) = &dynamic.y_expression {
match evalexpr::eval_with_context(expr, &context) {
Ok(val) => {
if let Ok(f) = val.as_float() {
let f: f64 = f;
let new_y = f as f32;
if anchor.offset.y != new_y {
trace!(
"Updating dynamic anchor Y: expr='{}', result={}, old_y={}, new_y={}, player_y={}, camera_y={}",
expr,
f,
anchor.offset.y,
new_y,
player_transform.translation.y,
camera_transform.translation.y
);
anchor.offset.y = new_y;
}
}
}
Err(e) => {
warn!(
"Failed to evaluate dynamic anchor expression '{}': {}",
expr, e
);
}
}
}
if let Some(expr) = &dynamic.x_expression
&& let Ok(val) = evalexpr::eval_with_context(expr, &context)
&& let Ok(f) = val.as_float()
{
let f: f64 = f;
anchor.offset.x = f as f32;
}
if let Some(expr) = &dynamic.z_expression
&& let Ok(val) = evalexpr::eval_with_context(expr, &context)
&& let Ok(f) = val.as_float()
{
let f: f64 = f;
anchor.offset.z = f as f32;
}
let new_translation = camera_transform.translation + anchor.offset;
if transform.translation != new_translation {
transform.translation = new_translation;
}
}
}
#[allow(clippy::type_complexity)]
pub(crate) fn update_camera_anchored_ui_on_change_system(
app_state: Res<State<crate::app_state::AppState>>,
overworld_state: Option<Res<State<OverworldState>>>,
camera_query: Query<&Transform, With<Camera2d>>,
mut anchored_ui_query: Query<
(&CameraAnchored, &mut Transform),
(
Without<Camera2d>,
Or<(Added<CameraAnchored>, Changed<CameraAnchored>)>,
),
>,
) {
let should_run = match app_state.get() {
crate::app_state::AppState::Battle => true,
crate::app_state::AppState::Overworld => overworld_state
.map(|s| matches!(s.get(), &OverworldState::Backpack | &OverworldState::Chase))
.unwrap_or(false),
_ => false,
};
if !should_run {
return;
}
let Ok(camera_transform) = camera_query.single() else {
warn_once!("No Camera2d available for anchoring UI");
return;
};
for (anchor, mut transform) in anchored_ui_query.iter_mut() {
let new_translation = camera_transform.translation + anchor.offset;
if transform.translation != new_translation {
transform.translation = new_translation;
}
}
}