tutorial main chunk, definitely buggy... for tomorrow

main
Elijah C. Voigt 2 years ago
parent 7a72257633
commit 8825736373

@ -45,17 +45,21 @@ Will you be a part of the universal rhyme, and if so, which side are you on?
[tutorial]
intro = [
"Greetings general, and welcome to 'Martian Advanced Rules for Generals: Long-Term engagement strategies'. As a part of your assignment to operation Red Rocks, it is in your best interest to thoroughly utilize this simulation to best prepare you for engagement with, and strategic domination of, Martian forces.",
"…",
"But first things first! How do you play the dang game?",
"""
Greetings general, and welcome to 'Martian Advanced Rules for Generals: Long-Term engagement strategies'. As a part of your assignment to operation Red Rocks, it is in your best interest to thoroughly utilize this simulation to best prepare you for engagement with, and strategic domination of, Martian forces.
""",
"""
...
""",
"""
But first things first! How do you play the dang game?
""",
]
objective = [
"The Goal of Martian Chess is to have more points than your opponent when the game ends.",
"You get points by capturing your opponent's pieces.",
"The game ends when one player has no more pieces in their zone.",
]
ownership = [
"""
> When player or AI moves a piece across the canal,
@ -63,23 +67,25 @@ This here line is called the canal. And blow me down if she aint a beaut! See he
""",
"Keep playing and try to score some points!",
]
promotions = """
promotions = [
"""
> When player has either no drones or no Queens
Oh and one last thing: real nerds occasionally employ the field promotions strategy. Heres how it works: If you control no drones, you may combine two pawns to make a drone. Similarly, if you control no Queens, you may combine two drones to make a queen.
Oh and one last thing: real nerds occasionally employ the field promotions strategy. Here's how it works: If you control no drones, you may combine two pawns to make a drone. Similarly, if you control no Queens, you may combine two drones to make a queen.
""",
]
outro = [
"""
outro = """
Ok I gotta go now, but You now know enough to prevent human extinction. Do you want to finish this practice round?
"""
""",
]
[tutorial.pieces]
prompt = "Try picking up a piece"
pawn = "Pawn. The Pawn is worth 1 point, and moves 1 space diagonally. This thing aint worth your time. Put it down."
drone = "Drone. The Drone is worth 2 points, and moves 1 or 2 spaces horizontally or vertically. Neat. Back on the board."
queen = "Queen. The Queen is worth 3 Points, and moves any distance in a straight line. Woah, watch where you point that thing. Best put it back down."
jumping = "Note that jumping is not allowed. Martians banned any and all jumping in their society long ago."
end = "Ok, now move some pieces somewheres!"
prompt = ["Try picking up a piece"]
pawn = ["Pawn. The Pawn is worth 1 point, and moves 1 space diagonally. This thing ain't worth your time. Put it down."]
drone = ["Drone. The Drone is worth 2 points, and moves 1 or 2 spaces horizontally or vertically. Neat. Back on the board."]
queen = ["Queen. The Queen is worth 3 Points, and moves any distance in a straight line. Woah, watch where you point that thing. Best put it back down."]
jumping = ["Note that jumping is not allowed. Martians banned any and all jumping in their society long ago."]
end = ["Ok, now move some pieces somewheres!"]
[resolution]

@ -73,6 +73,6 @@ fn update_credits(
})
.collect()
};
info!("Text sections: {:?}", text.sections);
debug!("Text sections: {:?}", text.sections);
});
}

@ -8,7 +8,9 @@ impl Plugin for IntroPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
OnExit(GameState::Loading),
init_intro_text.run_if(resource_exists::<tweak::GameTweaks>()),
init_intro_text
.run_if(resource_exists::<tweak::GameTweaks>())
.run_if(run_once()),
)
.init_resource::<IntroProgress>()
.add_systems(OnEnter(GameState::Intro), activate::<Intro>)
@ -74,6 +76,19 @@ fn init_intro_text(
..default()
},
))
.with_children(|parent| {
parent
.spawn((
Intro,
NodeBundle {
style: Style {
padding: UiRect::all(Val::Px(25.0)),
..default()
},
background_color: Color::BLACK.with_a(0.9).into(),
..default()
},
))
.with_children(|parent| {
parent.spawn((
Intro,
@ -88,6 +103,7 @@ fn init_intro_text(
.with_text_alignment(TextAlignment::Center),
));
});
});
}
fn manage_intro_progress(

@ -35,6 +35,8 @@ fn main() {
(
debug_state::<DisplayState>.run_if(resource_changed::<State<DisplayState>>()),
debug_state::<GameState>.run_if(resource_changed::<State<GameState>>()),
debug_state::<tutorial::TutorialState>
.run_if(resource_changed::<State<tutorial::TutorialState>>()),
),
);
app.add_systems(
@ -119,7 +121,10 @@ fn activate<Marker: Component>(
}
fn deactivate<Marker: Component>(
mut entities: Query<&mut Visibility, (With<Marker>, Without<game::Captured>)>,
mut entities: Query<
&mut Visibility,
(With<Marker>, Without<game::Captured>, Without<GameState>),
>,
) {
entities.iter_mut().for_each(|mut visibility| {
*visibility = Visibility::Hidden;

@ -13,7 +13,7 @@ impl Plugin for MenuPlugin {
.add_systems(
Update,
(
handle_menu_button,
handle_menu_button::<GameState>,
handle_menu_quit,
bevy::window::close_on_esc,
)
@ -85,6 +85,35 @@ fn init_menu_ui(mut commands: Commands) {
));
});
parent
.spawn((
GameState::Play,
tutorial::TutorialState::Intro,
ButtonBundle {
style: Style {
padding: UiRect::all(Val::Px(5.0)),
margin: UiRect::all(Val::Px(5.0)),
..default()
},
background_color: Color::ORANGE.with_a(0.5).into(),
..default()
},
))
.with_children(|parent| {
parent.spawn((
GameState::Play,
tutorial::TutorialState::Intro,
TextBundle::from_section(
"Tutorial",
TextStyle {
color: Color::BLACK,
font_size: 32.0,
..default()
},
),
));
});
parent
.spawn((
GameState::Credits,
@ -141,9 +170,9 @@ fn init_menu_ui(mut commands: Commands) {
});
}
fn handle_menu_button(
events: Query<(&Interaction, &GameState), (With<Button>, Changed<Interaction>)>,
mut next_gs: ResMut<NextState<GameState>>,
pub(crate) fn handle_menu_button<T: Component + States>(
events: Query<(&Interaction, &T), (With<Button>, Changed<Interaction>)>,
mut next_gs: ResMut<NextState<T>>,
) {
events.iter().for_each(|(i, gs)| match i {
Interaction::Pressed => {

@ -1,17 +1,56 @@
use bevy::utils::HashSet;
use crate::prelude::*;
pub(crate) struct TutorialPlugin;
impl Plugin for TutorialPlugin {
fn build(&self, app: &mut App) {
app.add_state::<TutorialState>();
app.add_state::<TutorialState>()
.add_systems(
OnExit(GameState::Loading),
initialize_tutorial
.run_if(resource_exists::<tweak::GameTweaks>())
.run_if(run_once()),
)
.add_systems(
Update,
menu::handle_menu_button::<TutorialState>.run_if(in_state(GameState::Menu)),
)
.add_systems(
Update,
(
// Run if we have a TutorialState *and* it is not TutorialState::None *and* we get a return keypress
transition
.run_if(state_exists::<TutorialState>())
.run_if(not(in_state(TutorialState::None))),
transition
.run_if(state_exists::<TutorialState>())
.run_if(not(in_state(TutorialState::None)))
.run_if(any_component_added::<game::Selected>),
transition
.run_if(state_exists::<TutorialState>())
.run_if(not(in_state(TutorialState::None)))
.run_if(any_component_changed::<game::Side>),
),
)
.add_systems(OnExit(GameState::Play), deactivate::<TutorialState>)
.add_systems(
OnEnter(GameState::Play),
activate_tutorial_step.run_if(state_exists::<TutorialState>()),
)
.add_systems(
Update,
activate_tutorial_step.run_if(state_changed::<TutorialState>()),
);
}
}
#[derive(Debug, States, Hash, Default, PartialEq, Eq, Clone)]
enum TutorialState {
#[derive(Debug, States, Hash, Default, PartialEq, Eq, Clone, Component)]
pub(crate) enum TutorialState {
#[default]
None,
Empty,
Intro,
Objective,
PieceIntro,
@ -21,7 +60,7 @@ enum TutorialState {
PieceJump,
PieceEnd,
Ownership,
Promotions,
_Promotions,
Outro,
}
@ -29,8 +68,250 @@ enum TutorialState {
// This plays out a usual game, but with tutorial text overlayed
// Once tutorial is done, the game can continue as usual
// Intro animations, initialize all text windows but make invisible
// Init animations, initialize all text windows but make invisible
// This should be run when TutorialState::Intro
fn initialize_tutorial(
tweaks_file: Res<tweak::GameTweaks>,
tweaks: Res<Assets<tweak::Tweaks>>,
mut commands: Commands,
) {
let tweak = tweaks.get(tweaks_file.handle.clone()).expect("Load tweaks");
info!("Initializing tutorial entities");
// List of (state, lines) for tutorial steps
[
(
TutorialState::Intro,
tweak
.get::<Vec<String>>("tutorial_intro")
.expect("Tutorial intro"),
),
(
TutorialState::Objective,
tweak
.get::<Vec<String>>("tutorial_objective")
.expect("Tutorial objective"),
),
(
TutorialState::Ownership,
tweak
.get::<Vec<String>>("tutorial_ownership")
.expect("Tutorial ownership"),
),
(
TutorialState::_Promotions,
tweak
.get::<Vec<String>>("tutorial_promotions")
.expect("Tutorial promotions"),
),
(
TutorialState::Outro,
tweak
.get::<Vec<String>>("tutorial_outro")
.expect("Tutorial pieces end"),
),
(
TutorialState::PieceIntro,
tweak
.get::<Vec<String>>("tutorial_pieces_prompt")
.expect("Tutorial pieces prompt"),
),
(
TutorialState::PieceJump,
tweak
.get::<Vec<String>>("tutorial_pieces_jumping")
.expect("Tutorial pieces jumping"),
),
(
TutorialState::PieceEnd,
tweak
.get::<Vec<String>>("tutorial_pieces_end")
.expect("Tutorial pieces end"),
),
(
TutorialState::PieceQueen,
tweak
.get::<Vec<String>>("tutorial_pieces_queen")
.expect("Tutorial pieces queen"),
),
(
TutorialState::PieceDrone,
tweak
.get::<Vec<String>>("tutorial_pieces_drone")
.expect("Tutorial pieces drone"),
),
(
TutorialState::PiecePawn,
tweak
.get::<Vec<String>>("tutorial_pieces_pawn")
.expect("Tutorial pieces pawn"),
),
]
.iter()
.for_each(|(step, lines)| {
commands
.spawn((
step.clone(),
NodeBundle {
style: Style {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
justify_content: JustifyContent::FlexStart,
align_items: AlignItems::Center,
flex_direction: FlexDirection::Column,
position_type: PositionType::Absolute,
padding: UiRect::all(Val::Px(50.0)),
..default()
},
background_color: Color::NONE.into(),
visibility: Visibility::Hidden,
..default()
},
))
.with_children(|parent| {
parent
.spawn((
step.clone(),
NodeBundle {
style: Style {
padding: UiRect::all(Val::Px(25.0)),
..default()
},
background_color: Color::BLACK.with_a(0.9).into(),
..default()
},
))
.with_children(|parent| {
parent.spawn((
step.clone(),
TextBundle {
text: Text {
sections: lines
.iter()
.map(|line| TextSection {
value: line.clone(),
style: TextStyle {
font_size: 16.0,
color: Color::WHITE.with_a(1.0),
..default()
},
})
.collect(),
alignment: TextAlignment::Left,
..default()
},
..default()
},
));
});
});
})
}
fn transition(
pieces: Query<&game::Piece, With<game::Selected>>,
transitions: Query<Entity, Changed<game::Side>>,
curr_state: Res<State<TutorialState>>,
mut next_state: ResMut<NextState<TutorialState>>,
keys: Res<Input<KeyCode>>,
mut seen: Local<HashSet<TutorialState>>,
) {
// TEMP: Early out, only handle Return keys
if !keys.just_pressed(KeyCode::Return) {
return;
}
info!("Transitioning tutorial state");
// Store the current state
// Used to avoid doing a prevoius state again
(*seen).insert(curr_state.get().clone());
next_state.set(match curr_state.get() {
// This transition is implicit. Menu transitions from None to Intro
TutorialState::None => TutorialState::Intro,
// From Intro we always go to Objective
TutorialState::Intro => TutorialState::Objective,
// From objective we go to the Pieces flow intro
TutorialState::Objective => TutorialState::PieceIntro,
// From PiecesIntro we can go to any other Pieces if a piece is selected
// Each pieces tutorial can transition to any other, as well as Ownership
TutorialState::PieceIntro
| TutorialState::PieceQueen
| TutorialState::PieceDrone
| TutorialState::PiecePawn
| TutorialState::PieceEnd
| TutorialState::PieceJump
| TutorialState::Empty
| TutorialState::Ownership => {
// When a queen is selected for the first time...
if pieces.iter().filter(|&p| *p == game::Piece::Queen).count() > 0
&& !(*seen).contains(&TutorialState::PieceQueen)
{
TutorialState::PieceQueen
// When a drone is selected for the first time...
} else if pieces.iter().filter(|&p| *p == game::Piece::Drone).count() > 0
&& !(*seen).contains(&TutorialState::PieceDrone)
{
TutorialState::PieceDrone
// When a pawn is selected for the first time...
} else if pieces.iter().filter(|&p| *p == game::Piece::Pawn).count() > 0
&& !(*seen).contains(&TutorialState::PiecePawn)
{
TutorialState::PiecePawn
// All pieces have been selected
} else if (*seen).contains(&TutorialState::PieceIntro)
&& (*seen).contains(&TutorialState::PieceQueen)
&& (*seen).contains(&TutorialState::PieceDrone)
&& (*seen).contains(&TutorialState::PiecePawn)
{
// And we have touched on jumping
if (*seen).contains(&TutorialState::PieceJump) {
TutorialState::PieceEnd
// We have not touched on jumping
} else {
TutorialState::PieceJump
}
// A piece moves sides, so talk about ownership
} else if !(*seen).contains(&TutorialState::Ownership) && transitions.iter().count() > 0 {
TutorialState::Ownership
// We have visited all relevant tutorial points, say goodbye
} else if (*seen).contains(&TutorialState::PieceIntro)
&& (*seen).contains(&TutorialState::PieceQueen)
&& (*seen).contains(&TutorialState::PieceDrone)
&& (*seen).contains(&TutorialState::PiecePawn)
&& (*seen).contains(&TutorialState::PieceJump)
&& (*seen).contains(&TutorialState::Ownership) {
TutorialState::Outro
// Default, empty (tutorial doesn't always need to show something)
} else {
TutorialState::Empty
}
}
// After the outro, we exit the tutorial
TutorialState::Outro => TutorialState::None,
TutorialState::_Promotions => todo!("Not implemented yet!"),
});
}
fn activate_tutorial_step(
state: Res<State<TutorialState>>,
mut query: Query<(&mut Visibility, &TutorialState), Without<GameState>>,
) {
info!("Activating step {:?}", state.get());
// Iterate over all entities with TutorialState components
query.iter_mut().for_each(|(mut v, s)| {
// If their component matches the current state, make visible
if s == state.get() {
*v = Visibility::Inherited;
// Otherwise hide it (again)
} else {
*v = Visibility::Hidden;
}
});
}
// Start by loading the same game as display3d does

Loading…
Cancel
Save