Skip to content

Commit

Permalink
Support arbitrary slots during initial upgrade sequence
Browse files Browse the repository at this point in the history
  • Loading branch information
benfrankel committed Dec 8, 2023
1 parent bc5b34a commit 7a7447c
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 138 deletions.
12 changes: 2 additions & 10 deletions src/state/editor_screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@ pub use crate::state::editor_screen::scene_view::SceneView;
pub use crate::state::editor_screen::scene_view::SceneViewBounds;
pub use crate::state::editor_screen::scene_view::WrapWithinSceneView;
use crate::state::editor_screen::upgrade_panel::spawn_upgrade_panel;
pub use crate::state::editor_screen::upgrade_panel::UpgradeContainer;
use crate::state::editor_screen::upgrade_panel::UpgradeSequence;
use crate::state::AppState::*;
use crate::upgrade::UpgradeList;
use crate::AppRoot;

pub struct EditorScreenStatePlugin;
Expand Down Expand Up @@ -112,8 +109,6 @@ fn enter_editor_screen(
mut commands: Commands,
root: Res<AppRoot>,
config: Res<Config>,
upgrade_list: Res<UpgradeList>,
simulation: Res<Simulation>,
time: Res<Time>,
) {
let config = &config.editor_screen;
Expand All @@ -125,17 +120,14 @@ fn enter_editor_screen(
commands.insert_resource(PassiveCodeTyper::default());
commands.insert_resource(PassiveEntitySpawner::default());
commands.insert_resource(UpgradeOutline::default());
commands.insert_resource(UpgradeSequence::default());

let screen = spawn_editor_screen(&mut commands, config, &upgrade_list, &simulation, true);
let screen = spawn_editor_screen(&mut commands, config, true);
commands.entity(screen).set_parent(root.ui);
}

pub fn spawn_editor_screen(
commands: &mut Commands,
config: &EditorScreenConfig,
upgrade_list: &UpgradeList,
simulation: &Simulation,
light_theme: bool,
) -> Entity {
let theme = if light_theme {
Expand Down Expand Up @@ -205,7 +197,7 @@ pub fn spawn_editor_screen(
};
commands.entity(code_panel).set_parent(vbox);

let upgrade_panel = spawn_upgrade_panel(commands, theme, upgrade_list, simulation);
let upgrade_panel = spawn_upgrade_panel(commands, theme);
commands.entity(upgrade_panel).set_parent(hbox);

editor_screen
Expand Down
122 changes: 19 additions & 103 deletions src/state/editor_screen/upgrade_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ use bevy::math::vec2;
use bevy::prelude::*;
use bevy::ui::Val::*;
use bevy_mod_picking::prelude::*;
use rand::seq::SliceRandom;
use rand::thread_rng;

use crate::config::Config;
use crate::simulation::Simulation;
Expand All @@ -20,8 +18,7 @@ use crate::ui::FONT_HANDLE;
use crate::upgrade::UpgradeEvent;
use crate::upgrade::UpgradeKind;
use crate::upgrade::UpgradeList;
use crate::upgrade::ALL_UPGRADE_KINDS;
use crate::upgrade::INITIAL_UPGRADES;
use crate::upgrade::UpgradeSequence;
use crate::util::pretty_num;
use crate::util::DespawnSet;
use crate::AppSet;
Expand All @@ -30,26 +27,21 @@ pub struct UpgradePanelPlugin;

impl Plugin for UpgradePanelPlugin {
fn build(&self, app: &mut App) {
app.register_type::<UpgradeContainer>()
app.register_type::<IsUpgradeContainer>()
.register_type::<UpgradeButton>()
.register_type::<UpgradeSequence>()
.init_resource::<UpgradeSequence>()
.add_systems(
Update,
offer_new_upgrades
.in_set(AppSet::Update)
.run_if(on_event::<UpgradeEvent>()),
offer_next_upgrades.in_set(AppSet::Update).run_if(
on_event::<UpgradeEvent>().or_else(
state_changed::<AppState>().and_then(in_state(AppState::EditorScreen)),
),
),
)
.add_systems(Update, update_upgrade_button_disabled.in_set(AppSet::End));
}
}

pub fn spawn_upgrade_panel(
commands: &mut Commands,
theme: &EditorScreenTheme,
upgrade_list: &UpgradeList,
simulation: &Simulation,
) -> Entity {
pub fn spawn_upgrade_panel(commands: &mut Commands, theme: &EditorScreenTheme) -> Entity {
let upgrade_panel = commands
.spawn((
Name::new("UpgradePanel"),
Expand Down Expand Up @@ -87,7 +79,7 @@ pub fn spawn_upgrade_panel(
))
.set_parent(upgrade_panel);

let upgrade_container = commands
commands
.spawn((
Name::new("UpgradeContainer"),
NodeBundle {
Expand All @@ -99,21 +91,9 @@ pub fn spawn_upgrade_panel(
},
..default()
},
UpgradeContainer { slots: 1 },
IsUpgradeContainer,
))
.set_parent(upgrade_panel)
.id();

let upgrade_button = spawn_upgrade_button(
commands,
theme,
upgrade_list,
INITIAL_UPGRADES[0],
simulation,
);
commands
.entity(upgrade_button)
.set_parent(upgrade_container);
.set_parent(upgrade_panel);

let submit_container = commands
.spawn((
Expand Down Expand Up @@ -304,92 +284,28 @@ fn update_upgrade_button_disabled(
}

#[derive(Component, Reflect)]
pub struct UpgradeContainer {
pub slots: usize,
}
struct IsUpgradeContainer;

#[derive(Resource, Reflect)]
#[reflect(Resource)]
pub struct UpgradeSequence {
sequence: Vec<UpgradeKind>,
next_idx: usize,
}

impl Default for UpgradeSequence {
fn default() -> Self {
Self {
sequence: INITIAL_UPGRADES[1..].to_vec(),
next_idx: 0,
}
}
}

impl UpgradeSequence {
fn next(
&mut self,
upgrade_list: &UpgradeList,
simulation: &Simulation,
outline: &UpgradeOutline,
) -> Option<UpgradeKind> {
while self.next_idx < self.sequence.len() {
self.next_idx += 1;
let kind = self.sequence[self.next_idx - 1];
if upgrade_list[kind].is_unlocked(simulation, outline) {
return Some(kind);
}
}
None
}
}

fn offer_new_upgrades(
fn offer_next_upgrades(
mut commands: Commands,
mut despawn: ResMut<DespawnSet>,
config: Res<Config>,
upgrade_list: Res<UpgradeList>,
mut upgrade_sequence: ResMut<UpgradeSequence>,
mut sequence: ResMut<UpgradeSequence>,
simulation: Res<Simulation>,
outline: Res<UpgradeOutline>,
container_query: Query<(Entity, &Children, &UpgradeContainer)>,
container_query: Query<(Entity, Option<&Children>), With<IsUpgradeContainer>>,
) {
let theme = &config.editor_screen.dark_theme;
for (entity, buttons, container) in &container_query {
for (entity, buttons) in &container_query {
// Despawn old upgrade options
for &button in buttons {
for &button in buttons.into_iter().flatten() {
despawn.recursive(button);
}

// Try to fill slots from the initial sequence of upgrades first
let mut slots = container.slots;
let mut next_upgrades = vec![];
while slots > 0 {
let Some(kind) = upgrade_sequence.next(&upgrade_list, &simulation, &outline) else {
break;
};
next_upgrades.push(kind);
slots -= 1;
}

// Filter the list of all upgrade kinds into just the ones that are unlocked
let unlocked_upgrades = ALL_UPGRADE_KINDS
.into_iter()
.filter(|&kind| {
!next_upgrades.contains(&kind)
&& upgrade_list[kind].is_unlocked(&simulation, &outline)
})
.collect::<Vec<_>>();

// Randomly choose the next upgrades for the remaining slots (weighted)
next_upgrades.extend(
unlocked_upgrades
.choose_multiple_weighted(&mut thread_rng(), slots, |&kind| {
upgrade_list[kind].weight
})
.unwrap(),
);

// Sort by name
// Sort next upgrades by name
// TODO: Sort some other way? Don't sort?
let mut next_upgrades = sequence.next(&upgrade_list, &simulation, &outline);
next_upgrades.sort_by_key(|&kind| &upgrade_list[kind].name);

for kind in next_upgrades {
Expand Down
104 changes: 79 additions & 25 deletions src/upgrade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use std::ops::IndexMut;
use bevy::ecs::event::ManualEventReader;
use bevy::ecs::system::SystemId;
use bevy::prelude::*;
use rand::seq::SliceRandom;
use rand::thread_rng;
use rand::Rng;
use strum::EnumCount;

Expand All @@ -17,7 +19,6 @@ use crate::simulation::SpawnEvent;
use crate::state::editor_screen::spawn_editor_screen;
use crate::state::editor_screen::SceneView;
use crate::state::editor_screen::SceneViewBounds;
use crate::state::editor_screen::UpgradeContainer;
use crate::state::editor_screen::UpgradeOutline;
use crate::state::AppState;
use crate::ui::CodeTyper;
Expand All @@ -30,10 +31,14 @@ pub struct UpgradePlugin;
impl Plugin for UpgradePlugin {
fn build(&self, app: &mut App) {
app.register_type::<UpgradeEvent>()
.register_type::<UpgradeSequence>()
.add_event::<UpgradeEvent>()
.init_resource::<UpgradeList>()
.init_resource::<UpgradeUpdateSystems>()
.add_systems(OnEnter(AppState::EditorScreen), load_upgrade_list)
.add_systems(
OnEnter(AppState::EditorScreen),
(load_upgrade_list, load_upgrade_sequence),
)
.add_systems(
Update,
(
Expand Down Expand Up @@ -219,24 +224,79 @@ impl IndexMut<UpgradeKind> for UpgradeList {
}
}

/// The initial sequence of upgrades.
pub const INITIAL_UPGRADES: [UpgradeKind; 8] = [
UpgradeKind::DarkMode,
UpgradeKind::TouchOfLifePlugin,
UpgradeKind::Inspiration,
UpgradeKind::VelocityPlugin,
UpgradeKind::ImportLibrary,
UpgradeKind::Coffee,
UpgradeKind::SplashOfLifePlugin,
UpgradeKind::Brainstorm,
];
#[derive(Resource, Reflect, Default)]
#[reflect(Resource)]
pub struct UpgradeSequence {
sequence: Vec<Vec<UpgradeKind>>,
next_idx: usize,
slots: usize,
}

impl UpgradeSequence {
fn new(sequence: Vec<Vec<UpgradeKind>>) -> Self {
Self {
sequence,
next_idx: 0,
slots: 1,
}
}

pub fn next(
&mut self,
upgrade_list: &UpgradeList,
simulation: &Simulation,
outline: &UpgradeOutline,
) -> Vec<UpgradeKind> {
// Use the initial sequence of upgrades first
while self.next_idx < self.sequence.len() {
self.next_idx += 1;
let upgrades = self.sequence[self.next_idx - 1]
.iter()
.copied()
.filter(|&kind| upgrade_list[kind].is_unlocked(simulation, outline))
.collect::<Vec<_>>();

if !upgrades.is_empty() {
return upgrades;
}
}

// Filter the list of all upgrade kinds into just the ones that are unlocked
// Then, (weighted) randomly choose from those upgrades for the remaining slots
ALL_UPGRADE_KINDS
.into_iter()
.filter(|&kind| upgrade_list[kind].is_unlocked(simulation, outline))
.collect::<Vec<_>>()
.choose_multiple_weighted(&mut thread_rng(), self.slots, |&kind| {
upgrade_list[kind].weight
})
.unwrap()
.copied()
.collect::<Vec<_>>()
}
}

/// Loads the sequence of upgrades offered.
fn load_upgrade_sequence(mut commands: Commands) {
use UpgradeKind::*;

commands.insert_resource(UpgradeSequence::new(vec![
vec![DarkMode],
vec![TouchOfLifePlugin],
vec![Inspiration],
vec![VelocityPlugin],
vec![ImportLibrary, SplashOfLifePlugin],
vec![Coffee],
vec![Brainstorm],
]));
}

/// A macro that generates UpgradeKind enum and load_upgrade_list system from the given
/// UpgradeKind: Upgrade pairs.
macro_rules! generate_upgrade_list {
(|$world:ident| $($enumname:ident: $upgrade:expr),+ $(,)?) => {
/// Enum containing all upgrade types.
#[derive(Reflect, Clone, Copy, PartialEq, Eq, Hash, EnumCount)]
#[derive(Reflect, Clone, Copy, PartialEq, Eq, Hash, EnumCount, Debug)]
pub enum UpgradeKind {
$($enumname),+
}
Expand Down Expand Up @@ -482,11 +542,9 @@ generate_upgrade_list!(
mut commands: Commands,
root: Res<AppRoot>,
config: Res<Config>,
upgrade_list: Res<UpgradeList>,
simulation: Res<Simulation>,
| {
commands.entity(root.ui).despawn_descendants();
let editor_screen = spawn_editor_screen(&mut commands, &config.editor_screen, &upgrade_list, &simulation, false);
let editor_screen = spawn_editor_screen(&mut commands, &config.editor_screen, false);
commands.entity(editor_screen).set_parent(root.ui);
})),
..default()
Expand Down Expand Up @@ -611,10 +669,8 @@ generate_upgrade_list!(
desc: "Adds 1 extra upgrade slot.".to_string(),
tech_debt: 0.0,
install: Some(
world.register_system(|mut query: Query<&mut UpgradeContainer>| {
for mut container in &mut query {
container.slots += 1;
}
world.register_system(|mut sequence: ResMut<UpgradeSequence>| {
sequence.slots += 1;
}),
),
..default()
Expand All @@ -627,10 +683,8 @@ generate_upgrade_list!(
base_cost: 20.0,
tech_debt: 0.0,
install: Some(
world.register_system(|mut query: Query<&mut UpgradeContainer>| {
for mut container in &mut query {
container.slots += 1;
}
world.register_system(|mut sequence: ResMut<UpgradeSequence>| {
sequence.slots += 1;
}),
),
..default()
Expand Down

0 comments on commit 7a7447c

Please sign in to comment.