Skip to content

Commit

Permalink
add filled_map save/load
Browse files Browse the repository at this point in the history
  • Loading branch information
acrylic-style committed Jul 20, 2024
1 parent e2d1a70 commit 1f7685e
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 14 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ plugins {
}

group = "net.azisaba"
version = "6.12.3"
version = "6.13.0"

java {
toolchain.languageVersion.set(JavaLanguageVersion.of(8))
Expand Down
36 changes: 27 additions & 9 deletions src/main/java/com/github/mori01231/lifecore/LifeCore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,21 @@ import com.github.mori01231.lifecore.gui.CommandListScreen
import com.github.mori01231.lifecore.gui.DropProtectScreen
import com.github.mori01231.lifecore.listener.*
import com.github.mori01231.lifecore.listener.item.*
import com.github.mori01231.lifecore.map.SerializedMapDataRenderer
import com.github.mori01231.lifecore.network.PacketHandler
import com.github.mori01231.lifecore.region.PlayerRegionManager
import com.github.mori01231.lifecore.util.GCListener
import com.github.mori01231.lifecore.util.NGWordsCache
import com.github.mori01231.lifecore.util.PlayerUtil
import com.github.mori01231.lifecore.util.*
import com.github.mori01231.lifecore.util.YAML.yaml
import com.github.mori01231.lifecore.util.decode
import com.github.mori01231.lifecore.util.encode
import com.github.mori01231.lifecore.util.getYamlMap
import com.github.mori01231.lifecore.util.nonNull
import com.github.mori01231.lifecore.util.runTaskTimerAsynchronously
import com.github.mori01231.lifecore.util.toReadonlyNode
import com.sun.net.httpserver.HttpServer
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import org.bukkit.Bukkit
import org.bukkit.command.CommandExecutor
import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer
import org.bukkit.craftbukkit.v1_15_R1.map.CraftMapRenderer
import org.bukkit.craftbukkit.v1_15_R1.map.CraftMapView
import org.bukkit.entity.ItemFrame
import org.bukkit.inventory.meta.MapMeta
import org.bukkit.plugin.java.JavaPlugin
import java.io.IOException
import java.net.InetSocketAddress
Expand Down Expand Up @@ -177,6 +175,26 @@ class LifeCore : JavaPlugin() {
}
}, 100, 100)

// check for maps
Bukkit.getScheduler().runTaskTimer(this, Runnable {
Bukkit.getOnlinePlayers().forEach player@ { player ->
player.inventory.contents.forEach { item ->
@Suppress("SENSELESS_COMPARISON")
if (item != null) {
MapUtil.initializeMapRenderer(player, item)
}
}
}
if (Bukkit.getOnlinePlayers().isEmpty()) return@Runnable
Bukkit.getWorlds().forEach world@ { world ->
world.getEntitiesByClass(ItemFrame::class.java).forEach { itemFrame ->
Bukkit.getOnlinePlayers().forEach { player ->
MapUtil.initializeMapRenderer(player, itemFrame.item)
}
}
}
}, 100, 100)

