Skip to content

Commit

Permalink
Add Tool::Custom which runs an Operation.
Browse files Browse the repository at this point in the history
This will be used for content-specific tools and may eventually be used
for most tools, if that works out cleanly.
  • Loading branch information
kpreid committed Oct 31, 2023
1 parent 2f7d09e commit 09245f7
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 13 deletions.
99 changes: 91 additions & 8 deletions all-is-cubes/src/inv/tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ use crate::character::{Character, CharacterTransaction, Cursor};
use crate::fluff::Fluff;
use crate::inv::{self, Icons, InventoryTransaction, StackLimit};
use crate::linking::BlockProvider;
use crate::math::{Cube, Face6, GridRotation};
use crate::math::{Cube, Face6, GridRotation, Gridgid};
use crate::op::{self, Operation};
use crate::space::{Space, SpaceTransaction};
use crate::transaction::{Merge, Transaction};
use crate::universe::{RefError, RefVisitor, URef, UniverseTransaction, VisitRefs};
use crate::universe::{RefError, RefVisitor, UBorrow, URef, UniverseTransaction, VisitRefs};

/// A `Tool` is an object which a character can use to have some effect in the game,
/// such as placing or removing a block. In particular, a tool use usually corresponds
Expand Down Expand Up @@ -66,7 +67,19 @@ pub enum Tool {
active: bool,
},

/// A tool which performs an arbitrary [`Operation`].
// ---
// TODO: Custom tools like this should be able to have their definitions stored in the
// Universe. Probably `Operation` should have that and tools become a case of it?
Custom {
/// Operation to perform when the tool is used.
op: Operation,
/// Icon for the tool.
icon: Block,
},

/// A tool which calls an arbitrary function.
/// TODO: This is not used and should perhaps be folded into `Custom`
ExternalAction {
// TODO: Rework this so that the external component gets to update the icon.
// (Perhaps that's "a block defined by an external source"?)
Expand Down Expand Up @@ -218,6 +231,36 @@ impl Tool {
Some(Self::Jetpack { active: !active }),
UniverseTransaction::default(),
)),
Self::Custom { ref op, icon: _ } => {
// TODO: This is a mess; figure out how much impedance-mismatch we want to fix here.

let cursor = input.cursor()?; // TODO: allow op to not be spatial, i.e. not always fail here?
let character_guard: Option<UBorrow<Character>> =
input.character.as_ref().map(|c| c.read()).transpose()?;

let (space_txn, inventory_txn) = op.apply(
&*cursor.space().read()?,
character_guard.as_ref().map(|c| c.inventory()),
// TODO: Should there be rotation based on cursor ray direction?
// Or is that a separate input?
// (Should `ToolInput` be merged into `Operation` stuff?)
Gridgid::from_translation(cursor.cube().lower_bounds().to_vector()),
)?;
let mut txn = space_txn.bind(cursor.space().clone());
if inventory_txn != InventoryTransaction::default() {
txn = txn
.merge(CharacterTransaction::inventory(inventory_txn).bind(
input.character.as_ref().cloned().ok_or_else(|| {
ToolError::Internal(format!(
"operation produced inventory transaction \
without being given an inventory: {op:?}"
))
})?,
))
.unwrap();
}
Ok((Some(self), txn))
}
Self::ExternalAction { ref function, .. } => {
if let Some(f) = function.try_ref() {
f(input);
Expand Down Expand Up @@ -267,6 +310,7 @@ impl Tool {
Self::Jetpack { active } => {
Cow::Borrowed(&predefined[Icons::Jetpack { active: *active }])
}
Self::Custom { icon, op: _ } => Cow::Borrowed(icon),
Self::ExternalAction { icon, .. } => Cow::Borrowed(icon),
}
}
Expand All @@ -284,6 +328,7 @@ impl Tool {
Tool::EditBlock => One,
Tool::PushPull => One,
Tool::Jetpack { .. } => One,
Tool::Custom { .. } => One, // TODO: let tool specify
Tool::ExternalAction { .. } => One,
}
}
Expand All @@ -300,6 +345,10 @@ impl VisitRefs for Tool {
Tool::EditBlock => {}
Tool::PushPull => {}
Tool::Jetpack { active: _ } => {}
Tool::Custom { op, icon } => {
op.visit_refs(visitor);
icon.visit_refs(visitor);
}
Tool::ExternalAction { function: _, icon } => {
icon.visit_refs(visitor);
}
Expand Down Expand Up @@ -455,12 +504,6 @@ impl std::error::Error for ToolError {
}
}

