premier commit
This commit is contained in:
+15
@@ -0,0 +1,15 @@
|
||||
# Gradle
|
||||
.gradle/
|
||||
.gradle_home/
|
||||
build/
|
||||
bin/
|
||||
out/
|
||||
._build
|
||||
|
||||
# Rust/Cargo
|
||||
/rust/target/
|
||||
|
||||
# OS-specific
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
._*
|
||||
@@ -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")
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
[build]
|
||||
target = "wasm32-wasip2"
|
||||
Generated
+640
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum GameState {
|
||||
Lobby,
|
||||
Playing,
|
||||
Ending,
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
rootProject.name = "bedwars"
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -0,0 +1,7 @@
|
||||
package com.bedwars;
|
||||
|
||||
public enum GameState {
|
||||
LOBBY,
|
||||
PLAYING,
|
||||
ENDING
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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]
|
||||
Reference in New Issue
Block a user