logger.info("LifeCore has been enabled.")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,12 @@ class CustomBlockManager(val plugin: LifeCore) {
plugin.server.pluginManager.registerEvents(CustomBlockListener(plugin), plugin)

// add recipe
plugin.server.addRecipe(ShapedRecipe(NamespacedKey(plugin, "wrench"), getWrenchItem()).apply {
shape("I I", " I ", " I ")
setIngredient('I', Material.IRON_INGOT)
})
try {
plugin.server.addRecipe(ShapedRecipe(NamespacedKey(plugin, "wrench"), getWrenchItem()).apply {
shape("I I", " I ", " I ")
setIngredient('I', Material.IRON_INGOT)
})
} catch (_: Exception) {}

reloadBlocks()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,26 @@ package com.github.mori01231.lifecore.command

import com.github.mori01231.lifecore.DBConnector
import com.github.mori01231.lifecore.LifeCore
import com.github.mori01231.lifecore.map.SerializedMapDataRenderer
import com.github.mori01231.lifecore.util.EncodeUtil
import com.github.mori01231.lifecore.util.ItemUtil
import com.github.mori01231.lifecore.util.MapUtil
import com.github.mori01231.lifecore.util.MapUtil.getCanvases
import net.minecraft.server.v1_15_R1.MojangsonParser
import net.minecraft.server.v1_15_R1.NBTTagByteArray
import net.minecraft.server.v1_15_R1.NBTTagCompound
import org.bukkit.Bukkit
import org.bukkit.ChatColor
import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import org.bukkit.command.TabExecutor
import org.bukkit.craftbukkit.v1_15_R1.CraftServer
import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer
import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftItemStack
import org.bukkit.craftbukkit.v1_15_R1.map.CraftMapRenderer
import org.bukkit.craftbukkit.v1_15_R1.map.CraftMapView
import org.bukkit.entity.Player
import org.bukkit.inventory.meta.MapMeta
import org.bukkit.util.Vector

class LifeCoreUtilCommand(val plugin: LifeCore) : TabExecutor {
Expand Down Expand Up @@ -40,6 +49,7 @@ class LifeCoreUtilCommand(val plugin: LifeCore) : TabExecutor {

private fun sendHelp(sender: CommandSender) {
Commands.entries.forEach { cmd ->
if (!sender.hasPermission("lifecore.lifecoreutil.${cmd.name}"))
if (cmd.description == null) {
sender.sendMessage("${ChatColor.AQUA}/lifecoreutil ${cmd.commandName}")
} else {
Expand Down Expand Up @@ -410,6 +420,29 @@ class LifeCoreUtilCommand(val plugin: LifeCore) : TabExecutor {
return super.suggest(plugin, player, args)
}
},
SaveMapData("地図をサーバー移動可能な形に変換します") {
override fun execute(plugin: LifeCore, player: Player, args: Array<String>) {
val meta = player.inventory.itemInMainHand.itemMeta as? MapMeta? ?: return player.sendMessage("this is not a map")
val mapView = meta.mapView ?: return player.sendMessage("mapView is null")
if (mapView.renderers.getOrNull(0) !is CraftMapRenderer) return player.sendMessage("renderers[0] is not an instance of CraftMapRenderer")
if (mapView is CraftMapView) mapView.render(player as CraftPlayer)
val canvas =
mapView.getCanvases()[mapView.renderers.first()]?.get(player as CraftPlayer)
?: mapView.getCanvases()[mapView.renderers.first()]?.get(null as CraftPlayer?)
?: return player.sendMessage("canvas not found")
val data = MapUtil.serializeCanvasToString(canvas)
player.inventory.setItemInMainHand(ItemUtil.setTag(
player.inventory.itemInMainHand,
"SerializedMapData",
NBTTagByteArray(EncodeUtil.encodeBase64AndGzip(data.toByteArray()))
))
}
},
LoadMapData("地図を読み込みます") {
override fun execute(plugin: LifeCore, player: Player, args: Array<String>) {
MapUtil.initializeMapRenderer(player, player.inventory.itemInMainHand)
}
},
;

abstract fun execute(plugin: LifeCore, player: Player, args: Array<String>)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.github.mori01231.lifecore.map

import kotlinx.serialization.Serializable
import org.bukkit.craftbukkit.v1_15_R1.map.CraftMapCanvas

@Serializable
data class SerializedMapCanvas(val buffer: ByteArray, val base: ByteArray) {
companion object {
fun asSerializableMirror(canvas: CraftMapCanvas): SerializedMapCanvas {
val buffer = CraftMapCanvas::class.java.getDeclaredField("buffer").apply { isAccessible = true }[canvas] as ByteArray
val base = CraftMapCanvas::class.java.getDeclaredField("base").apply { isAccessible = true }[canvas] as ByteArray
return SerializedMapCanvas(buffer, base)
}
}

fun injectToCraftMapCanvas(canvas: CraftMapCanvas) {
val nmsBuffer = CraftMapCanvas::class.java.getDeclaredField("buffer").apply { isAccessible = true }[canvas] as ByteArray
val nmsBase = CraftMapCanvas::class.java.getDeclaredField("base").apply { isAccessible = true }[canvas] as ByteArray
if (buffer.size != nmsBuffer.size) error("buffer size doesn't match")
if (base.size != nmsBase.size) error("base size doesn't match")
for ((index, byte) in buffer.withIndex()) {
nmsBuffer[index] = byte
}
for ((index, byte) in base.withIndex()) {
nmsBase[index] = byte
}
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as SerializedMapCanvas

if (!buffer.contentEquals(other.buffer)) return false
if (!base.contentEquals(other.base)) return false

return true
}

override fun hashCode(): Int {
var result = buffer.contentHashCode()
result = 31 * result + base.contentHashCode()
return result
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.github.mori01231.lifecore.map

import net.minecraft.server.v1_15_R1.WorldMap
import org.bukkit.craftbukkit.v1_15_R1.map.CraftMapCanvas
import org.bukkit.craftbukkit.v1_15_R1.map.CraftMapView
import org.bukkit.entity.Player
import org.bukkit.map.MapCanvas
import org.bukkit.map.MapRenderer
import org.bukkit.map.MapView

class SerializedMapDataRenderer(private val canvas: SerializedMapCanvas) : MapRenderer() {
private var init = false

override fun render(view: MapView, canvas: MapCanvas, player: Player) {
this.canvas.injectToCraftMapCanvas(canvas as CraftMapCanvas)
for (i in 0 until canvas.cursors.size()) {
canvas.cursors.removeCursor(canvas.cursors.getCursor(i))
}
val worldMap = CraftMapView::class.java.getDeclaredField("worldMap").apply { isAccessible = true }[view] as WorldMap
for (x in 0..127) {
for (y in 0..127) {
worldMap.decorations.clear()
worldMap.flagDirty(x, y)
}
}
}
}
21 changes: 21 additions & 0 deletions src/main/java/com/github/mori01231/lifecore/util/EncodeUtil.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.github.mori01231.lifecore.util

import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.util.Base64
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream

object EncodeUtil {
fun encodeBase64AndGzip(data: ByteArray): ByteArray = Base64.getEncoder().encode(gzipByteArray(data))

fun decodeBase64AndGunzip(data: ByteArray): ByteArray = gunzipByteArray(Base64.getDecoder().decode(data))

fun gzipByteArray(data: ByteArray): ByteArray =
ByteArrayOutputStream().use {
it.apply { GZIPOutputStream(this).use { gz -> gz.write(data) } }.toByteArray()
}

fun gunzipByteArray(data: ByteArray): ByteArray =
GZIPInputStream(ByteArrayInputStream(data)).use { it.readBytes() }
}
25 changes: 25 additions & 0 deletions src/main/java/com/github/mori01231/lifecore/util/ItemUtil.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.github.mori01231.lifecore.util;

import net.azisaba.itemstash.ItemStash;
import net.minecraft.server.v1_15_R1.NBTBase;
import net.minecraft.server.v1_15_R1.NBTTagCompound;
import org.bukkit.Material;
import org.bukkit.attribute.Attribute;
Expand Down Expand Up @@ -51,6 +52,30 @@ public static boolean isProbablyAdminSword(@Nullable ItemStack stack) {
return tag.getString(key);
}

@Contract("null, _ -> null")
public static @Nullable byte[] getByteArrayTag(@Nullable ItemStack stack, @NotNull String key) {
if (stack == null || stack.getType().isAir()) return null;
NBTTagCompound tag = CraftItemStack.asNMSCopy(stack).getTag();
if (tag == null) return null;
return tag.getByteArray(key);
}

public static @NotNull ItemStack setStringTag(@Nullable ItemStack stack, @NotNull String key, @NotNull String value) {
if (stack == null || stack.getType().isAir()) return new ItemStack(Material.AIR);
net.minecraft.server.v1_15_R1.ItemStack nms = CraftItemStack.asNMSCopy(stack);
NBTTagCompound tag = nms.getOrCreateTag();
tag.setString(key, value);
return CraftItemStack.asBukkitCopy(nms);
}

public static @NotNull ItemStack setTag(@Nullable ItemStack stack, @NotNull String key, @NotNull NBTBase nbt) {
if (stack == null || stack.getType().isAir()) return new ItemStack(Material.AIR);
net.minecraft.server.v1_15_R1.ItemStack nms = CraftItemStack.asNMSCopy(stack);
NBTTagCompound tag = nms.getOrCreateTag();
tag.set(key, nbt);
return CraftItemStack.asBukkitCopy(nms);
}

public static @NotNull String toString(@NotNull ItemStack stack) {
List<String> props = new ArrayList<>();
props.add("[Type: " + stack.getType().name() + "]");
Expand Down
66 changes: 66 additions & 0 deletions src/main/java/com/github/mori01231/lifecore/util/MapUtil.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.github.mori01231.lifecore.util

import com.github.mori01231.lifecore.map.SerializedMapCanvas
import com.github.mori01231.lifecore.map.SerializedMapDataRenderer
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.encodeToJsonElement
import org.bukkit.Material
import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer
import org.bukkit.craftbukkit.v1_15_R1.map.CraftMapCanvas
import org.bukkit.craftbukkit.v1_15_R1.map.CraftMapRenderer
import org.bukkit.craftbukkit.v1_15_R1.map.CraftMapView
import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.MapMeta
import org.bukkit.map.MapCanvas
import org.bukkit.map.MapRenderer
import org.bukkit.map.MapView

object MapUtil {
private fun convertCanvasToSerializable(canvas: MapCanvas) =
when (canvas) {
is CraftMapCanvas -> SerializedMapCanvas.asSerializableMirror(canvas)
else -> error("Unsupported type: ${canvas::class.java.name}")
}

fun serializeCanvasToJsonElement(canvas: MapCanvas): JsonElement =
Json.encodeToJsonElement(convertCanvasToSerializable(canvas))

fun serializeCanvasToString(canvas: MapCanvas): String =
Json.encodeToString(convertCanvasToSerializable(canvas))

fun deserializeCanvasFromJsonElement(element: JsonElement): SerializedMapCanvas =
Json.decodeFromJsonElement(element)

fun deserializeCanvasFromString(json: String): SerializedMapCanvas =
Json.decodeFromString(json)

@Suppress("UNCHECKED_CAST")
fun MapView.getCanvases() =
CraftMapView::class.java.getDeclaredField("canvases").apply { isAccessible = true }[this] as Map<MapRenderer, Map<CraftPlayer, CraftMapCanvas>>

fun initializeMapRenderer(player: Player, item: ItemStack) {
if (item.type != Material.FILLED_MAP) return
val meta = item.itemMeta as? MapMeta? ?: return
val mapView = meta.mapView ?: return
if (mapView.renderers.getOrNull(0) !is CraftMapRenderer) {
if (mapView is CraftMapView) mapView.render(player as CraftPlayer)
return
}
val serializedMapData =
try {
item.let { ItemUtil.getByteArrayTag(it, "SerializedMapData") ?: return }
.let { EncodeUtil.decodeBase64AndGunzip(it) }
.let { String(it) }
.let { deserializeCanvasFromString(it) }
} catch (_: Exception) {
return
}
mapView.removeRenderer(mapView.renderers[0])
mapView.addRenderer(SerializedMapDataRenderer(serializedMapData))
if (mapView is CraftMapView) mapView.render(player as CraftPlayer)
}
}

0 comments on commit 1f7685e

Please sign in to comment.