premier commit

This commit is contained in:
Cortex Builder
2026-06-11 20:14:58 +02:00
commit 16433a893f
33 changed files with 6356 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
# Gradle
.gradle/
.gradle_home/
build/
bin/
out/
._build
# Rust/Cargo
/rust/target/
# OS-specific
.DS_Store
Thumbs.db
._*
+33
View File
@@ -0,0 +1,33 @@
plugins {
java
}
group = "com.bedwars"
version = "1.0.0"
repositories {
mavenCentral()
maven {
name = "papermc"
url = uri("https://repo.papermc.io/repository/maven-public/")
}
}
dependencies {
compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT")
}
tasks.withType<JavaCompile> {
options.encoding = "UTF-8"
options.release.set(21)
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}
tasks.jar {
archiveFileName.set("Bedwars.jar")
}
+2
View File
@@ -0,0 +1,2 @@
[build]
target = "wasm32-wasip2"
+640
View File
@@ -0,0 +1,640 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "anyhow"
version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "atomic-polyfill"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
dependencies = [
"critical-section",
]
[[package]]
name = "bedwars_pumpkin"
version = "1.0.0"
dependencies = [
"pumpkin-plugin-api",
"rand",
"serde",
"serde_yaml",
"tracing",
]
[[package]]
name = "bitflags"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cobs"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
dependencies = [
"thiserror",
]
[[package]]
name = "critical-section"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "embedded-io"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
[[package]]
name = "embedded-io"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "foldhash"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hash32"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
dependencies = [
"byteorder",
]
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
dependencies = [
"foldhash",
]
[[package]]
name = "heapless"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
dependencies = [
"atomic-polyfill",
"hash32",
"rustc_version",
"serde",
"spin",
"stable_deref_trait",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "id-arena"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]]
name = "indexmap"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
"hashbrown",
"serde",
"serde_core",
]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "leb128fmt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
[[package]]
name = "memchr"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "postcard"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
dependencies = [
"cobs",
"embedded-io 0.4.0",
"embedded-io 0.6.1",
"serde",
]
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "pumpkin-plugin-api"
version = "0.1.0"
source = "git+https://github.com/Pumpkin-MC/Pumpkin#30303a8cbd9c3dedac4ff13abd1de64537c6203b"
dependencies = [
"postcard",
"serde_json",
"tracing",
"tracing-serde-structured",
"wit-bindgen",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "ryu"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
"once_cell",
]
[[package]]
name = "tracing-serde-structured"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0832510e9838a4ff7e45e278602ab0533686f9507bc6189e024e488602f29820"
dependencies = [
"hash32",
"heapless",
"serde",
"tracing-core",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasm-encoder"
version = "0.247.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30b6733b8b91d010a6ac5b0fb237dc46a19650bc4c67db66857e2e787d437204"
dependencies = [
"leb128fmt",
"wasmparser",
]
[[package]]
name = "wasm-metadata"
version = "0.247.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "665fe59e56cc9b419ca6fcca56673e3421d1a5011e3b65caf6b726fd9e041d10"
dependencies = [
"anyhow",
"indexmap",
"wasm-encoder",
"wasmparser",
]
[[package]]
name = "wasmparser"
version = "0.247.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e6fb4c2bee46c5ea4d40f8cdb5c131725cd976718ec56f1c8e82fbde5fa2a80"
dependencies = [
"bitflags",
"hashbrown",
"indexmap",
"semver",
]
[[package]]
name = "wit-bindgen"
version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
dependencies = [
"wit-bindgen-rust-macro",
]
[[package]]
name = "wit-bindgen-core"
version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02dee27a2dc20d1008016c742ec9fc6ea498492994ba3750be7454cbc97ff04c"
dependencies = [
"anyhow",
"heck",
"wit-parser",
]
[[package]]
name = "wit-bindgen-rust"
version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5007dae772945b7a5003d69d90a3a4a78929d41f19d004e980c4259a6af4484"
dependencies = [
"anyhow",
"heck",
"indexmap",
"prettyplease",
"syn",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
]
[[package]]
name = "wit-bindgen-rust-macro"
version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af9237d678e3513ad24e96fe98beacdc0db6405284ba2a2400418cf0d42caa89"
dependencies = [
"anyhow",
"prettyplease",
"proc-macro2",
"quote",
"syn",
"wit-bindgen-core",
"wit-bindgen-rust",
]
[[package]]
name = "wit-component"
version = "0.247.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d567162a6b9843080e5e0053f696623ff694bae8ae017c9ec536d1873bbe3d8"
dependencies = [
"anyhow",
"bitflags",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"wasm-encoder",
"wasm-metadata",
"wasmparser",
"wit-parser",
]
[[package]]
name = "wit-parser"
version = "0.247.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ffe4064318cdf3c08cb99343b44c039fcefe61ccdf58aa9975285f13d74d1fc"
dependencies = [
"anyhow",
"hashbrown",
"id-arena",
"indexmap",
"log",
"semver",
"serde",
"serde_derive",
"serde_json",
"unicode-xid",
"wasmparser",
]
[[package]]
name = "zerocopy"
version = "0.8.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "bedwars_pumpkin"
version = "1.0.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
pumpkin-plugin-api = { git = "https://github.com/Pumpkin-MC/Pumpkin", package = "pumpkin-plugin-api" }
serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.9"
tracing = "0.1"
rand = "0.8"
+36
View File
@@ -0,0 +1,36 @@
use std::collections::HashSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BlockPos {
pub x: i32,
pub y: i32,
pub z: i32,
}
pub struct ArenaManager {
pub player_placed_blocks: HashSet<BlockPos>,
}
impl ArenaManager {
pub fn new() -> Self {
Self {
player_placed_blocks: HashSet::new(),
}
}
pub fn track_block(&mut self, pos: BlockPos) {
self.player_placed_blocks.insert(pos);
}
pub fn untrack_block(&mut self, pos: &BlockPos) -> bool {
self.player_placed_blocks.remove(pos)
}
pub fn is_player_placed(&self, pos: &BlockPos) -> bool {
self.player_placed_blocks.contains(pos)
}
pub fn clear(&mut self) {
self.player_placed_blocks.clear();
}
}
+195
View File
@@ -0,0 +1,195 @@
use crate::config::{BedwarsConfig, Loc, TeamConfig, GeneratorConfig};
use crate::team::BedwarsTeam;
use crate::game::GameManager;
pub enum BedwarsCommand {
SetLobby,
SetSpawn { team: BedwarsTeam },
SetBed { team: BedwarsTeam },
SetShop { team: BedwarsTeam },
SetUpgrades { team: BedwarsTeam },
SetGenerator { team: BedwarsTeam },
SetEnderchest { team: BedwarsTeam },
AddGenerator { gen_type: String },
Save,
Start,
Help,
}
impl BedwarsCommand {
pub fn parse(args: &[String]) -> Result<Self, String> {
if args.is_empty() {
return Ok(Self::Help);
}
match args[0].to_lowercase().as_str() {
"setlobby" => Ok(Self::SetLobby),
"setspawn" => {
if args.len() < 2 {
return Err("Usage: /bw setspawn <red|blue|purple|yellow>".to_string());
}
let team = BedwarsTeam::from_str(&args[1]).ok_or("Invalid team name")?;
Ok(Self::SetSpawn { team })
}
"setbed" => {
if args.len() < 2 {
return Err("Usage: /bw setbed <red|blue|purple|yellow>".to_string());
}
let team = BedwarsTeam::from_str(&args[1]).ok_or("Invalid team name")?;
Ok(Self::SetBed { team })
}
"setshop" => {
if args.len() < 2 {
return Err("Usage: /bw setshop <red|blue|purple|yellow>".to_string());
}
let team = BedwarsTeam::from_str(&args[1]).ok_or("Invalid team name")?;
Ok(Self::SetShop { team })
}
"setupgrades" => {
if args.len() < 2 {
return Err("Usage: /bw setupgrades <red|blue|purple|yellow>".to_string());
}
let team = BedwarsTeam::from_str(&args[1]).ok_or("Invalid team name")?;
Ok(Self::SetUpgrades { team })
}
"setgenerator" => {
if args.len() < 2 {
return Err("Usage: /bw setgenerator <red|blue|purple|yellow>".to_string());
}
let team = BedwarsTeam::from_str(&args[1]).ok_or("Invalid team name")?;
Ok(Self::SetGenerator { team })
}
"setenderchest" => {
if args.len() < 2 {
return Err("Usage: /bw setenderchest <red|blue|purple|yellow>".to_string());
}
let team = BedwarsTeam::from_str(&args[1]).ok_or("Invalid team name")?;
Ok(Self::SetEnderchest { team })
}
"addgenerator" => {
if args.len() < 2 {
return Err("Usage: /bw addgenerator <diamond|emerald>".to_string());
}
let gen_type = args[1].to_lowercase();
if gen_type != "diamond" && gen_type != "emerald" {
return Err("Generator type must be 'diamond' or 'emerald'".to_string());
}
Ok(Self::AddGenerator { gen_type })
}
"save" => Ok(Self::Save),
"start" => Ok(Self::Start),
"help" => Ok(Self::Help),
_ => Err("Unknown command. Type /bw help for options.".to_string()),
}
}
pub fn execute(
&self,
config: &mut BedwarsConfig,
game_manager: &mut GameManager,
player_loc: Loc,
target_block_loc: Option<Loc>,
) -> Result<String, String> {
match self {
Self::SetLobby => {
config.locations.lobby = player_loc;
Ok("Lobby location updated in config".to_string())
}
Self::SetSpawn { team } => {
let team_name = team.get_name().to_lowercase();
let team_conf = config.locations.teams.entry(team_name.clone()).or_insert_with(|| TeamConfig {
enabled: true,
spawn: player_loc.clone(),
bed: player_loc.clone(),
generator: player_loc.clone(),
shop: player_loc.clone(),
upgrades: player_loc.clone(),
enderchest: player_loc.clone(),
});
team_conf.spawn = player_loc;
team_conf.enabled = true;
game_manager.enable_team(*team);
Ok(format!("Spawn for team {} set and enabled", team.get_name()))
}
Self::SetBed { team } => {
let block_loc = target_block_loc.ok_or("You must look at a bed block")?;
let team_name = team.get_name().to_lowercase();
if let Some(t_conf) = config.locations.teams.get_mut(&team_name) {
t_conf.bed = block_loc;
Ok(format!("Bed for team {} set", team.get_name()))
} else {
Err("Team not configured yet. Set spawn first.".to_string())
}
}
Self::SetShop { team } => {
let team_name = team.get_name().to_lowercase();
if let Some(t_conf) = config.locations.teams.get_mut(&team_name) {
t_conf.shop = player_loc;
Ok(format!("Shop for team {} set", team.get_name()))
} else {
Err("Team not configured yet. Set spawn first.".to_string())
}
}
Self::SetUpgrades { team } => {
let team_name = team.get_name().to_lowercase();
if let Some(t_conf) = config.locations.teams.get_mut(&team_name) {
t_conf.upgrades = player_loc;
Ok(format!("Upgrades for team {} set", team.get_name()))
} else {
Err("Team not configured yet. Set spawn first.".to_string())
}
}
Self::SetGenerator { team } => {
let team_name = team.get_name().to_lowercase();
if let Some(t_conf) = config.locations.teams.get_mut(&team_name) {
t_conf.generator = player_loc;
Ok(format!("Generator for team {} set", team.get_name()))
} else {
Err("Team not configured yet. Set spawn first.".to_string())
}
}
Self::SetEnderchest { team } => {
let team_name = team.get_name().to_lowercase();
if let Some(t_conf) = config.locations.teams.get_mut(&team_name) {
t_conf.enderchest = player_loc;
Ok(format!("Ender Chest for team {} set", team.get_name()))
} else {
Err("Team not configured yet. Set spawn first.".to_string())
}
}
Self::AddGenerator { gen_type } => {
config.locations.generators.push(GeneratorConfig {
x: player_loc.x,
y: player_loc.y,
z: player_loc.z,
generator_type: gen_type.to_uppercase(),
});
Ok(format!("Added a {} generator at your location", gen_type.to_uppercase()))
}
Self::Save => {
// Return string representation to write to file
Ok("Config ready to save".to_string())
}
Self::Start => {
game_manager.start_game()?;
Ok("Match starting now!".to_string())
}
Self::Help => {
let mut help = String::new();
help.push_str("§d§m================ §b§lBEDWARS SETUP (RUST) §d§m================\n");
help.push_str("§d/bw setlobby §7- Set waiting lobby coordinate\n");
help.push_str("§d/bw setspawn <team> §7- Set team player spawn point\n");
help.push_str("§d/bw setbed <team> §7- Register team bed (look at block)\n");
help.push_str("§d/bw setshop <team> §7- Set shop NPC position\n");
help.push_str("§d/bw setupgrades <team> §7- Set upgrades NPC position\n");
help.push_str("§d/bw setgenerator <team> §7- Set base spawner location\n");
help.push_str("§d/bw setenderchest <team> §7- Set team Ender Chest\n");
help.push_str("§d/bw addgenerator <diamond|emerald> §7- Add generator\n");
help.push_str("§d/bw save §7- Save config file\n");
help.push_str("§d/bw start §7- Force start game immediately\n");
help.push_str("§d§m================================================\n");
Ok(help)
}
}
}
}
+70
View File
@@ -0,0 +1,70 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Loc {
pub x: f64,
pub y: f64,
pub z: f64,
pub yaw: Option<f32>,
pub pitch: Option<f32>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct TeamConfig {
pub enabled: bool,
pub spawn: Loc,
pub bed: Loc,
pub generator: Loc,
pub shop: Loc,
pub upgrades: Loc,
pub enderchest: Loc,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct GeneratorConfig {
pub x: f64,
pub y: f64,
pub z: f64,
#[serde(rename = "type")]
pub generator_type: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct RatesConfig {
pub iron: u32,
pub gold: u32,
pub diamond: u32,
pub emerald: u32,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct LocationsConfig {
pub lobby: Loc,
pub teams: HashMap<String, TeamConfig>,
pub generators: Vec<GeneratorConfig>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct BedwarsConfig {
#[serde(rename = "world-name")]
pub world_name: String,
#[serde(rename = "lobby-server")]
pub lobby_server: String,
#[serde(rename = "min-players")]
pub min_players: u32,
#[serde(rename = "countdown-seconds")]
pub countdown_seconds: u32,
pub rates: RatesConfig,
pub locations: LocationsConfig,
}
impl BedwarsConfig {
pub fn load_from_str(content: &str) -> Result<Self, serde_yaml::Error> {
serde_yaml::from_str(content)
}
pub fn save_to_string(&self) -> Result<String, serde_yaml::Error> {
serde_yaml::to_string(self)
}
}
+155
View File
@@ -0,0 +1,155 @@
use crate::config::Loc;
use crate::team::BedwarsTeam;
use crate::game::GameManager;
use crate::arena::{ArenaManager, BlockPos};
use crate::state::GameState;
pub struct BedwarsEvents;
impl BedwarsEvents {
pub fn handle_block_place(
arena: &mut ArenaManager,
pos: BlockPos,
block_type: &str,
is_spectator: bool,
) -> Result<Option<String>, &'static str> {
if is_spectator {
return Err("Spectators cannot place blocks");
}
if block_type == "minecraft:tnt" {
// Return command to spawn primed TNT and bypass block placement
Ok(Some("spawn_primed_tnt".to_string()))
} else {
arena.track_block(pos);
Ok(None)
}
}
pub fn handle_block_break(
arena: &mut ArenaManager,
game_manager: &mut GameManager,
pos: BlockPos,
block_type: &str,
breaker_uuid: &str,
) -> Result<Option<BedwarsTeam>, &'static str> {
let breaker = game_manager.players.get(breaker_uuid).ok_or("Player not found")?;
if breaker.is_spectator {
return Err("Spectators cannot break blocks");
}
// 1. Bed Break logic
if block_type.ends_with("_bed") {
let breaker_team = breaker.team.ok_or("You are not on a team")?;
// Search config locations for which team's bed is broken
let mut target_team = None;
for team_state in game_manager.teams.values() {
if team_state.enabled && team_state.has_bed {
// In host adapter, we would check block coordinate equality.
// For logic module, we match block team color to block type.
let bed_mat = team_state.team.get_bed_material();
if block_type == format!("minecraft:{}", bed_mat) {
target_team = Some(team_state.team);
break;
}
}
}
if let Some(broken_team) = target_team {
if breaker_team == broken_team {
return Err("You cannot break your own bed!");
}
game_manager.set_bed_broken(broken_team);
if let Some(p) = game_manager.players.get_mut(breaker_uuid) {
p.beds_broken += 1;
}
return Ok(Some(broken_team));
} else {
return Err("This bed block is not registered to a team");
}
}
// 2. Standard Block Break logic
if arena.is_player_placed(&pos) {
arena.untrack_block(&pos);
Ok(None)
} else {
Err("You can only break blocks placed by players!")
}
}
pub fn handle_void_check(
game_manager: &mut GameManager,
uuid: &str,
y_coord: f64,
) -> bool {
if game_manager.state != GameState::Playing {
return false;
}
if let Some(player) = game_manager.players.get(uuid) {
if player.is_spectator {
return false;
}
if y_coord <= -20.0 {
game_manager.handle_death(uuid);
return true;
}
}
false
}
pub fn handle_pickup_resource(
item_type: &str,
is_generator_spawned: bool,
picker_uuid: &str,
game_manager: &GameManager,
teammate_distances: Vec<(String, f64)>, // uuid -> distance squared
) -> Vec<(String, String, u32)> {
// Vec of (teammate_uuid, item_type, amount) to replicate
// Critical dupe check - only replicate resource drops spawned by a generator
if !is_generator_spawned {
return Vec::new();
}
if item_type != "minecraft:iron_ingot" &&
item_type != "minecraft:gold_ingot" &&
item_type != "minecraft:diamond" &&
item_type != "minecraft:emerald" {
return Vec::new();
}
let picker = match game_manager.players.get(picker_uuid) {
Some(p) => p,
None => return Vec::new(),
};
let picker_team = match picker.team {
Some(t) => t,
None => return Vec::new(),
};
let mut replicate_spawns = Vec::new();
for (other_uuid, distance_sq) in teammate_distances {
if other_uuid == picker_uuid {
continue;
}
if let Some(other) = game_manager.players.get(&other_uuid) {
if !other.is_spectator && other.team == Some(picker_team) {
// Check if teammate is within 3.5 blocks (12.25 blocks squared)
if distance_sq <= 12.25 {
replicate_spawns.push((other_uuid, item_type.to_string(), 1));
}
}
}
}
replicate_spawns
}
}
+201
View File
@@ -0,0 +1,201 @@
use std::collections::HashMap;
use crate::state::GameState;
use crate::team::BedwarsTeam;
use crate::generator::Generator;
use crate::arena::ArenaManager;
#[derive(Debug, Clone)]
pub struct PlayerState {
pub uuid: String,
pub name: String,
pub team: Option<BedwarsTeam>,
pub is_spectator: bool,
pub kills: u32,
pub deaths: u32,
pub beds_broken: u32,
pub respawn_timer: Option<u32>, // seconds remaining
}
#[derive(Debug, Clone)]
pub struct TeamState {
pub team: BedwarsTeam,
pub has_bed: bool,
pub enabled: bool,
}
pub struct GameManager {
pub state: GameState,
pub players: HashMap<String, PlayerState>,
pub teams: HashMap<BedwarsTeam, TeamState>,
pub countdown: Option<u32>,
pub generators: Vec<Generator>,
pub arena: ArenaManager,
pub min_players: u32,
}
impl GameManager {
pub fn new(min_players: u32, countdown_seconds: u32) -> Self {
let mut teams = HashMap::new();
for team in &[BedwarsTeam::Red, BedwarsTeam::Blue, BedwarsTeam::Purple, BedwarsTeam::Yellow] {
teams.insert(*team, TeamState {
team: *team,
has_bed: true,
enabled: false,
});
}
Self {
state: GameState::Lobby,
players: HashMap::new(),
teams,
countdown: Some(countdown_seconds),
generators: Vec::new(),
arena: ArenaManager::new(),
min_players,
}
}
pub fn join_player(&mut self, uuid: &str, name: &str) {
let state = PlayerState {
uuid: uuid.to_string(),
name: name.to_string(),
team: None,
is_spectator: false,
kills: 0,
deaths: 0,
beds_broken: 0,
respawn_timer: None,
};
self.players.insert(uuid.to_string(), state);
}
pub fn quit_player(&mut self, uuid: &str) {
self.players.remove(uuid);
if self.state == GameState::Playing {
self.check_winner();
}
}
pub fn enable_team(&mut self, team: BedwarsTeam) {
if let Some(state) = self.teams.get_mut(&team) {
state.enabled = true;
}
}
pub fn start_game(&mut self) -> Result<(), &'static str> {
let enabled_teams: Vec<BedwarsTeam> = self.teams.values()
.filter(|t| t.enabled)
.map(|t| t.team)
.collect();
if enabled_teams.len() < 2 {
return Err("At least two teams must be enabled to start the game");
}
// Assign players to enabled teams evenly
let mut team_idx = 0;
for player in self.players.values_mut() {
if player.is_spectator {
continue;
}
let team = enabled_teams[team_idx];
player.team = Some(team);
team_idx = (team_idx + 1) % enabled_teams.len();
}
self.state = GameState::Playing;
self.countdown = None;
Ok(())
}
pub fn handle_death(&mut self, uuid: &str) {
if let Some(player) = self.players.get_mut(uuid) {
player.deaths += 1;
if let Some(team) = player.team {
let has_bed = self.teams.get(&team).map(|t| t.has_bed).unwrap_or(false);
if has_bed {
player.respawn_timer = Some(5); // 5 seconds respawn queue
} else {
player.is_spectator = true;
player.respawn_timer = None;
}
}
}
self.check_winner();
}
pub fn tick_game(&mut self) -> Vec<(String, String)> {
let mut events = Vec::new();
// Handle Lobby countdown
if self.state == GameState::Lobby {
if let Some(ref mut time) = self.countdown {
if self.players.len() as u32 >= self.min_players {
if *time > 0 {
*time -= 1;
if *time == 0 {
if let Err(e) = self.start_game() {
tracing::error!("Failed to start game: {}", e);
self.countdown = Some(10); // retry in 10s
}
}
}
} else {
// Reset countdown if player count drops below min_players
*time = 30;
}
}
}
// Handle Playing respawn timers
if self.state == GameState::Playing {
for player in self.players.values_mut() {
if let Some(ref mut time) = player.respawn_timer {
if *time > 0 {
*time -= 1;
if *time == 0 {
player.respawn_timer = None;
events.push((player.uuid.clone(), "respawn".to_string()));
}
}
}
}
}
events
}
pub fn set_bed_broken(&mut self, team: BedwarsTeam) {
if let Some(state) = self.teams.get_mut(&team) {
state.has_bed = false;
}
}
pub fn check_winner(&mut self) -> Option<BedwarsTeam> {
if self.state != GameState::Playing {
return None;
}
// Find teams that still have active, non-spectator players
let mut active_teams = HashMap::new();
for player in self.players.values() {
if !player.is_spectator {
if let Some(team) = player.team {
*active_teams.entry(team).or_insert(0) += 1;
}
}
}
if active_teams.len() == 1 {
let winning_team = *active_teams.keys().next().unwrap();
self.state = GameState::Ending;
Some(winning_team)
} else if active_teams.is_empty() {
// Tie or no players left
self.state = GameState::Ending;
None
} else {
None
}
}
}
+79
View File
@@ -0,0 +1,79 @@
use crate::config::Loc;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GeneratorType {
IronGold,
Diamond,
Emerald,
}
pub struct Generator {
pub location: Loc,
pub gen_type: GeneratorType,
pub ticks_until_spawn: i32,
pub max_spawn_ticks: i32,
pub upgrade_level: u32,
}
impl Generator {
pub fn new(location: Loc, gen_type: GeneratorType, max_spawn_ticks: i32) -> Self {
Self {
location,
gen_type,
ticks_until_spawn: max_spawn_ticks,
max_spawn_ticks,
upgrade_level: 1,
}
}
pub fn tick(&mut self, random_roll: f64) -> Option<Vec<&'static str>> {
self.ticks_until_spawn -= 1;
if self.ticks_until_spawn <= 0 {
let mut spawned = Vec::new();
match self.gen_type {
GeneratorType::IronGold => {
spawned.push("minecraft:iron_ingot");
// Gold at 1/4 rate, modified by upgrade
if random_roll < (0.25 * self.upgrade_level as f64) {
spawned.push("minecraft:gold_ingot");
}
self.ticks_until_spawn = std::cmp::max(5, self.max_spawn_ticks - (self.upgrade_level as i32 - 1) * 3);
}
GeneratorType::Diamond => {
spawned.push("minecraft:diamond");
self.ticks_until_spawn = self.max_spawn_ticks;
}
GeneratorType::Emerald => {
spawned.push("minecraft:emerald");
self.ticks_until_spawn = self.max_spawn_ticks;
}
}
Some(spawned)
} else {
None
}
}
}
pub struct GeneratorManager {
pub generators: Vec<Generator>,
}
impl GeneratorManager {
pub fn new() -> Self {
Self {
generators: Vec::new(),
}
}
pub fn tick_all(&mut self) -> Vec<(Loc, Vec<&'static str>)> {
let mut spawns = Vec::new();
for gen in &mut self.generators {
let roll = rand::random::<f64>(); // Bypassed in tests or run directly
if let Some(items) = gen.tick(roll) {
spawns.push((gen.location.clone(), items));
}
}
spawns
}
}
+54
View File
@@ -0,0 +1,54 @@
pub mod config;
pub mod team;
pub mod state;
pub mod generator;
pub mod arena;
pub mod shop;
pub mod game;
pub mod commands;
pub mod events;
use pumpkin_plugin_api::{Context, Plugin, PluginMetadata};
use tracing::info;
pub struct BedwarsPlugin {
pub game_manager: Option<game::GameManager>,
pub config: Option<config::BedwarsConfig>,
}
impl Plugin for BedwarsPlugin {
fn new() -> Self {
Self {
game_manager: None,
config: None,
}
}
fn metadata(&self) -> PluginMetadata {
PluginMetadata {
name: "Bedwars Rust".into(),
version: env!("CARGO_PKG_VERSION").into(),
authors: vec!["Antigravity".into()],
description: "Rewrite of Bedwars plugin in Rust for Pumpkin MC".into(),
dependencies: vec![],
permissions: vec![],
}
}
fn on_load(&mut self, _context: Context) -> pumpkin_plugin_api::Result<()> {
info!("Bedwars Rust Plugin has been loaded successfully!");
// Initialize default GameManager (requires min 2 players, 30 seconds lobby time)
let game_manager = game::GameManager::new(2, 30);
self.game_manager = Some(game_manager);
Ok(())
}
fn on_unload(&mut self, _context: Context) -> pumpkin_plugin_api::Result<()> {
info!("Bedwars Rust Plugin has been unloaded. Goodbye!");
Ok(())
}
}
pumpkin_plugin_api::register_plugin!(BedwarsPlugin);
+118
View File
@@ -0,0 +1,118 @@
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct ShopItem {
pub material: String,
pub cost_material: String,
pub cost_amount: u32,
pub display_name: String,
pub give_amount: u32,
}
impl ShopItem {
pub fn parse(config_str: &str) -> Option<Self> {
// Example format: WOOL:IRON:4:&fWhite Wool:16
let parts: Vec<&str> = config_str.split(':').collect();
if parts.len() < 4 {
return None;
}
let raw_mat = parts[0].to_uppercase();
let material = format!("minecraft:{}", raw_mat.to_lowercase());
let cost_type = parts[1].to_uppercase();
let cost_material = match cost_type.as_str() {
"IRON" => "minecraft:iron_ingot",
"GOLD" => "minecraft:gold_ingot",
"DIAMOND" => "minecraft:diamond",
"EMERALD" => "minecraft:emerald",
_ => "minecraft:iron_ingot",
}.to_string();
let cost_amount = parts[2].parse::<u32>().ok()?;
let display_name = parts[3].to_string();
let give_amount = if parts.len() >= 5 {
parts[4].parse::<u32>().unwrap_or(1)
} else {
1
};
Some(Self {
material,
cost_material,
cost_amount,
display_name,
give_amount,
})
}
}
pub struct PlayerInventory {
pub items: HashMap<String, u32>,
}
impl PlayerInventory {
pub fn new() -> Self {
Self {
items: HashMap::new(),
}
}
pub fn get_item_count(&self, item_id: &str) -> u32 {
*self.items.get(item_id).unwrap_or(&0)
}
pub fn take_items(&mut self, item_id: &str, amount: u32) -> bool {
let current = self.get_item_count(item_id);
if current >= amount {
if current == amount {
self.items.remove(item_id);
} else {
self.items.insert(item_id.to_string(), current - amount);
}
true
} else {
false
}
}
pub fn give_items(&mut self, item_id: &str, amount: u32) {
let current = self.get_item_count(item_id);
self.items.insert(item_id.to_string(), current + amount);
}
}
pub struct ShopManager {
pub categories: HashMap<String, Vec<ShopItem>>,
}
impl ShopManager {
pub fn new() -> Self {
Self {
categories: HashMap::new(),
}
}
pub fn add_item(&mut self, category: &str, item_str: &str) {
if let Some(item) = ShopItem::parse(item_str) {
self.categories.entry(category.to_string()).or_default().push(item);
}
}
pub fn attempt_purchase(
&self,
inventory: &mut PlayerInventory,
category: &str,
item_index: usize,
) -> Result<ShopItem, &'static str> {
let items = self.categories.get(category).ok_or("Category not found")?;
let item = items.get(item_index).ok_or("Item not found")?;
if inventory.take_items(&item.cost_material, item.cost_amount) {
inventory.give_items(&item.material, item.give_amount);
Ok(item.clone())
} else {
Err("Insufficient funds")
}
}
}
+6
View File
@@ -0,0 +1,6 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum GameState {
Lobby,
Playing,
Ending,
}
+70
View File
@@ -0,0 +1,70 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum BedwarsTeam {
Red,
Blue,
Purple,
Yellow,
}
impl BedwarsTeam {
pub fn from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"red" => Some(Self::Red),
"blue" => Some(Self::Blue),
"purple" => Some(Self::Purple),
"yellow" => Some(Self::Yellow),
_ => None,
}
}
pub fn get_name(&self) -> &'static str {
match self {
Self::Red => "Red",
Self::Blue => "Blue",
Self::Purple => "Purple",
Self::Yellow => "Yellow",
}
}
pub fn get_color_code(&self) -> &'static str {
match self {
Self::Red => "§c",
Self::Blue => "§9",
Self::Purple => "§5",
Self::Yellow => "§e",
}
}
pub fn get_prefix(&self) -> &'static str {
match self {
Self::Red => "§c[RED] ",
Self::Blue => "§9[BLUE] ",
Self::Purple => "§5[PURPLE] ",
Self::Yellow => "§e[YELLOW] ",
}
}
pub fn get_wool_material(&self) -> &'static str {
match self {
Self::Red => "red_wool",
Self::Blue => "blue_wool",
Self::Purple => "purple_wool",
Self::Yellow => "yellow_wool",
}
}
pub fn get_bed_material(&self) -> &'static str {
match self {
Self::Red => "red_bed",
Self::Blue => "blue_bed",
Self::Purple => "purple_bed",
Self::Yellow => "yellow_bed",
}
}
pub fn get_colorized_name(&self) -> String {
format!("{}{}", self.get_color_code(), self.get_name())
}
}
+1
View File
@@ -0,0 +1 @@
rootProject.name = "bedwars"
+381
View File
@@ -0,0 +1,381 @@
package com.bedwars;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.WorldCreator;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import java.io.*;
import java.nio.file.Files;
import java.util.HashSet;
import java.util.Set;
public class ArenaManager {
private final BedwarsPlugin plugin;
private final Set<Location> playerPlacedBlocks = new HashSet<>();
private final java.util.Map<Location, org.bukkit.block.data.BlockData> savedBedStates = new java.util.HashMap<>();
public ArenaManager(BedwarsPlugin plugin) {
this.plugin = plugin;
}
private File getTemplateFolder(String worldName) {
if (worldName.equalsIgnoreCase("world")) {
return new File(Bukkit.getWorldContainer(), "bedwars");
} else {
File customTemplate = new File(Bukkit.getWorldContainer(), worldName + "_template");
if (customTemplate.exists()) {
return customTemplate;
}
File bedwarsFolder = new File(Bukkit.getWorldContainer(), "bedwars");
if (bedwarsFolder.exists()) {
return bedwarsFolder;
}
File fallbackTemplate = new File(Bukkit.getWorldContainer(), "bedwars_template");
if (fallbackTemplate.exists()) {
return fallbackTemplate;
}
return new File(Bukkit.getWorldContainer(), "bedwars");
}
}
private File getWorldFolder(String worldName) {
return new File(Bukkit.getWorldContainer(), worldName);
}
/**
* Startup file copying logic (called in plugin onLoad() before Bukkit loads worlds).
* Automatically copies bedwars template directory to active world folder.
*/
public void onLoadCopy() {
// Manually load config.yml to get world-name early on Bukkit onLoad()
File configFile = new File(plugin.getDataFolder(), "config.yml");
String worldName = "world";
if (configFile.exists()) {
org.bukkit.configuration.file.YamlConfiguration config = org.bukkit.configuration.file.YamlConfiguration.loadConfiguration(configFile);
worldName = config.getString("world-name", "world");
} else {
worldName = plugin.getConfig().getString("world-name", "world");
}
File templateFolder = getTemplateFolder(worldName);
File worldFolder = getWorldFolder(worldName);
if (templateFolder.equals(worldFolder)) return;
if (templateFolder.exists()) {
Bukkit.getLogger().info("[Bedwars] onLoad: Overwriting world '" + worldName + "' from template '" + templateFolder.getName() + "'...");
deleteDirectory(worldFolder);
try {
copyDirectory(templateFolder, worldFolder);
Bukkit.getLogger().info("[Bedwars] onLoad: Successfully copied '" + templateFolder.getName() + "' template to '" + worldName + "'!");
} catch (IOException e) {
Bukkit.getLogger().severe("[Bedwars] onLoad: Failed to copy template: " + e.getMessage());
}
} else {
// First time start: if world exists but template doesn't, initialize template!
if (worldFolder.exists()) {
Bukkit.getLogger().info("[Bedwars] onLoad: Template '" + templateFolder.getName() + "' not found. Creating backup of '" + worldName + "' as template '" + templateFolder.getName() + "'...");
try {
copyDirectory(worldFolder, templateFolder);
Bukkit.getLogger().info("[Bedwars] onLoad: Successfully initialized template '" + templateFolder.getName() + "'!");
} catch (IOException e) {
Bukkit.getLogger().severe("[Bedwars] onLoad: Failed to create template: " + e.getMessage());
}
}
}
}
/**
* Saves the active game world back to the bedwars template directory (called on /bw save).
*/
public void setupTemplate() {
String worldName = plugin.getConfig().getString("world-name", "world");
File worldFolder = getWorldFolder(worldName);
File templateFolder = getTemplateFolder(worldName);
plugin.getLogger().info("Saving active world '" + worldName + "' to template '" + templateFolder.getName() + "'...");
// Save the active world first to ensure blocks are saved to disk
World world = Bukkit.getWorld(worldName);
if (world != null) {
world.save();
}
try {
// Overwrite template folder
deleteDirectory(templateFolder);
copyDirectory(worldFolder, templateFolder);
plugin.getLogger().info("Successfully updated '" + templateFolder.getName() + "' template with setup changes!");
} catch (IOException e) {
plugin.getLogger().severe("Failed to save changes to template: " + e.getMessage());
}
}
public void saveBedStates() {
savedBedStates.clear();
String worldName = plugin.getConfig().getString("world-name", "world");
World world = Bukkit.getWorld(worldName);
if (world == null) return;
for (BedwarsTeam team : BedwarsTeam.values()) {
String path = "locations.teams." + team.getName().toLowerCase() + ".bed";
if (plugin.getConfig().contains(path)) {
double x = plugin.getConfig().getDouble(path + ".x");
double y = plugin.getConfig().getDouble(path + ".y");
double z = plugin.getConfig().getDouble(path + ".z");
Location loc = new Location(world, x, y, z);
// Save this block and its surrounding blocks if they are beds
saveBedBlock(loc.getBlock());
saveBedBlock(loc.clone().add(1, 0, 0).getBlock());
saveBedBlock(loc.clone().add(-1, 0, 0).getBlock());
saveBedBlock(loc.clone().add(0, 0, 1).getBlock());
saveBedBlock(loc.clone().add(0, 0, -1).getBlock());
}
}
}
private void saveBedBlock(Block block) {
if (block.getType().name().contains("_BED")) {
savedBedStates.put(block.getLocation(), block.getBlockData().clone());
}
}
public void rollbackBlocks() {
String worldName = plugin.getConfig().getString("world-name", "world");
World world = Bukkit.getWorld(worldName);
if (world == null) {
plugin.getLogger().warning("Could not execute rollbackBlocks: World '" + worldName + "' is not loaded.");
playerPlacedBlocks.clear();
return;
}
// 1. Clear player-placed blocks
plugin.getLogger().info("Clearing " + playerPlacedBlocks.size() + " player-placed blocks...");
for (Location loc : playerPlacedBlocks) {
loc.setWorld(world);
loc.getBlock().setType(org.bukkit.Material.AIR);
}
playerPlacedBlocks.clear();
// 2. Restore bed states
plugin.getLogger().info("Restoring " + savedBedStates.size() + " bed blocks...");
for (java.util.Map.Entry<Location, org.bukkit.block.data.BlockData> entry : savedBedStates.entrySet()) {
Location loc = entry.getKey();
loc.setWorld(world);
Block block = loc.getBlock();
block.setBlockData(entry.getValue());
}
}
/**
* Resets the game world back to the original bedwars template.
*/
public void resetWorld() {
String worldName = plugin.getConfig().getString("world-name", "world");
World world = Bukkit.getWorld(worldName);
plugin.getLogger().info("Resetting world '" + worldName + "' from template '" + getTemplateFolder(worldName).getName() + "'...");
// Always execute in-memory rollback as primary/reliable mechanism!
rollbackBlocks();
// 1. Kick players to lobby proxy server
boolean unloaded = false;
if (world != null) {
for (Player player : world.getPlayers()) {
plugin.getGameManager().sendToLobby(player);
}
// Unload the world (only works for non-default worlds once players leave)
unloaded = Bukkit.unloadWorld(world, false);
}
if (unloaded) {
// 2. Overwrite the world folder from bedwars template
File worldFolder = getWorldFolder(worldName);
File templateFolder = getTemplateFolder(worldName);
if (templateFolder.exists()) {
deleteDirectory(worldFolder);
try {
copyDirectory(templateFolder, worldFolder);
plugin.getLogger().info("Successfully restored world '" + worldName + "' from template '" + templateFolder.getName() + "'!");
} catch (IOException e) {
plugin.getLogger().severe("Failed to copy bedwars template: " + e.getMessage());
}
} else {
plugin.getLogger().warning("Template folder '" + templateFolder.getName() + "' not found! Cannot reset world.");
}
// 3. Reload the world
World w = Bukkit.createWorld(new WorldCreator(worldName));
if (w != null) {
w.setGameRule(org.bukkit.GameRule.ANNOUNCE_ADVANCEMENTS, false);
}
plugin.getLogger().info("World '" + worldName + "' loaded and ready!");
} else {
plugin.getLogger().info("World '" + worldName + "' remained loaded. In-memory programmatic block rollback completed successfully!");
}
// 4. Regenerate waiting area lobby glass cage
createWaitingLobby();
}
public void clearAllMobs() {
String worldName = plugin.getConfig().getString("world-name", "world");
World world = Bukkit.getWorld(worldName);
if (world != null) {
int count = 0;
for (org.bukkit.entity.Entity entity : world.getEntities()) {
if (entity instanceof org.bukkit.entity.Mob || entity instanceof org.bukkit.entity.Ambient) {
if (entity.getType() == org.bukkit.entity.EntityType.VILLAGER) {
continue;
}
entity.remove();
count++;
}
}
plugin.getLogger().info("Purged " + count + " pre-existing mobs from world '" + worldName + "'!");
}
}
private final Set<Location> waitingLobbyBlocks = new HashSet<>();
/**
* Spawns a floating glass cage around the waiting lobby spawnpoint.
*/
public void createWaitingLobby() {
waitingLobbyBlocks.clear();
clearAllMobs();
String worldName = plugin.getConfig().getString("locations.lobby.world", "world");
World world = Bukkit.getWorld(worldName);
if (world == null) return;
double lx = plugin.getConfig().getDouble("locations.lobby.x", 129.5);
double ly = plugin.getConfig().getDouble("locations.lobby.y", 90.0);
double lz = plugin.getConfig().getDouble("locations.lobby.z", 149.5);
Location center = new Location(world, lx, ly, lz);
int cx = center.getBlockX();
int cy = center.getBlockY();
int cz = center.getBlockZ();
plugin.getLogger().info("Generating waiting lobby cage at: " + center);
// Generate 7x7 platform at floor (cy - 1) and ceiling (cy + 3)
for (int x = cx - 3; x <= cx + 3; x++) {
for (int z = cz - 3; z <= cz + 3; z++) {
// Floor
Block floor = world.getBlockAt(x, cy - 1, z);
floor.setType(org.bukkit.Material.GLASS);
waitingLobbyBlocks.add(floor.getLocation());
// Ceiling
Block ceiling = world.getBlockAt(x, cy + 3, z);
ceiling.setType(org.bukkit.Material.GLASS);
waitingLobbyBlocks.add(ceiling.getLocation());
}
}
// Walls
for (int y = cy; y < cy + 3; y++) {
for (int x = cx - 3; x <= cx + 3; x++) {
Block b1 = world.getBlockAt(x, y, cz - 3);
Block b2 = world.getBlockAt(x, y, cz + 3);
b1.setType(org.bukkit.Material.GLASS);
b2.setType(org.bukkit.Material.GLASS);
waitingLobbyBlocks.add(b1.getLocation());
waitingLobbyBlocks.add(b2.getLocation());
}
for (int z = cz - 3; z <= cz + 3; z++) {
Block b1 = world.getBlockAt(cx - 3, y, z);
Block b2 = world.getBlockAt(cx + 3, y, z);
b1.setType(org.bukkit.Material.GLASS);
b2.setType(org.bukkit.Material.GLASS);
waitingLobbyBlocks.add(b1.getLocation());
waitingLobbyBlocks.add(b2.getLocation());
}
}
}
/**
* Wipes the glass waiting lobby platform when the game starts.
*/
public void removeWaitingLobby() {
if (waitingLobbyBlocks.isEmpty()) return;
plugin.getLogger().info("Removing waiting lobby cage block by block...");
for (Location loc : waitingLobbyBlocks) {
loc.getBlock().setType(org.bukkit.Material.AIR);
}
waitingLobbyBlocks.clear();
}
public void trackBlockPlace(Block block) {
playerPlacedBlocks.add(block.getLocation());
}
public boolean isPlayerPlacedBlock(Block block) {
return playerPlacedBlocks.contains(block.getLocation());
}
public void removeTrackedBlock(Block block) {
playerPlacedBlocks.remove(block.getLocation());
}
// Helper method to delete a directory recursively
private void deleteDirectory(File path) {
if (path.exists()) {
File[] files = path.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
deleteDirectory(file);
} else {
// Skip session.lock or uid.dat if JVM locks them
if (file.getName().equalsIgnoreCase("session.lock") || file.getName().equalsIgnoreCase("uid.dat")) {
continue;
}
file.delete();
}
}
}
path.delete();
}
}
// Helper method to copy a directory recursively
private void copyDirectory(File source, File destination) throws IOException {
if (source.isDirectory()) {
if (!destination.exists()) {
destination.mkdirs();
}
String[] files = source.list();
if (files != null) {
for (String file : files) {
if (file.equalsIgnoreCase("session.lock") || file.equalsIgnoreCase("uid.dat")) {
continue; // Skip active lock files
}
File srcFile = new File(source, file);
File destFile = new File(destination, file);
copyDirectory(srcFile, destFile);
}
}
} else {
try (InputStream in = new FileInputStream(source);
OutputStream out = new FileOutputStream(destination)) {
byte[] buffer = new byte[1024];
int length;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
}
}
}
}
@@ -0,0 +1,304 @@
package com.bedwars;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import java.util.*;
public class BedwarsCommand implements CommandExecutor, TabCompleter {
private final BedwarsPlugin plugin;
public BedwarsCommand(BedwarsPlugin plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage("§cOnly players can execute setup commands!");
return true;
}
if (!player.hasPermission("bedwars.admin")) {
player.sendMessage("§cYou do not have permission to configure Bedwars!");
return true;
}
if (args.length == 0 || args[0].equalsIgnoreCase("help")) {
sendHelp(player);
return true;
}
String sub = args[0].toLowerCase();
Location loc = player.getLocation();
switch (sub) {
case "setlobby" -> {
float yaw = loc.getYaw();
float centeredYaw = Math.round(yaw / 90.0f) * 90.0f;
plugin.getConfig().set("locations.lobby.world", loc.getWorld().getName());
plugin.getConfig().set("locations.lobby.x", loc.getBlockX() + 0.5);
plugin.getConfig().set("locations.lobby.y", (double) loc.getBlockY());
plugin.getConfig().set("locations.lobby.z", loc.getBlockZ() + 0.5);
plugin.getConfig().set("locations.lobby.yaw", (double) centeredYaw);
plugin.getConfig().set("locations.lobby.pitch", 0.0);
plugin.saveConfig();
player.sendMessage("§a[Bedwars] Waiting lobby spawn point set to your location and saved!");
}
case "setspawn" -> {
if (args.length < 2) {
player.sendMessage("§cUsage: /bw setspawn <red|blue|purple|yellow>");
return true;
}
String team = args[1].toLowerCase();
if (!isValidTeam(team)) {
player.sendMessage("§cInvalid team! Use red, blue, purple, or yellow.");
return true;
}
float yaw = loc.getYaw();
float centeredYaw = Math.round(yaw / 90.0f) * 90.0f;
plugin.getConfig().set("locations.teams." + team + ".spawn.x", loc.getBlockX() + 0.5);
plugin.getConfig().set("locations.teams." + team + ".spawn.y", (double) loc.getBlockY());
plugin.getConfig().set("locations.teams." + team + ".spawn.z", loc.getBlockZ() + 0.5);
plugin.getConfig().set("locations.teams." + team + ".spawn.yaw", (double) centeredYaw);
plugin.getConfig().set("locations.teams." + team + ".spawn.pitch", 0.0);
plugin.getConfig().set("locations.teams." + team + ".enabled", true);
player.sendMessage("§a[Bedwars] Spawn point for team " + team.toUpperCase() + " set and team ENABLED!");
// Scanning and auto-setting the matching color Bed block within a 10x5x10 boundary box
Location spawnLoc = loc.clone();
org.bukkit.Material bedMat = BedwarsTeam.valueOf(team.toUpperCase()).getBedMaterial();
Location foundBed = null;
searchLoop:
for (int x = -10; x <= 10; x++) {
for (int y = -3; y <= 5; y++) {
for (int z = -10; z <= 10; z++) {
Block b = spawnLoc.clone().add(x, y, z).getBlock();
if (b.getType() == bedMat) {
foundBed = b.getLocation();
break searchLoop;
}
}
}
}
if (foundBed != null) {
plugin.getConfig().set("locations.teams." + team + ".bed.x", foundBed.getX());
plugin.getConfig().set("locations.teams." + team + ".bed.y", foundBed.getY());
plugin.getConfig().set("locations.teams." + team + ".bed.z", foundBed.getZ());
player.sendMessage("§a[Bedwars] Auto-detected matching " + team.toUpperCase() + " bed at " + foundBed.getBlockX() + ", " + foundBed.getBlockY() + ", " + foundBed.getBlockZ() + "!");
} else {
player.sendMessage("§e[Bedwars] Warning: Matching " + team.toUpperCase() + " bed not found nearby spawn. Remember to set it manually if needed!");
}
plugin.saveConfig();
}
case "setbed" -> {
if (args.length < 2) {
player.sendMessage("§cUsage: /bw setbed <red|blue|purple|yellow>");
return true;
}
String team = args[1].toLowerCase();
if (!isValidTeam(team)) {
player.sendMessage("§cInvalid team! Use red, blue, purple, or yellow.");
return true;
}
Block target = player.getTargetBlockExact(5);
if (target == null || !target.getType().name().contains("_BED")) {
player.sendMessage("§cYou must be looking at a Bed block within 5 blocks!");
return true;
}
Location bedLoc = target.getLocation();
plugin.getConfig().set("locations.teams." + team + ".bed.x", bedLoc.getX());
plugin.getConfig().set("locations.teams." + team + ".bed.y", bedLoc.getY());
plugin.getConfig().set("locations.teams." + team + ".bed.z", bedLoc.getZ());
plugin.saveConfig();
player.sendMessage("§a[Bedwars] Bed block for team " + team.toUpperCase() + " registered and saved!");
}
case "setshop" -> {
if (args.length < 2) {
player.sendMessage("§cUsage: /bw setshop <red|blue|purple|yellow>");
return true;
}
String team = args[1].toLowerCase();
if (!isValidTeam(team)) {
player.sendMessage("§cInvalid team! Use red, blue, purple, or yellow.");
return true;
}
float yaw = loc.getYaw();
float centeredYaw = Math.round(yaw / 90.0f) * 90.0f;
plugin.getConfig().set("locations.teams." + team + ".shop.x", loc.getBlockX() + 0.5);
plugin.getConfig().set("locations.teams." + team + ".shop.y", (double) loc.getBlockY());
plugin.getConfig().set("locations.teams." + team + ".shop.z", loc.getBlockZ() + 0.5);
plugin.getConfig().set("locations.teams." + team + ".shop.yaw", (double) centeredYaw);
plugin.getConfig().set("locations.teams." + team + ".shop.pitch", 0.0);
plugin.saveConfig();
player.sendMessage("§a[Bedwars] Shop NPC location for team " + team.toUpperCase() + " set and saved!");
}
case "setupgrades" -> {
if (args.length < 2) {
player.sendMessage("§cUsage: /bw setupgrades <red|blue|purple|yellow>");
return true;
}
String team = args[1].toLowerCase();
if (!isValidTeam(team)) {
player.sendMessage("§cInvalid team! Use red, blue, purple, or yellow.");
return true;
}
float yaw = loc.getYaw();
float centeredYaw = Math.round(yaw / 90.0f) * 90.0f;
plugin.getConfig().set("locations.teams." + team + ".upgrades.x", loc.getBlockX() + 0.5);
plugin.getConfig().set("locations.teams." + team + ".upgrades.y", (double) loc.getBlockY());
plugin.getConfig().set("locations.teams." + team + ".upgrades.z", loc.getBlockZ() + 0.5);
plugin.getConfig().set("locations.teams." + team + ".upgrades.yaw", (double) centeredYaw);
plugin.getConfig().set("locations.teams." + team + ".upgrades.pitch", 0.0);
plugin.saveConfig();
player.sendMessage("§a[Bedwars] Team Upgrades NPC location for team " + team.toUpperCase() + " set and saved!");
}
case "setgenerator" -> {
if (args.length < 2) {
player.sendMessage("§cUsage: /bw setgenerator <red|blue|purple|yellow>");
return true;
}
String team = args[1].toLowerCase();
if (!isValidTeam(team)) {
player.sendMessage("§cInvalid team! Use red, blue, purple, or yellow.");
return true;
}
plugin.getConfig().set("locations.teams." + team + ".generator.x", loc.getBlockX() + 0.5);
plugin.getConfig().set("locations.teams." + team + ".generator.y", (double) loc.getBlockY());
plugin.getConfig().set("locations.teams." + team + ".generator.z", loc.getBlockZ() + 0.5);
plugin.saveConfig();
player.sendMessage("§a[Bedwars] Base spawner (Iron/Gold) location centered and saved for team " + team.toUpperCase() + "!");
}
case "setenderchest" -> {
if (args.length < 2) {
player.sendMessage("§cUsage: /bw setenderchest <red|blue|purple|yellow>");
return true;
}
String team = args[1].toLowerCase();
if (!isValidTeam(team)) {
player.sendMessage("§cInvalid team! Use red, blue, purple, or yellow.");
return true;
}
Block b = loc.getBlock();
b.setType(org.bukkit.Material.ENDER_CHEST);
plugin.getConfig().set("locations.teams." + team + ".enderchest.x", (double) b.getX());
plugin.getConfig().set("locations.teams." + team + ".enderchest.y", (double) b.getY());
plugin.getConfig().set("locations.teams." + team + ".enderchest.z", (double) b.getZ());
plugin.saveConfig();
player.sendMessage("§a[Bedwars] Ender Chest for team " + team.toUpperCase() + " placed and saved!");
}
case "addgenerator" -> {
if (args.length < 2) {
player.sendMessage("§cUsage: /bw addgenerator <diamond|emerald>");
return true;
}
String type = args[1].toLowerCase();
if (!type.equals("diamond") && !type.equals("emerald")) {
player.sendMessage("§cInvalid spawner type! Use diamond or emerald.");
return true;
}
List<Map<String, Object>> genList = (List<Map<String, Object>>) plugin.getConfig().get("locations.generators");
if (genList == null) {
genList = new ArrayList<>();
}
Map<String, Object> newGen = new HashMap<>();
newGen.put("type", type.toUpperCase());
newGen.put("x", loc.getBlockX() + 0.5);
newGen.put("y", (double) loc.getBlockY());
newGen.put("z", loc.getBlockZ() + 0.5);
genList.add(newGen);
plugin.getConfig().set("locations.generators", genList);
plugin.saveConfig();
player.sendMessage("§a[Bedwars] Added new " + type.toUpperCase() + " generator centered to block coordinates and saved!");
}
case "save" -> {
plugin.saveConfig();
player.sendMessage("§a[Bedwars] Configuration saved to config.yml!");
player.sendMessage("§a[Bedwars] Preparing world backup copy as " + loc.getWorld().getName() + "_template...");
plugin.getArenaManager().setupTemplate();
}
case "start" -> {
if (plugin.getGameManager().getState() != GameState.LOBBY) {
player.sendMessage("§cThe game has already started!");
return true;
}
plugin.getGameManager().startGame();
player.sendMessage("§a[Bedwars] Force starting match!");
}
default -> player.sendMessage("§cUnknown subcommand. Use /bw to see setup options.");
}
return true;
}
private boolean isValidTeam(String team) {
return team.equals("red") || team.equals("blue") || team.equals("purple") || team.equals("yellow");
}
private void sendHelp(Player player) {
player.sendMessage("§d§m================ §b§lBEDWARS SETUP §d§m================");
player.sendMessage("§d/bw setlobby §7- Set waiting lobby coordinate");
player.sendMessage("§d/bw setspawn <team> §7- Set team player spawn point");
player.sendMessage("§d/bw setbed <team> §7- Register team bed (look at bed block)");
player.sendMessage("§d/bw setshop <team> §7- Set shop villager position");
player.sendMessage("§d/bw setupgrades <team> §7- Set upgrade villager position");
player.sendMessage("§d/bw setgenerator <team> §7- Set base Iron/Gold spawner");
player.sendMessage("§d/bw setenderchest <team> §7- Place/Set team Ender Chest");
player.sendMessage("§d/bw addgenerator <diamond|emerald> §7- Add Diamond/Emerald generator");
player.sendMessage("§d/bw save §7- Save config & back up current world as template");
player.sendMessage("§d/bw start §7- Force start the game immediately");
player.sendMessage("§d§m================================================");
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
if (args.length == 1) {
List<String> subs = Arrays.asList("setlobby", "setspawn", "setbed", "setshop", "setupgrades", "setgenerator", "setenderchest", "addgenerator", "save", "start", "help");
List<String> list = new ArrayList<>();
for (String s : subs) {
if (s.startsWith(args[0].toLowerCase())) {
list.add(s);
}
}
return list;
}
if (args.length == 2) {
String sub = args[0].toLowerCase();
if (sub.equals("setspawn") || sub.equals("setbed") || sub.equals("setshop") || sub.equals("setupgrades") || sub.equals("setgenerator") || sub.equals("setenderchest")) {
List<String> teams = Arrays.asList("red", "blue", "purple", "yellow");
List<String> list = new ArrayList<>();
for (String t : teams) {
if (t.startsWith(args[1].toLowerCase())) {
list.add(t);
}
}
return list;
}
if (sub.equals("addgenerator")) {
List<String> types = Arrays.asList("diamond", "emerald");
List<String> list = new ArrayList<>();
for (String t : types) {
if (t.startsWith(args[1].toLowerCase())) {
list.add(t);
}
}
return list;
}
}
return Collections.emptyList();
}
}
@@ -0,0 +1,132 @@
package com.bedwars;
import org.bukkit.Bukkit;
import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.java.JavaPlugin;
public class BedwarsPlugin extends JavaPlugin {
private static BedwarsPlugin instance;
// Managers
private GameManager gameManager;
private ArenaManager arenaManager;
private GeneratorManager generatorManager;
private ShopManager shopManager;
private UpgradesManager upgradesManager;
private ScoreboardManager scoreboardManager;
@Override
public void onLoad() {
instance = this;
// Reset active world from pristine template BEFORE Bukkit loads the world
new ArenaManager(this).onLoadCopy();
}
@Override
public void onEnable() {
instance = this;
// Save default config
saveDefaultConfig();
// Disable advancements gamerule for all loaded worlds at startup
for (org.bukkit.World world : Bukkit.getWorlds()) {
world.setGameRule(org.bukkit.GameRule.ANNOUNCE_ADVANCEMENTS, false);
}
// Instantiate Managers
this.arenaManager = new ArenaManager(this);
this.gameManager = new GameManager(this);
this.generatorManager = new GeneratorManager(this);
this.shopManager = new ShopManager(this);
this.upgradesManager = new UpgradesManager(this);
this.scoreboardManager = new ScoreboardManager(this);
// Register outgoing plugin messaging channels for Velocity Proxy redirection compatibility
getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
getServer().getMessenger().registerOutgoingPluginChannel(this, "bungeecord:main");
getServer().getMessenger().registerOutgoingPluginChannel(this, "nexoria:main");
// Load the active game world if it is different from the default world
String worldName = getConfig().getString("world-name", "world");
if (Bukkit.getWorld(worldName) == null) {
getLogger().info("Loading Bedwars game world '" + worldName + "'...");
org.bukkit.World w = Bukkit.createWorld(new org.bukkit.WorldCreator(worldName));
if (w != null) {
w.setGameRule(org.bukkit.GameRule.ANNOUNCE_ADVANCEMENTS, false);
}
} else {
org.bukkit.World w = Bukkit.getWorld(worldName);
if (w != null) {
w.setGameRule(org.bukkit.GameRule.ANNOUNCE_ADVANCEMENTS, false);
}
}
// Initialize game manager values
this.gameManager.init();
// Build waiting lobby glass platform cage
this.arenaManager.createWaitingLobby();
// Register Commands
PluginCommand bwCmd = getCommand("bw");
if (bwCmd != null) {
BedwarsCommand executor = new BedwarsCommand(this);
bwCmd.setExecutor(executor);
bwCmd.setTabCompleter(executor);
}
// Register Listeners
getServer().getPluginManager().registerEvents(new GameListener(this), this);
getServer().getPluginManager().registerEvents(new ShopListener(this), this);
getLogger().info("================================================");
getLogger().info(" Bedwars Plugin has been successfully enabled! ");
getLogger().info(" Compatible with 1.21.4+ and Nexoria Proxy ");
getLogger().info("================================================");
}
@Override
public void onDisable() {
// Cleanup all floating Text Displays (Holograms) to prevent visual dupes
if (generatorManager != null) {
generatorManager.cleanup();
}
// Unregister plugin channels cleanly
getServer().getMessenger().unregisterOutgoingPluginChannel(this, "BungeeCord");
getServer().getMessenger().unregisterOutgoingPluginChannel(this, "bungeecord:main");
getServer().getMessenger().unregisterOutgoingPluginChannel(this, "nexoria:main");
getLogger().info("Bedwars Plugin has been successfully disabled!");
}
public static BedwarsPlugin getInstance() {
return instance;
}
public GameManager getGameManager() {
return gameManager;
}
public ArenaManager getArenaManager() {
return arenaManager;
}
public GeneratorManager getGeneratorManager() {
return generatorManager;
}
public ShopManager getShopManager() {
return shopManager;
}
public UpgradesManager getUpgradesManager() {
return upgradesManager;
}
public ScoreboardManager getScoreboardManager() {
return scoreboardManager;
}
}
@@ -0,0 +1,49 @@
package com.bedwars;
import org.bukkit.ChatColor;
import org.bukkit.Material;
public enum BedwarsTeam {
RED("Red", ChatColor.RED, "§c[RED] ", Material.RED_WOOL, Material.RED_BED),
BLUE("Blue", ChatColor.BLUE, "§9[BLUE] ", Material.BLUE_WOOL, Material.BLUE_BED),
PURPLE("Purple", ChatColor.DARK_PURPLE, "§5[PURPLE] ", Material.PURPLE_WOOL, Material.PURPLE_BED),
YELLOW("Yellow", ChatColor.YELLOW, "§e[YELLOW] ", Material.YELLOW_WOOL, Material.YELLOW_BED);
private final String name;
private final ChatColor color;
private final String prefix;
private final Material woolMaterial;
private final Material bedMaterial;
BedwarsTeam(String name, ChatColor color, String prefix, Material woolMaterial, Material bedMaterial) {
this.name = name;
this.color = color;
this.prefix = prefix;
this.woolMaterial = woolMaterial;
this.bedMaterial = bedMaterial;
}
public String getName() {
return name;
}
public ChatColor getColor() {
return color;
}
public String getPrefix() {
return prefix;
}
public Material getWoolMaterial() {
return woolMaterial;
}
public Material getBedMaterial() {
return bedMaterial;
}
public String getColorizedName() {
return color + name;
}
}
+823
View File
@@ -0,0 +1,823 @@
package com.bedwars;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.block.data.type.Bed;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.FoodLevelChangeEvent;
import org.bukkit.event.entity.EntityPickupItemEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.ItemStack;
public class GameListener implements Listener {
private final BedwarsPlugin plugin;
private final java.util.Map<java.util.UUID, Long> fireballCooldowns = new java.util.HashMap<>();
public GameListener(BedwarsPlugin plugin) {
this.plugin = plugin;
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
int online = Bukkit.getOnlinePlayers().size();
int max = Bukkit.getMaxPlayers();
event.setJoinMessage("§a[+] §7" + player.getName() + " §8[" + online + "/" + max + "]");
// Setup Scoreboard
plugin.getScoreboardManager().setupScoreboard(player);
GameManager gm = plugin.getGameManager();
gm.updateTabName(player);
if (gm.getState() == GameState.LOBBY) {
player.setGameMode(GameMode.SURVIVAL);
player.setHealth(20.0);
player.setFoodLevel(20);
player.getInventory().clear();
player.getEnderChest().clear();
// Clear potion effects
for (org.bukkit.potion.PotionEffect effect : player.getActivePotionEffects()) {
player.removePotionEffect(effect.getType());
}
// Teleport to lobby spawn
teleportToLobby(player);
gm.checkStart();
} else {
// Join as spectator if game is already running
gm.addSpectator(player);
player.sendMessage("§eMatch in progress! You are spectating.");
}
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
int online = Bukkit.getOnlinePlayers().size() - 1;
int max = Bukkit.getMaxPlayers();
event.setQuitMessage("§c[-] §7" + player.getName() + " §8[" + online + "/" + max + "]");
plugin.getScoreboardManager().removeScoreboard(player);
plugin.getShopManager().resetPlayerShopData(player);
GameManager gm = plugin.getGameManager();
if (online == 0 && gm.getState() == GameState.PLAYING) {
gm.resetGameImmediately();
} else if (gm.getState() == GameState.PLAYING) {
gm.handlePlayerQuit(player);
}
}
private void teleportToLobby(Player player) {
if (plugin.getConfig().contains("locations.lobby")) {
String worldName = plugin.getConfig().getString("locations.lobby.world", "world");
World w = Bukkit.getWorld(worldName);
if (w != null) {
double x = plugin.getConfig().getDouble("locations.lobby.x");
double y = plugin.getConfig().getDouble("locations.lobby.y");
double z = plugin.getConfig().getDouble("locations.lobby.z");
float yaw = (float) plugin.getConfig().getDouble("locations.lobby.yaw");
float pitch = (float) plugin.getConfig().getDouble("locations.lobby.pitch");
player.teleport(new Location(w, x, y, z, yaw, pitch));
return;
}
}
// Fallback
player.teleport(player.getWorld().getSpawnLocation());
}
@EventHandler
public void onBlockPlace(BlockPlaceEvent event) {
GameManager gm = plugin.getGameManager();
if (gm.getState() != GameState.PLAYING) {
if (!event.getPlayer().hasPermission("bedwars.admin")) {
event.setCancelled(true);
}
return;
}
Player player = event.getPlayer();
if (gm.isSpectator(player)) {
event.setCancelled(true);
return;
}
Block block = event.getBlockPlaced();
// Custom TNT auto-explode logic
if (block.getType() == Material.TNT) {
event.setCancelled(true);
// Deduct 1 TNT from hand
ItemStack handItem = event.getItemInHand();
if (handItem != null) {
handItem.setAmount(handItem.getAmount() - 1);
}
// Spawn primed TNT
Location spawnLoc = block.getLocation().add(0.5, 0.5, 0.5);
org.bukkit.entity.TNTPrimed tnt = spawnLoc.getWorld().spawn(spawnLoc, org.bukkit.entity.TNTPrimed.class);
tnt.setFuseTicks(40); // 2 seconds fuse
tnt.setSource(player);
// Play ignite sound
player.getWorld().playSound(spawnLoc, Sound.ENTITY_TNT_PRIMED, 1.0f, 1.0f);
return;
}
// Track player placed blocks so they can be broken
plugin.getArenaManager().trackBlockPlace(block);
}
@EventHandler
public void onBlockBreak(BlockBreakEvent event) {
GameManager gm = plugin.getGameManager();
Player player = event.getPlayer();
if (player.getGameMode() == GameMode.CREATIVE && player.hasPermission("bedwars.admin")) {
return; // Admins bypass
}
if (gm.getState() != GameState.PLAYING) {
event.setCancelled(true);
return;
}
if (gm.isSpectator(player)) {
event.setCancelled(true);
return;
}
Block block = event.getBlock();
Material mat = block.getType();
// 1. Check if Bed is broken
if (mat.name().contains("_BED")) {
event.setCancelled(true); // Don't drop item, handle custom deletion
Location breakLoc = block.getLocation();
BedwarsTeam brokenTeam = null;
// Search for matching team bed coordinate
for (BedwarsTeam team : BedwarsTeam.values()) {
if (gm.isTeamEnabled(team)) {
String path = "locations.teams." + team.getName().toLowerCase() + ".bed";
if (plugin.getConfig().contains(path)) {
double x = plugin.getConfig().getDouble(path + ".x");
double y = plugin.getConfig().getDouble(path + ".y");
double z = plugin.getConfig().getDouble(path + ".z");
Location bedLoc = new Location(breakLoc.getWorld(), x, y, z);
// Distance check <= 2.25 blocks squared to cover both halves of the bed
if (breakLoc.distanceSquared(bedLoc) <= 2.5) {
brokenTeam = team;
break;
}
}
}
}
if (brokenTeam != null) {
BedwarsTeam breakerTeam = gm.getPlayerTeam(player);
if (breakerTeam == brokenTeam) {
player.sendMessage("§cYou cannot break your own bed!");
return;
}
// Bed is broken!
gm.setBedBroken(brokenTeam, true);
gm.addBedBroken(player);
// Silently erase both halves of the bed block in world
if (block.getBlockData() instanceof Bed bedData) {
Block otherHalf = block.getRelative(bedData.getFacing());
if (otherHalf.getType().name().contains("_BED")) {
otherHalf.setType(Material.AIR);
}
}
block.setType(Material.AIR);
// Global Announce
Bukkit.broadcastMessage("§c§lBED DESTRUCTION!");
Bukkit.broadcastMessage(" " + brokenTeam.getColorizedName() + " Bed §fwas destroyed by " + breakerTeam.getColor() + player.getName() + "!");
// Title and Sound
for (Player p : Bukkit.getOnlinePlayers()) {
p.playSound(p.getLocation(), Sound.ENTITY_ENDER_DRAGON_GROWL, 0.8f, 1.0f);
BedwarsTeam pTeam = gm.getPlayerTeam(p);
if (pTeam == brokenTeam) {
p.sendTitle("§c§lBED DESTROYED!", "§fYou will no longer respawn!", 10, 60, 10);
}
}
gm.checkWinner();
} else {
player.sendMessage("§cThis is an un-registered bed block!");
block.setType(Material.AIR);
}
return;
}
// 2. Standard block break (Must be player placed block!)
if (plugin.getArenaManager().isPlayerPlacedBlock(block)) {
plugin.getArenaManager().removeTrackedBlock(block);
} else {
event.setCancelled(true);
player.sendMessage("§cYou can only break blocks placed by players!");
}
}
@EventHandler
public void onPlayerMove(PlayerMoveEvent event) {
GameManager gm = plugin.getGameManager();
if (gm.getState() != GameState.PLAYING) return;
Player player = event.getPlayer();
if (gm.isSpectator(player)) return;
// Void Fall Detector
if (player.getLocation().getY() <= -20.0) {
// Trigger instant death sequence
Player killer = gm.getLastAttacker(player);
gm.handlePlayerDeath(player, killer);
}
}
@EventHandler
public void onEntityDamage(EntityDamageEvent event) {
if (!(event.getEntity() instanceof Player player)) return;
GameManager gm = plugin.getGameManager();
// 1. Lobby and ending protection
if (gm.getState() != GameState.PLAYING) {
event.setCancelled(true);
return;
}
// 2. Spectator protection
if (gm.isSpectator(player)) {
event.setCancelled(true);
return;
}
// Intercept death and trigger custom Respawn sequences instead of Vanilla screen
if (player.getHealth() - event.getFinalDamage() <= 0) {
event.setCancelled(true);
Player killer = null;
if (event instanceof EntityDamageByEntityEvent entityEvent) {
if (entityEvent.getDamager() instanceof Player p) {
killer = p;
} else if (entityEvent.getDamager() instanceof Projectile proj) {
if (proj.getShooter() instanceof Player p) {
killer = p;
}
}
}
if (killer == null) {
killer = gm.getLastAttacker(player);
}
gm.handlePlayerDeath(player, killer);
}
}
@EventHandler
public void onEntityDamageByEntity(EntityDamageByEntityEvent event) {
if (!(event.getEntity() instanceof Player victim)) return;
GameManager gm = plugin.getGameManager();
if (gm.getState() != GameState.PLAYING) {
event.setCancelled(true);
return;
}
// Prevent spectator damage
if (gm.isSpectator(victim)) {
event.setCancelled(true);
return;
}
org.bukkit.entity.Entity damager = event.getDamager();
Player attacker = null;
LastDamageType type = LastDamageType.OTHER;
// 1. Handle Fireball Explosion / Direct Damage
if (damager instanceof org.bukkit.entity.Fireball fireball) {
Player shooter = fireball.getShooter() instanceof Player ? (Player) fireball.getShooter() : null;
if (shooter != null) {
attacker = shooter;
type = LastDamageType.FIREBALL;
// Teammate and self-damage handling
if (shooter.equals(victim)) {
// Self fireball damage is scaled down to 1.0 (0.5 heart)
event.setDamage(1.0);
} else {
BedwarsTeam t1 = gm.getPlayerTeam(shooter);
BedwarsTeam t2 = gm.getPlayerTeam(victim);
if (t1 != null && t1 == t2) {
event.setCancelled(true);
return;
}
// Enemy fireball damage scaled down to max 3.0 (1.5 hearts)
event.setDamage(Math.min(event.getDamage(), 3.0));
}
}
}
// 2. Handle TNT Explosion Damage
else if (damager instanceof org.bukkit.entity.TNTPrimed tnt) {
Player source = tnt.getSource() instanceof Player ? (Player) tnt.getSource() : null;
if (source != null) {
attacker = source;
type = LastDamageType.TNT;
// Teammate and self-damage handling
if (source.equals(victim)) {
// Self TNT damage is scaled down to 2.0 (1 heart)
event.setDamage(2.0);
} else {
BedwarsTeam t1 = gm.getPlayerTeam(source);
BedwarsTeam t2 = gm.getPlayerTeam(victim);
if (t1 != null && t1 == t2) {
event.setCancelled(true);
return;
}
// Enemy TNT damage scaled down to max 5.0 (2.5 hearts)
event.setDamage(Math.min(event.getDamage(), 5.0));
}
}
}
// 3. Handle physical hits (Melee) or Bow Projectiles
else {
if (damager instanceof Player p) {
attacker = p;
type = LastDamageType.MELEE;
} else if (damager instanceof Projectile proj) {
if (proj.getShooter() instanceof Player p) {
attacker = p;
type = LastDamageType.PROJECTILE;
}
}
if (attacker != null) {
// Self damage from standard physical hits is impossible but check anyway
if (attacker.equals(victim)) {
return;
}
// Friendly Fire block
BedwarsTeam t1 = gm.getPlayerTeam(attacker);
BedwarsTeam t2 = gm.getPlayerTeam(victim);
if (t1 != null && t1 == t2) {
event.setCancelled(true);
attacker.sendMessage("§cYou cannot attack your teammate!");
return;
}
}
}
// Apply general checks and tracking if there's a valid attacker
if (attacker != null) {
if (gm.isSpectator(attacker)) {
event.setCancelled(true);
return;
}
// Track last attacker for kill attribution
gm.setLastAttacker(victim, attacker, type);
}
}
@EventHandler
public void onFoodLevelChange(FoodLevelChangeEvent event) {
// Disable hunger during lobby or match
event.setCancelled(true);
if (event.getEntity() instanceof Player player) {
player.setFoodLevel(20);
}
}
@EventHandler
public void onPickupItem(EntityPickupItemEvent event) {
if (event.getEntity() instanceof Player player) {
GameManager gm = plugin.getGameManager();
if (gm.isSpectator(player)) {
event.setCancelled(true);
return;
}
// Apply sharpness automatically when sword is picked up
plugin.getUpgradesManager().applyTeamUpgrades(player);
// Generator resource sharing (split) logic
Material type = event.getItem().getItemStack().getType();
if (type == Material.IRON_INGOT || type == Material.GOLD_INGOT ||
type == Material.DIAMOND || type == Material.EMERALD) {
// Only split resources that were spawned by a generator, preventing duplication on player drops
org.bukkit.NamespacedKey key = new org.bukkit.NamespacedKey(plugin, "generator_spawned");
if (!event.getItem().getPersistentDataContainer().has(key, org.bukkit.persistence.PersistentDataType.BYTE)) {
return;
}
Location pickupLoc = event.getItem().getLocation();
Generator nearbyGen = null;
for (Generator gen : plugin.getGeneratorManager().getActiveGenerators()) {
if (gen.getLocation().getWorld().equals(pickupLoc.getWorld()) &&
gen.getLocation().distanceSquared(pickupLoc) <= 25.0) { // 5 blocks radius
nearbyGen = gen;
break;
}
}
if (nearbyGen != null) {
int amount = event.getItem().getItemStack().getAmount();
BedwarsTeam team = plugin.getGameManager().getPlayerTeam(player);
if (team != null) {
for (Player other : player.getWorld().getPlayers()) {
if (other.equals(player)) continue;
if (plugin.getGameManager().isSpectator(other)) continue;
// Check if other player is teammate
if (plugin.getGameManager().getPlayerTeam(other) == team) {
// Check if other player is near the picking player (within 3.5 blocks)
if (other.getLocation().distanceSquared(player.getLocation()) <= 12.25) {
// Give them the same item!
ItemStack copy = new ItemStack(type, amount);
other.getInventory().addItem(copy);
other.playSound(other.getLocation(), Sound.ENTITY_ITEM_PICKUP, 0.5f, 1.5f);
}
}
}
}
}
}
}
}
private String formatMaterialName(Material material) {
switch (material) {
case IRON_INGOT:
return "§fIron";
case GOLD_INGOT:
return "§eGold";
case DIAMOND:
return "§bDiamond";
case EMERALD:
return "§aEmerald";
default:
return material.name();
}
}
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
if (event.getWhoClicked() instanceof Player player) {
GameManager gm = plugin.getGameManager();
// Handle Teleporter inventory click
if (event.getView().getTitle().equals("§8Teleporter")) {
event.setCancelled(true);
ItemStack clicked = event.getCurrentItem();
if (clicked != null && clicked.getType() == Material.PLAYER_HEAD) {
org.bukkit.inventory.meta.SkullMeta meta = (org.bukkit.inventory.meta.SkullMeta) clicked.getItemMeta();
if (meta != null && meta.getOwningPlayer() != null) {
Player target = Bukkit.getPlayer(meta.getOwningPlayer().getUniqueId());
if (target != null && target.isOnline()) {
player.teleport(target.getLocation());
player.sendMessage("§aTeleported to " + clicked.getItemMeta().getDisplayName() + "§a!");
player.closeInventory();
} else {
player.sendMessage("§cThat player is no longer online!");
}
}
}
return;
}
// Block spectators from doing anything in inventories
if (gm.isSpectator(player)) {
event.setCancelled(true);
return;
}
// Block taking off armor
if (event.getSlotType() == org.bukkit.event.inventory.InventoryType.SlotType.ARMOR) {
event.setCancelled(true);
player.sendMessage("§cYou cannot take off your team armor!");
return;
}
// Apply team upgrades when inventory changes or items are moved
Bukkit.getScheduler().runTaskLater(plugin, () -> {
plugin.getUpgradesManager().applyTeamUpgrades(player);
}, 1L);
}
}
@EventHandler
public void onCreatureSpawn(org.bukkit.event.entity.CreatureSpawnEvent event) {
String worldName = plugin.getConfig().getString("world-name", "world");
if (event.getEntity().getWorld().getName().equalsIgnoreCase(worldName)) {
// Cancel all spawns except command or custom (e.g. Villagers, utility mobs)
org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason = event.getSpawnReason();
if (reason != org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CUSTOM &&
reason != org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.COMMAND) {
event.setCancelled(true);
}
}
}
@EventHandler
public void onBlockPhysics(org.bukkit.event.block.BlockPhysicsEvent event) {
Material mat = event.getChangedType();
String name = mat.name();
if (name.contains("CONCRETE_POWDER") || name.contains("SAND") || name.equals("GRAVEL")) {
event.setCancelled(true);
}
}
@EventHandler
public void onFallingBlockSpawn(org.bukkit.event.entity.EntitySpawnEvent event) {
if (event.getEntity() instanceof org.bukkit.entity.FallingBlock fallingBlock) {
Material mat = fallingBlock.getBlockData().getMaterial();
String name = mat.name();
if (name.contains("CONCRETE_POWDER") || name.contains("SAND") || name.equals("GRAVEL")) {
event.setCancelled(true);
}
}
}
@EventHandler
public void onChunkLoad(org.bukkit.event.world.ChunkLoadEvent event) {
String worldName = plugin.getConfig().getString("world-name", "world");
if (event.getChunk().getWorld().getName().equalsIgnoreCase(worldName)) {
for (org.bukkit.entity.Entity entity : event.getChunk().getEntities()) {
if (entity instanceof org.bukkit.entity.Mob || entity instanceof org.bukkit.entity.Ambient) {
if (entity.getType() == org.bukkit.entity.EntityType.VILLAGER) {
continue;
}
entity.remove();
}
}
}
}
@EventHandler
public void onPlayerAdvancementDone(org.bukkit.event.player.PlayerAdvancementDoneEvent event) {
Player player = event.getPlayer();
org.bukkit.advancement.Advancement advancement = event.getAdvancement();
org.bukkit.advancement.AdvancementProgress progress = player.getAdvancementProgress(advancement);
for (String criteria : progress.getAwardedCriteria()) {
progress.revokeCriteria(criteria);
}
}
@EventHandler
public void onWorldLoad(org.bukkit.event.world.WorldLoadEvent event) {
event.getWorld().setGameRule(org.bukkit.GameRule.ANNOUNCE_ADVANCEMENTS, false);
}
@EventHandler
public void onEntityExplode(org.bukkit.event.entity.EntityExplodeEvent event) {
GameManager gm = plugin.getGameManager();
if (gm.getState() != GameState.PLAYING) {
event.setCancelled(true);
return;
}
org.bukkit.entity.Entity exploder = event.getEntity();
boolean isFireball = exploder instanceof org.bukkit.entity.Fireball;
boolean isTNT = exploder instanceof org.bukkit.entity.TNTPrimed;
// Apply custom explosion propulsion to nearby players
if (isFireball || isTNT) {
Location explosionLoc = event.getLocation();
double radius = isFireball ? 5.5 : 6.5;
double maxPower = isFireball ? 1.65 : 2.1;
double verticalBoost = isFireball ? 0.75 : 0.95;
for (Player player : explosionLoc.getWorld().getPlayers()) {
if (gm.isSpectator(player)) continue;
double distance = player.getLocation().distance(explosionLoc);
if (distance <= radius) {
// Calculate custom knockback vector
org.bukkit.util.Vector direction = player.getLocation().toVector().subtract(explosionLoc.toVector());
if (distance < 0.1) {
direction = new org.bukkit.util.Vector(0, 1, 0);
distance = 0.1;
} else {
direction.normalize();
}
// Power calculation with distance falloff but strong baseline (Hypixel style)
double ratio = 1.0 - (distance / radius);
double power = maxPower * (0.45 + 0.55 * ratio);
org.bukkit.util.Vector velocity = direction.multiply(power);
// Upward launch enhancement
double yVal = direction.getY() * verticalBoost;
if (yVal < 0) yVal = 0;
double finalY = Math.max(0.42, yVal + 0.38 * ratio);
velocity.setY(finalY);
// Smooth blending with player's current horizontal momentum
org.bukkit.util.Vector currentVel = player.getVelocity();
double blendedX = currentVel.getX() * 0.45 + velocity.getX();
double blendedZ = currentVel.getZ() * 0.45 + velocity.getZ();
org.bukkit.util.Vector finalVelocity = new org.bukkit.util.Vector(blendedX, finalY, blendedZ);
// Clamp to prevent extreme/glitchy speeds
double maxSpeed = isFireball ? 3.0 : 3.8;
if (finalVelocity.length() > maxSpeed) {
finalVelocity.normalize().multiply(maxSpeed);
}
// Schedule velocity change on next tick to cleanly overwrite vanilla explosion knockback
Bukkit.getScheduler().runTaskLater(plugin, () -> {
player.setVelocity(finalVelocity);
}, 1L);
}
}
}
// Only allow breaking player placed blocks, and exclude blast-proof blocks (Glass and Obsidian)
java.util.Iterator<Block> iterator = event.blockList().iterator();
while (iterator.hasNext()) {
Block block = iterator.next();
Material type = block.getType();
if (type == Material.GLASS || type == Material.OBSIDIAN) {
iterator.remove();
continue;
}
if (!plugin.getArenaManager().isPlayerPlacedBlock(block)) {
iterator.remove();
} else {
// Remove from tracking since it will be destroyed
plugin.getArenaManager().removeTrackedBlock(block);
}
}
}
@EventHandler
public void onPlayerInteract(org.bukkit.event.player.PlayerInteractEvent event) {
Player player = event.getPlayer();
GameManager gm = plugin.getGameManager();
if (gm.isSpectator(player)) {
event.setCancelled(true);
ItemStack item = event.getItem();
if (item != null) {
if (item.getType() == Material.COMPASS) {
openTeleporterGui(player);
} else if (item.getType() == Material.RED_BED) {
gm.sendToLobby(player);
}
}
return;
}
if (event.getAction() == org.bukkit.event.block.Action.RIGHT_CLICK_AIR ||
event.getAction() == org.bukkit.event.block.Action.RIGHT_CLICK_BLOCK) {
ItemStack item = event.getItem();
if (item != null && item.getType() == Material.FIRE_CHARGE) {
// Cancel setting fire to blocks
event.setCancelled(true);
if (gm.getState() != GameState.PLAYING || gm.isSpectator(player)) {
return;
}
// Cooldown check (500ms)
long now = System.currentTimeMillis();
if (fireballCooldowns.containsKey(player.getUniqueId())) {
long lastShoot = fireballCooldowns.get(player.getUniqueId());
if (now - lastShoot < 500) {
player.playSound(player.getLocation(), Sound.ENTITY_ENDER_DRAGON_FLAP, 0.5f, 1.5f);
return; // Silent cancel to prevent spam
}
}
fireballCooldowns.put(player.getUniqueId(), now);
// Shoot fireball
org.bukkit.entity.Fireball fireball = player.launchProjectile(org.bukkit.entity.Fireball.class);
fireball.setYield(2.5f); // Safe explosion size
fireball.setIsIncendiary(false); // Don't set fires
fireball.setShooter(player);
// Apply straight and fast velocity like Hypixel
org.bukkit.util.Vector direction = player.getLocation().getDirection();
fireball.setDirection(direction);
fireball.setVelocity(direction.multiply(1.6));
// Play shoot sound
player.getWorld().playSound(player.getLocation(), Sound.ENTITY_GHAST_SHOOT, 1.0f, 1.0f);
// Deduct 1 fireball
int amount = item.getAmount();
if (amount > 1) {
item.setAmount(amount - 1);
} else {
player.getInventory().setItemInMainHand(null);
}
}
}
}
@EventHandler
public void onCraftItem(org.bukkit.event.inventory.CraftItemEvent event) {
// Disable all crafting
event.setCancelled(true);
}
@EventHandler
public void onPrepareCraft(org.bukkit.event.inventory.PrepareItemCraftEvent event) {
// Clear crafting result to disable personal 2x2 crafting in player inventory
event.getInventory().setResult(new org.bukkit.inventory.ItemStack(org.bukkit.Material.AIR));
}
@EventHandler
public void onAdvancementDone(org.bukkit.event.player.PlayerAdvancementDoneEvent event) {
// Silently revoke all advancements immediately so players never earn them or get spammed
final Player player = event.getPlayer();
final org.bukkit.advancement.Advancement advancement = event.getAdvancement();
final org.bukkit.advancement.AdvancementProgress progress = player.getAdvancementProgress(advancement);
for (String criteria : progress.getAwardedCriteria()) {
Bukkit.getScheduler().runTask(plugin, () -> progress.revokeCriteria(criteria));
}
}
@EventHandler
public void onPlayerDropItem(org.bukkit.event.player.PlayerDropItemEvent event) {
Player player = event.getPlayer();
GameManager gm = plugin.getGameManager();
if (gm.isSpectator(player)) {
event.setCancelled(true);
}
}
private void openTeleporterGui(Player player) {
java.util.List<Player> alivePlayers = new java.util.ArrayList<>();
GameManager gm = plugin.getGameManager();
for (Player p : Bukkit.getOnlinePlayers()) {
if (!gm.isSpectator(p)) {
alivePlayers.add(p);
}
}
if (alivePlayers.isEmpty()) {
player.sendMessage("§cNo active players to spectate!");
return;
}
int size = ((alivePlayers.size() / 9) + 1) * 9;
if (size > 54) size = 54;
org.bukkit.inventory.Inventory inv = Bukkit.createInventory(null, size, "§8Teleporter");
for (int i = 0; i < Math.min(alivePlayers.size(), size); i++) {
Player target = alivePlayers.get(i);
ItemStack head = new ItemStack(Material.PLAYER_HEAD);
org.bukkit.inventory.meta.SkullMeta meta = (org.bukkit.inventory.meta.SkullMeta) head.getItemMeta();
if (meta != null) {
meta.setOwningPlayer(target);
meta.setDisplayName(gm.getFormattedName(target));
java.util.List<String> lore = new java.util.ArrayList<>();
lore.add("§7Click to teleport to player");
meta.setLore(lore);
head.setItemMeta(meta);
}
inv.setItem(i, head);
}
player.openInventory(inv);
}
@EventHandler
public void onEntityRegainHealth(org.bukkit.event.entity.EntityRegainHealthEvent event) {
if (event.getEntity() instanceof Player player) {
GameManager gm = plugin.getGameManager();
if (gm.getState() == GameState.PLAYING && !gm.isSpectator(player)) {
if (event.getRegainReason() == org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.SATIATED) {
event.setCancelled(true);
}
}
}
}
}
File diff suppressed because it is too large Load Diff
+7
View File
@@ -0,0 +1,7 @@
package com.bedwars;
public enum GameState {
LOBBY,
PLAYING,
ENDING
}
+191
View File
@@ -0,0 +1,191 @@
package com.bedwars;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.entity.Item;
import org.bukkit.entity.TextDisplay;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
public class Generator {
private final Location location;
private final GeneratorType type;
private int ticksUntilSpawn;
private final int maxSpawnTicks;
private int upgradeLevel = 1; // Used for base generator upgrades
private TextDisplay textDisplay;
public Generator(Location location, GeneratorType type, int maxSpawnTicks) {
this.location = location;
this.type = type;
this.maxSpawnTicks = maxSpawnTicks;
this.ticksUntilSpawn = maxSpawnTicks;
spawnHologram();
}
private void spawnHologram() {
if (type == GeneratorType.IRON_GOLD) {
return; // No hologram needed for base spawners
}
// Spawn a TextDisplay entity 2 blocks above the generator, perfectly centered
double cx = Math.floor(location.getX()) + 0.5;
double cy = location.getY() + 2.0;
double cz = Math.floor(location.getZ()) + 0.5;
Location displayLoc = new Location(location.getWorld(), cx, cy, cz);
if (displayLoc.getWorld() != null) {
// Delete any existing text display entities nearby first to avoid duplicates
displayLoc.getWorld().getNearbyEntities(displayLoc, 1, 2, 1).forEach(entity -> {
if (entity instanceof TextDisplay) {
entity.remove();
}
});
textDisplay = displayLoc.getWorld().spawn(displayLoc, TextDisplay.class);
textDisplay.setBillboard(TextDisplay.Billboard.CENTER);
textDisplay.setGravity(false);
textDisplay.setInvulnerable(true);
textDisplay.setPersistent(false);
// Set transparent background
textDisplay.setBackgroundColor(org.bukkit.Color.fromARGB(0, 0, 0, 0));
updateHologramText();
}
}
public void updateHologramText() {
if (textDisplay == null || !textDisplay.isValid()) return;
double secondsLeft = ticksUntilSpawn / 20.0;
String color = type == GeneratorType.DIAMOND ? "§b§l" : "§a§l";
String name = type.getDisplayName().toUpperCase();
String text = color + name + " GENERATOR\n" +
"§eSpawning in §c" + String.format("%.1f", secondsLeft) + "s\n" +
"§fLevel " + upgradeLevel;
textDisplay.setText(text);
}
public void tick() {
// Handle Base Spawner (Iron and Gold)
if (type == GeneratorType.IRON_GOLD) {
ticksUntilSpawn--;
if (ticksUntilSpawn <= 0) {
spawnItem(Material.IRON_INGOT);
// Spawn Gold at 1/4 the rate (upgrade increases rate)
if (Math.random() < (0.25 * upgradeLevel)) {
spawnItem(Material.GOLD_INGOT);
}
// Reset with upgrade level modifier (faster spawns)
ticksUntilSpawn = Math.max(5, maxSpawnTicks - (upgradeLevel - 1) * 3);
}
return;
}
// Handle Diamond / Emerald Spawners
ticksUntilSpawn--;
if (ticksUntilSpawn % 5 == 0) {
updateHologramText();
}
if (ticksUntilSpawn <= 0) {
// Spawn resource item
spawnItem(type.getMaterial());
// Sparkle effect
if (location.getWorld() != null) {
location.getWorld().spawnParticle(
type == GeneratorType.DIAMOND ? Particle.GLOW : Particle.HAPPY_VILLAGER,
location.clone().add(0.5, 0.5, 0.5), 15, 0.2, 0.2, 0.2, 0.05
);
// Premium chime sound
location.getWorld().playSound(
location, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 0.5f, 1.2f
);
}
ticksUntilSpawn = maxSpawnTicks;
updateHologramText();
}
}
private int getExistingItemCount(Material material) {
if (location.getWorld() == null) return 0;
int count = 0;
double radius = 1.5;
for (org.bukkit.entity.Entity entity : location.getWorld().getNearbyEntities(location.clone().add(0.5, 0.5, 0.5), radius, radius, radius)) {
if (entity instanceof Item itemEntity) {
ItemStack stack = itemEntity.getItemStack();
if (stack.getType() == material) {
count += stack.getAmount();
}
}
}
return count;
}
private int getMaxLimit(Material material) {
switch (material) {
case EMERALD: return 4;
case DIAMOND: return 8;
case GOLD_INGOT: return 12;
case IRON_INGOT: return 48;
default: return Integer.MAX_VALUE;
}
}
private void spawnItem(Material material) {
if (location.getWorld() == null) return;
int limit = getMaxLimit(material);
int currentCount = getExistingItemCount(material);
if (currentCount >= limit) {
return; // Cap reached, do not drop item
}
double cx = Math.floor(location.getX()) + 0.5;
double cy = location.getY() + 0.5;
double cz = Math.floor(location.getZ()) + 0.5;
Location spawnLoc = new Location(location.getWorld(), cx, cy, cz);
Item item = location.getWorld().dropItem(spawnLoc, new ItemStack(material, 1));
// Zero out initial velocity so items drop cleanly on the spawner
item.setVelocity(new Vector(0, 0, 0));
// Mark this item as spawned by a generator to prevent duplication when dropped by a player
BedwarsPlugin plugin = BedwarsPlugin.getInstance();
if (plugin != null) {
org.bukkit.NamespacedKey key = new org.bukkit.NamespacedKey(plugin, "generator_spawned");
item.getPersistentDataContainer().set(key, org.bukkit.persistence.PersistentDataType.BYTE, (byte) 1);
}
}
public void remove() {
if (textDisplay != null && textDisplay.isValid()) {
textDisplay.remove();
}
}
public Location getLocation() {
return location;
}
public GeneratorType getType() {
return type;
}
public int getUpgradeLevel() {
return upgradeLevel;
}
public void setUpgradeLevel(int upgradeLevel) {
this.upgradeLevel = upgradeLevel;
}
}
@@ -0,0 +1,131 @@
package com.bedwars;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class GeneratorManager {
private final BedwarsPlugin plugin;
private final List<Generator> activeGenerators = new ArrayList<>();
public GeneratorManager(BedwarsPlugin plugin) {
this.plugin = plugin;
}
/**
* Loads generators from the plugin config and activates them in the world.
*/
public void loadGenerators() {
cleanup(); // Clean up existing first
String worldName = plugin.getConfig().getString("world-name", "bedwars");
World world = Bukkit.getWorld(worldName);
if (world == null) {
plugin.getLogger().warning("Cannot load generators: World '" + worldName + "' is not loaded!");
return;
}
int ironRate = plugin.getConfig().getInt("rates.iron", 20) * 2;
int goldRate = plugin.getConfig().getInt("rates.gold", 80) * 2;
int diamondRate = plugin.getConfig().getInt("rates.diamond", 600) * 2;
int emeraldRate = plugin.getConfig().getInt("rates.emerald", 1200) * 2;
// Load map-wide generators (Diamond & Emerald) from the list in config
List<Map<?, ?>> generatorList = plugin.getConfig().getMapList("locations.generators");
for (Map<?, ?> genMap : generatorList) {
try {
String typeStr = (String) genMap.get("type");
double x = ((Number) genMap.get("x")).doubleValue();
double y = ((Number) genMap.get("y")).doubleValue();
double z = ((Number) genMap.get("z")).doubleValue();
GeneratorType type = GeneratorType.valueOf(typeStr.toUpperCase());
Location loc = new Location(world, x, y, z);
int ticks = type == GeneratorType.DIAMOND ? diamondRate : emeraldRate;
activeGenerators.add(new Generator(loc, type, ticks));
} catch (Exception e) {
plugin.getLogger().severe("Failed to parse generator: " + e.getMessage());
}
}
// Load base generators for active teams
ConfigurationSection teamsSec = plugin.getConfig().getConfigurationSection("locations.teams");
if (teamsSec != null) {
for (String key : teamsSec.getKeys(false)) {
ConfigurationSection teamSec = teamsSec.getConfigurationSection(key);
if (teamSec != null && teamSec.getBoolean("enabled", false)) {
if (teamSec.contains("generator")) {
try {
double x = teamSec.getDouble("generator.x");
double y = teamSec.getDouble("generator.y");
double z = teamSec.getDouble("generator.z");
Location loc = new Location(world, x, y, z);
// Base generator produces Iron and Gold
activeGenerators.add(new Generator(loc, GeneratorType.IRON_GOLD, ironRate));
} catch (Exception e) {
plugin.getLogger().severe("Failed to load base generator for team " + key + ": " + e.getMessage());
}
}
}
}
}
plugin.getLogger().info("Loaded " + activeGenerators.size() + " Bedwars resource generators!");
}
/**
* Ticks all active generators. Should be run every tick.
*/
public void tick() {
for (Generator gen : activeGenerators) {
gen.tick();
}
}
/**
* Upgrades base generator rates for a specific team.
*/
public void upgradeTeamGenerator(BedwarsTeam team, int level) {
String worldName = plugin.getConfig().getString("world-name", "bedwars");
World world = Bukkit.getWorld(worldName);
if (world == null) return;
double targetX = plugin.getConfig().getDouble("locations.teams." + team.getName().toLowerCase() + ".generator.x");
double targetY = plugin.getConfig().getDouble("locations.teams." + team.getName().toLowerCase() + ".generator.y");
double targetZ = plugin.getConfig().getDouble("locations.teams." + team.getName().toLowerCase() + ".generator.z");
for (Generator gen : activeGenerators) {
if (gen.getType() == GeneratorType.IRON_GOLD) {
Location loc = gen.getLocation();
if (loc.getWorld() != null && loc.getWorld().equals(world) &&
Math.abs(loc.getX() - targetX) < 1.0 &&
Math.abs(loc.getY() - targetY) < 1.0 &&
Math.abs(loc.getZ() - targetZ) < 1.0) {
gen.setUpgradeLevel(level);
}
}
}
}
/**
* Removes all active generators and deletes their hologram entities.
*/
public void cleanup() {
for (Generator gen : activeGenerators) {
gen.remove();
}
activeGenerators.clear();
}
public List<Generator> getActiveGenerators() {
return activeGenerators;
}
}
@@ -0,0 +1,32 @@
package com.bedwars;
import org.bukkit.ChatColor;
import org.bukkit.Material;
public enum GeneratorType {
IRON_GOLD("Iron & Gold", ChatColor.GRAY, Material.IRON_INGOT),
DIAMOND("Diamond", ChatColor.AQUA, Material.DIAMOND),
EMERALD("Emerald", ChatColor.GREEN, Material.EMERALD);
private final String displayName;
private final ChatColor color;
private final Material material;
GeneratorType(String displayName, ChatColor color, Material material) {
this.displayName = displayName;
this.color = color;
this.material = material;
}
public String getDisplayName() {
return displayName;
}
public ChatColor getColor() {
return color;
}
public Material getMaterial() {
return material;
}
}
@@ -0,0 +1,9 @@
package com.bedwars;
public enum LastDamageType {
MELEE,
PROJECTILE,
FIREBALL,
TNT,
OTHER
}
@@ -0,0 +1,159 @@
package com.bedwars;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import org.bukkit.scoreboard.*;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class ScoreboardManager {
private final BedwarsPlugin plugin;
private final Map<UUID, Scoreboard> playerScoreboards = new HashMap<>();
public ScoreboardManager(BedwarsPlugin plugin) {
this.plugin = plugin;
}
public void setupScoreboard(Player player) {
org.bukkit.scoreboard.ScoreboardManager scoreboardManager = Bukkit.getScoreboardManager();
if (scoreboardManager == null) return;
Scoreboard board = scoreboardManager.getNewScoreboard();
Objective objective = board.registerNewObjective("bedwars", "dummy", "§e§lBEDWARS");
objective.setDisplaySlot(DisplaySlot.SIDEBAR);
player.setScoreboard(board);
playerScoreboards.put(player.getUniqueId(), board);
updateScoreboard(player);
}
public void updateScoreboard(Player player) {
Scoreboard board = playerScoreboards.get(player.getUniqueId());
if (board == null) return;
GameManager gm = plugin.getGameManager();
// Register Bedwars teams and spectator team on this scoreboard for name tag and tab list colors
for (BedwarsTeam t : BedwarsTeam.values()) {
Team scoreTeam = board.getTeam(t.getName());
if (scoreTeam == null) {
scoreTeam = board.registerNewTeam(t.getName());
}
scoreTeam.setColor(t.getColor());
scoreTeam.setPrefix(t.getColor().toString());
scoreTeam.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.NEVER);
// Clear existing entries to prevent stale data
for (String entry : new java.util.ArrayList<>(scoreTeam.getEntries())) {
scoreTeam.removeEntry(entry);
}
}
Team specTeam = board.getTeam("Spectators");
if (specTeam == null) {
specTeam = board.registerNewTeam("Spectators");
}
specTeam.setColor(ChatColor.GRAY);
specTeam.setPrefix("§7");
specTeam.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.NEVER);
for (String entry : new java.util.ArrayList<>(specTeam.getEntries())) {
specTeam.removeEntry(entry);
}
// Add all online players to their respective teams on this scoreboard
for (Player p : Bukkit.getOnlinePlayers()) {
if (gm.isSpectator(p)) {
specTeam.addEntry(p.getName());
} else {
BedwarsTeam pTeam = gm.getPlayerTeam(p);
if (pTeam != null) {
Team scoreTeam = board.getTeam(pTeam.getName());
if (scoreTeam != null) {
scoreTeam.addEntry(p.getName());
}
}
}
}
Objective objective = board.getObjective("bedwars");
if (objective == null) return;
// Clear existing scores by unregistering and re-registering to ensure clean slate
objective.unregister();
objective = board.registerNewObjective("bedwars", "dummy", "§e§lBEDWARS");
objective.setDisplaySlot(DisplaySlot.SIDEBAR);
int line = 15;
setLine(objective, "§7" + java.time.LocalDate.now().format(java.time.format.DateTimeFormatter.ofPattern("dd/MM/yy")), line--);
setLine(objective, " ", line--);
GameState state = gm.getState();
if (state == GameState.LOBBY) {
setLine(objective, "§fPlayers: §a" + Bukkit.getOnlinePlayers().size() + "§7/§a" + Bukkit.getMaxPlayers(), line--);
setLine(objective, " ", line--);
if (gm.isCountingDown()) {
setLine(objective, "§fStarting in: §a" + gm.getCountdownTime() + "s", line--);
} else {
setLine(objective, "§fWaiting for players...", line--);
}
} else if (state == GameState.PLAYING) {
setLine(objective, "§fMap: §a" + plugin.getConfig().getString("world-name", "bedwars"), line--);
setLine(objective, " ", line--);
// Display team statuses
for (BedwarsTeam team : BedwarsTeam.values()) {
if (gm.isTeamEnabled(team)) {
String status;
if (gm.hasBed(team)) {
status = "§a✔";
} else {
int alive = gm.getAliveTeamCount(team);
if (alive > 0) {
status = "§e" + alive;
} else {
status = "§c✘";
}
}
setLine(objective, team.getColor() + team.getName().substring(0, 1) + " §f" + team.getName() + ": " + status, line--);
}
}
setLine(objective, " ", line--);
setLine(objective, "§fKills: §a" + gm.getKills(player), line--);
setLine(objective, "§fBeds Broken: §a" + gm.getBedsBroken(player), line--);
} else if (state == GameState.ENDING) {
setLine(objective, "§a§lGAME OVER!", line--);
setLine(objective, " ", line--);
BedwarsTeam winner = gm.getWinnerTeam();
if (winner != null) {
setLine(objective, "§fWinner: " + winner.getColorizedName(), line--);
} else {
setLine(objective, "§fWinner: §7None", line--);
}
}
setLine(objective, " ", line--);
setLine(objective, "§dnexoria", line--);
}
public void removeScoreboard(Player player) {
playerScoreboards.remove(player.getUniqueId());
player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard());
}
public void updateAll() {
for (Player p : Bukkit.getOnlinePlayers()) {
updateScoreboard(p);
}
}
private void setLine(Objective obj, String text, int scoreValue) {
Score score = obj.getScore(text);
score.setScore(scoreValue);
}
}
+107
View File
@@ -0,0 +1,107 @@
package com.bedwars;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.entity.Villager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
public class ShopListener implements Listener {
private final BedwarsPlugin plugin;
public ShopListener(BedwarsPlugin plugin) {
this.plugin = plugin;
}
@EventHandler
public void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
if (!(event.getRightClicked() instanceof Villager villager)) return;
String name = villager.getCustomName();
if (name == null) return;
Player player = event.getPlayer();
GameManager gm = plugin.getGameManager();
if (gm.getState() != GameState.PLAYING || gm.isSpectator(player)) {
event.setCancelled(true);
return;
}
if (name.contains("ITEM SHOP")) {
event.setCancelled(true);
plugin.getShopManager().openShop(player, "blocks");
player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_YES, 0.8f, 1.2f);
} else if (name.contains("TEAM UPGRADES")) {
event.setCancelled(true);
plugin.getUpgradesManager().openUpgradesGui(player);
player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_YES, 0.8f, 1.2f);
}
}
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
if (!(event.getWhoClicked() instanceof Player player)) return;
String title = event.getView().getTitle();
// 1. Handle Item Shop clicks
if (title.startsWith("§8Item Shop - ")) {
event.setCancelled(true);
// Make sure the click is inside the top inventory
if (event.getClickedInventory() == null || event.getClickedInventory().equals(player.getInventory())) {
return;
}
int slot = event.getSlot();
String currentCategory = title.replace("§8Item Shop - ", "").toLowerCase();
// Clicked Category Switcher (slots 1 to 7)
if (slot >= 1 && slot <= 7) {
String[] categories = {"blocks", "weapons", "armor", "tools", "bows", "potions", "utility"};
String targetCat = categories[slot - 1];
if (!targetCat.equalsIgnoreCase(currentCategory)) {
plugin.getShopManager().openShop(player, targetCat);
player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 0.8f, 1.4f);
}
return;
}
// Clicked item purchase area (slots 18 to 44)
if (slot >= 18 && slot <= 44) {
// Ensure they didn't click border pane slots
if (slot == 26 || slot == 27 || slot == 35 || slot == 36) return;
plugin.getShopManager().purchaseItem(player, currentCategory, slot);
}
return;
}
// 2. Handle Upgrades GUI clicks
if (title.equals("§8Team Upgrades")) {
event.setCancelled(true);
if (event.getClickedInventory() == null || event.getClickedInventory().equals(player.getInventory())) {
return;
}
int slot = event.getSlot();
if (slot >= 10 && slot <= 13) {
plugin.getUpgradesManager().purchaseUpgrade(player, slot);
}
}
}
@EventHandler
public void onInventoryDrag(InventoryDragEvent event) {
String title = event.getView().getTitle();
if (title.startsWith("§8Item Shop - ") || title.equals("§8Team Upgrades")) {
event.setCancelled(true);
}
}
}
+790
View File
@@ -0,0 +1,790 @@
package com.bedwars;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.PotionMeta;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import java.util.*;
public class ShopManager {
private final BedwarsPlugin plugin;
// Track tool and armor levels for players
// 0 = None, 1 = Wood, 2 = Stone, 3 = Iron, 4 = Diamond
private final Map<UUID, Integer> pickaxeLevels = new HashMap<>();
private final Map<UUID, Integer> axeLevels = new HashMap<>();
private final Set<UUID> hasShears = new HashSet<>();
private final Map<UUID, Integer> armorLevels = new HashMap<>();
public ShopManager(BedwarsPlugin plugin) {
this.plugin = plugin;
}
public void resetPlayerShopData(Player player) {
UUID uuid = player.getUniqueId();
pickaxeLevels.remove(uuid);
axeLevels.remove(uuid);
hasShears.remove(uuid);
armorLevels.remove(uuid);
}
public void resetAllShopData() {
pickaxeLevels.clear();
axeLevels.clear();
hasShears.clear();
armorLevels.clear();
}
public int getArmorTier(Player player) {
return armorLevels.getOrDefault(player.getUniqueId(), 0);
}
public void equipArmorTier(Player player, int tier) {
Material legMat = Material.LEATHER_LEGGINGS;
Material bootMat = Material.LEATHER_BOOTS;
if (tier == 1) {
legMat = Material.CHAINMAIL_LEGGINGS;
bootMat = Material.CHAINMAIL_BOOTS;
} else if (tier == 2) {
legMat = Material.IRON_LEGGINGS;
bootMat = Material.IRON_BOOTS;
} else if (tier == 3) {
legMat = Material.DIAMOND_LEGGINGS;
bootMat = Material.DIAMOND_BOOTS;
}
ItemStack legs = new ItemStack(legMat);
ItemStack boots = new ItemStack(bootMat);
ItemMeta m1 = legs.getItemMeta();
ItemMeta m2 = boots.getItemMeta();
if (m1 != null) { m1.setUnbreakable(true); legs.setItemMeta(m1); }
if (m2 != null) { m2.setUnbreakable(true); boots.setItemMeta(m2); }
player.getInventory().setLeggings(legs);
player.getInventory().setBoots(boots);
// Reapply team upgrades if present
plugin.getUpgradesManager().applyTeamUpgrades(player);
}
public void handlePlayerDeath(Player player) {
UUID uuid = player.getUniqueId();
// Bedwars rule: keep shears (do not remove), and degrade pickaxe/axe by one tier (keep wooden tier 1)
if (pickaxeLevels.containsKey(uuid)) {
int current = pickaxeLevels.get(uuid);
if (current > 1) {
pickaxeLevels.put(uuid, current - 1);
}
}
if (axeLevels.containsKey(uuid)) {
int current = axeLevels.get(uuid);
if (current > 1) {
axeLevels.put(uuid, current - 1);
}
}
}
public boolean hasShears(Player player) {
return hasShears.contains(player.getUniqueId());
}
public int getPickaxeLevel(Player player) {
return pickaxeLevels.getOrDefault(player.getUniqueId(), 0);
}
public int getAxeLevel(Player player) {
return axeLevels.getOrDefault(player.getUniqueId(), 0);
}
public Material getPickaxeMaterial(int level) {
switch (level) {
case 1: return Material.WOODEN_PICKAXE;
case 2: return Material.STONE_PICKAXE;
case 3: return Material.IRON_PICKAXE;
case 4: return Material.DIAMOND_PICKAXE;
default: return null;
}
}
public Material getAxeMaterial(int level) {
switch (level) {
case 1: return Material.WOODEN_AXE;
case 2: return Material.STONE_AXE;
case 3: return Material.IRON_AXE;
case 4: return Material.DIAMOND_AXE;
default: return null;
}
}
public void giveTools(Player player) {
UUID uuid = player.getUniqueId();
// 1. Give shears if they have them
if (hasShears.contains(uuid)) {
ItemStack shears = new ItemStack(Material.SHEARS);
ItemMeta m = shears.getItemMeta();
if (m != null) {
m.setUnbreakable(true);
shears.setItemMeta(m);
}
player.getInventory().addItem(shears);
}
// 2. Give pickaxe if they have it
int pickLevel = pickaxeLevels.getOrDefault(uuid, 0);
if (pickLevel > 0) {
Material pickMat = getPickaxeMaterial(pickLevel);
if (pickMat != null) {
ItemStack pick = new ItemStack(pickMat);
ItemMeta m = pick.getItemMeta();
if (m != null) {
m.setUnbreakable(true);
pick.setItemMeta(m);
}
player.getInventory().addItem(pick);
}
}
// 3. Give axe if they have it
int axeLevel = axeLevels.getOrDefault(uuid, 0);
if (axeLevel > 0) {
Material axeMat = getAxeMaterial(axeLevel);
if (axeMat != null) {
ItemStack axe = new ItemStack(axeMat);
ItemMeta m = axe.getItemMeta();
if (m != null) {
m.setUnbreakable(true);
axe.setItemMeta(m);
}
player.getInventory().addItem(axe);
}
}
}
/**
* Opens the Bedwars Shop GUI for a player.
*/
public void openShop(Player player, String category) {
Inventory inv = Bukkit.createInventory(null, 54, "§8Item Shop - " + capitalize(category));
// 1. Fill categories row (Row 0)
setupCategories(inv, category);
// 2. Fill separator row (Row 1)
for (int i = 9; i < 18; i++) {
inv.setItem(i, createGuiItem(Material.BLACK_STAINED_GLASS_PANE, "§r", null));
}
// 3. Load items from config for selected category
List<String> itemsStr = plugin.getConfig().getStringList("shop." + category);
int slot = 19; // Start listing in middle rows
for (String itemStr : itemsStr) {
// Check if slot falls out of boundaries or enters right border
if (slot == 26 || slot == 35 || slot == 44) slot += 2; // skip borders
try {
ParsedShopItem shopItem = parseShopItem(itemStr, player);
if (shopItem != null) {
ItemStack displayStack = buildShopDisplayItem(shopItem, player);
inv.setItem(slot++, displayStack);
}
} catch (Exception e) {
plugin.getLogger().warning("Failed to parse shop item line: " + itemStr + " - Error: " + e.getMessage());
}
}
player.openInventory(inv);
}
private void setupCategories(Inventory inv, String activeCategory) {
String[] categories = {"blocks", "weapons", "armor", "tools", "bows", "potions", "utility"};
Material[] icons = {
Material.RED_WOOL, Material.GOLDEN_SWORD, Material.CHAINMAIL_BOOTS,
Material.STONE_PICKAXE, Material.BOW, Material.BREWING_STAND, Material.TNT
};
String[] names = {"§aBlocks", "§aWeapons", "§aArmor", "§aTools", "§aBows", "§aPotions", "§aUtility"};
for (int i = 0; i < categories.length; i++) {
int slot = i + 1; // slots 1 to 7
ItemStack item = new ItemStack(icons[i]);
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.setDisplayName(names[i]);
List<String> lore = new ArrayList<>();
lore.add("§7Click to open category");
meta.setLore(lore);
item.setItemMeta(meta);
}
inv.setItem(slot, item);
}
// Left and right border padding for Row 0
inv.setItem(0, createGuiItem(Material.GRAY_STAINED_GLASS_PANE, "§r", null));
inv.setItem(8, createGuiItem(Material.GRAY_STAINED_GLASS_PANE, "§r", null));
}
private ParsedShopItem parseShopItem(String raw, Player player) {
String[] parts = raw.split(":");
if (parts.length < 3) return null;
String matStr = parts[0];
String costMatStr = parts[1];
int costAmount = Integer.parseInt(parts[2]);
String displayName = ChatColor.translateAlternateColorCodes('&', parts[3]);
int amount = parts.length >= 5 ? Integer.parseInt(parts[4]) : 1;
// Resolve Wool color dynamically based on player's team
Material material;
if (matStr.equalsIgnoreCase("WOOL")) {
BedwarsTeam team = plugin.getGameManager().getPlayerTeam(player);
material = team != null ? team.getWoolMaterial() : Material.WHITE_WOOL;
} else if (matStr.equalsIgnoreCase("BOW_POWER")) {
material = Material.BOW;
} else if (matStr.equalsIgnoreCase("KNOCKBACK_STICK")) {
material = Material.STICK;
} else if (matStr.equalsIgnoreCase("SPEED_POTION") || matStr.equalsIgnoreCase("INVISIBILITY_POTION") || matStr.equalsIgnoreCase("JUMP_POTION")) {
material = Material.POTION;
} else {
material = Material.valueOf(matStr.toUpperCase());
}
// Resolve Cost Material
Material costMaterial;
if (costMatStr.equalsIgnoreCase("IRON")) costMaterial = Material.IRON_INGOT;
else if (costMatStr.equalsIgnoreCase("GOLD")) costMaterial = Material.GOLD_INGOT;
else if (costMatStr.equalsIgnoreCase("DIAMOND")) costMaterial = Material.DIAMOND;
else if (costMatStr.equalsIgnoreCase("EMERALD")) costMaterial = Material.EMERALD;
else costMaterial = Material.valueOf(costMatStr.toUpperCase());
return new ParsedShopItem(raw, material, costMaterial, costAmount, displayName, amount);
}
private ItemStack buildShopDisplayItem(ParsedShopItem item, Player player) {
// Prevent buying weaker/equal armor visually
if (item.material == Material.CHAINMAIL_BOOTS || item.material == Material.IRON_BOOTS || item.material == Material.DIAMOND_BOOTS) {
int itemTier = 1;
if (item.material == Material.IRON_BOOTS) itemTier = 2;
else if (item.material == Material.DIAMOND_BOOTS) itemTier = 3;
int currentTier = getArmorTier(player);
if (currentTier >= itemTier) {
ItemStack stack = new ItemStack(item.material, item.amount);
ItemMeta meta = stack.getItemMeta();
if (meta != null) {
meta.setDisplayName(item.displayName);
List<String> lore = new ArrayList<>();
lore.add("§7Amount: §b" + item.amount);
lore.add("");
lore.add("§a§lALREADY OWNED");
meta.setLore(lore);
stack.setItemMeta(meta);
}
return stack;
}
}
// Prevent buying weaker/equal swords visually
if (item.material.name().contains("SWORD")) {
int itemTier = getSwordTier(item.material);
int currentBestTier = getBestSwordTier(player);
if (currentBestTier >= itemTier) {
ItemStack stack = new ItemStack(item.material, item.amount);
ItemMeta meta = stack.getItemMeta();
if (meta != null) {
meta.setDisplayName(item.displayName);
List<String> lore = new ArrayList<>();
lore.add("§7Amount: §b" + item.amount);
lore.add("");
lore.add("§a§lALREADY OWNED");
meta.setLore(lore);
stack.setItemMeta(meta);
}
return stack;
}
}
// Prevent buying shears visually if already owned
if (item.material == Material.SHEARS && hasShears(player)) {
ItemStack stack = new ItemStack(item.material, item.amount);
ItemMeta meta = stack.getItemMeta();
if (meta != null) {
meta.setDisplayName(item.displayName);
List<String> lore = new ArrayList<>();
lore.add("§7Amount: §b" + item.amount);
lore.add("");
lore.add("§a§lALREADY OWNED");
meta.setLore(lore);
stack.setItemMeta(meta);
}
return stack;
}
// Prevent buying pickaxe visually if already maxed
if (item.material == Material.WOODEN_PICKAXE && getPickaxeLevel(player) >= 4) {
ItemStack stack = new ItemStack(Material.DIAMOND_PICKAXE, item.amount);
ItemMeta meta = stack.getItemMeta();
if (meta != null) {
meta.setDisplayName(item.displayName);
List<String> lore = new ArrayList<>();
lore.add("§7Amount: §b" + item.amount);
lore.add("");
lore.add("§a§lMAXED OUT");
meta.setLore(lore);
stack.setItemMeta(meta);
}
return stack;
}
// Prevent buying axe visually if already maxed
if (item.material == Material.WOODEN_AXE && getAxeLevel(player) >= 4) {
ItemStack stack = new ItemStack(Material.DIAMOND_AXE, item.amount);
ItemMeta meta = stack.getItemMeta();
if (meta != null) {
meta.setDisplayName(item.displayName);
List<String> lore = new ArrayList<>();
lore.add("§7Amount: §b" + item.amount);
lore.add("");
lore.add("§a§lMAXED OUT");
meta.setLore(lore);
stack.setItemMeta(meta);
}
return stack;
}
ItemStack stack = new ItemStack(item.material, item.amount);
// Handle special modifications for tools, armor, potions, weapons
applySpecialAttributes(stack, item, player);
ItemMeta meta = stack.getItemMeta();
if (meta != null) {
meta.setDisplayName(item.displayName);
List<String> lore = new ArrayList<>();
lore.add("§7Amount: §b" + item.amount);
lore.add("§7Cost: " + getCurrencyColor(item.costMaterial) + item.costAmount + " " + getCurrencyName(item.costMaterial));
lore.add("");
boolean canAfford = hasEnoughResource(player, item.costMaterial, item.costAmount);
if (canAfford) {
lore.add("§e§lCLICK TO PURCHASE");
} else {
lore.add("§c§lYOU CANNOT AFFORD THIS");
}
meta.setLore(lore);
stack.setItemMeta(meta);
}
return stack;
}
private void applySpecialAttributes(ItemStack stack, ParsedShopItem item, Player player) {
ItemMeta meta = stack.getItemMeta();
if (meta == null) return;
// 1. Knockback Stick
if (item.rawString.contains("KNOCKBACK_STICK")) {
stack.setType(Material.STICK);
meta.addEnchant(Enchantment.KNOCKBACK, 1, true);
stack.setItemMeta(meta);
}
// 2. Bow with Power I
if (item.rawString.contains("BOW_POWER")) {
stack.setType(Material.BOW);
meta.addEnchant(Enchantment.POWER, 1, true);
stack.setItemMeta(meta);
}
// 3. Potions
if (item.material == Material.POTION || item.material == Material.SPLASH_POTION) {
PotionMeta potMeta = (PotionMeta) meta;
if (item.rawString.contains("SPEED_POTION")) {
potMeta.addCustomEffect(new PotionEffect(PotionEffectType.SPEED, 900, 1), true); // Speed II for 45s
} else if (item.rawString.contains("INVISIBILITY_POTION")) {
potMeta.addCustomEffect(new PotionEffect(PotionEffectType.INVISIBILITY, 600, 0), true); // Invisibility for 30s
} else if (item.rawString.contains("JUMP_POTION")) {
potMeta.addCustomEffect(new PotionEffect(PotionEffectType.JUMP_BOOST, 900, 4), true); // Jump V for 45s
}
stack.setItemMeta(potMeta);
}
// 4. Tools Progression
if (item.material == Material.WOODEN_PICKAXE) {
int currentLevel = pickaxeLevels.getOrDefault(player.getUniqueId(), 0);
int nextLevel = Math.min(4, currentLevel + 1);
Material nextMat = Material.WOODEN_PICKAXE;
String toolName = "§7Wooden Pickaxe";
if (nextLevel == 2) { nextMat = Material.STONE_PICKAXE; toolName = "§7Stone Pickaxe"; }
else if (nextLevel == 3) { nextMat = Material.IRON_PICKAXE; toolName = "§fIron Pickaxe"; }
else if (nextLevel == 4) { nextMat = Material.DIAMOND_PICKAXE; toolName = "§bDiamond Pickaxe"; }
stack.setType(nextMat);
item.material = nextMat;
item.displayName = toolName + " §e(Tier " + nextLevel + ")";
}
if (item.material == Material.WOODEN_AXE) {
int currentLevel = axeLevels.getOrDefault(player.getUniqueId(), 0);
int nextLevel = Math.min(4, currentLevel + 1);
Material nextMat = Material.WOODEN_AXE;
String toolName = "§7Wooden Axe";
if (nextLevel == 2) { nextMat = Material.STONE_AXE; toolName = "§7Stone Axe"; }
else if (nextLevel == 3) { nextMat = Material.IRON_AXE; toolName = "§fIron Axe"; }
else if (nextLevel == 4) { nextMat = Material.DIAMOND_AXE; toolName = "§bDiamond Axe"; }
stack.setType(nextMat);
item.material = nextMat;
item.displayName = toolName + " §e(Tier " + nextLevel + ")";
}
}
/**
* Executes the purchase of a shop item.
*/
public void purchaseItem(Player player, String category, int slot) {
List<String> itemsStr = plugin.getConfig().getStringList("shop." + category);
// Match slot back to config indices
int expectedIndex = 0;
int currentSlot = 19;
boolean found = false;
for (String ignored : itemsStr) {
if (currentSlot == 26 || currentSlot == 35 || currentSlot == 44) currentSlot += 2;
if (currentSlot == slot) {
found = true;
break;
}
currentSlot++;
expectedIndex++;
}
if (!found || expectedIndex >= itemsStr.size()) return;
String itemStr = itemsStr.get(expectedIndex);
ParsedShopItem shopItem = parseShopItem(itemStr, player);
if (shopItem == null) return;
// Prevent buying weaker/equal armor
if (shopItem.material == Material.CHAINMAIL_BOOTS || shopItem.material == Material.IRON_BOOTS || shopItem.material == Material.DIAMOND_BOOTS) {
int itemTier = 1;
if (shopItem.material == Material.IRON_BOOTS) itemTier = 2;
else if (shopItem.material == Material.DIAMOND_BOOTS) itemTier = 3;
int currentTier = getArmorTier(player);
if (currentTier >= itemTier) {
player.sendMessage("§cYou already own equal or better armor!");
player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1.0f, 1.0f);
return;
}
}
// Prevent buying weaker/equal swords
if (shopItem.material.name().contains("SWORD")) {
int itemTier = getSwordTier(shopItem.material);
int currentBestTier = getBestSwordTier(player);
if (currentBestTier >= itemTier) {
player.sendMessage("§cYou already own an equal or better sword!");
player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1.0f, 1.0f);
return;
}
}
// Prevent buying shears if already owned
if (shopItem.material == Material.SHEARS && hasShears(player)) {
player.sendMessage("§cYou already own shears!");
player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1.0f, 1.0f);
return;
}
// Prevent buying pickaxe if already maxed
if (shopItem.material == Material.WOODEN_PICKAXE && getPickaxeLevel(player) >= 4) {
player.sendMessage("§cYour Pickaxe is already maxed!");
player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1.0f, 1.0f);
return;
}
// Prevent buying axe if already maxed
if (shopItem.material == Material.WOODEN_AXE && getAxeLevel(player) >= 4) {
player.sendMessage("§cYour Axe is already maxed!");
player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1.0f, 1.0f);
return;
}
// Apply special modifications for display/upgrade levels
ItemStack dummyStack = new ItemStack(shopItem.material);
applySpecialAttributes(dummyStack, shopItem, player);
// Check if player has enough resource
if (!hasEnoughResource(player, shopItem.costMaterial, shopItem.costAmount)) {
player.sendMessage("§cYou cannot afford this item!");
player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1.0f, 1.0f);
return;
}
// Deduct cost
deductResource(player, shopItem.costMaterial, shopItem.costAmount);
// Deliver item
deliverPurchase(player, shopItem);
// Refresh Shop
openShop(player, category);
player.playSound(player.getLocation(), Sound.ENTITY_ITEM_PICKUP, 0.8f, 1.2f);
}
private void deliverPurchase(Player player, ParsedShopItem shopItem) {
UUID uuid = player.getUniqueId();
Material mat = shopItem.material;
// 1. Shears
if (mat == Material.SHEARS) {
if (hasShears.contains(uuid)) {
player.sendMessage("§cYou already have shears!");
return;
}
hasShears.add(uuid);
player.getInventory().addItem(new ItemStack(Material.SHEARS));
player.sendMessage("§aPurchased Shears!");
return;
}
// 2. Pickaxe Progression
if (mat == Material.WOODEN_PICKAXE || mat == Material.STONE_PICKAXE ||
mat == Material.IRON_PICKAXE || mat == Material.DIAMOND_PICKAXE) {
int currentLevel = pickaxeLevels.getOrDefault(uuid, 0);
int nextLevel = Math.min(4, currentLevel + 1);
pickaxeLevels.put(uuid, nextLevel);
// Remove old pickaxes
removeMaterials(player, Material.WOODEN_PICKAXE, Material.STONE_PICKAXE, Material.IRON_PICKAXE, Material.DIAMOND_PICKAXE);
ItemStack pick = new ItemStack(mat);
ItemMeta m = pick.getItemMeta();
if (m != null) {
m.setUnbreakable(true);
pick.setItemMeta(m);
}
player.getInventory().addItem(pick);
player.sendMessage("§aUpgraded Pickaxe to Tier " + nextLevel + "!");
return;
}
// 3. Axe Progression
if (mat == Material.WOODEN_AXE || mat == Material.STONE_AXE ||
mat == Material.IRON_AXE || mat == Material.DIAMOND_AXE) {
int currentLevel = axeLevels.getOrDefault(uuid, 0);
int nextLevel = Math.min(4, currentLevel + 1);
axeLevels.put(uuid, nextLevel);
// Remove old axes
removeMaterials(player, Material.WOODEN_AXE, Material.STONE_AXE, Material.IRON_AXE, Material.DIAMOND_AXE);
ItemStack axe = new ItemStack(mat);
ItemMeta m = axe.getItemMeta();
if (m != null) {
m.setUnbreakable(true);
axe.setItemMeta(m);
}
player.getInventory().addItem(axe);
player.sendMessage("§aUpgraded Axe to Tier " + nextLevel + "!");
return;
}
// 4. Armor Upgrades (Chainmail, Iron, Diamond)
if (mat == Material.CHAINMAIL_BOOTS || mat == Material.IRON_BOOTS || mat == Material.DIAMOND_BOOTS) {
int targetTier = 1;
if (mat == Material.IRON_BOOTS) targetTier = 2;
else if (mat == Material.DIAMOND_BOOTS) targetTier = 3;
armorLevels.put(uuid, targetTier);
equipArmorTier(player, targetTier);
player.sendMessage("§aArmor upgraded successfully!");
return;
}
// 5. Swords replacing logic
if (mat.name().contains("SWORD")) {
handleSwordPurchase(player, mat, shopItem.displayName);
return;
}
// 5. Standard Item
ItemStack purchasedStack = new ItemStack(mat, shopItem.amount);
// Reapply custom attributes (Knockback stick, bow, potions, etc.)
applySpecialAttributes(purchasedStack, shopItem, player);
player.getInventory().addItem(purchasedStack);
player.sendMessage("§aPurchased " + shopItem.displayName + "!");
}
private void removeMaterials(Player player, Material... materials) {
List<Material> list = Arrays.asList(materials);
for (int i = 0; i < player.getInventory().getSize(); i++) {
ItemStack item = player.getInventory().getItem(i);
if (item != null && list.contains(item.getType())) {
player.getInventory().setItem(i, null);
}
}
}
// Checking currency counts
public boolean hasEnoughResource(Player player, Material currency, int amount) {
int count = 0;
for (ItemStack item : player.getInventory().getContents()) {
if (item != null && item.getType() == currency) {
count += item.getAmount();
}
}
return count >= amount;
}
private void deductResource(Player player, Material currency, int amount) {
int leftToDeduct = amount;
ItemStack[] contents = player.getInventory().getContents();
for (int i = 0; i < contents.length; i++) {
ItemStack item = contents[i];
if (item != null && item.getType() == currency) {
if (item.getAmount() > leftToDeduct) {
item.setAmount(item.getAmount() - leftToDeduct);
break;
} else {
leftToDeduct -= item.getAmount();
player.getInventory().setItem(i, null);
}
}
if (leftToDeduct <= 0) break;
}
}
private String getCurrencyColor(Material material) {
if (material == Material.IRON_INGOT) return "§f";
if (material == Material.GOLD_INGOT) return "§6";
if (material == Material.DIAMOND) return "§b";
if (material == Material.EMERALD) return "§a";
return "§7";
}
private String getCurrencyName(Material material) {
if (material == Material.IRON_INGOT) return "Iron";
if (material == Material.GOLD_INGOT) return "Gold";
if (material == Material.DIAMOND) return "Diamond";
if (material == Material.EMERALD) return "Emerald";
return material.name();
}
private String capitalize(String text) {
if (text == null || text.isEmpty()) return "";
return text.substring(0, 1).toUpperCase() + text.substring(1).toLowerCase();
}
private ItemStack createGuiItem(Material mat, String name, List<String> lore) {
ItemStack item = new ItemStack(mat);
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.setDisplayName(name);
meta.setLore(lore);
item.setItemMeta(meta);
}
return item;
}
private int getBestSwordTier(Player player) {
int bestTier = -1; // -1 means no sword
for (ItemStack item : player.getInventory().getContents()) {
if (item != null) {
int tier = getSwordTier(item.getType());
if (tier > bestTier) {
bestTier = tier;
}
}
}
return bestTier;
}
private int getSwordTier(Material material) {
if (material == null) return -1;
switch (material) {
case WOODEN_SWORD: return 0;
case STONE_SWORD: return 1;
case IRON_SWORD: return 2;
case DIAMOND_SWORD: return 3;
default: return -1;
}
}
private void handleSwordPurchase(Player player, Material newSwordMaterial, String displayName) {
ItemStack[] contents = player.getInventory().getContents();
int bestSwordSlot = -1;
int bestSwordTier = -1;
for (int i = 0; i < contents.length; i++) {
ItemStack item = contents[i];
if (item != null) {
int tier = getSwordTier(item.getType());
if (tier > bestSwordTier) {
bestSwordTier = tier;
bestSwordSlot = i;
}
}
}
ItemStack newSword = new ItemStack(newSwordMaterial);
ItemMeta m = newSword.getItemMeta();
if (m != null) {
m.setUnbreakable(true);
newSword.setItemMeta(m);
}
if (bestSwordSlot != -1) {
// Replace the old sword at its slot!
player.getInventory().setItem(bestSwordSlot, newSword);
player.sendMessage("§aUpgraded sword to " + displayName + "!");
} else {
// Otherwise, just add it to the inventory
player.getInventory().addItem(newSword);
player.sendMessage("§aPurchased " + displayName + "!");
}
// Apply team upgrades (like sharpness!) to the new sword
plugin.getUpgradesManager().applyTeamUpgrades(player);
}
private static class ParsedShopItem {
final String rawString;
Material material;
final Material costMaterial;
final int costAmount;
String displayName;
final int amount;
ParsedShopItem(String rawString, Material material, Material costMaterial, int costAmount, String displayName, int amount) {
this.rawString = rawString;
this.material = material;
this.costMaterial = costMaterial;
this.costAmount = costAmount;
this.displayName = displayName;
this.amount = amount;
}
}
}
@@ -0,0 +1,254 @@
package com.bedwars;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import java.util.*;
public class UpgradesManager {
private final BedwarsPlugin plugin;
// Upgrades maps per team
private final Map<BedwarsTeam, Integer> sharpnessLevels = new HashMap<>();
private final Map<BedwarsTeam, Integer> protectionLevels = new HashMap<>();
private final Map<BedwarsTeam, Integer> hasteLevels = new HashMap<>();
private final Map<BedwarsTeam, Integer> forgeLevels = new HashMap<>();
public UpgradesManager(BedwarsPlugin plugin) {
this.plugin = plugin;
}
public void resetTeamUpgrades() {
sharpnessLevels.clear();
protectionLevels.clear();
hasteLevels.clear();
forgeLevels.clear();
}
public int getSharpnessLevel(BedwarsTeam team) {
return sharpnessLevels.getOrDefault(team, 0);
}
public int getProtectionLevel(BedwarsTeam team) {
return protectionLevels.getOrDefault(team, 0);
}
public int getHasteLevel(BedwarsTeam team) {
return hasteLevels.getOrDefault(team, 0);
}
public int getForgeLevel(BedwarsTeam team) {
return forgeLevels.getOrDefault(team, 0);
}
/**
* Opens the Team Upgrades GUI for a player.
*/
public void openUpgradesGui(Player player) {
BedwarsTeam team = plugin.getGameManager().getPlayerTeam(player);
if (team == null) return;
Inventory inv = Bukkit.createInventory(null, 27, "§8Team Upgrades");
// Padding
for (int i = 0; i < 27; i++) {
inv.setItem(i, createGuiItem(Material.BLACK_STAINED_GLASS_PANE, "§r", null));
}
// 1. Sharpness (Slot 10)
setupUpgradeIcon(inv, team, 10, Material.IRON_SWORD, "Sharpness", "§fPermanent Sharpness I on swords", "sharpness", getSharpnessLevel(team), 1);
// 2. Protection (Slot 11)
setupUpgradeIcon(inv, team, 11, Material.IRON_CHESTPLATE, "Protection", "§fPermanent Protection on armor", "protection", getProtectionLevel(team), 4);
// 3. Haste (Slot 12)
setupUpgradeIcon(inv, team, 12, Material.GOLDEN_PICKAXE, "Haste", "§fPermanent Haste effect", "haste", getHasteLevel(team), 2);
// 4. Forge (Slot 13)
setupUpgradeIcon(inv, team, 13, Material.FURNACE, "Forge", "§fSpawns base resources faster", "forge", getForgeLevel(team), 4);
player.openInventory(inv);
}
private void setupUpgradeIcon(Inventory inv, BedwarsTeam team, int slot, Material icon, String name, String benefit, String key, int currentLvl, int maxLvl) {
ItemStack item = new ItemStack(icon);
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.setDisplayName("§b§l" + name);
List<String> lore = new ArrayList<>();
lore.add("§7Benefit: " + benefit);
lore.add("§7Current Level: §f" + (currentLvl == 0 ? "None" : "Tier " + currentLvl));
lore.add("");
if (currentLvl >= maxLvl) {
lore.add("§a§lMAX LEVEL REACHED");
} else {
int nextLvl = currentLvl + 1;
int cost = plugin.getConfig().getIntegerList("upgrades." + key + ".cost").get(nextLvl - 1);
String costMatName = plugin.getConfig().getString("upgrades." + key + ".material", "DIAMOND");
lore.add("§7Next Level: §aTier " + nextLvl);
lore.add("§7Cost: §b" + cost + " " + costMatName);
lore.add("");
lore.add("§eClick to buy upgrade");
}
meta.setLore(lore);
item.setItemMeta(meta);
}
inv.setItem(slot, item);
}
/**
* Handles clicking an upgrade icon in the GUI.
*/
public void purchaseUpgrade(Player player, int slot) {
BedwarsTeam team = plugin.getGameManager().getPlayerTeam(player);
if (team == null) return;
String key = null;
int maxLvl = 1;
int currentLvl = 0;
if (slot == 10) { key = "sharpness"; maxLvl = 1; currentLvl = getSharpnessLevel(team); }
else if (slot == 11) { key = "protection"; maxLvl = 4; currentLvl = getProtectionLevel(team); }
else if (slot == 12) { key = "haste"; maxLvl = 2; currentLvl = getHasteLevel(team); }
else if (slot == 13) { key = "forge"; maxLvl = 4; currentLvl = getForgeLevel(team); }
if (key == null) return;
if (currentLvl >= maxLvl) {
player.sendMessage("§cYou have already maxed out this upgrade!");
player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1.0f, 1.0f);
return;
}
int nextLvl = currentLvl + 1;
int cost = plugin.getConfig().getIntegerList("upgrades." + key + ".cost").get(nextLvl - 1);
String costMatStr = plugin.getConfig().getString("upgrades." + key + ".material", "DIAMOND");
Material costMat = Material.valueOf(costMatStr.toUpperCase());
if (!plugin.getShopManager().hasEnoughResource(player, costMat, cost)) {
player.sendMessage("§cYour team doesn't have enough Diamonds for this upgrade!");
player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1.0f, 1.0f);
return;
}
// Deduct diamonds
deductResource(player, costMat, cost);
// Apply levels
if (key.equals("sharpness")) sharpnessLevels.put(team, nextLvl);
else if (key.equals("protection")) protectionLevels.put(team, nextLvl);
else if (key.equals("haste")) hasteLevels.put(team, nextLvl);
else if (key.equals("forge")) {
forgeLevels.put(team, nextLvl);
// Upgrade spawner ticks
plugin.getGeneratorManager().upgradeTeamGenerator(team, nextLvl);
}
// Apply perks to team members instantly
applyTeamUpgradesToAll(team);
// Notify team
for (Player p : Bukkit.getOnlinePlayers()) {
if (plugin.getGameManager().getPlayerTeam(p) == team) {
p.sendMessage("§a§lUPGRADE BOUGHT! §f" + player.getName() + " bought " + key.toUpperCase() + " Tier " + nextLvl + "!");
p.playSound(p.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 0.8f, 1.2f);
}
}
// Reopen GUI
openUpgradesGui(player);
}
/**
* Applies upgrades to a specific team player (usually on spawn/respawn or when bought).
*/
public void applyTeamUpgrades(Player player) {
BedwarsTeam team = plugin.getGameManager().getPlayerTeam(player);
if (team == null) return;
// 1. Sharpness
int sharpLvl = getSharpnessLevel(team);
if (sharpLvl > 0) {
for (ItemStack item : player.getInventory().getContents()) {
if (item != null && item.getType().name().contains("SWORD")) {
ItemMeta meta = item.getItemMeta();
if (meta != null && !meta.hasEnchant(Enchantment.SHARPNESS)) {
meta.addEnchant(Enchantment.SHARPNESS, sharpLvl, true);
item.setItemMeta(meta);
}
}
}
}
// 2. Protection
int protLvl = getProtectionLevel(team);
if (protLvl > 0) {
for (ItemStack armor : player.getInventory().getArmorContents()) {
if (armor != null && armor.getType() != Material.AIR) {
ItemMeta meta = armor.getItemMeta();
if (meta != null) {
meta.addEnchant(Enchantment.PROTECTION, protLvl, true);
armor.setItemMeta(meta);
}
}
}
}
// 3. Haste
int hasteLvl = getHasteLevel(team);
if (hasteLvl > 0) {
// Remove old haste first
player.removePotionEffect(PotionEffectType.HASTE);
// Apply infinite haste effect
player.addPotionEffect(new PotionEffect(PotionEffectType.HASTE, Integer.MAX_VALUE, hasteLvl - 1, true, false));
}
}
public void applyTeamUpgradesToAll(BedwarsTeam team) {
for (Player p : Bukkit.getOnlinePlayers()) {
if (plugin.getGameManager().getPlayerTeam(p) == team) {
applyTeamUpgrades(p);
}
}
}
private void deductResource(Player player, Material currency, int amount) {
int leftToDeduct = amount;
ItemStack[] contents = player.getInventory().getContents();
for (int i = 0; i < contents.length; i++) {
ItemStack item = contents[i];
if (item != null && item.getType() == currency) {
if (item.getAmount() > leftToDeduct) {
item.setAmount(item.getAmount() - leftToDeduct);
break;
} else {
leftToDeduct -= item.getAmount();
player.getInventory().setItem(i, null);
}
}
if (leftToDeduct <= 0) break;
}
}
private ItemStack createGuiItem(Material mat, String name, List<String> lore) {
ItemStack item = new ItemStack(mat);
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.setDisplayName(name);
meta.setLore(lore);
item.setItemMeta(meta);
}
return item;
}
}
+247
View File
@@ -0,0 +1,247 @@
world-name: map
lobby-server: lobby
min-players: 2
countdown-seconds: 30
rates:
iron: 20
gold: 80
diamond: 600
emerald: 1200
# Format: Material:CostMaterial:CostAmount:Name:Amount (Optional)
shop:
blocks:
- WOOL:IRON:4:&fWhite Wool:16
- TERRACOTTA:IRON:12:&6Hardened Clay:16
- GLASS:IRON:16:&eBlast-proof Glass:4
- END_STONE:IRON:24:&eEnd Stone:12
- OAK_PLANKS:GOLD:4:&Wood:12
- OBSIDIAN:EMERALD:4:&5Obsidian:4
weapons:
- STONE_SWORD:IRON:10:&7Stone Sword:1
- IRON_SWORD:GOLD:7:&fIron Sword:1
- DIAMOND_SWORD:EMERALD:4:&bDiamond Sword:1
armor:
- CHAINMAIL_BOOTS:IRON:40:&7Chainmail Armor:1
- IRON_BOOTS:GOLD:12:&fIron Armor:1
- DIAMOND_BOOTS:EMERALD:6:&bDiamond Armor:1
tools:
- SHEARS:IRON:20:&aShears:1
- WOODEN_PICKAXE:IRON:10:&7Pickaxe (Progression):1
- WOODEN_AXE:IRON:10:&7Axe (Progression):1
bows:
- BOW:GOLD:12:&aBow:1
- BOW_POWER:GOLD:24:&aBow (Power I):1
- ARROW:GOLD:2:&aArrow:8
potions:
- SPEED_POTION:EMERALD:1:&bSpeed II Potion:1
- INVISIBILITY_POTION:EMERALD:2:&7Invisibility Potion:1
- JUMP_POTION:EMERALD:1:&aJump V Potion:1
utility:
- GOLDEN_APPLE:GOLD:3:&6Golden Apple:1
- FIRE_CHARGE:IRON:40:&cFireball:1
- ENDER_PEARL:EMERALD:4:&5Ender Pearl:1
- SPONGE:GOLD:6:&eSponge:4
- TNT:GOLD:4:&cTNT:1
upgrades:
sharpness:
cost:
- 4
- 8
material: DIAMOND
protection:
cost:
- 2
- 4
- 8
- 16
material: DIAMOND
haste:
cost:
- 2
- 4
material: DIAMOND
forge:
cost:
- 2
- 4
- 6
- 8
material: DIAMOND
locations:
lobby:
world: world
x: 129.5
y: 90.0
z: 149.5
yaw: 0.0
pitch: 0.0
teams:
red:
enabled: true
spawn:
x: 128.5
y: 5.0
z: 56.5
yaw: 0.0
pitch: 0.0
bed:
x: 128.0
y: 5.0
z: 63.0
generator:
x: 128.5
y: 5.0
z: 45.5
shop:
x: 135.5
y: 5.0
z: 54.5
yaw: 90.0
pitch: 0.0
upgrades:
x: 120.5
y: 5.0
z: 54.5
yaw: -90.0
pitch: 0.0
enderchest:
x: 121.0
y: 5.0
z: 61.0
blue:
enabled: true
spawn:
x: 222.5
y: 5.0
z: 150.5
yaw: 90.0
pitch: 0.0
bed:
x: 214.0
y: 5.0
z: 150.0
shop:
x: 224.5
y: 5.0
z: 157.5
yaw: 180.0
pitch: 0.0
upgrades:
x: 224.5
y: 5.0
z: 142.5
yaw: 0.0
pitch: 0.0
generator:
x: 233.5
y: 5.0
z: 150.5
enderchest:
x: 217.0
y: 5.0
z: 143.0
purple:
enabled: true
spawn:
x: 128.5
y: 5.0
z: 244.5
yaw: -180.0
pitch: 0.0
bed:
x: 128.0
y: 5.0
z: 236.0
generator:
x: 128.5
y: 5.0
z: 255.5
shop:
x: 121.5
y: 5.0
z: 246.5
yaw: -90.0
pitch: 0.0
upgrades:
x: 136.5
y: 5.0
z: 246.5
yaw: 90.0
pitch: 0.0
enderchest:
x: 135.0
y: 5.0
z: 239.0
yellow:
enabled: true
spawn:
x: 34.5
y: 5.0
z: 150.5
yaw: -90.0
pitch: 0.0
bed:
x: 41.0
y: 5.0
z: 150.0
generator:
x: 23.5
y: 5.0
z: 150.5
shop:
x: 32.5
y: 5.0
z: 143.5
yaw: 0.0
pitch: 0.0
upgrades:
x: 32.5
y: 5.0
z: 158.5
yaw: -180.0
pitch: 0.0
enderchest:
x: 39.0
y: 5.0
z: 157.0
generators:
- x: 129.5
y: 8.0
z: 149.5
type: EMERALD
- x: 144.5
y: 8.0
z: 134.5
type: EMERALD
- x: 114.5
y: 8.0
z: 134.5
type: EMERALD
- x: 114.5
y: 8.0
z: 164.5
type: EMERALD
- x: 144.5
y: 8.0
z: 164.5
type: EMERALD
- x: 182.5
y: 7.0
z: 201.5
type: DIAMOND
- x: 77.5
y: 7.0
z: 202.5
type: DIAMOND
- x: 76.5
y: 7.0
z: 97.5
type: DIAMOND
- x: 181.5
y: 7.0
z: 96.5
type: DIAMOND
+11
View File
@@ -0,0 +1,11 @@
name: Bedwars
version: 1.0.0
main: com.bedwars.BedwarsPlugin
api-version: 1.21
description: A premium Bedwars plugin for 1.21.4+ with world resets, shops, upgrades, and Velocity proxy integration.
author: kyouki
commands:
bw:
description: Bedwars administration and setup command
permission: bedwars.admin
aliases: [bedwars]