mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-10 02:21:33 -10:00
Merge pull request #5520 from levzzz5154/mods/analog-output
Native mods: a mod block's comparator output
This commit is contained in:
7 files changed
+181
-8
No files matched your search
@@ -1102,6 +1102,26 @@ impl LevelAccess for GameLevel<'_> {
|
||||
}
|
||||
signal.clamp(0, 15)
|
||||
}
|
||||
fn adjust_has_analog_output(&self, state: BlockStateId, vanilla: bool) -> bool {
|
||||
let mut output = vanilla;
|
||||
if self.native_authoritative {
|
||||
self.native_has_analog_output(state, &mut output);
|
||||
}
|
||||
output
|
||||
}
|
||||
fn adjust_analog_output(
|
||||
&self,
|
||||
pos: BlockPos,
|
||||
state: BlockStateId,
|
||||
direction: Direction,
|
||||
vanilla: i32,
|
||||
) -> i32 {
|
||||
let mut output = vanilla;
|
||||
if self.native_authoritative {
|
||||
self.native_analog_output(self.dimension, pos, state, direction, &mut output);
|
||||
}
|
||||
output.clamp(0, 15)
|
||||
}
|
||||
fn before_random_tick(
|
||||
&mut self,
|
||||
ticks: &mut Ticks,
|
||||
|
||||
@@ -364,6 +364,39 @@ impl GameLevel<'_> {
|
||||
let _ = (state, source);
|
||||
}
|
||||
|
||||
/// `BlockState.hasAnalogOutputSignal`: set `output` for a block of yours
|
||||
/// that comparators read (how full or far along it is, as a composter's
|
||||
/// level), so they ask `native_analog_output`. Asked from inside the
|
||||
/// level's update: answer from `state` alone. Authoritative servers only.
|
||||
/// API @API:analog_output@.
|
||||
#[inline(always)]
|
||||
pub(super) fn native_has_analog_output(&self, state: BlockStateId, output: &mut bool) {
|
||||
let _ = (state, output);
|
||||
}
|
||||
|
||||
/// `BlockState.getAnalogOutputSignal`: the signal a comparator reads from
|
||||
/// the block `state` at `pos`, `face` the side that touches the comparator
|
||||
/// (or the conductor in front of it). `output` holds vanilla's answer:
|
||||
/// change it when the block is yours (0 to 15; anything else is clamped).
|
||||
/// Asked only for blocks `native_has_analog_output` answered for, from
|
||||
/// inside the level's update: don't change the world here. When the
|
||||
/// answer changes without the block's state changing, call
|
||||
/// `native_update_output_neighbors`; a change of state through
|
||||
/// `native_set_block` or `native_set_block_property` (neighbour updates
|
||||
/// on) tells the comparators itself, as vanilla's `Level.setBlock` does.
|
||||
/// Authoritative servers only. API @API:analog_output@.
|
||||
#[inline(always)]
|
||||
pub(super) fn native_analog_output(
|
||||
&self,
|
||||
dimension: LevelDimension,
|
||||
pos: BlockPos,
|
||||
state: BlockStateId,
|
||||
face: Direction,
|
||||
output: &mut i32,
|
||||
) {
|
||||
let _ = (dimension, pos, state, face, output);
|
||||
}
|
||||
|
||||
/// An authoritative server's `ServerLevel.tickChunk` is about to
|
||||
/// random-tick the block `state` at `pos` (`BlockState.randomTick`):
|
||||
/// only blocks that tick randomly, a mod's as its vanilla template block
|
||||
@@ -1923,6 +1956,26 @@ impl Game<'_> {
|
||||
let at = BlockPos::new(pos[0], pos[1], pos[2]);
|
||||
self.with_level(|b, level, ticks| b.set_block(level, ticks, at, id, flags))
|
||||
}
|
||||
/// The block at `pos` changed its comparator output
|
||||
/// (`native_analog_output`) without changing its state: the comparators
|
||||
/// beside it, or behind a conductor there, read it again
|
||||
/// (`Level.updateNeighbourForOutputSignal`). Acts on the level in place:
|
||||
/// false (and nothing happens) when `dimension` is another, or this
|
||||
/// window isn't a server. API @API:analog_output@.
|
||||
pub(super) fn native_update_output_neighbors(
|
||||
&mut self,
|
||||
dimension: &str,
|
||||
pos: [i32; 3],
|
||||
) -> bool {
|
||||
if (self.serving.is_none() && self.options.dedicated.is_none())
|
||||
|| dimension != self.dim.id()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let at = BlockPos::new(pos[0], pos[1], pos[2]);
|
||||
self.with_level(|b, level, ticks| b.update_neighbour_for_output_signal(level, ticks, at));
|
||||
true
|
||||
}
|
||||
/// The block at `pos` changed the signal it gives: its neighbours, and
|
||||
/// theirs (a block it powers passes the signal on), read it again, as
|
||||
/// around a lever (`Level.updateNeighborsAt`). Call it when the answer
|
||||
|
||||
@@ -510,6 +510,23 @@ pub trait LevelAccess {
|
||||
fn adjust_signal_source(&self, _state: BlockStateId, vanilla: bool) -> bool {
|
||||
vanilla
|
||||
}
|
||||
/// `BlockState.hasAnalogOutputSignal` once the library answered `vanilla`:
|
||||
/// whether comparators read the block. The default keeps it.
|
||||
fn adjust_has_analog_output(&self, _state: BlockStateId, vanilla: bool) -> bool {
|
||||
vanilla
|
||||
}
|
||||
/// `BlockState.getAnalogOutputSignal(level, pos, direction)` once the
|
||||
/// library answered `vanilla`, `direction` pointing from the block to the
|
||||
/// comparator reading it: the output to use. The default keeps it.
|
||||
fn adjust_analog_output(
|
||||
&self,
|
||||
_pos: BlockPos,
|
||||
_state: BlockStateId,
|
||||
_direction: Direction,
|
||||
vanilla: i32,
|
||||
) -> i32 {
|
||||
vanilla
|
||||
}
|
||||
/// `BlockState.getSignal` (or `getDirectSignal` when `direct`) once the
|
||||
/// library answered `vanilla`: the signal to use. The default keeps it.
|
||||
fn adjust_signal(
|
||||
|
||||
@@ -514,13 +514,13 @@ impl<L: LevelAccess + ?Sized> Sim<'_, L> {
|
||||
};
|
||||
if b.instance_of(state, Class::Comparator) {
|
||||
let target_state = self.state(target);
|
||||
if b.has_flag(target_state, state_flags::ANALOG_OUTPUT) {
|
||||
if self.has_analog_output(target_state) {
|
||||
result = self.analog_output(target_state, target, facing.opposite());
|
||||
} else if result < 15 && self.is_redstone_conductor(target_state, target) {
|
||||
let further = rel(target, facing);
|
||||
let further_state = self.state(further);
|
||||
let block = b
|
||||
.has_flag(further_state, state_flags::ANALOG_OUTPUT)
|
||||
let block = self
|
||||
.has_analog_output(further_state)
|
||||
.then(|| self.analog_output(further_state, further, facing.opposite()));
|
||||
// ComparatorBlock.getInputSignal: an item frame behind the
|
||||
// conductor counts too, the larger signal winning.
|
||||
@@ -667,12 +667,29 @@ impl<L: LevelAccess + ?Sized> Sim<'_, L> {
|
||||
}
|
||||
}
|
||||
|
||||
/// `getAnalogOutputSignal` for state-only sources; others ask the caller.
|
||||
/// `BlockState.hasAnalogOutputSignal`, as the level answers it (a mod's block).
|
||||
pub(crate) fn has_analog_output(&self, state: BlockStateId) -> bool {
|
||||
let vanilla = self.b.has_flag(state, state_flags::ANALOG_OUTPUT);
|
||||
self.level.adjust_has_analog_output(state, vanilla)
|
||||
}
|
||||
/// `BlockState.getAnalogOutputSignal`, as the level answers it (a mod's block), 0 to 15.
|
||||
pub(crate) fn analog_output(
|
||||
&self,
|
||||
state: BlockStateId,
|
||||
pos: BlockPos,
|
||||
direction: Direction,
|
||||
) -> i32 {
|
||||
let vanilla = self.vanilla_analog_output(state, pos, direction);
|
||||
self.level
|
||||
.adjust_analog_output(pos, state, direction, vanilla)
|
||||
.clamp(0, 15)
|
||||
}
|
||||
/// `getAnalogOutputSignal` for state-only sources; others ask the caller.
|
||||
fn vanilla_analog_output(
|
||||
&self,
|
||||
state: BlockStateId,
|
||||
pos: BlockPos,
|
||||
direction: Direction,
|
||||
) -> i32 {
|
||||
let b = self.b;
|
||||
let p = b.props;
|
||||
|
||||
@@ -55,6 +55,10 @@ pub(crate) struct TestLevel {
|
||||
/// What `adjust_signal` answers at a position: the weak and direct signal,
|
||||
/// and the only direction they go in, if any.
|
||||
pub signals: HashMap<BlockPos, (i32, i32, Option<Direction>)>,
|
||||
/// What `adjust_analog_output` answers at a position, for the states in
|
||||
/// `analog_states` (`adjust_has_analog_output`).
|
||||
pub analog: HashMap<BlockPos, i32>,
|
||||
pub analog_states: HashSet<BlockStateId>,
|
||||
}
|
||||
impl TestLevel {
|
||||
fn new(b: &Behaviours) -> Self {
|
||||
@@ -63,6 +67,8 @@ impl TestLevel {
|
||||
air: b.air(),
|
||||
random: LegacyRandom::new(42),
|
||||
signals: HashMap::new(),
|
||||
analog: HashMap::new(),
|
||||
analog_states: HashSet::new(),
|
||||
time: 0,
|
||||
day_time: 0,
|
||||
events: Vec::new(),
|
||||
@@ -73,6 +79,18 @@ impl TestLevel {
|
||||
}
|
||||
}
|
||||
impl LevelAccess for TestLevel {
|
||||
fn adjust_has_analog_output(&self, state: BlockStateId, vanilla: bool) -> bool {
|
||||
vanilla || self.analog_states.contains(&state)
|
||||
}
|
||||
fn adjust_analog_output(
|
||||
&self,
|
||||
pos: BlockPos,
|
||||
_state: BlockStateId,
|
||||
_direction: Direction,
|
||||
vanilla: i32,
|
||||
) -> i32 {
|
||||
self.analog.get(&pos).copied().unwrap_or(vanilla)
|
||||
}
|
||||
fn adjust_signal(
|
||||
&self,
|
||||
pos: BlockPos,
|
||||
@@ -1971,6 +1989,49 @@ fn a_level_can_answer_for_a_blocks_signal() {
|
||||
assert_eq!(read(&mut w, beside), 15);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_level_can_answer_for_a_blocks_comparator_output() {
|
||||
let mut w = world!();
|
||||
w.floor(0, 4, "stone");
|
||||
let block = BlockPos::new(0, 1, 0);
|
||||
let comparator = BlockPos::new(1, 1, 0);
|
||||
let after = BlockPos::new(2, 1, 0);
|
||||
// A comparator facing west reads the block west of it.
|
||||
let facing_west = w.id("comparator", &[("facing", "west")]);
|
||||
w.set(comparator, facing_west);
|
||||
let read = |w: &mut World| {
|
||||
for _ in 0..4 {
|
||||
w.full_tick();
|
||||
}
|
||||
w.f.behaviours
|
||||
.best_neighbor_signal(&mut w.level, &mut w.ticks, after)
|
||||
};
|
||||
let gold = w.id("gold_block", &[]);
|
||||
w.level.analog.insert(block, 9);
|
||||
w.set(block, gold);
|
||||
assert_eq!(read(&mut w), 0, "gold has no output of its own");
|
||||
|
||||
// Placing a block that says it has one tells the comparator, as Level.setBlock does.
|
||||
w.level.analog_states.insert(gold);
|
||||
w.set(block, w.id("stone", &[]));
|
||||
w.set(block, gold);
|
||||
assert_eq!(read(&mut w), 9);
|
||||
|
||||
// An output that changes without the block: updateNeighbourForOutputSignal.
|
||||
w.level.analog.insert(block, 4);
|
||||
assert_eq!(read(&mut w), 9, "nothing told the comparator yet");
|
||||
w.f.behaviours
|
||||
.update_neighbour_for_output_signal(&mut w.level, &mut w.ticks, block);
|
||||
assert_eq!(read(&mut w), 4);
|
||||
|
||||
// Behind a conductor, and out of range answers are clamped.
|
||||
let further = BlockPos::new(-1, 1, 0);
|
||||
w.set(block, w.id("stone", &[]));
|
||||
w.level.analog.insert(further, 40);
|
||||
w.set(further, gold);
|
||||
assert_eq!(read(&mut w), 15);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sculk_sensor_phases_and_signal() {
|
||||
let mut w = world!();
|
||||
|
||||
@@ -281,10 +281,7 @@ impl<L: LevelAccess + ?Sized> Sim<'_, L> {
|
||||
if update_flags & flags::UPDATE_NEIGHBORS != 0 {
|
||||
let old_block = self.block_of(old);
|
||||
self.update_neighbors_at(pos, old_block);
|
||||
if self
|
||||
.b
|
||||
.has_flag(state, super::data::state_flags::ANALOG_OUTPUT)
|
||||
{
|
||||
if self.has_analog_output(state) {
|
||||
let block = self.block_of(state);
|
||||
self.update_neighbour_for_output_signal(pos, block);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"hooks": [
|
||||
{"name": "has_analog_output", "target": "horizon_client::game::native_hooks::GameLevel<'_>::native_has_analog_output"},
|
||||
{"name": "analog_output", "target": "horizon_client::game::native_hooks::GameLevel<'_>::native_analog_output"}
|
||||
],
|
||||
"rust_summary": "comparator output from a mod's block (`native_has_analog_output`,\n`native_analog_output`, and the helper `native_update_output_neighbors`).",
|
||||
"doc_summary": "a mod block's comparator output, as vanilla's `BlockBehaviour.hasAnalogOutputSignal` and `getAnalogOutputSignal`: `native_has_analog_output` says a block of yours has one,\nand `native_analog_output` gives it (0 to 15) for its state and position. A state change with neighbour updates tells the comparators itself; when the output\nchanges otherwise, `native_update_output_neighbors(dimension, pos)` makes them read it again (`Level.updateNeighbourForOutputSignal`). Bundles need `\"hooks\": @API:analog_output@`."
|
||||
}
|
||||
Reference in new issue
Block a user