aboutsummaryrefslogtreecommitdiff
path: root/server/src/entity/tutorial.rs
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/entity/tutorial.rs')
-rw-r--r--server/src/entity/tutorial.rs218
1 files changed, 215 insertions, 3 deletions
diff --git a/server/src/entity/tutorial.rs b/server/src/entity/tutorial.rs
index 20b1d03e..24c83550 100644
--- a/server/src/entity/tutorial.rs
+++ b/server/src/entity/tutorial.rs
@@ -1,16 +1,68 @@
use super::{Entity, EntityContext};
use anyhow::Result;
-use hurrycurry_protocol::{glam::IVec2, PlayerID};
+use hurrycurry_protocol::{
+ glam::IVec2, ItemIndex, Message, PacketC, PlayerID, Recipe, RecipeIndex, TileIndex,
+};
+use log::{debug, warn};
pub struct Tutorial {
player: PlayerID,
+ target: ItemIndex,
+
+ current_hint: Option<(Option<IVec2>, Message)>,
+ delete_timer: f32,
+}
+
+impl Tutorial {
+ pub fn new(player: PlayerID, item: ItemIndex) -> Self {
+ Self {
+ player,
+ target: item,
+ current_hint: None,
+ delete_timer: 1.5,
+ }
+ }
}
impl Entity for Tutorial {
- fn tick(&mut self, _c: EntityContext<'_>) -> Result<()> {
+ fn finished(&self) -> bool {
+ self.delete_timer <= 0.
+ }
+ fn tick(&mut self, c: EntityContext<'_>) -> Result<()> {
+ let mut hint = StepContext {
+ ent: &c,
+ player: self.player,
+ recursion_abort: 0,
+ }
+ .aquire_item(self.target)
+ .err();
+ if hint.is_none() {
+ self.delete_timer -= c.dt;
+ if self.delete_timer <= 0. {
+ hint = None
+ } else {
+ hint = Some((None, Message::Text("Tutorial finished.".to_string())));
+ }
+ }
+
+ if hint != self.current_hint {
+ if let Some((position, _)) = self.current_hint.take() {
+ c.packet_out.push_back(PacketC::ServerHint {
+ position,
+ message: None,
+ });
+ }
+ if let Some((position, message)) = hint.clone() {
+ c.packet_out.push_back(PacketC::ServerHint {
+ position,
+ message: Some(message),
+ });
+ }
+ self.current_hint = hint;
+ }
+
Ok(())
}
- fn destructor(&mut self, _c: EntityContext<'_>) {}
fn interact(
&mut self,
_c: EntityContext<'_>,
@@ -20,3 +72,163 @@ impl Entity for Tutorial {
Ok(false)
}
}
+
+struct StepContext<'a> {
+ ent: &'a EntityContext<'a>,
+ recursion_abort: usize,
+ player: PlayerID,
+}
+
+impl<'a> StepContext<'a> {
+ fn is_hand_item(&self, item: ItemIndex) -> bool {
+ self.ent
+ .game
+ .players
+ .get(&self.player)
+ .map_or(false, |p| p.item.as_ref().map_or(false, |i| i.kind == item))
+ }
+ fn find_recipe_with_output(&self, item: ItemIndex) -> Option<RecipeIndex> {
+ self.ent
+ .game
+ .data
+ .recipes
+ .iter()
+ .enumerate()
+ .find(|(_, r)| r.outputs().contains(&item))
+ .map(|(i, _)| RecipeIndex(i))
+ }
+ fn find_item_on_map(&self, item: ItemIndex) -> Option<IVec2> {
+ self.ent
+ .game
+ .tiles
+ .iter()
+ .find(|(_, t)| t.item.as_ref().map_or(false, |t| t.kind == item))
+ .map(|(p, _)| *p)
+ }
+ fn find_tile(&self, tile: TileIndex) -> Option<IVec2> {
+ self.ent
+ .game
+ .tiles
+ .iter()
+ .find(|(_, t)| t.kind == tile)
+ .map(|(p, _)| *p)
+ }
+ fn aquire_placed_item(&mut self, item: ItemIndex) -> Result<IVec2, (Option<IVec2>, Message)> {
+ debug!(
+ "aquire placed item {:?}",
+ self.ent.game.data.item_names[item.0]
+ );
+ if let Some(pos) = self.find_item_on_map(item) {
+ return Ok(pos);
+ }
+ self.aquire_item(item)?;
+ Err((None, Message::Text("put down this item".to_string())))
+ }
+ fn aquire_item(&mut self, item: ItemIndex) -> Result<(), (Option<IVec2>, Message)> {
+ debug!("aquire item {:?}", self.ent.game.data.item_names[item.0]);
+ self.recursion_abort += 1;
+ if self.recursion_abort > 32 {
+ warn!("too much recursion");
+ return Err((
+ None,
+ Message::Text("server cant handle the recipe, too much recursion".to_string()),
+ ));
+ }
+ if self.is_hand_item(item) {
+ return Ok(());
+ }
+ if let Some(pos) = self.find_item_on_map(item) {
+ return Err((Some(pos), Message::Text("pickup".to_string())));
+ }
+ if let Some(recipe) = self.find_recipe_with_output(item) {
+ let r = &self.ent.game.data.recipes[recipe.0];
+ match r {
+ Recipe::Instant {
+ tile: Some(tile),
+ inputs: [None, None],
+ ..
+ } => {
+ if let Some(pos) = self.find_tile(*tile) {
+ return Err((Some(pos), Message::Text("take from crate".to_string())));
+ }
+ }
+ Recipe::Instant {
+ tile: None,
+ inputs: [Some(a), Some(b)],
+ ..
+ } => {
+ let apos = self.aquire_placed_item(*a)?;
+ self.aquire_item(*b)?;
+ return Err((Some(apos), Message::Text("interact here".to_string())));
+ }
+ Recipe::Instant {
+ tile: None,
+ inputs: [Some(input), None],
+ ..
+ } => {
+ self.aquire_item(*input)?;
+ return Err((None, Message::Text("interact with empty tile".to_string())));
+ }
+ Recipe::Active {
+ tile: Some(tile),
+ input,
+ speed,
+ ..
+ } => {
+ if let Some(pos) = self.find_tile(*tile) {
+ if let Some(item) = &self.ent.game.tiles.get(&pos).unwrap().item {
+ if item.kind == *input {
+ return Err((
+ Some(pos),
+ Message::Text(format!("hold interact here")),
+ ));
+ } else {
+ return Err((Some(pos), Message::Text(format!("clear tile"))));
+ }
+ }
+ self.aquire_item(*input)?;
+ return Err((
+ Some(pos),
+ Message::Text(format!("active here for {:.01}s", 1. / speed)),
+ ));
+ }
+ }
+ Recipe::Passive {
+ tile: Some(tile),
+ input,
+ ..
+ } => {
+ if let Some(pos) = self.find_tile(*tile) {
+ if let Some(item) = &self.ent.game.tiles.get(&pos).unwrap().item {
+ if item.kind == *input {
+ return Err((Some(pos), Message::Text(format!("wait for finish"))));
+ } else {
+ return Err((Some(pos), Message::Text(format!("clear tile"))));
+ }
+ }
+ self.aquire_item(*input)?;
+ return Err((
+ Some(pos),
+ Message::Text(format!(
+ "put on {}",
+ self.ent.game.data.tile_name(*tile)
+ )),
+ ));
+ }
+ }
+ Recipe::Passive {
+ tile: None, input, ..
+ } => {
+ self.aquire_item(*input)?;
+ return Err((None, Message::Text(format!("wait for finish"))));
+ }
+ _ => warn!("recipe too hard {r:?}"),
+ }
+ }
+ warn!(
+ "stuck at making item {:?}",
+ self.ent.game.data.item_names[item.0]
+ );
+ Err((None, Message::Text(format!("stuck"))))
+ }
+}