impl From<RefError> for ToolError {
fn from(value: RefError) -> Self {
ToolError::SpaceRef(value)
}
}

impl ToolError {
/// Return [`Fluff`] to accompany this error.
///
Expand All @@ -471,6 +514,18 @@ impl ToolError {
}
}

impl From<op::OperationError> for ToolError {
fn from(value: op::OperationError) -> Self {
match value {}
}
}

impl From<RefError> for ToolError {
fn from(value: RefError) -> Self {
ToolError::SpaceRef(value)
}
}

/// A wrapper around a value which cannot be printed or serialized,
/// used primarily to allow external functions to be called from objects
/// within a [`Universe`](crate::universe::Universe).
Expand Down Expand Up @@ -545,6 +600,7 @@ mod tests {
use crate::block::Primitive;
use crate::character::cursor_raycast;
use crate::content::{make_some_blocks, make_some_voxel_blocks};
use crate::drawing::VoxelBrush;
use crate::inv::Slot;
use crate::math::{FreeCoordinate, GridRotation};
use crate::raycast::Ray;
Expand Down Expand Up @@ -906,6 +962,33 @@ mod tests {
assert_eq!(&tester.space()[[1, 0, 0]], &existing);
}

#[test]
fn use_custom_success() {
// TODO: also test an operation that cares about the existing block

let [existing, icon, placed] = make_some_blocks();
let tool = Tool::Custom {
op: Operation::Paint(VoxelBrush::single(placed.clone())),
icon,
};
let tester = ToolTester::new(|space| {
space.set([0, 0, 0], &existing).unwrap();
});

let transaction = tester.equip_and_use_tool(tool).unwrap();

assert_eq!(
transaction,
SpaceTransaction::set_cube(
[0, 0, 0],
None,
Some(placed.clone().rotate(GridRotation::CLOCKWISE)),
)
.nonconserved() // TODO: this is just because ::Paint does this
.bind(tester.space_ref.clone())
);
}

#[test]
fn use_external_action() {
use core::sync::atomic::{AtomicU32, Ordering};
Expand Down
5 changes: 5 additions & 0 deletions all-is-cubes/src/save/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,10 @@ mod inv {
Tool::EditBlock => schema::ToolSer::EditBlockV1 {},
Tool::PushPull => schema::ToolSer::PushPullV1 {},
Tool::Jetpack { active } => schema::ToolSer::JetpackV1 { active },
Tool::Custom { ref op, ref icon } => schema::ToolSer::CustomV1 {
op: op.clone(),
icon: icon.clone(),
},
Tool::ExternalAction {
function: _,
ref icon,
Expand All @@ -561,6 +565,7 @@ mod inv {
schema::ToolSer::EditBlockV1 {} => Tool::EditBlock,
schema::ToolSer::PushPullV1 {} => Tool::PushPull,
schema::ToolSer::JetpackV1 { active } => Tool::Jetpack { active },
schema::ToolSer::CustomV1 { op, icon } => Tool::Custom { op, icon },
schema::ToolSer::ExternalActionV1 { icon } => Tool::ExternalAction {
function: EphemeralOpaque(None),
icon,
Expand Down
24 changes: 19 additions & 5 deletions all-is-cubes/src/save/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,14 +246,28 @@ pub(crate) struct InvStackSer {
#[serde(tag = "type")]
pub(crate) enum ToolSer {
ActivateV1 {},
RemoveBlockV1 { keep: bool },
BlockV1 { block: Block },
InfiniteBlocksV1 { block: Block },
RemoveBlockV1 {
keep: bool,
},
BlockV1 {
block: Block,
},
InfiniteBlocksV1 {
block: Block,
},
CopyFromSpaceV1 {},
EditBlockV1 {},
PushPullV1 {},
JetpackV1 { active: bool },
ExternalActionV1 { icon: Block },
JetpackV1 {
active: bool,
},
CustomV1 {
op: crate::op::Operation,
icon: Block,
},
ExternalActionV1 {
icon: Block,
},
}

//------------------------------------------------------------------------------------------------//
Expand Down

0 comments on commit 09245f7

Please sign in to comment.