Skip to content

Commit 42e78d0

Browse files
committed
Fix mining animation
Greatly simplify code Add an ad hoc gamemode changing system
1 parent bbfd69b commit 42e78d0

File tree

5 files changed

+99
-242
lines changed

5 files changed

+99
-242
lines changed

feather/common/src/block_break.rs

Lines changed: 66 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -1,150 +1,74 @@
1-
use std::mem;
2-
3-
use base::{BlockKind, ItemStack, ValidBlockPosition};
4-
use ecs::{EntityBuilder, SysResult, SystemExecutor};
1+
use anyhow::Context;
2+
use base::{inventory::SLOT_HOTBAR_OFFSET, BlockKind, ItemStack, ValidBlockPosition};
3+
use ecs::{Entity, SysResult, SystemExecutor};
54
use libcraft_items::EnchantmentKind;
6-
use quill_common::{entities::Player, entity_init::EntityInit};
5+
use quill_common::components::Instabreak;
76

87
pub struct DestroyStateChange(pub ValidBlockPosition, pub u8);
98

10-
use crate::{Game, World};
9+
use crate::{entities::player::HotbarSlot, Game, Window, World};
1110

1211
// Comparing to 0.7 ensures good feeling in the client
1312
pub const BREAK_THRESHOLD: f32 = 0.7;
1413

