use bevy::{color::palettes::css::*, prelude::*, time::common_conditions::on_timer}; use bevy_dogoap::prelude::*; use rand::Rng; use std::{collections::HashMap, time::Duration}; // A more involved example that resembles a simulation of sorts. // The simulation is of a miner, who wants to earn 3 gold // In order to get gold, they need to sell metal at the Merchant // And in order to get metal, the miner needs to smelt some ore // And in order to get ore, the miner needs to go out, find ore and mine it // And besides those requirements, the miner also have hunger and energy // that constantly change, and they need to sleep and eat in order // to satisfy those needs. // Put another way, the miner has to: // Eat and Sleep every now and then // Mine to get Ore // Smelt Ore to get Metal // Sell Metal to get Gold #[derive(Clone, Default, Reflect, Copy, EnumDatum)] enum Location { #[default] House, Outside, Mushroom, Ore, Smelter, Merchant, } /// This is our marker components, so we can keep track of the various in-game entities #[derive(Component)] struct Miner; #[derive(Component)] struct House; #[derive(Component)] struct Smelter; #[derive(Component)] struct Mushroom; #[derive(Component)] struct Ore; #[derive(Component)] struct Merchant; // Various actions our Miner can perform #[derive(Component, Clone, Reflect, Default, ActionComponent)] struct EatAction; #[derive(Component, Clone, Reflect, Default, ActionComponent)] struct SleepAction; #[derive(Component, Clone, Reflect, Default, ActionComponent)] struct MineOreAction; #[derive(Component, Clone, Reflect, Default, ActionComponent)] struct SmeltOreAction; #[derive(Component, Clone, Reflect, Default, ActionComponent)] struct SellMetalAction; #[derive(Component, Clone, Reflect, Default, ActionComponent)] struct GoToOutsideAction; #[derive(Component, Clone, Reflect, Default, ActionComponent)] struct GoToHouseAction; #[derive(Component, Clone, Reflect, Default, ActionComponent)] struct GoToMushroomAction; #[derive(Component, Clone, Reflect, Default, ActionComponent)] struct GoToOreAction; #[derive(Component, Clone, Reflect, Default, ActionComponent)] struct GoToSmelterAction; #[derive(Component, Clone, Reflect, Default, ActionComponent)] struct GoToMerchantAction; // All of our State fields #[derive(Component, Clone, DatumComponent)] struct Hunger(f64); #[derive(Component, Clone, DatumComponent)] struct Energy(f64); #[derive(Component, Clone, EnumComponent)] struct AtLocation(Location); #[derive(Component, Clone, DatumComponent)] struct HasOre(bool); #[derive(Component, Clone, DatumComponent)] struct HasMetal(bool); #[derive(Component, Clone, DatumComponent)] struct GoldAmount(i64); // UI elements #[derive(Component)] struct NeedsText; fn startup(mut commands: Commands, windows: Query<&Window>) { for i in 0..1 { let gold_goal = Goal::from_reqs(&[GoldAmount::is(3)]); let sleep_action = SleepAction::new() .add_precondition(Energy::is_less(50.0)) .add_precondition(AtLocation::is(Location::House)) .add_mutator(Energy::increase(100.0)) .set_cost(1); let eat_action = EatAction::new() .add_precondition(Hunger::is_more(50.0)) .add_precondition(AtLocation::is(Location::Mushroom)) .add_mutator(Hunger::decrease(25.0)) .add_mutator(AtLocation::set(Location::Outside)) .set_cost(2); let mine_ore_action = MineOreAction::new() .add_precondition(Energy::is_more(10.0)) .add_precondition(Hunger::is_less(75.0)) .add_precondition(AtLocation::is(Location::Ore)) .add_mutator(HasOre::set(true)) .set_cost(3); let smelt_ore_action = SmeltOreAction::new() .add_precondition(Energy::is_more(10.0)) .add_precondition(Hunger::is_less(75.0)) .add_precondition(AtLocation::is(Location::Smelter)) .add_precondition(HasOre::is(true)) .add_mutator(HasOre::set(false)) .add_mutator(HasMetal::set(true)) .set_cost(4); let sell_metal_action = SellMetalAction::new() .add_precondition(Energy::is_more(10.0)) .add_precondition(Hunger::is_less(75.0)) .add_precondition(AtLocation::is(Location::Merchant)) .add_precondition(HasMetal::is(true)) .add_mutator(GoldAmount::increase(1)) .add_mutator(HasMetal::set(false)) .set_cost(5); let go_to_outside_action = GoToOutsideAction::new() .add_mutator(AtLocation::set(Location::Outside)) .set_cost(1); let go_to_house_action = GoToHouseAction::new() .add_precondition(AtLocation::is(Location::Outside)) .add_mutator(AtLocation::set(Location::House)) .set_cost(1); let go_to_mushroom_action = GoToMushroomAction::new() .add_precondition(AtLocation::is(Location::Outside)) .add_mutator(AtLocation::set(Location::Mushroom)) .set_cost(2); let go_to_ore_action = GoToOreAction::new() .add_precondition(AtLocation::is(Location::Outside)) .add_mutator(AtLocation::set(Location::Ore)) .set_cost(3); let go_to_smelter_action = GoToSmelterAction::new() .add_precondition(AtLocation::is(Location::Outside)) .add_mutator(AtLocation::set(Location::Smelter)) .set_cost(4); let go_to_merchant_action = GoToMerchantAction::new() .add_precondition(AtLocation::is(Location::Outside)) .add_mutator(AtLocation::set(Location::Merchant)) .set_cost(5); let (mut planner, components) = create_planner!({ actions: [ (EatAction, eat_action), (SleepAction, sleep_action), (MineOreAction, mine_ore_action), (SmeltOreAction, smelt_ore_action), (SellMetalAction, sell_metal_action), // (GoToOutsideAction, go_to_outside_action), (GoToHouseAction, go_to_house_action), (GoToMushroomAction, go_to_mushroom_action), (GoToOreAction, go_to_ore_action), (GoToSmelterAction, go_to_smelter_action), (GoToMerchantAction, go_to_merchant_action), ], state: [GoldAmount(0), Hunger(25.0), Energy(75.0), AtLocation(Location::Outside), HasOre(false), HasMetal(false)], goals: [gold_goal], }); // Don't remove the goal if there is no plan found planner.remove_goal_on_no_plan_found = false; // Re-calculate our plan constantly planner.always_plan = true; // Set current goal to be to acquire gold planner.current_goal = Some(gold_goal.clone()); let text_style = TextStyle { font_size: 18.0, ..default() }; commands .spawn(( Name::new("Miner"), Miner, planner, components, Transform::from_translation(Vec3::ZERO.with_x(50.0 * i as f32)), GlobalTransform::from_translation(Vec3::ZERO.with_x(50.0 * i as f32)), )) .with_children(|subcommands| { subcommands.spawn(( Text2dBundle { transform: Transform::from_translation(Vec3::new(10.0, -10.0, 10.0)), text: Text::from_section("", text_style.clone()) .with_justify(JustifyText::Left), text_anchor: bevy::sprite::Anchor::TopLeft, ..default() }, NeedsText, )); }); } commands.spawn(( Name::new("House"), House, Transform::from_translation(Vec3::new(100.0, 100.0, 0.0)), )); commands.spawn(( Name::new("Smelter"), Smelter, Transform::from_translation(Vec3::new(0.0, -200.0, 0.0)), )); commands.spawn(( Name::new("Merchant"), Merchant, Transform::from_translation(Vec3::new(-300.0, -50.0, 0.0)), )); let window = windows.get_single().expect("Expected only one window! Wth"); let window_height = window.height() / 2.0; let window_width = window.width() / 2.0; let mut rng = rand::thread_rng(); // Begin with three mushrooms our miner can eat for _i in 0..3 { let y = rng.gen_range(-window_height..window_height); let x = rng.gen_range(-window_width..window_width); commands.spawn(( Name::new("Mushroom"), Mushroom, Transform::from_translation(Vec3::new(x, y, 0.0)), )); } // Spawn 10 ores we can mine as well for _i in 0..10 { let y = rng.gen_range(-window_height..window_height); let x = rng.gen_range(-window_width..window_width); commands.spawn(( Name::new("Ore"), Ore, Transform::from_translation(Vec3::new(x, y, 0.0)), )); } // Spawn a camera so we see something commands.spawn(Camera2dBundle::default()); } // Spawn new mushrooms if there are less than 10 fn spawn_random_mushroom( windows: Query<&Window>, mut commands: Commands, mushrooms: Query>, ) { if mushrooms.iter().len() < 10 { let window = windows.get_single().expect("Expected only one window! Wth"); let window_height = window.height() / 2.0; let window_width = window.width() / 2.0; let mut rng = rand::thread_rng(); let y = rng.gen_range(-window_height..window_height); let x = rng.gen_range(-window_width..window_width); commands.spawn(( Name::new("Mushroom"), Mushroom, Transform::from_translation(Vec3::new(x, y, 0.0)), )); } } // Spawn new mushrooms if there are less than 10 fn spawn_random_ore( windows: Query<&Window>, mut commands: Commands, ores: Query>, ) { if ores.iter().len() < 10 { let window = windows.get_single().expect("Expected only one window! Wth"); let window_height = window.height() / 2.0; let window_width = window.width() / 2.0; let mut rng = rand::thread_rng(); let y = rng.gen_range(-window_height..window_height); let x = rng.gen_range(-window_width..window_width); commands.spawn(( Name::new("Ore"), Ore, Transform::from_translation(Vec3::new(x, y, 0.0)), )); } } // Helper function to handle the GoTo* actions fn go_to_location( at_location: &mut AtLocation, delta: f32, origin: &mut Transform, destination: Vec3, destination_enum: Location, entity: Entity, commands: &mut Commands, ) where T: Component, { if origin.translation.distance(destination) > 5.0 { // We're not quite there yet, move closer let direction = (destination - origin.translation).normalize(); origin.translation += direction * 128.0 * delta; } else { // We're there! at_location.0 = destination_enum; // Remove our action to signal we've completed the move commands.entity(entity).remove::(); } } fn handle_go_to_house_action( mut commands: Commands, time: Res