Skip to content

Commit 38d4af8

Browse files
committed
optional dungeon breaker feature
1 parent 7ba8730 commit 38d4af8

File tree

6 files changed

+180
-49
lines changed

6 files changed

+180
-49
lines changed

Cargo.lock

Lines changed: 0 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ base64 = "0.22.1"
2424
glam = "0.30.5"
2525
reqwest = "0.12.24"
2626
zip = "6.0.0"
27-
maplit = "1.0.2"
2827
enumset = "1.1.10"
2928
fstr = { path = "crates/fstr" }
3029

@@ -34,3 +33,6 @@ opt-level = 3
3433
[profile.release]
3534
codegen-units = 1
3635
lto = "thin"
36+
37+
[features]
38+
dungeon_breaker = []

server/src/types/aabb.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,33 @@ impl AABB {
1313
max: DVec3::ZERO,
1414
};
1515

16-
pub fn new(min: DVec3, max: DVec3) -> Self {
16+
pub const fn new(min: DVec3, max: DVec3) -> Self {
1717
Self {
1818
min,
1919
max,
2020
}
2121
}
2222

23-
pub fn intersects(&self, other: &AABB) -> bool {
23+
pub const fn intersects(&self, other: &AABB) -> bool {
2424
self.min.x <= other.max.x && self.max.x >= other.min.x &&
2525
self.min.y <= other.max.y && self.max.y >= other.min.y &&
2626
self.min.z <= other.max.z && self.max.z >= other.min.z
2727
}
2828

29+
pub const fn volume(&self) -> f64 {
30+
let dx = (self.max.x - self.min.x).max(0.0);
31+
let dy = (self.max.y - self.min.y).max(0.0);
32+
let dz = (self.max.z - self.min.z).max(0.0);
33+
dx * dy * dz
34+
}
35+
36+
pub const fn intersection_volume(&self, other: &AABB) -> f64 {
37+
let dx = (self.max.x.min(other.max.x) - self.min.x.max(other.min.x)).max(0.0);
38+
let dy = (self.max.y.min(other.max.y) - self.min.y.max(other.min.y)).max(0.0);
39+
let dz = (self.max.z.min(other.max.z) - self.min.z.max(other.min.z)).max(0.0);
40+
dx * dy * dz
41+
}
42+
2943
pub const fn from_height_width(height: f64, width: f64) -> Self {
3044
Self {
3145
min: DVec3 { x: -width / 2.0, y: 0.0, z: -width / 2.0 },

src/dungeon/dungeon.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,11 @@ impl WorldExtension for Dungeon {
148148
current_room: None,
149149
cooldowns: HashMap::new(),
150150
active_abilities: Cell::new(Vec::new()),
151+
152+
#[cfg(feature = "dungeon_breaker")]
153+
pickaxe_charges: 20,
154+
#[cfg(feature = "dungeon_breaker")]
155+
broken_blocks: vec![],
151156
}
152157
);
153158

src/dungeon/dungeon_player.rs

Lines changed: 108 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ pub struct DungeonPlayer {
3030
// however if you pair with cooldowns it should be fine
3131
pub active_abilities: Cell<Vec<ActiveAbility>>,
3232
pub cooldowns: HashMap<DungeonItem, Cooldown>,
33+
34+
#[cfg(feature = "dungeon_breaker")]
35+
pub pickaxe_charges: usize,
36+
#[cfg(feature = "dungeon_breaker")]
37+
pub broken_blocks: Vec<(IVec3, Blocks, usize)>,
38+
3339
}
3440

3541
impl PlayerExtension for DungeonPlayer {
@@ -70,29 +76,101 @@ impl PlayerExtension for DungeonPlayer {
7076
cooldown.ticks_remaining -= 1;
7177
cooldown.ticks_remaining != 0
7278
});
79+
80+
#[cfg(feature = "dungeon_breaker")]
81+
{
82+
if player.ticks_existed.is_multiple_of(20) {
83+
if player.extension.pickaxe_charges != 20 {
84+
let min = min(20 - player.extension.pickaxe_charges, 2);
85+
player.extension.pickaxe_charges += min;
86+
}
87+
}
88+
89+
let chunk_grid = &mut player.world_mut().chunk_grid;
90+
player.extension.broken_blocks.retain_mut(|(position, block, ticks)| {
91+
*ticks -= 1;
92+
if *ticks == 0 {
93+
// for last 3 seconds, every second plays a particle and sound
94+
chunk_grid.set_block_at(*block, position.x, position.y, position.z);
95+
}
96+
*ticks != 0
97+
});
98+
}
7399
}
74100

75101
fn dig(player: &mut Player<Self>, position: IVec3, action: &PlayerDiggingAction) {
76-
let mut restore_block = false;
77-
match action {
78-
PlayerDiggingAction::StartDestroyBlock => {
79-
if let Some(item) = *player.inventory.get_hotbar_slot(player.held_slot as usize) {
80-
if matches!(item, DungeonItem::Pickaxe) {
81-
restore_block = true;
102+
103+
#[cfg(not(feature = "dungeon_breaker"))]
104+
{
105+
let mut restore_block = false;
106+
match action {
107+
PlayerDiggingAction::StartDestroyBlock => {
108+
if let Some(item) = *player.inventory.get_hotbar_slot(player.held_slot as usize) {
109+
if matches!(item, DungeonItem::Pickaxe) {
110+
restore_block = true;
111+
}
82112
}
113+
114+
// only doors can be interacted with left click I think
115+
let world = player.world_mut();
116+
DungeonPlayer::try_open_door(player, world, &position);
83117
}
84-
85-
// only doors can be interacted with left click I think
86-
let world = player.world_mut();
87-
DungeonPlayer::try_open_door(player, world, &position);
118+
PlayerDiggingAction::FinishDestroyBlock => {
119+
restore_block = true;
120+
}
121+
_ => {}
88122
}
89-
PlayerDiggingAction::FinishDestroyBlock => {
90-
restore_block = true;
123+
if restore_block {
124+
let block = player.world().chunk_grid.get_block_at(position.x, position.y, position.z);
125+
player.write_packet(&BlockChange {
126+
block_pos: position,
127+
block_state: block.get_block_state_id(),
128+
})
91129
}
92-
_ => {}
93130
}
94-
if restore_block {
95-
let block = player.world().chunk_grid.get_block_at(position.x, position.y, position.z);
131+
132+
#[cfg(feature = "dungeon_breaker")]
133+
{
134+
let world = &mut player.world_mut();
135+
136+
let can_break = world.has_started() && player.extension.pickaxe_charges != 0;
137+
if matches!(action, PlayerDiggingAction::StartDestroyBlock) && can_break {
138+
139+
let opened_door = DungeonPlayer::try_open_door(player, world, &position);
140+
let held_slot = player.inventory.get_hotbar_slot(player.held_slot as usize);
141+
142+
if !opened_door && matches!(held_slot, Some(DungeonItem::Pickaxe)) {
143+
let chunk_grid = &mut world.chunk_grid;
144+
145+
let block_aabb = AABB::new(
146+
position.as_dvec3() + dvec3(-0.75, -0.75, -0.75),
147+
position.as_dvec3() + dvec3(1.5, 1.5, 1.5),
148+
);
149+
150+
if let Some((room_rc, _)) = &player.extension.current_room {
151+
let room = room_rc.borrow();
152+
153+
// check if room doesn't allow, check if overlaps with secrets
154+
155+
let mut volume_inside = 0.0;
156+
157+
for bounds in room.room_bounds.iter() {
158+
volume_inside += block_aabb.intersection_volume(&bounds.aabb);
159+
}
160+
// if volume doesn't match, that means the block is likely on the border
161+
if block_aabb.volume() == volume_inside {
162+
let previous = chunk_grid.get_block_at(position.x, position.y, position.z);
163+
player.extension.broken_blocks.push((position, previous, 200));
164+
165+
chunk_grid.set_block_at(Blocks::Air, position.x, position.y, position.z);
166+
player.extension.pickaxe_charges -= 1;
167+
return;
168+
}
169+
}
170+
}
171+
}
172+
173+
let block = world.chunk_grid.get_block_at(position.x, position.y, position.z);
96174
player.write_packet(&BlockChange {
97175
block_pos: position,
98176
block_state: block.get_block_state_id(),
@@ -164,7 +242,7 @@ impl DungeonPlayer {
164242
None
165243
}
166244

167-
pub fn try_open_door(player: &mut Player<Self>, world: &mut World<Dungeon>, position: &IVec3) {
245+
pub fn try_open_door(player: &mut Player<Self>, world: &mut World<Dungeon>, position: &IVec3) -> bool {
168246
if world.has_started() {
169247
if let Some(room_rc) = player.extension.get_current_room() {
170248
for neighbour in room_rc.borrow().neighbours() {
@@ -185,10 +263,12 @@ impl DungeonPlayer {
185263
door.open(world);
186264
}
187265
neighbour.room.borrow_mut().discovered = true;
188-
world.map.draw_room(&neighbour.room.borrow())
266+
world.map.draw_room(&neighbour.room.borrow());
267+
return true;
189268
}
190269
}
191270
}
271+
false
192272
}
193273

194274
fn update_sidebar(player: &mut Player<DungeonPlayer>) {
@@ -267,12 +347,22 @@ impl DungeonPlayer {
267347
Keys: §c■ {has_blood_key} §8■ §a{wither_key_count}x
268348
Time elapsed: §a§a{time}
269349
Cleared: §c{clear_percent}% §8§8({score})
270-
350+
271351
"#,
272352
clear_percent = "0",
273353
score = "0",
274354
});
275355

356+
// if cfg!(feature = "dungeon_breaker") {
357+
#[cfg(feature = "dungeon_breaker")]
358+
sidebar.push(&format!{r#"
359+
charges {charges}
360+
361+
"#,
362+
charges = player.extension.pickaxe_charges,
363+
});
364+
// }
365+
276366
if world.players.len() == 1 {
277367
sidebar.push(indoc! {r#"
278368
§3§lSolo

src/dungeon/items/dungeon_items.rs

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -128,21 +128,47 @@ impl Item for DungeonItem {
128128
NBT::byte("HideFlags", 127),
129129
])),
130130
},
131-
DungeonItem::Pickaxe => ItemStack {
132-
item: 278,
133-
stack_size: 1,
134-
metadata: 0,
135-
tag_compound: Some(NBT::with_nodes(vec![
136-
NBT::list("ench", TAG_COMPOUND_ID, vec![
137-
NBTNode::Compound({
138-
let mut map = HashMap::new();
139-
map.insert("lvl".into(), NBTNode::Short(10));
140-
map.insert("id".into(), NBTNode::Short(32));
141-
map
142-
})
143-
]),
144-
NBT::compound("display", vec![
145-
NBT::list_from_string("Lore", indoc! {r#"
131+
DungeonItem::Pickaxe => if cfg!(feature = "dungeon_breaker") {
132+
ItemStack {
133+
item: 278,
134+
stack_size: 1,
135+
metadata: 0,
136+
tag_compound: Some(NBT::with_nodes(vec![
137+
NBT::list("ench", TAG_COMPOUND_ID, vec![
138+
NBTNode::Compound({
139+
let mut map = HashMap::new();
140+
map.insert("lvl".into(), NBTNode::Short(10));
141+
map.insert("id".into(), NBTNode::Short(32));
142+
map
143+
})
144+
]),
145+
NBT::compound("display", vec![
146+
NBT::list_from_string("Lore", indoc! {r#"
147+
148+
§9§l§kE§r§9§l RARE PICKAXE §kE
149+
"#}),
150+
NBT::string("Name", "§cDungeon Breaker"),
151+
]),
152+
NBT::byte("Unbreakable", 1),
153+
NBT::byte("HideFlags", 127),
154+
])),
155+
}
156+
} else {
157+
ItemStack {
158+
item: 278,
159+
stack_size: 1,
160+
metadata: 0,
161+
tag_compound: Some(NBT::with_nodes(vec![
162+
NBT::list("ench", TAG_COMPOUND_ID, vec![
163+
NBTNode::Compound({
164+
let mut map = HashMap::new();
165+
map.insert("lvl".into(), NBTNode::Short(10));
166+
map.insert("id".into(), NBTNode::Short(32));
167+
map
168+
})
169+
]),
170+
NBT::compound("display", vec![
171+
NBT::list_from_string("Lore", indoc! {r#"
146172
§8Breaking Power 4
147173
148174
§9Efficiency X
@@ -151,12 +177,13 @@ impl Item for DungeonItem {
151177
152178
§9§l§kE§r§9§l RARE PICKAXE §kE
153179
"#}),
154-
NBT::string("Name", "§9Diamond Pickaxe"),
155-
]),
156-
NBT::byte("Unbreakable", 1),
157-
NBT::byte("HideFlags", 127),
158-
])),
159-
},
180+
NBT::string("Name", "§9Diamond Pickaxe"),
181+
]),
182+
NBT::byte("Unbreakable", 1),
183+
NBT::byte("HideFlags", 127),
184+
])),
185+
}
186+
}
160187
}
161188
}
162189

0 commit comments

Comments
 (0)