15-
#[derive(Clone)]
16-
pub enum BlockBreaker {
17-
Active(ActiveBreaker),
18-
Finished(FinishedBreaker),
19-
Inactive,
20-
}
21-
impl BlockBreaker {
22-
/// Create a new active instance pointing to `block_pos`. Calculates the time needed using `world.block_at(block_pos)` and `equipped_item`.
23-
pub fn new(
24-
world: &mut World,
25-
block_pos: ValidBlockPosition,
26-
equipped_item: Option<&ItemStack>,
27-
) -> Self {
28-
ActiveBreaker::new(world, block_pos, equipped_item)
29-
.map(Self::Active)
30-
.unwrap_or(Self::Inactive)
31-
}
32-
/// If active, produces a `DestroyStateChange` event for the adequate position.
33-
pub fn destroy_change_event(&self) -> Option<DestroyStateChange> {
34-
Some(DestroyStateChange(self.position()?, self.destroy_stage()))
35-
}
36-
/// If active or finished, returns the pointed to position.
37-
pub fn position(&self) -> Option<ValidBlockPosition> {
38-
match self {
39-
BlockBreaker::Active(a) => Some(a.position),
40-
BlockBreaker::Finished(f) => Some(f.position),
41-
BlockBreaker::Inactive => None,
42-
}
43-
}
44-
/// If active, returns the underlying `ActiveBreaker`.
45-
pub fn active(&self) -> Option<&ActiveBreaker> {
46-
match self {
47-
Self::Active(a) => Some(a),
48-
_ => None,
49-
}
50-
}
51-
/// If finished, returns the underlying `FinishedBreaker`.
52-
pub fn finished(&self) -> Option<&FinishedBreaker> {
53-
match self {
54-
Self::Finished(f) => Some(f),
55-
_ => None,
56-
}
57-
}
58-
/// Progresses block breaking. Returns a (newly_finished, do_destry_state_change) tuple.
59-
/// If this operation finishes block breaking, this turns `self` into `Self::Finished` with the same position.
60-
pub fn tick(&mut self) -> (bool, bool) {
61-
let (block_break, stage_update) = if let Self::Active(breaker) = self {
62-
breaker.tick()
63-
} else {
64-
(false, false)
14+
pub fn start_digging(
15+
game: &mut Game,
16+
player: Entity,
17+
position: ValidBlockPosition,
18+
) -> anyhow::Result<bool> {
19+
if game.ecs.get::<Instabreak>(player)?.0 {
20+
game.break_block(position);
21+
} else {
22+
let breaker = {
23+
let window = game.ecs.get::<Window>(player)?;
24+
let hotbar_slot = game.ecs.get::<HotbarSlot>(player)?.get();
25+
let main_hand = window.item(SLOT_HOTBAR_OFFSET + hotbar_slot)?;
26+
ActiveBreaker::new(&mut game.world, position, main_hand.option_ref())
27+
.context("Cannot mine this block")?
6528
};
66-
if block_break {
67-
let fin = match mem::take(self) {
68-
Self::Active(a) => a.finish(),
69-
_ => unreachable!(),
70-
};
71-
*self = Self::Finished(fin);
72-
}
73-
(block_break, stage_update)
74-
}
75-
/// Returns the block destroying progress in a range of 0 - 9. When inactive or finished, returns 10.
76-
pub fn destroy_stage(&self) -> u8 {
77-
match self {
78-
BlockBreaker::Active(a) => a.destroy_stage(),
79-
_ => 10,
80-
}
81-
}
82-
/// Set `self` to `Self::Inactive`.
83-
pub fn cancel(&mut self) {
84-
*self = Self::Inactive;
85-
}
86-
/// Check if the breaker points to `pos`. Returns `true` when `self` is `Self::Inactive`.
87-
pub fn matches_position(&self, pos: ValidBlockPosition) -> bool {
88-
match self {
89-
BlockBreaker::Active(a) => a.position == pos,
90-
BlockBreaker::Finished(f) => f.position == pos,
91-
BlockBreaker::Inactive => true,
92-
}
93-
}
94-
/// Attempts to finish breaking the target block, optionally turning `self` into `Self::Finished`.
95-
pub fn try_finish(&mut self) -> Option<FinishedBreaker> {
96-
let this = self.clone();
97-
match this {
98-
BlockBreaker::Active(a) => {
99-
if a.can_break() {
100-
let fin = a.finish();
101-
*self = Self::Finished(fin.clone());
102-
Some(fin)
103-
} else {
104-
None
105-
}
106-
}
107-
BlockBreaker::Finished(f) => Some(f),
108-
BlockBreaker::Inactive => None,
109-
}
110-
}
111-
}
112-
impl Default for BlockBreaker {
113-
fn default() -> Self {
114-
Self::Inactive
29+
game.ecs.insert(player, breaker)?;
11530
}
31+
Ok(true)
11632
}
117-
#[derive(Clone)]
118-
pub struct FinishedBreaker {
119-
pub position: ValidBlockPosition,
120-
pub drop_item: bool,
121-
pub fake_finished: bool,
33+
pub fn cancel_digging(
34+
game: &mut Game,
35+
player: Entity,
36+
position: ValidBlockPosition,
37+
) -> anyhow::Result<bool> {
38+
if game.ecs.get::<ActiveBreaker>(player).is_err() {
39+
return Ok(false);
40+
}
41+
game.ecs.remove::<ActiveBreaker>(player)?;
42+
game.ecs
43+
.insert_entity_event(player, DestroyStateChange(position, 10))?;
44+
Ok(true)
12245
}
123-
impl FinishedBreaker {
124-
/// Breaks the targeted block and spawns its drops. TODO: make drops work.
125-
pub fn break_block(&self, game: &mut Game) -> SysResult {
126-
let target_block = match game.block(self.position) {
127-
Some(b) => b,
128-
None => anyhow::bail!("cannot break unloaded block"),
129-
};
130-
game.break_block(self.position);
131-
if let Some(_item_drop) = base::Item::from_name(target_block.kind().name()) {
132-
if !self.drop_item {
133-
return Ok(());
134-
}
135-
let mut item_entity = EntityBuilder::new();
136-
crate::entities::item::build_default(&mut item_entity);
137-
let builder = game.create_entity_builder(self.position.position(), EntityInit::Item);
138-
game.spawn_entity(builder);
139-
}
140-
Ok(())
141-
}
46+
pub fn finish_digging(
47+
game: &mut Game,
48+
player: Entity,
49+
position: ValidBlockPosition,
50+
) -> anyhow::Result<bool> {
51+
if game.ecs.get::<Instabreak>(player)?.0 {
52+
return Ok(true);
53+
}
54+
let success = if let Ok(breaker) = game.ecs.get::<ActiveBreaker>(player) {
55+
breaker.can_break()
56+
} else {
57+
false
58+
};
59+
if success {
60+
let pos = game.ecs.get::<ActiveBreaker>(player)?.position;
61+
game.break_block(pos); // TODO: drop an item
62+
game.ecs.remove::<ActiveBreaker>(player)?;
63+
}
64+
game.ecs
65+
.insert_entity_event(player, DestroyStateChange(position, 10))?;
66+
Ok(success)
14267
}
14368
#[derive(Clone)]
14469
pub struct ActiveBreaker {
14570
pub position: ValidBlockPosition,
14671
pub drop_item: bool,
147-
pub fake_finished: bool,
14872
pub progress: f32,
14973
pub damage: f32,
15074
}
@@ -154,7 +78,7 @@ impl ActiveBreaker {
15478
self.progress += self.damage;
15579
let after = self.destroy_stage();
15680
let break_block = self.can_break();
157-
let change_stage = before != after || break_block;
81+
let change_stage = break_block || before != after;
15882
(break_block, change_stage)
15983
}
16084
/// Check if the block has been damaged enough to break.
@@ -175,21 +99,16 @@ impl ActiveBreaker {
17599
(Some(_), None) => false,
176100
(Some(tools), Some(tool)) => tools.contains(&tool.item()),
177101
};
178-
let dig_multiplier = block
102+
let dig_multiplier = block // TODO: calculate with Haste effect
179103
.dig_multipliers()
180104
.iter()
181105
.find_map(|(item, speed)| {
182-
equipped_item
183-
.and_then(|e| {
184-
bool::then_some(e.item() == *item, *speed)
185-
})
106+
equipped_item.and_then(|e| bool::then_some(e.item() == *item, *speed))
186107
})
187108
.unwrap_or(1.0);
188109
let effi_level = equipped_item
189110
.and_then(ItemStack::metadata)
190-
.and_then(|meta| {
191-
meta.get_enchantment_level(EnchantmentKind::Efficiency)
192-
});
111+
.and_then(|meta| meta.get_enchantment_level(EnchantmentKind::Efficiency));
193112
let effi_speed = effi_level.map(|level| level * level + 1).unwrap_or(0) as f32;
194113
let damage = if harvestable {
195114
(dig_multiplier + effi_speed) / block.hardness() / 30.0
@@ -199,25 +118,16 @@ impl ActiveBreaker {
199118
Some(Self {
200119
position: block_pos,
201120
drop_item: true,
202-
fake_finished: false,
203-
progress: 0.0,
121+
progress: damage,
204122
damage,
205123
})
206124
}
207125
/// Get the destroying progress.
208126
pub fn destroy_stage(&self) -> u8 {
209-
if self.fake_finished {
210-
10
211-
} else {
212-
(self.progress / BREAK_THRESHOLD * 9.0).round() as u8
213-
}
127+
(self.progress * 9.0).round() as u8
214128
}
215-
pub fn finish(self) -> FinishedBreaker {
216-
FinishedBreaker {
217-
position: self.position,
218-
drop_item: self.drop_item,
219-
fake_finished: self.fake_finished,
220-
}
129+
pub fn destroy_change_event(&self) -> DestroyStateChange {
130+
DestroyStateChange(self.position, self.destroy_stage())
221131
}
222132
}
223133

@@ -226,36 +136,19 @@ pub fn register(systems: &mut SystemExecutor<Game>) {
226136
}
227137

228138
fn process_block_breaking(game: &mut Game) -> SysResult {
229-
let mut break_queue = vec![];
230139
let mut update_queue = vec![];
231-
for (entity, breaker) in game.ecs.query::<&mut BlockBreaker>().iter() {
232-
let (break_block, update_stage) = breaker.tick();
140+
for (entity, breaker) in game.ecs.query::<&mut ActiveBreaker>().iter() {
141+
let (_, update_stage) = breaker.tick();
233142
if update_stage {
234143
update_queue.push(entity);
235144
}
236-
// Break block when client requests to finish in order to prevent desyncs
237-
if break_block && breaker.finished().unwrap().fake_finished
238-
|| game.ecs.get::<Player>(entity).is_err()
239-
{
240-
break_queue.push(entity);
241-
}
242145
}
243146
for entity in update_queue {
244147
let event = game
245148
.ecs
246-
.get_mut::<BlockBreaker>(entity)?
247-
.destroy_change_event()
248-
.unwrap();
149+
.get_mut::<ActiveBreaker>(entity)?
150+
.destroy_change_event();
249151
game.ecs.insert_entity_event(entity, event)?;
250152
}
251-
for entity in break_queue.into_iter() {
252-
let breaker = game
253-
.ecs
254-
.get::<BlockBreaker>(entity)?
255-
.finished()
256-
.unwrap()
257-
.clone();
258-
breaker.break_block(game)?;
259-
}
260153
Ok(())
261154
}

feather/common/src/game.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
use std::{cell::RefCell, mem, rc::Rc, sync::Arc};
22

3-
use base::{BlockId, ChunkPosition, Position, Text, Title, ValidBlockPosition};
3+
use base::{BlockId, ChunkPosition, Gamemode, Position, Text, Title, ValidBlockPosition};
44
use ecs::{
55
Ecs, Entity, EntityBuilder, HasEcs, HasResources, NoSuchEntity, Resources, SysResult,
66
SystemExecutor,
77
};
8-
use quill_common::events::{EntityCreateEvent, EntityRemoveEvent, PlayerJoinEvent};
8+
use quill_common::events::{EntityCreateEvent, EntityRemoveEvent, GamemodeEvent, PlayerJoinEvent};
99
use quill_common::{entities::Player, entity_init::EntityInit};
1010

1111
use crate::{
@@ -230,6 +230,11 @@ impl Game {
230230
pub fn break_block(&mut self, pos: ValidBlockPosition) -> bool {
231231
self.set_block(pos, BlockId::air())
232232
}
233+
234+
pub fn set_gamemode(&mut self, player: Entity, new: Gamemode) -> SysResult {
235+
self.ecs.insert_entity_event(player, GamemodeEvent(new))?;
236+
Ok(())
237+
}
233238
}
234239

235240
impl HasResources for Game {

feather/server/src/packet_handlers.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use base::{Position, Text};
1+
use base::{Gamemode, Position, Text};
22
use common::{chat::ChatKind, Game};
33
use ecs::{Entity, EntityRef, SysResult};
44
use interaction::{
@@ -45,7 +45,7 @@ pub fn handle_packet(
4545

4646
ClientPlayPacket::Animation(packet) => handle_animation(server, player, packet),
4747

48-
ClientPlayPacket::ChatMessage(packet) => handle_chat_message(game, player, packet),
48+
ClientPlayPacket::ChatMessage(packet) => handle_chat_message(game, player_id, packet),
4949

5050
ClientPlayPacket::PlayerDigging(packet) => {
5151
handle_player_digging(game, server, packet, player_id)
@@ -132,8 +132,16 @@ fn handle_animation(
132132
Ok(())
133133
}
134134

135-
fn handle_chat_message(game: &Game, player: EntityRef, packet: client::ChatMessage) -> SysResult {
136-
let name = player.get::<Name>()?;
135+
fn handle_chat_message(game: &mut Game, player: Entity, packet: client::ChatMessage) -> SysResult {
136+
if let m @ ("c" | "s") = &*packet.message {
137+
if m == "c" {
138+
game.set_gamemode(player, Gamemode::Creative)?;
139+
} else {
140+
game.set_gamemode(player, Gamemode::Survival)?;
141+
}
142+
}
143+
144+
let name = game.ecs.get::<Name>(player)?;
137145
let message = Text::translate_with("chat.type.text", vec![name.to_string(), packet.message]);
138146
game.broadcast_chat(ChatKind::PlayerChat, message);
139147
Ok(())

0 commit comments

Comments
 (0)