diff --git a/server/Cargo.toml b/server/Cargo.toml index 88f40f8..81b51be 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -13,7 +13,9 @@ const_format = "0.2.32" feistel_rs = "0.1.0" hyper = "1.0.1" hyper-util = "0.1.1" -rstar = "0.11.0" +nalgebra = { version = "0.32.3", features = ["serde-serialize"] } +pathfinding = "4.6.0" +rstar = { version = "0.11.0", features = ["serde"] } rustmatica = "0.1.1" serde = "1.0.193" serde_json = "1.0.108" diff --git a/server/src/blocks.rs b/server/src/blocks.rs new file mode 100644 index 0000000..5dc9b51 --- /dev/null +++ b/server/src/blocks.rs @@ -0,0 +1,32 @@ +use rstar::{self, RTree, RTreeObject, AABB, PointDistance}; +use serde::{Deserialize, Serialize}; +use pathfinding::prelude::astar; + +use crate::Vec3; + +pub type World = RTree; + +#[derive(Serialize, Deserialize)] +pub struct Block { + pub name: String, + pub pos: super::Vec3, +} + + +impl RTreeObject for Block { + type Envelope = AABB<[i32;3]>; + + fn envelope(&self) -> Self::Envelope { + AABB::from_point(self.pos.into()) + } +} + +impl PointDistance for Block { + fn distance_2( + &self, + point: &[i32;3], + ) -> i32 { + (self.pos - Vec3::from(*point)).abs().sum() + } + +} diff --git a/server/src/main.rs b/server/src/main.rs index b0ea385..2608593 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,12 +1,13 @@ -use std::{env::args, sync::Arc, fs, io::ErrorKind}; +use std::{env::args, sync::Arc, fs, io::ErrorKind, collections::VecDeque}; use axum::{ routing::{get, post}, Router, extract::{State, Path}, Json, http::{request, Request}, }; -use anyhow::{Error, Ok}; -use rstar; -use rustmatica::{BlockState, util::{UVec3, Vec3}}; +use anyhow::{Error, Ok, Context}; +use blocks::World; +use rstar::{self, AABB}; +use rustmatica::{BlockState}; mod names; use names::Name; @@ -17,8 +18,16 @@ use const_format::formatcp; use hyper_util::rt::TokioIo; use tower::Service; use hyper::body::Incoming; +use nalgebra::Vector3; -#[derive(Serialize, Deserialize)] +use crate::blocks::Block; +mod blocks; + +mod turtle; + +pub type Vec3 = Vector3; + +#[derive(Serialize, Deserialize, Clone, Hash, PartialEq, Eq, Copy)] enum Direction { North, South, @@ -61,20 +70,21 @@ struct Turtle { /// movement vector of last given command queued_movement: Vec3, position: Vec3, + goal: Option, facing: Direction, } impl Turtle { fn new(id: u32, position: Vec3, facing: Direction, fuel: usize) -> Self { - Self { name: Name::from_num(id), fuel, queued_movement: Vec3::new(0, 0, 0), position, facing } + Self { name: Name::from_num(id), fuel, queued_movement: Vec3::new(0, 0, 0), position, goal: None, facing } } } #[derive(Serialize, Deserialize)] struct ControlState { - turtles: Vec - //world: unimplemented!(), + turtles: Vec, + world: blocks::World, //chunkloaders: unimplemented!(), } @@ -89,7 +99,7 @@ async fn main() -> Result<(), Error> { }, tokio::io::Result::Err(e) => match e.kind() { ErrorKind::NotFound => { - ControlState { turtles: Vec::new() } + ControlState { turtles:Vec::new(), world: World::new() } }, _ => panic!() } @@ -184,7 +194,7 @@ async fn create_turtle( Json(req): Json, ) -> Json { let turtles = &mut state.write().await.turtles; - let id = (turtles.len() + 1) as u32; + let id = turtles.len() as u32; turtles.push(Turtle::new(id, req.position, req.facing, req.fuel)); println!("turt {id}"); @@ -197,12 +207,108 @@ async fn command( State(state): State, Json(req): Json, ) -> Json { - let turtles = &state.read().await.turtles; - println!("{id}"); - println!("above: {}, below: {}, ahead: {}", req.above, req.below, req.ahead); + let mut state = &mut state.write().await; + + if id as usize > state.turtles.len() { + return Json(TurtleCommand::Update); + } + + Json( + process_turtle_update(id, &mut state, req).unwrap_or(TurtleCommand::Update), + + ) +} + +fn process_turtle_update( + id: u32, + state: &mut ControlState, + update: TurtleUpdate, + ) -> anyhow::Result { + let turtle = state.turtles.get_mut(id as usize).context("nonexisting turtle")?; + let world = &mut state.world; + + println!("above: {}, below: {}, ahead: {}", update.above, update.below, update.ahead); + if turtle.fuel != update.fuel { + turtle.fuel = update.fuel; + + turtle.position += turtle.queued_movement; + } + + world.insert(Block { + name: update.above, + pos: turtle.position + Vec3::new(0, 1, 0), + }); + + world.insert(Block { + name: update.ahead, + pos: turtle.position + turtle.facing.clone().unit(), + }); + + world.insert(Block { + name: update.below, + pos: turtle.position - turtle.facing.clone().unit(), + }); + + turtle.queued_movement = turtle.facing.clone().unit(); + + Ok(TurtleCommand::Wait) +} + +#[derive(Serialize, Deserialize)] +enum TurtleTask { + Mining(TurtleMineJob), + Idle, +} + +type Position = (Vec3, Direction); + +/// Get a turtle command to map two adjacent positions +fn difference(from: Position, to: Position) -> Option { + use TurtleCommand::*; + + if from.0 == to.0 { + if to.1 == from.1.left() { + Some(Left) + } else if to.1 == from.1.right() { + Some(Right) + } else { + None + } + } else if to.1 == from.1 { + if to.0 == from.0 + from.1.unit() { + Some(Forward) + } else if to.0 == from.0 - from.1.unit() { + Some(Backward) + } else { + None + } + + } else { + None + } +} + +#[derive(Serialize, Deserialize)] +struct TurtleMineJobParams { + region: AABB<[i32;3]>, + to_mine: Vec, + method: TurtleMineMethod, + refuel: Position, + storage: Position, +} + +#[derive(Serialize, Deserialize)] +struct TurtleMineJob { + to_mine: VecDeque, + mined: AABB<[i32;3]>, + params: TurtleMineJobParams, +} - Json(TurtleCommand::Wait) +#[derive(Serialize, Deserialize)] +enum TurtleMineMethod { + Clear, + Strip, } #[derive(Serialize, Deserialize)] diff --git a/server/src/turtle.rs b/server/src/turtle.rs new file mode 100644 index 0000000..303056c --- /dev/null +++ b/server/src/turtle.rs @@ -0,0 +1,55 @@ +use std::rc::Rc; + +use pathfinding::prelude::astar; +use crate::{blocks::{World, Block}, Position}; + +use super::Vec3; + +pub fn route(from: Position, to: Position, world: World) -> Vec { + let world = Rc::new(world); + let route = astar(&from, move |p| {next(p, world.clone())} , |p1| {(p1.0 - &to.0).abs().sum() as u32}, |p| {p == &to}).unwrap(); + route.0 +} + +fn next(from: &Position, world: Rc) -> Vec<(Position, u32)> { + let mut vec: Vec<(Position, u32)> = Vec::new(); + vec.push(((from.0, from.1.left()),1)); + vec.push(((from.0, from.1.right()),1)); + let ahead = from.0 + from.1.unit(); + + let empty: Block = Block { + name: String::from("minecraft:air"), + pos: Vec3::zeros(), + }; + + let block_ahead = world.locate_at_point(&ahead.into()).unwrap_or(&empty); + difficulty(&block_ahead.name).map(|d| vec.push(((ahead, from.1), d))); + + let behind = from.0 - from.1.unit(); + let block_behind = world.locate_at_point(&behind.into()).unwrap_or(&empty); + difficulty(&block_behind.name).map(|d| vec.push(((behind, from.1), d))); + + let above = from.0 + Vec3::y(); + let block_above = world.locate_at_point(&above.into()).unwrap_or(&empty); + difficulty(&block_above.name).map(|d| vec.push(((above, from.1), d))); + + let below = from.0 - Vec3::y(); + let block_below = world.locate_at_point(&below.into()).unwrap_or(&empty); + difficulty(&block_below.name).map(|d| vec.push(((below, from.1), d))); + + vec +} + +/// Blocks that are fine to tunnel through +const GARBAGE: [&str; 3] = [ + "minecraft:stone", + "minecraft:dirt", + "minecraft:andesite", +]; + +// time to go somewhere +fn difficulty(name: &str) -> Option { + if name == "minecraft:air" { return Some(1) }; + if GARBAGE.contains(&name) { return Some(2)}; + None +}