Skip to content

Commit

Permalink
feat: Improved /schematic command
Browse files Browse the repository at this point in the history
  • Loading branch information
phinner committed Oct 6, 2023
1 parent 5e2beb7 commit 97d3df6
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ import arc.math.Mathf
import arc.math.geom.Point2
import arc.mock.MockSettings
import arc.struct.IntMap
import arc.struct.OrderedSet
import arc.struct.Seq
import arc.struct.StringMap
import arc.util.Log
import arc.util.io.CounterInputStream
import arc.util.io.Reads
import arc.util.io.Writes
import arc.util.serialization.Base64Coder
import com.google.common.cache.CacheBuilder
import com.xpdustry.imperium.common.application.ImperiumApplication
Expand All @@ -57,7 +59,9 @@ import mindustry.core.World
import mindustry.ctype.ContentType
import mindustry.entities.units.BuildPlan
import mindustry.game.Schematic
import mindustry.game.Schematic.Stile
import mindustry.game.Team
import mindustry.io.JsonIO
import mindustry.io.MapIO
import mindustry.io.SaveFileReader
import mindustry.io.SaveIO
Expand All @@ -73,9 +77,11 @@ import java.awt.geom.AffineTransform
import java.awt.image.BufferedImage
import java.io.ByteArrayInputStream
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
Expand All @@ -84,6 +90,7 @@ import java.nio.file.Files
import java.nio.file.Path
import java.time.Duration
import java.time.temporal.ChronoUnit
import java.util.zip.DeflaterOutputStream
import java.util.zip.InflaterInputStream
import java.util.zip.ZipInputStream
import javax.imageio.ImageIO
Expand Down Expand Up @@ -270,12 +277,12 @@ class AnukenMindustryContentHandler(directory: Path, private val config: ServerC
}

