1
Fork 0
avarus/server/src/construct.rs

183 lines
5.9 KiB
Rust

use std::{sync::{atomic::{AtomicBool, AtomicUsize, Ordering, AtomicI32}, Arc}, borrow::Cow};
use anyhow::{Context, Ok};
use serde::{Serialize, Deserialize};
use swarmbot_interfaces::types::BlockState;
use tokio::task::AbortHandle;
use tracing::{error, info, trace};
use typetag::serde;
use crate::{blocks::{Vec3, Position, World, Block, SharedWorld, Direction}, mine::{ChunkedTask, fill}, turtle::{TurtleCommander, TurtleCommandResponse, TurtleCommand}, tasks::{Task, TaskState}, vendored::schematic::Schematic};
fn schematic2world(region: &Schematic) -> anyhow::Result<World> {
let mut world = World::new();
let min = region.origin().context("bad schematic")?;
for (position, block) in region.blocks() {
let name = match block {
BlockState::AIR => None,
BlockState(20) => None, // Glass
BlockState(102) => None, // Glass pane
BlockState(95) => None, // Stained glass
BlockState(160) => None, // Stained glass pane
// who cares
_ => Some("terrestria:hemlock_planks")
}.map(|s| s.to_string());
if let Some(name) = name {
let block = Block {
name,
pos: position - min,
};
world.set(block);
}
}
Ok(world)
}
#[derive(Serialize, Deserialize,Clone)]
pub struct BuildSimple {
pos: Vec3,
size: Vec3,
#[serde(skip)]
region: Option<SharedWorld>,
/// Input chest with the block to use, assumed infinite
input: Position,
#[serde(skip_deserializing)]
miners: Arc<AtomicUsize>,
progress: Arc<AtomicI32>,
height: i32,
}
impl BuildSimple {
pub fn new(position: Vec3, schematic: &Schematic, input: Position) -> Self {
let size = Vec3::new(
schematic.width() as i32,
schematic.height() as i32,
schematic.length() as i32,
);
Self {
pos: position,
size,
region: Some(SharedWorld::from_world(schematic2world(schematic).unwrap())),
input,
miners: Default::default(),
progress: Default::default(),
height: size.y,
}
}
async fn place_block(&self, turtle: TurtleCommander, at: Vec3) -> Option<()> {
let mut near = turtle.goto_adjacent(at).await?;
while let TurtleCommandResponse::Failure = turtle.execute(near.place(at)?).await.ret {
if turtle.world().occupied(at).await {
trace!("{at} already filled");
return None;
};
trace!("failed, looking for blocks");
if let Some(slot) = turtle.inventory().await.iter().enumerate()
.filter(|n| n.1.clone().is_some_and(|s| s.count > 0))
.map(|n| n.0).next() {
turtle.execute(TurtleCommand::Select(slot as u32 + 1)).await;
} else {
trace!("docking");
turtle.goto(self.input).await;
for _ in 1..=16 {
turtle.execute(TurtleCommand::SuckFront(64)).await;
}
near = turtle.goto_adjacent(at).await?;
}
}
Some(())
}
async fn build_layer(&self, turtle: TurtleCommander, layer: i32) -> Option<()> {
let layer_size = Vec3::new(self.size.x, 1, self.size.z);
// assume the layer is empty for better pathfinding
let mut world = turtle.world().lock_mut().await;
for point in (0..layer_size.product()).map(|n| fill(layer_size, n)) {
if let None = world.get(point) {
world.set(Block { name: "minecraft:air".into(), pos: point })
}
}
drop(world);
for point in (0..layer_size.product())
.map(|n| fill(layer_size, n)) {
let point = point + Vec3::y() * layer;
trace!("block {point}");
if self.region.as_ref()?.get(point).await.is_none() {
trace!("empty: {:?}", self.region.as_ref()?.get(point).await);
continue;
}
let point = point + self.pos;
if turtle.world().occupied(point).await {
trace!("already full: {:?}", turtle.world().get(point).await);
continue;
}
self.place_block(turtle.clone(), point).await;
}
Some(())
}
}
#[serde]
impl Task for BuildSimple {
fn run(&mut self,turtle:TurtleCommander) -> AbortHandle {
let owned = self.clone();
tokio::spawn(async move {
if turtle.fuel() < 5000 {
turtle.dock().await;
}
let layer = owned.progress.fetch_add(1, Ordering::AcqRel);
if owned.height < layer {
error!("scheduled layer out of range");
return;
}
info!("layer {}", layer);
if let None = owned.build_layer(turtle, layer).await {
error!("building layer {} failed", layer);
owned.progress.fetch_sub(1, Ordering::AcqRel);
} else {
trace!("building layer {} successful", layer);
}
owned.miners.fetch_sub(1, Ordering::AcqRel);
}).abort_handle()
}
fn poll(&mut self) -> TaskState {
if self.region.is_none() {
error!("attempted to restart schematic printing, which is unimplemented");
return TaskState::Complete;
}
let layer = self.progress.load(Ordering::SeqCst);
if layer > self.height {
return TaskState::Complete;
}
let only = self.miners.fetch_update(Ordering::AcqRel, Ordering::Acquire, |n| {
if n < 1 {
Some(n+1)
}else {
None
}
}).is_ok();
if only {
return TaskState::Ready(Position::new(self.pos, Direction::North));
}
TaskState::Waiting
}
}