Skip to content

Commit c36fe98

Browse files
committed
WIP
Breaking blocks in survival works 90% of the time Instamine and creative mostly untested Time to mine blocks off by 20% for unknown reasons Netherite tools are not included in `BlockKind::harvest_tools` nor in `BlockKind::dig_multipliers`
1 parent 316f490 commit c36fe98

File tree

3 files changed

+199
-63
lines changed

3 files changed

+199
-63
lines changed

feather/common/src/block_break.rs

Lines changed: 152 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,106 @@
11
use base::{ItemStack, ValidBlockPosition};
22
use ecs::{EntityBuilder, SysResult, SystemExecutor};
33
use libcraft_items::EnchantmentKind;
4-
use quill_common::entity_init::EntityInit;
4+
use quill_common::{entities::Player, entity_init::EntityInit};
55

66
pub struct DestroyStateChange(pub ValidBlockPosition, pub u8);
77

88
use crate::{Game, World};
99

10-
pub type BlockBreaker = Option<ActiveBlockBreaker>;
11-
#[derive(Clone, Copy)]
12-
pub struct ActiveBlockBreaker {
13-
pub position: ValidBlockPosition,
14-
pub drop_item: bool,
15-
pub total_ticks: u32,
16-
pub ticks_remaining: u32,
10+
#[derive(Clone)]
11+
pub enum BlockBreaker {
12+
Active(ActiveBreaker),
13+
Finished(FinishedBreaker),
14+
Inactive,
1715
}
18-
impl ActiveBlockBreaker {
16+
impl BlockBreaker {
17+
pub fn new(
18+
world: &mut World,
19+
block_pos: ValidBlockPosition,
20+
equipped_item: Option<&ItemStack>,
21+
) -> Self {
22+
ActiveBreaker::new(world, block_pos, equipped_item)
23+
.map(Self::Active)
24+
.unwrap_or(Self::Inactive)
25+
}
26+
pub fn destroy_change_event(&self) -> Option<DestroyStateChange> {
27+
Some(DestroyStateChange(self.position()?, self.destroy_stage()))
28+
}
29+
pub fn position(&self) -> Option<ValidBlockPosition> {
30+
match self {
31+
BlockBreaker::Active(a) => Some(a.position),
32+
BlockBreaker::Finished(f) => Some(f.position),
33+
BlockBreaker::Inactive => None,
34+
}
35+
}
36+
pub fn active(&self) -> Option<&ActiveBreaker> {
37+
match self {
38+
Self::Active(a) => Some(a),
39+
_ => None,
40+
}
41+
}
42+
pub fn finished(&self) -> Option<&FinishedBreaker> {
43+
match self {
44+
Self::Finished(f) => Some(f),
45+
_ => None,
46+
}
47+
}
1948
pub fn tick(&mut self) -> (bool, bool) {
20-
let before = self.destroy_stage();
21-
self.ticks_remaining = self.ticks_remaining.saturating_sub(1);
22-
let after = self.destroy_stage();
23-
(self.ticks_remaining == 0, before != after)
49+
let (block_break, stage_update) = if let Self::Active(breaker) = self {
50+
breaker.tick()
51+
} else {
52+
(false, false)
53+
};
54+
if block_break {
55+
let fin = match self {
56+
Self::Active(a) => a.clone().finish(),
57+
_ => unreachable!(),
58+
};
59+
*self = Self::Finished(fin);
60+
}
61+
(block_break, stage_update)
2462
}
25-
pub fn break_block(self, game: &mut Game) -> SysResult {
63+
pub fn destroy_stage(&self) -> u8 {
64+
match self {
65+
BlockBreaker::Active(a) => a.destroy_stage(),
66+
_ => 10,
67+
}
68+
}
69+
pub fn cancel(&mut self) {
70+
*self = Self::Inactive;
71+
}
72+
pub fn matches_position(&self, pos: ValidBlockPosition) -> bool {
73+
match self {
74+
BlockBreaker::Active(a) => a.position == pos,
75+
BlockBreaker::Finished(f) => f.position == pos,
76+
BlockBreaker::Inactive => true,
77+
}
78+
}
79+
pub fn try_finish(&mut self) -> Option<FinishedBreaker> {
80+
let this = self.clone();
81+
match this {
82+
BlockBreaker::Active(a) => {
83+
if a.ticks_remaining == 1 {
84+
let fin = a.finish();
85+
*self = Self::Finished(fin.clone());
86+
Some(fin)
87+
} else {
88+
None
89+
}
90+
}
91+
BlockBreaker::Finished(f) => Some(f),
92+
BlockBreaker::Inactive => None,
93+
}
94+
}
95+
}
96+
#[derive(Clone)]
97+
pub struct FinishedBreaker {
98+
pub position: ValidBlockPosition,
99+
pub drop_item: bool,
100+
pub fake_finished: bool,
101+
}
102+
impl FinishedBreaker {
103+
pub fn break_block(&self, game: &mut Game) -> SysResult {
26104
let target_block = match game.block(self.position) {
27105
Some(b) => b,
28106
None => anyhow::bail!("cannot break unloaded block"),
@@ -39,16 +117,34 @@ impl ActiveBlockBreaker {
39117
}
40118
Ok(())
41119
}
42-
pub fn new_player(
120+
}
121+
#[derive(Clone)]
122+
pub struct ActiveBreaker {
123+
pub position: ValidBlockPosition,
124+
pub drop_item: bool,
125+
pub fake_finished: bool,
126+
pub total_ticks: u32,
127+
pub ticks_remaining: u32,
128+
}
129+
impl ActiveBreaker {
130+
pub fn tick(&mut self) -> (bool, bool) {
131+
let before = self.destroy_stage();
132+
self.ticks_remaining = self.ticks_remaining.saturating_sub(1);
133+
let after = self.destroy_stage();
134+
let break_block = self.ticks_remaining == 0;
135+
let change_stage = before != after || break_block;
136+
(break_block, change_stage)
137+
}
138+
pub fn new(
43139
world: &mut World,
44140
block_pos: ValidBlockPosition,
45-
main_hand: Option<&ItemStack>,
141+
equipped_item: Option<&ItemStack>,
46142
) -> Option<Self> {
47143
let block = world.block_at(block_pos)?.kind();
48144
if !block.diggable() {
49145
return None;
50146
}
51-
let harvestable = match (block.harvest_tools(), main_hand) {
147+
let harvestable = match (block.harvest_tools(), equipped_item) {
52148
(None, None | Some(_)) => true,
53149
(Some(_), None) => false,
54150
(Some(tools), Some(tool)) => tools.contains(&tool.item()),
@@ -57,7 +153,7 @@ impl ActiveBlockBreaker {
57153
.dig_multipliers()
58154
.iter()
59155
.find_map(|(item, speed)| {
60-
main_hand
156+
equipped_item
61157
.map(|e| {
62158
if e.item() == *item {
63159
Some(*speed)
@@ -68,7 +164,7 @@ impl ActiveBlockBreaker {
68164
.flatten()
69165
})
70166
.unwrap_or(1.0);
71-
let effi_level = main_hand
167+
let effi_level = equipped_item
72168
.map(ItemStack::metadata)
73169
.flatten()
74170
.map(|meta| {
@@ -90,17 +186,37 @@ impl ActiveBlockBreaker {
90186
let ticks = if damage > 1.0 {
91187
0
92188
} else {
93-
(1.0 / damage).ceil() as u32 - 4
189+
(1.0 / damage / 1.2).ceil() as u32
94190
};
191+
println!(
192+
"Mining {} with {} takes {} ticks",
193+
block.display_name(),
194+
equipped_item
195+
.map(|e| e.get_item().item().display_name())
196+
.unwrap_or("bare hands"),
197+
ticks
198+
);
95199
Some(Self {
96200
position: block_pos,
97201
drop_item: true,
202+
fake_finished: false,
98203
total_ticks: ticks,
99204
ticks_remaining: ticks,
100205
})
101206
}
102207
pub fn destroy_stage(&self) -> u8 {
103-
9 - (self.ticks_remaining as f32 / self.total_ticks as f32 * 9.0).round() as u8
208+
if self.fake_finished {
209+
10
210+
} else {
211+
9 - (self.ticks_remaining as f32 / self.total_ticks as f32 * 9.0).round() as u8
212+
}
213+
}
214+
pub fn finish(self) -> FinishedBreaker {
215+
FinishedBreaker {
216+
position: self.position,
217+
drop_item: self.drop_item,
218+
fake_finished: self.fake_finished,
219+
}
104220
}
105221
}
106222

@@ -112,35 +228,28 @@ fn process_block_breaking(game: &mut Game) -> SysResult {
112228
let mut break_queue = vec![];
113229
let mut update_queue = vec![];
114230
for (entity, breaker) in game.ecs.query::<&mut BlockBreaker>().iter() {
115-
if let Some(active) = breaker {
116-
let (break_block, update_stage) = active.tick();
117-
if update_stage {
118-
update_queue.push(entity);
119-
}
120-
if break_block {
231+
let (break_block, update_stage) = breaker.tick();
232+
if update_stage {
233+
update_queue.push(entity);
234+
}
235+
// Break block when client requests to finish in order to prevent desyncs
236+
if break_block {
237+
if breaker.finished().unwrap().fake_finished || !game.ecs.get::<Player>(entity).is_ok()
238+
{
121239
break_queue.push(entity);
122240
}
123241
}
124242
}
125243
for entity in update_queue {
126-
let breaker = { game.ecs.get_mut::<BlockBreaker>(entity).unwrap().unwrap() };
127-
game.ecs.insert_entity_event(
128-
entity,
129-
DestroyStateChange(breaker.position, breaker.destroy_stage()),
130-
)?;
244+
let event = game
245+
.ecs
246+
.get_mut::<BlockBreaker>(entity)?
247+
.destroy_change_event()
248+
.unwrap();
249+
game.ecs.insert_entity_event(entity, event)?;
131250
}
132251
for entity in break_queue.into_iter() {
133-
// Set block breakers to None
134-
let breaker = {
135-
game.ecs
136-
.get_mut::<BlockBreaker>(entity)
137-
.unwrap()
138-
.take()
139-
.unwrap()
140-
};
141-
game.ecs
142-
.insert_entity_event(entity, DestroyStateChange(breaker.position, 10))
143-
.unwrap();
252+
let breaker = game.ecs.get::<BlockBreaker>(entity)?.finished().unwrap().clone();
144253
breaker.break_block(game)?;
145254
}
146255
Ok(())

feather/server/src/packet_handlers/interaction.rs

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::{ClientId, NetworkId, Server};
22
use base::inventory::{SLOT_HOTBAR_OFFSET, SLOT_OFFHAND};
33
use base::Gamemode;
4-
use common::block_break::{ActiveBlockBreaker, BlockBreaker, DestroyStateChange};
4+
use common::block_break::{ActiveBreaker, BlockBreaker, DestroyStateChange};
55
use common::entities::player::HotbarSlot;
66
use common::interactable::InteractableRegistry;
77
use common::{Game, Window};
@@ -120,6 +120,7 @@ pub fn handle_player_digging(
120120
player: Entity,
121121
) -> SysResult {
122122
log::trace!("Got player digging with status {:?}", packet.status);
123+
let client = server.clients.get(*game.ecs.get(player)?).unwrap();
123124
match packet.status {
124125
PlayerDiggingStatus::StartDigging => {
125126
if matches!(
@@ -132,26 +133,32 @@ pub fn handle_player_digging(
132133
let window = game.ecs.get::<Window>(player)?;
133134
let hotbar_slot = game.ecs.get::<HotbarSlot>(player)?.get();
134135
let main_hand = window.item(SLOT_HOTBAR_OFFSET + hotbar_slot)?;
135-
let _ = breaker.insert(
136-
ActiveBlockBreaker::new_player(
137-
&mut game.world,
138-
packet.position,
139-
main_hand.item_stack(),
140-
)
141-
.unwrap(),
136+
*breaker = BlockBreaker::Active(
137+
ActiveBreaker::new(&mut game.world, packet.position, main_hand.item_stack())
138+
.unwrap(),
142139
);
143140
}
144-
141+
let block = match game.block(packet.position) {
142+
Some(s) => s,
143+
None => return Ok(()),
144+
};
145+
client.acknowledge_player_digging(
146+
packet.position,
147+
block,
148+
protocol::packets::server::PlayerDiggingStatus::Started,
149+
true,
150+
);
145151
Ok(())
146152
}
147153
PlayerDiggingStatus::CancelDigging => {
148-
let breaker = game.ecs.get_mut::<BlockBreaker>(player)?.take();
149-
if let Some(s) = breaker {
150-
game.ecs
151-
.insert_entity_event(player, DestroyStateChange(s.position, 10))?;
152-
}
153-
154-
let client = server.clients.get(*game.ecs.get(player)?).unwrap();
154+
let success = {
155+
let mut breaker = game.ecs.get_mut::<BlockBreaker>(player)?;
156+
let a = breaker.matches_position(packet.position);
157+
breaker.cancel();
158+
a
159+
};
160+
game.ecs
161+
.insert_entity_event(player, DestroyStateChange(packet.position, 10))?;
155162
let block = match game.block(packet.position) {
156163
Some(s) => s,
157164
None => return Ok(()),
@@ -160,13 +167,33 @@ pub fn handle_player_digging(
160167
packet.position,
161168
block,
162169
protocol::packets::server::PlayerDiggingStatus::Cancelled,
163-
false,
170+
success,
164171
);
165172
Ok(())
166173
}
167174
PlayerDiggingStatus::FinishDigging => {
168-
let success = game.ecs.get::<BlockBreaker>(player)?.is_some();
169-
let client = server.clients.get(*game.ecs.get(player)?).unwrap();
175+
let mut finished = None;
176+
let success = {
177+
let mut breaker = game.ecs.get_mut::<BlockBreaker>(player)?;
178+
let success = if let Some(f) = breaker.try_finish() {
179+
finished = Some(f);
180+
breaker.cancel();
181+
true
182+
} else {
183+
if let BlockBreaker::Active(a) = &mut *breaker {
184+
a.fake_finished = true;
185+
println!("{} ticks remaining", a.ticks_remaining);
186+
}
187+
false
188+
};
189+
success && breaker.matches_position(packet.position)
190+
};
191+
if success {
192+
println!("confirm");
193+
finished.unwrap().break_block(game)?;
194+
}
195+
game.ecs
196+
.insert_entity_event(player, DestroyStateChange(packet.position, 10))?;
170197
let block = match game.block(packet.position) {
171198
Some(s) => s,
172199
None => return Ok(()),

feather/server/src/systems/player_join.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ fn accept_new_player(game: &mut Game, server: &mut Server, client_id: ClientId)
124124
.map(|data| data.animal.health)
125125
.unwrap_or(20.0),
126126
))
127-
.add(BlockBreaker::None)
127+
.add(BlockBreaker::Inactive)
128128
.add(abilities.walk_speed)
129129
.add(abilities.fly_speed)
130130
.add(abilities.is_flying)

0 commit comments

Comments
 (0)