override suspend fun getSchematic(stream: InputStream): Result<Schematic> =
withContext(ImperiumScope.MAIN.coroutineContext) {
withContext(ImperiumScope.IO.coroutineContext) {
getSchematic0(stream)
}

override suspend fun getSchematic(string: String): Result<Schematic> =
withContext(ImperiumScope.MAIN.coroutineContext) {
withContext(ImperiumScope.IO.coroutineContext) {
getSchematic0(
try {
ByteArrayInputStream(Base64Coder.decode(string))
Expand All @@ -286,15 +293,16 @@ class AnukenMindustryContentHandler(directory: Path, private val config: ServerC
}

private fun getSchematic0(stream: InputStream): Result<Schematic> = runCatching {
val header = byteArrayOf('m'.code.toByte(), 's'.code.toByte(), 'c'.code.toByte(), 'h'.code.toByte())
for (b in header) {
for (b in SCHEMATIC_HEADER) {
if (stream.read() != b.toInt()) {
throw IOException("Not a schematic file (missing header).")
}
}

// discard version
stream.read()
if (stream.read() != 1) {
throw IOException("Unsupported schematic version.")
}

DataInputStream(InflaterInputStream(stream)).use { data ->
val width = data.readShort()
val height = data.readShort()
Expand Down Expand Up @@ -372,6 +380,42 @@ class AnukenMindustryContentHandler(directory: Path, private val config: ServerC
}
}

override suspend fun writeSchematic(schematic: Schematic, output: OutputStream): Result<Unit> =
withContext(ImperiumScope.IO.coroutineContext) {
runCatching {
output.write(SCHEMATIC_HEADER)
output.write(1)

DataOutputStream(DeflaterOutputStream(output)).use { stream ->
stream.writeShort(schematic.width)
stream.writeShort(schematic.height)
val tags = StringMap().apply { putAll(schematic.tags) }
tags.put("labels", JsonIO.write(schematic.labels.toArray<Any>(String::class.java)))
stream.writeByte(tags.size)
for (e in tags.entries()) {
stream.writeUTF(e.key)
stream.writeUTF(e.value)
}
val blocks = OrderedSet<Block>()
schematic.tiles.forEach { blocks.add(it.block) }

// create dictionary
stream.writeByte(blocks.size)
for (i in 0 until blocks.size) {
stream.writeUTF(blocks.orderedItems()[i].name)
}
stream.writeInt(schematic.tiles.size)
// write each tile
for (tile in schematic.tiles) {
stream.writeByte(blocks.orderedItems().indexOf(tile.block))
stream.writeInt(Point2.pack(tile.x.toInt(), tile.y.toInt()))
TypeIO.writeObject(Writes.get(stream), tile.config)
stream.writeByte(tile.rotation.toInt())
}
}
}
}

override suspend fun getMapMetadata(stream: InputStream): Result<MapMetadata> =
withContext(ImperiumScope.IO.coroutineContext) {
getMapMetadataWithPreview0(stream, false).map { it.first }
Expand Down Expand Up @@ -516,5 +560,6 @@ class AnukenMindustryContentHandler(directory: Path, private val config: ServerC
private val logger by LoggerDelegate()
private val SCHEMATIC_PREVIEW_SCOPE = CoroutineScope(SupervisorJob() + Dispatchers.IO.limitedParallelism(1))
private val MAP_PREVIEW_SCOPE = CoroutineScope(SupervisorJob() + Dispatchers.IO.limitedParallelism(1))
private val SCHEMATIC_HEADER = byteArrayOf('m'.code.toByte(), 's'.code.toByte(), 'c'.code.toByte(), 'h'.code.toByte())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ package com.xpdustry.imperium.discord.content
import mindustry.game.Schematic
import java.awt.image.BufferedImage
import java.io.InputStream
import java.io.OutputStream

interface MindustryContentHandler {
suspend fun getSchematic(stream: InputStream): Result<Schematic>
suspend fun getSchematic(string: String): Result<Schematic>
suspend fun getSchematicPreview(schematic: Schematic): Result<BufferedImage>
suspend fun writeSchematic(schematic: Schematic, output: OutputStream): Result<Unit>
suspend fun getMapMetadata(stream: InputStream): Result<MapMetadata>
suspend fun getMapMetadataWithPreview(stream: InputStream): Result<Pair<MapMetadata, BufferedImage>>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,16 @@ package com.xpdustry.imperium.discord.interaction.command.standard
import com.xpdustry.imperium.common.application.ImperiumApplication
import com.xpdustry.imperium.common.inject.InstanceManager
import com.xpdustry.imperium.common.inject.get
import com.xpdustry.imperium.common.misc.stripMindustryColors
import com.xpdustry.imperium.discord.content.MindustryContentHandler
import com.xpdustry.imperium.discord.interaction.InteractionActor
import com.xpdustry.imperium.discord.interaction.command.Command
import org.javacord.api.entity.Attachment
import org.javacord.api.entity.message.embed.EmbedBuilder
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import kotlin.random.Random
import kotlin.random.nextInt

class SchematicCommand(instances: InstanceManager) : ImperiumApplication.Listener {
private val content = instances.get<MindustryContentHandler>()
Expand All @@ -37,12 +42,21 @@ class SchematicCommand(instances: InstanceManager) : ImperiumApplication.Listene
return
}

actor.respond(
EmbedBuilder()
.setAuthor(actor.user)
.setTitle(result.getOrThrow().name())
.setImage(content.getSchematicPreview(result.getOrThrow()).getOrThrow()),
)
val parsed = result.getOrThrow()
val bytes = ByteArrayOutputStream()
content.writeSchematic(parsed, bytes).getOrThrow()

val name = "${parsed.name().stripMindustryColors()}_${Random.nextInt(1000..9999)}.msch"
actor.respond {
addAttachment(ByteArrayInputStream(bytes.toByteArray()), name)
addEmbed(
EmbedBuilder()
.setAuthor(actor.user)
.setTitle(parsed.name())
.setImage(content.getSchematicPreview(parsed).getOrThrow())
.setTimestampToNow(),
)
}
}

@Command("schematic", "file", ephemeral = false)
Expand All @@ -52,17 +66,33 @@ class SchematicCommand(instances: InstanceManager) : ImperiumApplication.Listene
return
}

if (file.size > MAX_FILE_SIZE) {
actor.respond("Schematic file is too large!")
return
}

val result = content.getSchematic(file.asInputStream())
if (result.isFailure) {
actor.respond("Failed to parse the schematic.")
return
}

actor.respond(
EmbedBuilder()
.setAuthor(actor.user)
.setTitle(result.getOrThrow().name())
.setImage(content.getSchematicPreview(result.getOrThrow()).getOrThrow()),
)
val parsed = result.getOrThrow()
val name = "${parsed.name().stripMindustryColors()}_${Random.nextInt(1000..9999)}.msch"
actor.respond {
addAttachment(file.asInputStream(), name)
addEmbed(
EmbedBuilder()
.setAuthor(actor.user)
.setTitle(parsed.name())
.setImage(content.getSchematicPreview(parsed).getOrThrow())
.setTimestampToNow(),
)
}
}

companion object {
// 2MB
private const val MAX_FILE_SIZE = 2 * 1024 * 1024
}
}

0 comments on commit 97d3df6

Please sign in to comment.