Compare commits

...

21 Commits
master ... dev

Author SHA1 Message Date
TONY_All ef8b5c973f
format code 2024-08-08 20:12:34 +08:00
TONY_All ff96cf59da
fix 重生后村民不可见
finish 方块禁止破坏
update 计分板显示
finish TNT自动点燃
finish 优化传送体验
finish 防止重复加入检测
2024-08-08 20:11:55 +08:00
TONY_All a063e8c6dd
update TabooLib 2024-08-04 16:22:11 +08:00
TONY_All be0bc6ffba
finish item generating part
update TabooLib
2024-08-04 16:11:12 +08:00
TONY_All 439cbccfe2
finish ore generate part (Part Of MAXMC-T-4) 2023-06-25 02:15:01 +08:00
tony_all 31e52382b2 sync 2023-06-23 17:03:52 +08:00
tony_all 9f5f657ea1
finish rule match part 2023-06-21 16:22:42 +08:00
TONY_All 45629b0d1b
rule change start 2023-06-21 00:44:44 +08:00
TONY_All b462eaa518
remove due to bug 2023-06-19 02:19:36 +08:00
TONY_All 29e9b188b1
fix artifact uploading 2023-06-16 11:57:00 +08:00
TONY_All 202eb473fa
fix code error 2023-06-16 11:50:16 +08:00
TONY_All 2aa6761412
fix code error 2023-06-16 11:47:24 +08:00
TONY_All 1d753801dd
update automation script & test 2023-06-16 11:44:18 +08:00
TONY_All 60295330de
add runtime map file 2023-06-16 02:09:20 +08:00
TONY_All f6e34d8efc
fix placing rule 2023-06-16 02:07:14 +08:00
TONY_All 6f1a727691
fix MAXMC-T-8 2023-06-16 01:56:10 +08:00
TONY_All 3d09b48da8
fix MAXMC-T-6 2023-06-16 01:44:03 +08:00
TONY_All 9416250e13
fix MAXMC-T-6 2023-06-16 01:41:22 +08:00
TONY_All 7f4ef2a447
optimize MAXMC-T-2 2023-06-16 01:34:10 +08:00
TONY_All 1053fde43f
fix MAXMC-T-2 2023-06-16 01:27:30 +08:00
TONY_All f1ae6d7f4c
sync 2023-06-16 00:14:37 +08:00
27 changed files with 1784 additions and 103 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
.idea .idea
build/ build/
test_server

View File

@ -1,10 +1,16 @@
job("Build and run tests") { job("Build and run tests") {
// run 'gradlew build' // run 'gradlew build'
gradlew("azul/zulu-openjdk:17", "build") { gradlew("azul/zulu-openjdk:17", "build", "--no-daemon") {
// mountDir = "/root"
// cache {
// storeKey = "dot-gradle"
// localPath = "/root/.gradle"
// }
fileArtifacts { fileArtifacts {
localPath = "build" localPath = "build/libs/*"
archive = true archive = true
remotePath = "{{ run:number }}/build.zip" remotePath = "{{ run:number }}/build"
onStatus = OnStatus.SUCCESS onStatus = OnStatus.SUCCESS
} }
} }

View File

@ -1,8 +1,9 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import io.izzel.taboolib.gradle.*
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins { plugins {
kotlin("jvm") version "1.7.20" kotlin("jvm") version "2.0.0"
id("io.izzel.taboolib") version "1.50" id("io.izzel.taboolib") version "2.0.11"
} }
group = "cc.maxmc.blastingcrisis" group = "cc.maxmc.blastingcrisis"
@ -11,12 +12,14 @@ version = "1.0-SNAPSHOT"
repositories { repositories {
mavenCentral() mavenCentral()
mavenLocal() mavenLocal()
maven("https://repo.vip.maxmc.cc:30443/releases")
} }
dependencies { dependencies {
implementation(kotlin("stdlib")) implementation(kotlin("stdlib"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0-RC")
implementation("ink.ptms.core:v10800:10800") implementation("ink.ptms.core:v10800:10800")
// taboo("cc.maxmc.agones:AgonesKt:0.1.3")
// implementation("ink.ptms.core:v11200:11200") // implementation("ink.ptms.core:v11200:11200")
} }
@ -27,16 +30,22 @@ taboolib {
name("TONY_All") name("TONY_All")
} }
} }
install("common", "common-5") env {
install("platform-bukkit") // install("common", "common-5")
install("module-nms", "module-nms-util") install(BUKKIT_ALL)
install("module-chat", "module-lang", "module-configuration") install(NMS, NMS_UTIL)
install(CHAT, LANG, CONFIGURATION)
classifier = null
options("skip-kotlin-relocate")
version = "6.0.10-12"
} }
tasks.withType<KotlinCompile> { // classifier = null
kotlinOptions.jvmTarget = "1.8" // options("skip-kotlin-relocate")
version {
taboolib = "6.1.2-beta2"
}
}
kotlin {
compilerOptions {
jvmTarget = JvmTarget.JVM_1_8
}
} }

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

BIN
runtime/bcmap.schematic Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -9,11 +9,13 @@ import cc.maxmc.blastingcrisis.misc.GameManager
import cc.maxmc.blastingcrisis.misc.info import cc.maxmc.blastingcrisis.misc.info
import cc.maxmc.blastingcrisis.misc.pluginScope import cc.maxmc.blastingcrisis.misc.pluginScope
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.ChatColor import org.bukkit.ChatColor
import org.bukkit.Location import org.bukkit.Location
import taboolib.common.env.RuntimeDependency import taboolib.common.env.RuntimeDependency
import taboolib.common.platform.Plugin import taboolib.common.platform.Plugin
import taboolib.common.platform.function.submit
@RuntimeDependency( @RuntimeDependency(
"org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4", "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4",
@ -23,9 +25,21 @@ object BlastingCrisis : Plugin() {
info("§a| §7Loading BlastingCrisis") info("§a| §7Loading BlastingCrisis")
DebugCommand.debug("debugcmd") DebugCommand.debug("debugcmd")
createDefaultGame() createDefaultGame()
submit(delay = 1) {
pluginScope.launch {
// AgonesSDK().use { sdk ->
// sdk.ready()
// }
}
}
} }
override fun onDisable() { override fun onDisable() {
pluginScope.launch {
// AgonesSDK().use { sdk ->
// sdk.shutdown()
// }
}
pluginScope.cancel() pluginScope.cancel()
} }
@ -34,18 +48,17 @@ object BlastingCrisis : Plugin() {
Location(Bukkit.getWorlds().first(), x.toDouble(), y.toDouble(), z.toDouble()) Location(Bukkit.getWorlds().first(), x.toDouble(), y.toDouble(), z.toDouble())
val teamRed = MapTeam( val teamRed = MapTeam(
"红队", name = "红队",
ChatColor.RED, color = ChatColor.RED,
location(0, 72, -44), spawn = location(0, 72, -44),
location(0, 72, -44), villager = location(0, 72, -44),
location(-8, 73, -47), upgrade = location(-8, 73, -47),
Area(location(14, 72, -48), location(-14, 78, -34)), home = Area(location(14, 72, -48), location(-14, 78, -34)),
Area(location(-13, 78, -33), location(13, 72, -33)), wall = Area(location(-13, 78, -33), location(13, 72, -33)),
Area(location(-13, 78, -32), location(13, 72, -1)), mine = Area(location(-13, 78, -32), location(13, 72, -1)),
Area(location(7, 72, -45), location(9, 74, -48)), teleport = Area(location(7, 72, -45), location(9, 74, -48)),
listOf( sides = listOf(
Area(location(15, 72, -1), location(19, 78, -48)), Area(location(15, 72, -1), location(19, 78, -48)), Area(location(-19, 78, -48), location(-15, 72, -1))
Area(location(-19, 78, -48), location(-15, 72, -1))
), ),
) )
val teamGreen = MapTeam( val teamGreen = MapTeam(
@ -57,10 +70,10 @@ object BlastingCrisis : Plugin() {
Area(location(-14, 72, 48), location(14, 78, 34)), Area(location(-14, 72, 48), location(14, 78, 34)),
Area(location(13, 78, 33), location(-13, 72, 33)), Area(location(13, 78, 33), location(-13, 72, 33)),
Area(location(13, 78, 32), location(-13, 72, 1)), Area(location(13, 78, 32), location(-13, 72, 1)),
Area(location(-7, 72, 45), location(-9, 74, 48)), Area(location(-7, 71, 48), location(-9, 71, 46)),
// Area(location(-7, 72, 45), location(-9, 74, 48)),
listOf( listOf(
Area(location(-15, 72, 1), location(-19, 78, 48)), Area(location(-15, 72, 1), location(-19, 78, 48)), Area(location(19, 78, 48), location(15, 72, 1))
Area(location(19, 78, 48), location(15, 72, 1))
), ),
) )
val map = GameMap( val map = GameMap(
@ -70,7 +83,12 @@ object BlastingCrisis : Plugin() {
listOf( listOf(
teamRed, teamGreen teamRed, teamGreen
), ),
1 1,
"default",
listOf(
location(-5, 72, -44), location(5, 72, -44), location(0, 72, -48),
location(-5, 72, 44), location(5, 72, 44), location(0, 72, 48)
)
) )
DebugCommand.game = Game(map) DebugCommand.game = Game(map)
GameManager.currentGame = DebugCommand.game GameManager.currentGame = DebugCommand.game

View File

@ -2,8 +2,11 @@ package cc.maxmc.blastingcrisis.command
import cc.maxmc.blastingcrisis.game.Game import cc.maxmc.blastingcrisis.game.Game
import cc.maxmc.blastingcrisis.game.GameOreGenerator import cc.maxmc.blastingcrisis.game.GameOreGenerator
import cc.maxmc.blastingcrisis.game.GamePlaceBreakRule.MatchResult
import cc.maxmc.blastingcrisis.game.GameState import cc.maxmc.blastingcrisis.game.GameState
import cc.maxmc.blastingcrisis.misc.Area import cc.maxmc.blastingcrisis.misc.Area
import cc.maxmc.blastingcrisis.misc.GameManager
import cc.maxmc.blastingcrisis.misc.UniArea
import cc.maxmc.blastingcrisis.packet.BEntityVillager import cc.maxmc.blastingcrisis.packet.BEntityVillager
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import org.bukkit.Bukkit import org.bukkit.Bukkit
@ -16,11 +19,25 @@ import taboolib.module.configuration.Configuration
import taboolib.platform.BukkitPlugin import taboolib.platform.BukkitPlugin
import taboolib.platform.util.sendActionBar import taboolib.platform.util.sendActionBar
import java.io.File import java.io.File
import java.util.*
import cc.maxmc.blastingcrisis.misc.debug as debuglog
object DebugCommand { object DebugCommand {
lateinit var game: Game lateinit var game: Game
lateinit var villager: BEntityVillager lateinit var villager: BEntityVillager
var autoRespawn = false
val builders = ArrayList<UUID>()
private val init by lazy {
// allow builders
debuglog("init builder rule")
GameManager.currentGame.placeBreakRule.addRuleFirst("builder") { (player, _, _, _) ->
// cc.maxmc.blastingcrisis.misc.debug("matching builder rule")
if (builders.contains(player.uniqueId)) MatchResult.ALLOW else MatchResult.DEFAULT
}
}
fun debug(cmd: String) = command(cmd) { fun debug(cmd: String) = command(cmd) {
literal("game") { literal("game") {
literal("join") { literal("join") {
execute<Player> { sender, _, _ -> execute<Player> { sender, _, _ ->
@ -77,6 +94,20 @@ object DebugCommand {
} }
} }
literal("respawn") {
execute<Player> { sender, _, _ ->
villager.removeViewer(sender)
villager.addViewer(sender)
}
}
literal("autoRespawn") {
execute<Player> { sender, _, _ ->
autoRespawn = !autoRespawn
sender.sendMessage("Auto Respawn is now $autoRespawn.")
}
}
literal("rename") { literal("rename") {
dynamic { dynamic {
execute<Player> { _, _, arg -> execute<Player> { _, _, arg ->
@ -116,11 +147,24 @@ object DebugCommand {
println("saved") println("saved")
val config = Configuration.loadFromFile(File(getDataFolder(), "ore-generators/default.yml")) val config = Configuration.loadFromFile(File(getDataFolder(), "ore-generators/default.yml"))
val gen = GameOreGenerator(config) val gen = GameOreGenerator(config)
val job = gen.enable(area) val job = gen.enable(UniArea(listOf(area)))
Bukkit.getScheduler().runTaskLater(BukkitPlugin.getInstance(), { Bukkit.getScheduler().runTaskLater(BukkitPlugin.getInstance(), {
job.cancel("Stop by hand") job.cancel("Stop by hand")
}, 60 * 20) }, 60 * 20)
} }
} }
literal("build_mode") {
execute<Player> { sender, _, _ ->
init
if (builders.contains(sender.uniqueId)) {
builders.remove(sender.uniqueId)
sender.sendMessage("U r no longer in build mode")
return@execute
}
sender.sendMessage("U r now in build mode")
builders.add(sender.uniqueId)
}
}
} }
} }

View File

@ -8,7 +8,10 @@ object GlobalSettings {
lateinit var settings: Configuration lateinit var settings: Configuration
val timeToStart val timeToStart
get() = settings.getInt("time-to-start") get() = settings.getInt("time_to_start")
val itemGenDelay
get() = settings.getLong("item_gen_delay")
object GameSettings { object GameSettings {
val villagerMaxHealth val villagerMaxHealth

View File

@ -1,10 +1,17 @@
package cc.maxmc.blastingcrisis.debug package cc.maxmc.blastingcrisis.debug
import cc.maxmc.blastingcrisis.command.DebugCommand
import cc.maxmc.blastingcrisis.command.DebugCommand.villager
import net.minecraft.server.v1_8_R3.PacketPlayOutEntityDestroy
import net.minecraft.server.v1_8_R3.PacketPlayOutSpawnEntityLiving import net.minecraft.server.v1_8_R3.PacketPlayOutSpawnEntityLiving
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.event.player.PlayerRespawnEvent
import taboolib.common.platform.event.SubscribeEvent import taboolib.common.platform.event.SubscribeEvent
import taboolib.common.platform.function.submit
import taboolib.library.reflex.Reflex.Companion.getProperty
import taboolib.module.nms.PacketSendEvent import taboolib.module.nms.PacketSendEvent
import taboolib.platform.BukkitPlugin import taboolib.platform.BukkitPlugin
import taboolib.platform.util.broadcast
object DListener { object DListener {
@SubscribeEvent @SubscribeEvent
@ -12,13 +19,29 @@ object DListener {
Bukkit.getScheduler().runTask(BukkitPlugin.getInstance()) { Bukkit.getScheduler().runTask(BukkitPlugin.getInstance()) {
if (it.player.name != "TONY_All") return@runTask if (it.player.name != "TONY_All") return@runTask
// val source = it.packet.source // val source = it.packet.source
if (!it.packet.name.lowercase().contains("entity")) return@runTask // if (!it.packet.name.lowercase().contains("entity")) return@runTask
// println(it.packet.name) val pname = listOf("PacketPlayOutSpawnEntityLiving", "PacketPlayOutEntityDestroy")
if (it.packet.source !is PacketPlayOutSpawnEntityLiving) { if (pname.contains(it.packet.name)) println(it.packet.name)
return@runTask
if (it.packet.source is PacketPlayOutSpawnEntityLiving) {
it.packet.source.getProperty<Int>("a")!!.broadcast()
it.packet.source.getProperty<Int>("b")!!.broadcast()
}
if (it.packet.source is PacketPlayOutEntityDestroy) {
it.packet.source.getProperty<IntArray>("a")!!.contentToString().broadcast()
}
}
}
@SubscribeEvent
fun debugRespawn(respawnE: PlayerRespawnEvent) {
if (DebugCommand.autoRespawn) {
submit(delay = 1L) {
villager.removeViewer(respawnE.player)
"Respawning".broadcast()
villager.addViewer(respawnE.player)
} }
// it.packet.source.getProperty<Int>("a")!!.broadcast()
// it.packet.source.getProperty<Int>("b")!!.broadcast()
} }
} }
} }

View File

@ -1,7 +1,11 @@
package cc.maxmc.blastingcrisis.game package cc.maxmc.blastingcrisis.game
import cc.maxmc.blastingcrisis.map.GameMap import cc.maxmc.blastingcrisis.map.GameMap
import cc.maxmc.blastingcrisis.misc.Area
import cc.maxmc.blastingcrisis.misc.BlockGenManager
import cc.maxmc.blastingcrisis.misc.UniArea
import cc.maxmc.blastingcrisis.misc.debug import cc.maxmc.blastingcrisis.misc.debug
import kotlinx.coroutines.Job
import org.bukkit.Material import org.bukkit.Material
import org.bukkit.entity.Player import org.bukkit.entity.Player
import taboolib.common.platform.function.submit import taboolib.common.platform.function.submit
@ -16,7 +20,10 @@ class Game(
val timer: GameTimer = GameTimer(this) val timer: GameTimer = GameTimer(this)
val players = ArrayList<Player>() val players = ArrayList<Player>()
val placeBreakRule = GamePlaceBreakRule(this) val placeBreakRule = GamePlaceBreakRule(this)
val generator = BlockGenManager.getGenerator(map.blockGen)!!
var state: GameState = GameState.WAITING var state: GameState = GameState.WAITING
private lateinit var oreGenJob: Job
private lateinit var itemGenJob: List<Job>
private fun autoJoinTeam() { private fun autoJoinTeam() {
players.filter { it.team == null }.shuffled().forEach { players.filter { it.team == null }.shuffled().forEach {
@ -25,6 +32,10 @@ class Game(
} }
fun join(player: Player) { fun join(player: Player) {
if (players.map { it.uniqueId }.contains(player.uniqueId)) {
player.sendLang("game_already_join")
return
}
players += player players += player
broadcast { broadcast {
it.sendLang("game_join", player.name) it.sendLang("game_join", player.name)
@ -46,23 +57,31 @@ class Game(
checkEnd() checkEnd()
} }
fun respawnPlayer(player: Player) {
submit(delay = 1L) {
teams.forEach { it.villager.respawn(player) }
}
}
fun start() { fun start() {
debug("game ${map.name} started.") debug("game ${map.name} started.")
placeBreakRule.defaultRule() placeBreakRule.loadDefaultRule()
startOreGen()
startItemGen()
state = GameState.START state = GameState.START
timer.startTimer() timer.startTimer()
timer.submitEvent("wall_fall", Duration.ofMinutes(1)) { timer.submitEvent("wall_fall", Duration.ofMinutes(1)) {
broadcast { it.sendLang("game_wall_fall") } broadcast { it.sendLang("game_wall_fall") }
placeBreakRule.addRuleLast("allow_center_wall") { (_, _, loc, _) ->
if (map.wall.contains(loc)) GamePlaceBreakRule.MatchResult.ALLOW else GamePlaceBreakRule.MatchResult.DEFAULT
}
submit { submit {
map.wall.forBlocksInArea().forEach { map.wall.forBlocksInArea().forEach {
it.block.type = Material.AIR it.block.type = Material.AIR
} }
placeBreakRule.addRule("allow_center_wall") { _, _, loc ->
map.wall.contains(loc)
}
} }
} }
autoJoinTeam() autoJoinTeam()
teams.forEach { teams.forEach {
it.start() it.start()
@ -81,17 +100,34 @@ class Game(
} }
} }
private fun startOreGen() {
val mines = map.teams.fold(ArrayList<Area>()) { list, team ->
list += team.mine
list += team.sides
list
}
oreGenJob = generator.enable(UniArea(mines))
}
private fun startItemGen() {
val generator = GameItemGenerator(Material.LOG)
itemGenJob = map.itemGen.map {
generator.generate(it)
}
}
fun checkEnd() { fun checkEnd() {
if (teams.filter { it.teamSurvive }.size > 1) { if (teams.filter { it.teamSurvive }.size > 1) {
return return
} }
broadcast { it.sendLang("game_end") } broadcast {
it.sendLang("game_end")
}
//TODO restart game logic //TODO restart game logic
} }
fun broadcast(action: (Player) -> Unit) { fun broadcast(action: (Player) -> Unit) {
players.forEach(action) players.forEach(action)
} }
} }

View File

@ -0,0 +1,31 @@
package cc.maxmc.blastingcrisis.game
import cc.maxmc.blastingcrisis.configuration.GlobalSettings
import cc.maxmc.blastingcrisis.misc.debug
import cc.maxmc.blastingcrisis.misc.pluginScope
import cc.maxmc.blastingcrisis.misc.toEntityLocation
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.bukkit.Bukkit
import org.bukkit.Location
import org.bukkit.Material
import org.bukkit.inventory.ItemStack
import org.bukkit.util.Vector
import taboolib.platform.BukkitPlugin
class GameItemGenerator(val type: Material) {
fun generate(location: Location): Job = pluginScope.launch {
while (true) {
Bukkit.getScheduler().runTask(BukkitPlugin.getInstance()) {
val item = location.world.dropItem(location.toEntityLocation(), ItemStack(type))
item.teleport(location.toEntityLocation())
item.velocity = Vector(0.0, 0.2, 0.0)
debug("Generate Item at ${location.toVector()}")
}
delay(GlobalSettings.itemGenDelay * 50)
}
}
}

View File

@ -1,9 +1,6 @@
package cc.maxmc.blastingcrisis.game package cc.maxmc.blastingcrisis.game
import cc.maxmc.blastingcrisis.misc.Area import cc.maxmc.blastingcrisis.misc.*
import cc.maxmc.blastingcrisis.misc.WeightRandom
import cc.maxmc.blastingcrisis.misc.debug
import cc.maxmc.blastingcrisis.misc.pluginScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -24,13 +21,14 @@ class GameOreGenerator(config: Configuration) {
} }
private val random = WeightRandom(ores.map { it.key to it.value }) private val random = WeightRandom(ores.map { it.key to it.value })
fun enable(area: Area): Job { fun enable(area: UniArea): Job {
return pluginScope.launch { return pluginScope.launch {
while (true) { while (true) {
val airPercent = area.getAirPercentage() val targetArea = area.randomizer.random()
val airPercent = targetArea.getAirPercentage()
debug("remain air: $airPercent.") debug("remain air: $airPercent.")
if (airPercent > 0.5) { if (airPercent > 0.5) {
generate(area) generate(targetArea)
} }
delay(rate * 50L) delay(rate * 50L)
} }
@ -54,4 +52,5 @@ class GameOreGenerator(config: Configuration) {
return generate(area, times + 1) return generate(area, times + 1)
} }
} }
} }

View File

@ -1,23 +1,43 @@
package cc.maxmc.blastingcrisis.game package cc.maxmc.blastingcrisis.game
import cc.maxmc.blastingcrisis.game.GamePlaceBreakRule.ActionType.BREAK import cc.maxmc.blastingcrisis.game.GamePlaceBreakRule.ActionType.BREAK
import cc.maxmc.blastingcrisis.game.GamePlaceBreakRule.ActionType.PLACE
import cc.maxmc.blastingcrisis.misc.EnhancedLinkedList
import cc.maxmc.blastingcrisis.misc.debug import cc.maxmc.blastingcrisis.misc.debug
import org.bukkit.Location import org.bukkit.Location
import org.bukkit.entity.Player import org.bukkit.entity.Player
import taboolib.library.xseries.XMaterial
typealias PlaceBreakRule = (GamePlaceBreakRule.GamePlaceBreakEvent) -> GamePlaceBreakRule.MatchResult
@Suppress("unused") // API
class GamePlaceBreakRule(val game: Game) { class GamePlaceBreakRule(val game: Game) {
enum class ActionType { enum class ActionType {
PLACE, PLACE, BREAK
BREAK
} }
private val rules = HashMap<String, (Player, ActionType, Location) -> Boolean>() enum class MatchResult {
fun matchRule(player: Player, actionType: ActionType, location: Location): Boolean { DEFAULT, ALLOW, DENY
}
data class GamePlaceBreakEvent(
val player: Player,
val actionType: ActionType,
val loc: Location,
val type: XMaterial
)
private val rules = HashMap<String, PlaceBreakRule>()
private val rulesPriority = EnhancedLinkedList<String>()
fun matchRule(player: Player, actionType: ActionType, location: Location, type: XMaterial): Boolean {
// match rules // match rules
rules.forEach { (name, func) -> rules.forEach { (name, func) ->
if (func(player, actionType, location)) { val result = func(GamePlaceBreakEvent(player, actionType, location, type))
if (result != MatchResult.DEFAULT) {
debug("Hit rule $name") debug("Hit rule $name")
return true return result == MatchResult.ALLOW
} }
} }
@ -25,7 +45,7 @@ class GamePlaceBreakRule(val game: Game) {
return false return false
} }
fun addRule(name: String, rule: (player: Player, type: ActionType, loc: Location) -> Boolean) { private fun recordRule(name: String, rule: PlaceBreakRule) {
if (rules.containsKey(name)) { if (rules.containsKey(name)) {
throw IllegalArgumentException("Rule $name already exists.") throw IllegalArgumentException("Rule $name already exists.")
} }
@ -33,27 +53,69 @@ class GamePlaceBreakRule(val game: Game) {
rules[name] = rule rules[name] = rule
} }
fun defaultRule() { fun addRuleLast(
name: String, rule: PlaceBreakRule
) {
recordRule(name, rule)
rulesPriority.addLast(name)
}
fun addRuleFirst(
name: String, rule: PlaceBreakRule
) {
recordRule(name, rule)
rulesPriority.addFirst(name)
}
fun addRuleBefore(
base: String, name: String, rule: PlaceBreakRule
) {
recordRule(name, rule)
rulesPriority.addBefore(base, name)
}
fun addRuleAfter(
base: String, name: String, rule: PlaceBreakRule
) {
recordRule(name, rule)
rulesPriority.addAfter(base, name)
}
fun loadDefaultRule() {
// allow mine // allow mine
addRule("allow_mine") { _, _, loc -> addRuleLast("allow_mine") { (_, _, loc, _) ->
game.map.teams.flatMap { val result = game.map.teams.flatMap {
it.sides + it.mine it.sides + it.mine
}.map { }.map {
it.containsBlock(loc) it.containsBlock(loc)
}.reduce { a, b -> a || b } }.reduce { a, b -> a || b }
if (result) MatchResult.ALLOW else MatchResult.DEFAULT
} }
// allow enemy wall // allow enemy wall
addRule("allow_wall_conditional") { player, type, loc -> addRuleLast("allow_wall_conditional") { (player, actionType, loc, _) ->
val team = player.team ?: return@addRule false val team = player.team ?: return@addRuleLast MatchResult.DENY
val isWall = game.teams val isWall = game.teams.map {
.map { it.teamInfo.wall.containsBlock(loc)
it.teamInfo.wall.containsBlock(loc).also { result -> debug("area ${it.teamInfo.wall} contains $loc is $result") } .also { result -> debug("area ${it.teamInfo.wall} contains $loc is $result") }
} }.reduce { a, b -> a || b }
.reduce { a, b -> a || b } if (!isWall) return@addRuleLast MatchResult.DEFAULT
if (!isWall) return@addRule false
val isHome = team.teamInfo.wall.containsBlock(loc) val isHome = team.teamInfo.wall.containsBlock(loc)
(type == BREAK) xor isHome if ((actionType == BREAK) xor isHome) MatchResult.ALLOW else MatchResult.DENY
}
// allow tnt in enemy's home
addRuleFirst("allow_tnt_conditional") { (player, actionType, loc, type) ->
if (type != XMaterial.TNT) return@addRuleFirst MatchResult.DEFAULT
val team = player.team ?: return@addRuleFirst MatchResult.DENY
val isHome = game.teams.map {
it.teamInfo.home.containsBlock(loc)
.also { result -> debug("area ${it.teamInfo.home} contains $loc is $result") }
}.reduce { a, b -> a || b }
if (!isHome) return@addRuleFirst MatchResult.DENY
val isSelf = team.teamInfo.home.containsBlock(loc)
if ((actionType == PLACE) xor isSelf) MatchResult.ALLOW else MatchResult.DENY
} }
} }
} }

View File

@ -29,9 +29,10 @@ class GameScoreboard(val game: Game) {
appendLine(player.asLangText("scoreboard_team_info", it.teamInfo.name, it.villager.health)) appendLine(player.asLangText("scoreboard_team_info", it.teamInfo.name, it.villager.health))
} }
} }
val eventName = player.asLangText("event_${event.second}")
player.asLangText( player.asLangText(
"scoreboard_start", "scoreboard_start",
event.second, eventName,
"${event.first.inWholeMinutes}:${event.first.inWholeSeconds}", "${event.first.inWholeMinutes}:${event.first.inWholeSeconds}",
teamMessage, teamMessage,
player.team?.villager?.health ?: throw IllegalStateException("player ${player.name} has no team."), player.team?.villager?.health ?: throw IllegalStateException("player ${player.name} has no team."),
@ -43,7 +44,5 @@ class GameScoreboard(val game: Game) {
} }
player.sendScoreboard(*scoreboardText.lines().toTypedArray()) player.sendScoreboard(*scoreboardText.lines().toTypedArray())
} }
} }

View File

@ -3,7 +3,7 @@ package cc.maxmc.blastingcrisis.game
import cc.maxmc.blastingcrisis.listener.GameListener import cc.maxmc.blastingcrisis.listener.GameListener
import cc.maxmc.blastingcrisis.map.MapTeam import cc.maxmc.blastingcrisis.map.MapTeam
import cc.maxmc.blastingcrisis.misc.debug import cc.maxmc.blastingcrisis.misc.debug
import cc.maxmc.blastingcrisis.misc.toPlayerLocation import cc.maxmc.blastingcrisis.misc.toEntityLocation
import org.bukkit.entity.Player import org.bukkit.entity.Player
import taboolib.platform.util.sendLang import taboolib.platform.util.sendLang
@ -15,7 +15,7 @@ class GameTeam(val game: Game, val teamInfo: MapTeam) {
private set private set
fun start() { fun start() {
players.forEach { it.teleport(teamInfo.spawn.toPlayerLocation()) } players.forEach { it.teleport(teamInfo.spawn.toEntityLocation()) }
villager.spawn() villager.spawn()
GameListener.interactSubscribed[teamInfo.upgrade] = { GameListener.interactSubscribed[teamInfo.upgrade] = {
debug("interact team ${teamInfo.name} upgrade.") debug("interact team ${teamInfo.name} upgrade.")
@ -24,7 +24,7 @@ class GameTeam(val game: Game, val teamInfo: MapTeam) {
val player = it.player ?: throw IllegalStateException("Bukkit API LOL") val player = it.player ?: throw IllegalStateException("Bukkit API LOL")
debug("teleporting $player to battle field") debug("teleporting $player to battle field")
val team = player.team ?: throw IllegalStateException("Player ${player.name} should have a team") val team = player.team ?: throw IllegalStateException("Player ${player.name} should have a team")
player.teleport(team.teamInfo.mine.randomLocation().toPlayerLocation()) player.teleport(team.teamInfo.mine.randomLocationRestrict().toEntityLocation())
}) })
} }

View File

@ -1,14 +1,16 @@
package cc.maxmc.blastingcrisis.game package cc.maxmc.blastingcrisis.game
import cc.maxmc.blastingcrisis.configuration.GlobalSettings import cc.maxmc.blastingcrisis.configuration.GlobalSettings
import cc.maxmc.blastingcrisis.misc.toPlayerLocation import cc.maxmc.blastingcrisis.misc.debug
import cc.maxmc.blastingcrisis.misc.toEntityLocation
import cc.maxmc.blastingcrisis.packet.BEntityVillager import cc.maxmc.blastingcrisis.packet.BEntityVillager
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.entity.Player
import taboolib.platform.BukkitPlugin import taboolib.platform.BukkitPlugin
import taboolib.platform.util.asLangText import taboolib.platform.util.asLangText
class TeamVillager(private val team: GameTeam) { class TeamVillager(private val team: GameTeam) {
private val packetVillager = BEntityVillager.create(team.teamInfo.villager.toPlayerLocation()) private val packetVillager = BEntityVillager.create(team.teamInfo.villager.toEntityLocation())
var health: Int = GlobalSettings.GameSettings.villagerMaxHealth var health: Int = GlobalSettings.GameSettings.villagerMaxHealth
private set private set
@ -40,5 +42,10 @@ class TeamVillager(private val team: GameTeam) {
team.game.checkEnd() team.game.checkEnd()
} }
fun respawn(player: Player) {
debug("respawning for ${player.name}")
packetVillager.respawnForPlayer(player)
}
} }

View File

@ -1,16 +1,28 @@
package cc.maxmc.blastingcrisis.listener package cc.maxmc.blastingcrisis.listener
import cc.maxmc.blastingcrisis.game.GamePlaceBreakRule import cc.maxmc.blastingcrisis.game.GamePlaceBreakRule
import cc.maxmc.blastingcrisis.game.team
import cc.maxmc.blastingcrisis.misc.Area import cc.maxmc.blastingcrisis.misc.Area
import cc.maxmc.blastingcrisis.misc.GameManager import cc.maxmc.blastingcrisis.misc.GameManager
import cc.maxmc.blastingcrisis.misc.debug import cc.maxmc.blastingcrisis.misc.debug
import cc.maxmc.blastingcrisis.misc.toEntityLocation
import org.bukkit.Location import org.bukkit.Location
import org.bukkit.Material
import org.bukkit.entity.EntityType
import org.bukkit.entity.Player
import org.bukkit.event.block.BlockBreakEvent import org.bukkit.event.block.BlockBreakEvent
import org.bukkit.event.block.BlockPlaceEvent import org.bukkit.event.block.BlockPlaceEvent
import org.bukkit.event.entity.EntityExplodeEvent import org.bukkit.event.entity.EntityDamageEvent
import org.bukkit.event.entity.ExplosionPrimeEvent
import org.bukkit.event.entity.FoodLevelChangeEvent
import org.bukkit.event.player.PlayerInteractEvent import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.event.player.PlayerMoveEvent import org.bukkit.event.player.PlayerMoveEvent
import org.bukkit.event.player.PlayerQuitEvent
import org.bukkit.event.player.PlayerRespawnEvent
import taboolib.common.platform.event.EventPriority
import taboolib.common.platform.event.SubscribeEvent import taboolib.common.platform.event.SubscribeEvent
import taboolib.library.xseries.XMaterial
import taboolib.platform.util.buildItem
import taboolib.platform.util.sendLang import taboolib.platform.util.sendLang
object GameListener { object GameListener {
@ -32,33 +44,100 @@ object GameListener {
} }
@SubscribeEvent @SubscribeEvent
fun onTNT(tntExplode: EntityExplodeEvent) { fun onTNT(tntExplode: ExplosionPrimeEvent) {
debug("${tntExplode.entityType} exploded at ${tntExplode.location}") debug("${tntExplode.entityType} exploded at ${tntExplode.entity.location}")
tntExplode.blockList().clear() tntExplode.radius = 0.0f
if (!GameManager.currentGame.state.isStarted()) return if (!GameManager.currentGame.state.isStarted()) return
GameManager.currentGame.teams.findLast { GameManager.currentGame.teams.findLast {
it.teamSurvive && it.teamInfo.home.contains(tntExplode.location) it.teamSurvive && it.teamInfo.home.contains(tntExplode.entity.location)
}?.apply { }?.apply {
villager.damage() villager.damage()
debug("team ${teamInfo.name}'s villager damaged") debug("team ${teamInfo.name}'s villager damaged")
} ?: throw IllegalStateException("TNT Exploded at no team, which shouldn't happen") } ?: return run {
debug("§cTNT Exploded at no team, which shouldn't happen")
tntExplode.isCancelled = true
}
} }
@SubscribeEvent @SubscribeEvent(priority = EventPriority.LOW)
fun onBreak(breakEvent: BlockBreakEvent) { fun onBreak(breakEvent: BlockBreakEvent) {
val loc = breakEvent.block.location ?: return val loc = breakEvent.block.location ?: return
val player = breakEvent.player ?: return val player = breakEvent.player ?: return
if (GameManager.currentGame.placeBreakRule.matchRule(player, GamePlaceBreakRule.ActionType.BREAK, loc)) return if (GameManager.currentGame.placeBreakRule.matchRule(
player,
GamePlaceBreakRule.ActionType.BREAK,
loc,
XMaterial.matchXMaterial(breakEvent.block.type)
)
) return
breakEvent.isCancelled = true breakEvent.isCancelled = true
player.sendLang("game_cant_break_block") player.sendLang("game_cant_break_block")
} }
@SubscribeEvent @SubscribeEvent(priority = EventPriority.LOW)
fun onPlace(placeEvent: BlockPlaceEvent) { fun onPlace(placeEvent: BlockPlaceEvent) {
val loc = placeEvent.block.location ?: return val loc = placeEvent.block.location ?: return
val player = placeEvent.player ?: return val player = placeEvent.player ?: return
if (GameManager.currentGame.placeBreakRule.matchRule(player, GamePlaceBreakRule.ActionType.PLACE, loc)) return if (GameManager.currentGame.placeBreakRule.matchRule(
player,
GamePlaceBreakRule.ActionType.PLACE,
loc,
XMaterial.matchXMaterial(placeEvent.blockPlaced.type)
)
) return
placeEvent.isCancelled = true placeEvent.isCancelled = true
player.sendLang("game_cant_place_block") player.sendLang("game_cant_place_block")
} }
@SubscribeEvent
fun onRespawn(respawnEvent: PlayerRespawnEvent) {
val team = respawnEvent.player.team ?: return // ignore none game player
respawnEvent.respawnLocation = team.teamInfo.spawn.toEntityLocation()
GameManager.currentGame.respawnPlayer(respawnEvent.player)
}
// @SubscribeEvent
fun protectDamageBeforeGame(damageEvent: EntityDamageEvent) {
if (damageEvent.entity !is Player) return // ignore none player entity
if (GameManager.currentGame.state.isStarted()) return
damageEvent.isCancelled = true
}
@SubscribeEvent
fun protectHunger(hungerEvent: FoodLevelChangeEvent) {
hungerEvent.isCancelled = true
(hungerEvent.entity as Player).run {
foodLevel = 20
saturation = 20f
}
}
@SubscribeEvent(EventPriority.HIGH, true)
fun tntIgnite(tntPlace: BlockPlaceEvent) {
if (tntPlace.blockPlaced.type != Material.TNT) return
val loc = tntPlace.blockPlaced.location
tntPlace.block.type = Material.AIR
loc.world.spawnEntity(loc.clone().apply { y += 0.5 }, EntityType.PRIMED_TNT)
}
@SubscribeEvent
fun onLeft(leftEvent: PlayerQuitEvent) {
if (!GameManager.currentGame.players.contains(leftEvent.player)) return
GameManager.currentGame.leave(leftEvent.player)
}
private val ores = mapOf(Material.IRON_ORE to Material.IRON_INGOT, Material.GOLD_ORE to Material.GOLD_INGOT)
@SubscribeEvent(ignoreCancelled = true)
fun autoSmelt(breakEvent: BlockBreakEvent) {
if (!GameManager.currentGame.players.contains(breakEvent.player)) return
val type = breakEvent.block.type
val loc = breakEvent.block.location
if (!ores.containsKey(type)) return
debug("Auto Smelting: ${breakEvent.block.type}")
breakEvent.block.type = Material.AIR
breakEvent.isCancelled = true
loc.world.dropItemNaturally(loc.toEntityLocation().apply { y += 0.5 }, buildItem(ores[type]!!))
}
} }

View File

@ -1,13 +1,16 @@
package cc.maxmc.blastingcrisis.map package cc.maxmc.blastingcrisis.map
import cc.maxmc.blastingcrisis.misc.Area import cc.maxmc.blastingcrisis.misc.Area
import org.bukkit.Location
class GameMap( class GameMap(
val name: String, val name: String,
val area: Area, val area: Area,
val wall: Area, val wall: Area,
val teams: List<MapTeam>, val teams: List<MapTeam>,
val maxPlayersPerTeam: Int val maxPlayersPerTeam: Int,
val blockGen: String,
val itemGen: List<Location>
) { ) {
val maxPlayer: Int val maxPlayer: Int
get() = maxPlayersPerTeam * teams.size get() = maxPlayersPerTeam * teams.size

View File

@ -32,9 +32,9 @@ class Area(loc1: Location, loc2: Location) : ConfigurationSerializable {
*/ */
fun contains(location: Location): Boolean { fun contains(location: Location): Boolean {
if (location.world != locTop.world) return false if (location.world != locTop.world) return false
if (location.x !in (locMin.x - 1)..(locTop.x + 1)) return false if (location.x !in (locMin.x)..(locTop.x + 1)) return false
if (location.y !in locMin.y..locTop.y + 1) return false if (location.y !in locMin.y..locTop.y + 1) return false
return location.z in (locMin.z - 1)..(locTop.z + 1) return location.z in (locMin.z)..(locTop.z + 1)
} }
fun containsBlock(location: Location): Boolean { fun containsBlock(location: Location): Boolean {
@ -80,7 +80,7 @@ class Area(loc1: Location, loc2: Location) : ConfigurationSerializable {
* *
* @return a random Location that player can stand * @return a random Location that player can stand
*/ */
tailrec fun randomLocation(): Location { tailrec fun randomLocationRestrict(): Location {
val x = random(locMin.blockX, locTop.blockX) val x = random(locMin.blockX, locTop.blockX)
val z = random(locMin.blockZ, locTop.blockZ) val z = random(locMin.blockZ, locTop.blockZ)
for (y in locMin.blockY..locTop.blockY) { for (y in locMin.blockY..locTop.blockY) {
@ -88,9 +88,21 @@ class Area(loc1: Location, loc2: Location) : ConfigurationSerializable {
val add = loc.clone().add(0.0, 1.0, 0.0) val add = loc.clone().add(0.0, 1.0, 0.0)
if (loc.block.type == Material.AIR && add.block.type == Material.AIR) return loc if (loc.block.type == Material.AIR && add.block.type == Material.AIR) return loc
} }
return randomLocation() return randomLocationRestrict()
} }
tailrec fun randomLocationAny(): Location {
val x = random(locMin.blockX, locTop.blockX)
val y = random(locMin.blockY, locTop.blockY)
val z = random(locMin.blockZ, locTop.blockZ)
val loc = Vector(x, y, z).toLocation(locMin.world)
if (loc.block.type == Material.AIR) return loc
return randomLocationAny()
}
fun volume(): Int =
(locTop.blockX - locMin.blockX) * (locTop.blockY - locMin.blockY) * (locTop.blockZ - locMin.blockZ)
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false

View File

@ -0,0 +1,25 @@
package cc.maxmc.blastingcrisis.misc
import cc.maxmc.blastingcrisis.game.GameOreGenerator
import taboolib.common.platform.function.getDataFolder
import taboolib.module.configuration.Configuration
object BlockGenManager {
private val generators = HashMap<String, GameOreGenerator>()
init {
load()
}
private fun load() {
getDataFolder().resolve("ore-generators").listFiles()!!.forEach {
try {
generators[it.nameWithoutExtension] = GameOreGenerator(Configuration.loadFromFile(it))
} catch (e: Exception) {
info("§c| §7加载矿石生成器 §c${it.nameWithoutExtension} §7时出现错误已跳过.")
}
}
}
fun getGenerator(name: String): GameOreGenerator? = generators[name]
}

View File

@ -11,7 +11,7 @@ class WeightRandom<K, V : Number>(list: List<Pair<K, V>>) {
init { init {
Preconditions.checkNotNull(list, "list can NOT be null!") Preconditions.checkNotNull(list, "list can NOT be null!")
for (pair in list) { for (pair in list) {
Preconditions.checkArgument(pair.second.toDouble() > 0, "非法权重值pair=$pair") // Preconditions.checkArgument(pair.second.toDouble() > 0, "非法权重值pair=$pair")
val lastWeight: Double = if (weightMap.size == 0) 0.0 else weightMap.lastKey().toDouble() //统一转为double val lastWeight: Double = if (weightMap.size == 0) 0.0 else weightMap.lastKey().toDouble() //统一转为double
weightMap[pair.second.toDouble() + lastWeight] = pair.first //权重累加 weightMap[pair.second.toDouble() + lastWeight] = pair.first //权重累加
} }
@ -24,7 +24,11 @@ class WeightRandom<K, V : Number>(list: List<Pair<K, V>>) {
} }
} }
fun Location.toPlayerLocation(): Location = clone().apply { fun <K, V : Number> List<Pair<K, V>>.weightedRandom(): WeightRandom<K, V> {
return WeightRandom(this)
}
fun Location.toEntityLocation(): Location = clone().apply {
x = blockX + 0.5 x = blockX + 0.5
y = blockY.toDouble() y = blockY.toDouble()
z = blockZ + 0.5 z = blockZ + 0.5

View File

@ -0,0 +1,6 @@
package cc.maxmc.blastingcrisis.misc
class UniArea(subArea: List<Area>) {
val randomizer = subArea.map { it to it.volume() }.weightedRandom()
}

View File

@ -1,6 +1,7 @@
package cc.maxmc.blastingcrisis.packet package cc.maxmc.blastingcrisis.packet
import cc.maxmc.blastingcrisis.listener.BEntityInteract import cc.maxmc.blastingcrisis.listener.BEntityInteract
import cc.maxmc.blastingcrisis.misc.debug
import gnu.trove.TDecorators import gnu.trove.TDecorators
import gnu.trove.map.hash.TIntObjectHashMap import gnu.trove.map.hash.TIntObjectHashMap
import net.minecraft.server.v1_8_R3.* import net.minecraft.server.v1_8_R3.*
@ -11,6 +12,7 @@ import taboolib.library.reflex.Reflex.Companion.getProperty
import taboolib.library.reflex.Reflex.Companion.setProperty import taboolib.library.reflex.Reflex.Companion.setProperty
import taboolib.library.reflex.Reflex.Companion.unsafeInstance import taboolib.library.reflex.Reflex.Companion.unsafeInstance
import taboolib.module.nms.sendPacket import taboolib.module.nms.sendPacket
import taboolib.module.nms.sendPacketBlocking
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.ReentrantReadWriteLock import java.util.concurrent.locks.ReentrantReadWriteLock
@ -58,7 +60,7 @@ abstract class BEntity(var loc: Location, val entityType: Int) {
} }
packet.setProperty("l", dataWatcher) packet.setProperty("l", dataWatcher)
viewers.forEach { it.sendPacket(packet) } player.sendPacketBlocking(packet)
} }
open fun addViewer(viewer: Player) { open fun addViewer(viewer: Player) {
@ -67,10 +69,18 @@ abstract class BEntity(var loc: Location, val entityType: Int) {
} }
open fun removeViewer(viewer: Player) { open fun removeViewer(viewer: Player) {
viewer.sendPacket(PacketPlayOutEntityDestroy(entityID)) viewer.sendPacketBlocking(PacketPlayOutEntityDestroy(entityID))
viewers.remove(viewer) viewers.remove(viewer)
} }
fun respawnForPlayer(player: Player) {
debug(viewers.toString())
if (!viewers.contains(player)) return
debug("Respawning?")
removeViewer(player)
addViewer(player)
}
fun destroy() { fun destroy() {
ArrayList(viewers).forEach { removeViewer(it) } ArrayList(viewers).forEach { removeViewer(it) }
} }

View File

@ -30,6 +30,7 @@ class BEntityVillager private constructor(loc: Location) : BEntity(loc, 120) {
nameTag.removeViewer(viewer) nameTag.removeViewer(viewer)
} }
override fun DataWatcher.initEntity() { override fun DataWatcher.initEntity() {
a(6, 1.0f) // Health a(6, 1.0f) // Health
a(7, 0) // Potion Effect Color a(7, 0) // Potion Effect Color

View File

@ -1,7 +1,9 @@
# Time before game start # Time before game start
# Unit: second # Unit: second
time-to-start: 15 time_to_start: 15
# Unit: tick
item_gen_delay: 20
game: game:
villager_max_health: 150 villager_max_health: 150

Binary file not shown.