Add mob drop viewer to item list
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package moe.nea.firmament.gui.entity
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import net.minecraft.entity.LivingEntity
|
||||
|
||||
fun interface EntityModifier {
|
||||
fun apply(entity: LivingEntity, info: JsonObject): LivingEntity
|
||||
}
|
||||
202
src/main/kotlin/moe/nea/firmament/gui/entity/EntityRenderer.kt
Normal file
202
src/main/kotlin/moe/nea/firmament/gui/entity/EntityRenderer.kt
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package moe.nea.firmament.gui.entity
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonObject
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.joml.Quaternionf
|
||||
import org.joml.Vector3f
|
||||
import kotlin.math.atan
|
||||
import net.minecraft.client.gui.DrawContext
|
||||
import net.minecraft.client.gui.screen.ingame.InventoryScreen
|
||||
import net.minecraft.entity.Entity
|
||||
import net.minecraft.entity.EntityType
|
||||
import net.minecraft.entity.LivingEntity
|
||||
import net.minecraft.util.Identifier
|
||||
import moe.nea.firmament.util.MC
|
||||
import moe.nea.firmament.util.assertNotNullOr
|
||||
import moe.nea.firmament.util.iterate
|
||||
import moe.nea.firmament.util.openFirmamentResource
|
||||
import moe.nea.firmament.util.render.enableScissorWithTranslation
|
||||
|
||||
object EntityRenderer {
|
||||
val fakeWorld = FakeWorld()
|
||||
private fun <T : Entity> t(entityType: EntityType<T>): () -> T {
|
||||
return { entityType.create(fakeWorld)!! }
|
||||
}
|
||||
|
||||
val entityIds: Map<String, () -> LivingEntity> = mapOf(
|
||||
"Zombie" to t(EntityType.ZOMBIE),
|
||||
"Chicken" to t(EntityType.CHICKEN),
|
||||
"Slime" to t(EntityType.SLIME),
|
||||
"Wolf" to t(EntityType.WOLF),
|
||||
"Skeleton" to t(EntityType.SKELETON),
|
||||
"Creeper" to t(EntityType.CREEPER),
|
||||
"Ocelot" to t(EntityType.OCELOT),
|
||||
"Blaze" to t(EntityType.BLAZE),
|
||||
"Rabbit" to t(EntityType.RABBIT),
|
||||
"Sheep" to t(EntityType.SHEEP),
|
||||
"Horse" to t(EntityType.HORSE),
|
||||
"Eisengolem" to t(EntityType.IRON_GOLEM),
|
||||
"Silverfish" to t(EntityType.SILVERFISH),
|
||||
"Witch" to t(EntityType.WITCH),
|
||||
"Endermite" to t(EntityType.ENDERMITE),
|
||||
"Snowman" to t(EntityType.SNOW_GOLEM),
|
||||
"Villager" to t(EntityType.VILLAGER),
|
||||
"Guardian" to t(EntityType.GUARDIAN),
|
||||
"ArmorStand" to t(EntityType.ARMOR_STAND),
|
||||
"Squid" to t(EntityType.SQUID),
|
||||
"Bat" to t(EntityType.BAT),
|
||||
"Spider" to t(EntityType.SPIDER),
|
||||
"CaveSpider" to t(EntityType.CAVE_SPIDER),
|
||||
"Pigman" to t(EntityType.ZOMBIFIED_PIGLIN),
|
||||
"Ghast" to t(EntityType.GHAST),
|
||||
"MagmaCube" to t(EntityType.MAGMA_CUBE),
|
||||
"Wither" to t(EntityType.WITHER),
|
||||
"Enderman" to t(EntityType.ENDERMAN),
|
||||
"Mooshroom" to t(EntityType.MOOSHROOM),
|
||||
"WitherSkeleton" to t(EntityType.WITHER_SKELETON),
|
||||
"Cow" to t(EntityType.COW),
|
||||
"Dragon" to t(EntityType.ENDER_DRAGON),
|
||||
"Player" to { makeGuiPlayer(fakeWorld) },
|
||||
"Pig" to t(EntityType.PIG),
|
||||
"Giant" to t(EntityType.GIANT),
|
||||
)
|
||||
val entityModifiers: Map<String, EntityModifier> = mapOf(
|
||||
"playerdata" to ModifyPlayerSkin,
|
||||
"equipment" to ModifyEquipment,
|
||||
"riding" to ModifyRiding,
|
||||
"charged" to ModifyCharged,
|
||||
"witherdata" to ModifyWither,
|
||||
"invisible" to ModifyInvisible,
|
||||
"age" to ModifyAge,
|
||||
"horse" to ModifyHorse,
|
||||
"name" to ModifyName,
|
||||
)
|
||||
|
||||
val logger = LogManager.getLogger("Firmament.Entity")
|
||||
fun applyModifiers(entityId: String, modifiers: List<JsonObject>): LivingEntity? {
|
||||
val entityType = assertNotNullOr(entityIds[entityId]) {
|
||||
logger.error("Could not create entity with id $entityId")
|
||||
return null
|
||||
}
|
||||
var entity = entityType()
|
||||
for (modifierJson in modifiers) {
|
||||
val modifier = assertNotNullOr(modifierJson["type"]?.asString?.let(entityModifiers::get)) {
|
||||
logger.error("Unknown modifier $modifierJson")
|
||||
return null
|
||||
}
|
||||
entity = modifier.apply(entity, modifierJson)
|
||||
}
|
||||
return entity
|
||||
}
|
||||
|
||||
fun constructEntity(info: JsonObject): LivingEntity? {
|
||||
val modifiers = (info["modifiers"] as JsonArray?)?.map { it.asJsonObject } ?: emptyList()
|
||||
val entityType = assertNotNullOr(info["entity"]?.asString) {
|
||||
logger.error("Missing entity type on entity object")
|
||||
return null
|
||||
}
|
||||
return applyModifiers(entityType, modifiers)
|
||||
}
|
||||
|
||||
private val gson = Gson()
|
||||
fun constructEntity(location: Identifier): LivingEntity? {
|
||||
return constructEntity(
|
||||
gson.fromJson(
|
||||
location.openFirmamentResource().bufferedReader(), JsonObject::class.java
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun renderEntity(
|
||||
entity: LivingEntity,
|
||||
renderContext: DrawContext,
|
||||
posX: Int,
|
||||
posY: Int,
|
||||
mouseX: Float,
|
||||
mouseY: Float
|
||||
) {
|
||||
var bottomOffset = 0.0F
|
||||
var currentEntity = entity
|
||||
val maxSize = entity.iterate { it.firstPassenger as? LivingEntity }
|
||||
.map { it.height }
|
||||
.sum()
|
||||
while (true) {
|
||||
currentEntity.age = MC.player?.age ?: 0
|
||||
drawEntity(
|
||||
renderContext,
|
||||
posX,
|
||||
posY,
|
||||
posX + 50,
|
||||
posY + 80,
|
||||
(2F / maxSize * 30).toInt(),
|
||||
-bottomOffset,
|
||||
mouseX,
|
||||
mouseY,
|
||||
currentEntity
|
||||
)
|
||||
val next = currentEntity.firstPassenger as? LivingEntity ?: break
|
||||
bottomOffset += currentEntity.getPassengerRidingPos(next).y.toFloat() * 0.75F
|
||||
currentEntity = next
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun drawEntity(
|
||||
context: DrawContext,
|
||||
x1: Int,
|
||||
y1: Int,
|
||||
x2: Int,
|
||||
y2: Int,
|
||||
size: Int,
|
||||
bottomOffset: Float,
|
||||
mouseX: Float,
|
||||
mouseY: Float,
|
||||
entity: LivingEntity
|
||||
) {
|
||||
context.enableScissorWithTranslation(x1.toFloat(), y1.toFloat(), x2.toFloat(), y2.toFloat())
|
||||
val centerX = (x1 + x2) / 2f
|
||||
val centerY = (y1 + y2) / 2f
|
||||
val targetYaw = atan(((centerX - mouseX) / 40.0f).toDouble()).toFloat()
|
||||
val targetPitch = atan(((centerY - mouseY) / 40.0f).toDouble()).toFloat()
|
||||
val rotateToFaceTheFront = Quaternionf().rotateZ(Math.PI.toFloat())
|
||||
val rotateToFaceTheCamera = Quaternionf().rotateX(targetPitch * 20.0f * (Math.PI.toFloat() / 180))
|
||||
rotateToFaceTheFront.mul(rotateToFaceTheCamera)
|
||||
val oldBodyYaw = entity.bodyYaw
|
||||
val oldYaw = entity.yaw
|
||||
val oldPitch = entity.pitch
|
||||
val oldPrevHeadYaw = entity.prevHeadYaw
|
||||
val oldHeadYaw = entity.headYaw
|
||||
entity.bodyYaw = 180.0f + targetYaw * 20.0f
|
||||
entity.yaw = 180.0f + targetYaw * 40.0f
|
||||
entity.pitch = -targetPitch * 20.0f
|
||||
entity.headYaw = entity.yaw
|
||||
entity.prevHeadYaw = entity.yaw
|
||||
val vector3f = Vector3f(0.0f, entity.height / 2.0f + bottomOffset, 0.0f)
|
||||
InventoryScreen.drawEntity(
|
||||
context,
|
||||
centerX,
|
||||
centerY,
|
||||
size,
|
||||
vector3f,
|
||||
rotateToFaceTheFront,
|
||||
rotateToFaceTheCamera,
|
||||
entity
|
||||
)
|
||||
entity.bodyYaw = oldBodyYaw
|
||||
entity.yaw = oldYaw
|
||||
entity.pitch = oldPitch
|
||||
entity.prevHeadYaw = oldPrevHeadYaw
|
||||
entity.headYaw = oldHeadYaw
|
||||
context.disableScissor()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
40
src/main/kotlin/moe/nea/firmament/gui/entity/EntityWidget.kt
Normal file
40
src/main/kotlin/moe/nea/firmament/gui/entity/EntityWidget.kt
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package moe.nea.firmament.gui.entity
|
||||
|
||||
import me.shedaniel.math.Dimension
|
||||
import me.shedaniel.math.Point
|
||||
import me.shedaniel.math.Rectangle
|
||||
import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds
|
||||
import net.minecraft.client.gui.DrawContext
|
||||
import net.minecraft.client.gui.Element
|
||||
import net.minecraft.entity.LivingEntity
|
||||
|
||||
class EntityWidget(val entity: LivingEntity, val point: Point) : WidgetWithBounds() {
|
||||
override fun children(): List<Element> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
var hasErrored = false
|
||||
|
||||
override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
try {
|
||||
if (!hasErrored)
|
||||
EntityRenderer.renderEntity(entity, context, point.x, point.y, mouseX.toFloat(), mouseY.toFloat())
|
||||
} catch (ex: Exception) {
|
||||
EntityRenderer.logger.error("Failed to render constructed entity: $entity", ex)
|
||||
hasErrored = true
|
||||
}
|
||||
if (hasErrored) {
|
||||
context.fill(point.x, point.y, point.x + 50, point.y + 80, 0xFFAA2222.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
override fun getBounds(): Rectangle {
|
||||
return Rectangle(point, Dimension(50, 80))
|
||||
}
|
||||
}
|
||||
491
src/main/kotlin/moe/nea/firmament/gui/entity/FakeWorld.kt
Normal file
491
src/main/kotlin/moe/nea/firmament/gui/entity/FakeWorld.kt
Normal file
@@ -0,0 +1,491 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package moe.nea.firmament.gui.entity
|
||||
|
||||
import com.mojang.datafixers.util.Pair
|
||||
import com.mojang.serialization.Lifecycle
|
||||
import java.util.*
|
||||
import java.util.function.BooleanSupplier
|
||||
import java.util.function.Consumer
|
||||
import java.util.stream.Stream
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import kotlin.streams.asSequence
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.client.world.ClientWorld
|
||||
import net.minecraft.entity.Entity
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.fluid.Fluid
|
||||
import net.minecraft.item.map.MapState
|
||||
import net.minecraft.recipe.RecipeManager
|
||||
import net.minecraft.registry.BuiltinRegistries
|
||||
import net.minecraft.registry.DynamicRegistryManager
|
||||
import net.minecraft.registry.Registry
|
||||
import net.minecraft.registry.RegistryKey
|
||||
import net.minecraft.registry.RegistryKeys
|
||||
import net.minecraft.registry.RegistryWrapper
|
||||
import net.minecraft.registry.entry.RegistryEntry
|
||||
import net.minecraft.registry.entry.RegistryEntryList
|
||||
import net.minecraft.registry.entry.RegistryEntryOwner
|
||||
import net.minecraft.registry.tag.TagKey
|
||||
import net.minecraft.resource.featuretoggle.FeatureFlag
|
||||
import net.minecraft.resource.featuretoggle.FeatureFlags
|
||||
import net.minecraft.resource.featuretoggle.FeatureSet
|
||||
import net.minecraft.scoreboard.Scoreboard
|
||||
import net.minecraft.sound.SoundCategory
|
||||
import net.minecraft.sound.SoundEvent
|
||||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.TypeFilter
|
||||
import net.minecraft.util.function.LazyIterationConsumer
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.Box
|
||||
import net.minecraft.util.math.ChunkPos
|
||||
import net.minecraft.util.math.Direction
|
||||
import net.minecraft.util.math.Vec3d
|
||||
import net.minecraft.util.math.random.Random
|
||||
import net.minecraft.util.profiler.DummyProfiler
|
||||
import net.minecraft.world.BlockView
|
||||
import net.minecraft.world.Difficulty
|
||||
import net.minecraft.world.GameRules
|
||||
import net.minecraft.world.MutableWorldProperties
|
||||
import net.minecraft.world.World
|
||||
import net.minecraft.world.biome.Biome
|
||||
import net.minecraft.world.biome.BiomeKeys
|
||||
import net.minecraft.world.chunk.Chunk
|
||||
import net.minecraft.world.chunk.ChunkManager
|
||||
import net.minecraft.world.chunk.ChunkStatus
|
||||
import net.minecraft.world.chunk.EmptyChunk
|
||||
import net.minecraft.world.chunk.light.LightingProvider
|
||||
import net.minecraft.world.entity.EntityLookup
|
||||
import net.minecraft.world.event.GameEvent
|
||||
import net.minecraft.world.tick.OrderedTick
|
||||
import net.minecraft.world.tick.QueryableTickScheduler
|
||||
import net.minecraft.world.tick.TickManager
|
||||
|
||||
fun <T> makeRegistry(registryWrapper: RegistryWrapper.Impl<T>, key: RegistryKey<out Registry<T>>): Registry<T> {
|
||||
val inverseLookup = registryWrapper.streamEntries()
|
||||
.asSequence().map { it.value() to it.registryKey() }
|
||||
.toMap()
|
||||
val idLookup = registryWrapper.streamEntries()
|
||||
.asSequence()
|
||||
.map { it.registryKey() }
|
||||
.withIndex()
|
||||
.associate { it.value to it.index }
|
||||
val map = registryWrapper.streamEntries().asSequence().map { it.registryKey() to it.value() }.toMap(mutableMapOf())
|
||||
val inverseIdLookup = idLookup.asIterable().associate { (k, v) -> v to k }
|
||||
return object : Registry<T> {
|
||||
override fun get(key: RegistryKey<T>?): T? {
|
||||
return registryWrapper.getOptional(key).getOrNull()?.value()
|
||||
}
|
||||
|
||||
override fun iterator(): MutableIterator<T> {
|
||||
return object : MutableIterator<T> {
|
||||
val iterator = registryWrapper.streamEntries().iterator()
|
||||
override fun hasNext(): Boolean {
|
||||
return iterator.hasNext()
|
||||
}
|
||||
|
||||
override fun next(): T {
|
||||
return iterator.next().value()
|
||||
}
|
||||
|
||||
override fun remove() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getRawId(value: T?): Int {
|
||||
return idLookup[inverseLookup[value ?: return -1] ?: return -1] ?: return -1
|
||||
}
|
||||
|
||||
override fun get(id: Identifier?): T? {
|
||||
return get(RegistryKey.of(key, id))
|
||||
}
|
||||
|
||||
override fun get(index: Int): T? {
|
||||
return get(inverseIdLookup[index] ?: return null)
|
||||
}
|
||||
|
||||
override fun size(): Int {
|
||||
return idLookup.size
|
||||
}
|
||||
|
||||
override fun getKey(): RegistryKey<out Registry<T>> {
|
||||
return key
|
||||
}
|
||||
|
||||
override fun getLifecycle(): Lifecycle {
|
||||
return Lifecycle.stable()
|
||||
}
|
||||
|
||||
override fun getIds(): MutableSet<Identifier> {
|
||||
return idLookup.keys.mapTo(mutableSetOf()) { it.value }
|
||||
}
|
||||
|
||||
override fun getEntrySet(): MutableSet<MutableMap.MutableEntry<RegistryKey<T>, T>> {
|
||||
return map.entries
|
||||
}
|
||||
|
||||
override fun getKeys(): MutableSet<RegistryKey<T>> {
|
||||
return map.keys
|
||||
}
|
||||
|
||||
override fun getRandom(random: Random?): Optional<RegistryEntry.Reference<T>> {
|
||||
return registryWrapper.streamEntries().findFirst()
|
||||
}
|
||||
|
||||
override fun containsId(id: Identifier?): Boolean {
|
||||
return idLookup.containsKey(RegistryKey.of(key, id ?: return false))
|
||||
}
|
||||
|
||||
override fun freeze(): Registry<T> {
|
||||
return this
|
||||
}
|
||||
|
||||
override fun getEntry(rawId: Int): Optional<RegistryEntry.Reference<T>> {
|
||||
val x = inverseIdLookup[rawId] ?: return Optional.empty()
|
||||
return Optional.of(RegistryEntry.Reference.standAlone(registryWrapper, x))
|
||||
}
|
||||
|
||||
override fun streamEntries(): Stream<RegistryEntry.Reference<T>> {
|
||||
return registryWrapper.streamEntries()
|
||||
}
|
||||
|
||||
override fun streamTagsAndEntries(): Stream<Pair<TagKey<T>, RegistryEntryList.Named<T>>> {
|
||||
return streamTags().map { Pair(it, getOrCreateEntryList(it)) }
|
||||
}
|
||||
|
||||
override fun streamTags(): Stream<TagKey<T>> {
|
||||
return registryWrapper.streamTagKeys()
|
||||
}
|
||||
|
||||
override fun clearTags() {
|
||||
}
|
||||
|
||||
override fun getEntryOwner(): RegistryEntryOwner<T> {
|
||||
return registryWrapper
|
||||
}
|
||||
|
||||
override fun getReadOnlyWrapper(): RegistryWrapper.Impl<T> {
|
||||
return registryWrapper
|
||||
}
|
||||
|
||||
override fun populateTags(tagEntries: MutableMap<TagKey<T>, MutableList<RegistryEntry<T>>>?) {
|
||||
}
|
||||
|
||||
override fun getOrCreateEntryList(tag: TagKey<T>?): RegistryEntryList.Named<T> {
|
||||
return getEntryList(tag).orElseGet { RegistryEntryList.of(registryWrapper, tag) }
|
||||
}
|
||||
|
||||
override fun getEntryList(tag: TagKey<T>?): Optional<RegistryEntryList.Named<T>> {
|
||||
return registryWrapper.getOptional(tag ?: return Optional.empty())
|
||||
}
|
||||
|
||||
override fun getEntry(value: T): RegistryEntry<T> {
|
||||
return registryWrapper.getOptional(inverseLookup[value]!!).get()
|
||||
}
|
||||
|
||||
override fun getEntry(key: RegistryKey<T>?): Optional<RegistryEntry.Reference<T>> {
|
||||
return registryWrapper.getOptional(key ?: return Optional.empty())
|
||||
}
|
||||
|
||||
override fun createEntry(value: T): RegistryEntry.Reference<T> {
|
||||
TODO()
|
||||
}
|
||||
|
||||
override fun contains(key: RegistryKey<T>?): Boolean {
|
||||
return getEntry(key).isPresent
|
||||
}
|
||||
|
||||
override fun getEntryLifecycle(entry: T): Lifecycle {
|
||||
return Lifecycle.stable()
|
||||
}
|
||||
|
||||
override fun getId(value: T): Identifier? {
|
||||
return (inverseLookup[value] ?: return null).value
|
||||
}
|
||||
|
||||
override fun getKey(entry: T): Optional<RegistryKey<T>> {
|
||||
return Optional.ofNullable(inverseLookup[entry ?: return Optional.empty()])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun createDynamicRegistry(): DynamicRegistryManager.Immutable {
|
||||
val wrapperLookup = BuiltinRegistries.createWrapperLookup()
|
||||
return object : DynamicRegistryManager.Immutable {
|
||||
override fun <E : Any?> getOptional(key: RegistryKey<out Registry<out E>>): Optional<Registry<E>> {
|
||||
val lookup = wrapperLookup.getOptionalWrapper(key).getOrNull() ?: return Optional.empty()
|
||||
val registry = makeRegistry(lookup, key as RegistryKey<out Registry<E>>)
|
||||
return Optional.of(registry)
|
||||
}
|
||||
|
||||
fun <T> entry(reg: RegistryKey<out Registry<T>>): DynamicRegistryManager.Entry<T> {
|
||||
return DynamicRegistryManager.Entry(reg, getOptional(reg).get())
|
||||
}
|
||||
|
||||
override fun streamAllRegistries(): Stream<DynamicRegistryManager.Entry<*>> {
|
||||
return wrapperLookup.streamAllRegistryKeys()
|
||||
.map { entry(it as RegistryKey<out Registry<Any>>) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FakeWorld(registries: DynamicRegistryManager.Immutable = createDynamicRegistry()) : World(
|
||||
Properties,
|
||||
RegistryKey.of(RegistryKeys.WORLD, Identifier.of("firmament", "fakeworld")),
|
||||
registries,
|
||||
registries[RegistryKeys.DIMENSION_TYPE].entryOf(
|
||||
RegistryKey.of(
|
||||
RegistryKeys.DIMENSION_TYPE,
|
||||
Identifier("minecraft", "overworld")
|
||||
)
|
||||
),
|
||||
{ DummyProfiler.INSTANCE },
|
||||
true,
|
||||
false,
|
||||
0, 0
|
||||
) {
|
||||
object Properties : MutableWorldProperties {
|
||||
override fun getSpawnX(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun getSpawnY(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun getSpawnZ(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun getSpawnAngle(): Float {
|
||||
return 0F
|
||||
}
|
||||
|
||||
override fun getTime(): Long {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun getTimeOfDay(): Long {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun isThundering(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun isRaining(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun setRaining(raining: Boolean) {
|
||||
}
|
||||
|
||||
override fun isHardcore(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun getGameRules(): GameRules {
|
||||
return GameRules()
|
||||
}
|
||||
|
||||
override fun getDifficulty(): Difficulty {
|
||||
return Difficulty.HARD
|
||||
}
|
||||
|
||||
override fun isDifficultyLocked(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun setSpawnX(spawnX: Int) {
|
||||
}
|
||||
|
||||
override fun setSpawnY(spawnY: Int) {
|
||||
}
|
||||
|
||||
override fun setSpawnZ(spawnZ: Int) {
|
||||
}
|
||||
|
||||
override fun setSpawnAngle(spawnAngle: Float) {
|
||||
}
|
||||
}
|
||||
|
||||
override fun getPlayers(): List<PlayerEntity> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun getBrightness(direction: Direction?, shaded: Boolean): Float {
|
||||
return 1f
|
||||
}
|
||||
|
||||
override fun getGeneratorStoredBiome(biomeX: Int, biomeY: Int, biomeZ: Int): RegistryEntry<Biome> {
|
||||
return registryManager.get(RegistryKeys.BIOME).entryOf(BiomeKeys.PLAINS)
|
||||
}
|
||||
|
||||
override fun getEnabledFeatures(): FeatureSet {
|
||||
return FeatureFlags.VANILLA_FEATURES
|
||||
}
|
||||
|
||||
class FakeTickScheduler<T> : QueryableTickScheduler<T> {
|
||||
override fun scheduleTick(orderedTick: OrderedTick<T>?) {
|
||||
}
|
||||
|
||||
override fun isQueued(pos: BlockPos?, type: T): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getTickCount(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun isTicking(pos: BlockPos?, type: T): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun getBlockTickScheduler(): QueryableTickScheduler<Block> {
|
||||
return FakeTickScheduler()
|
||||
}
|
||||
|
||||
override fun getFluidTickScheduler(): QueryableTickScheduler<Fluid> {
|
||||
return FakeTickScheduler()
|
||||
}
|
||||
|
||||
|
||||
class FakeChunkManager(val world: FakeWorld) : ChunkManager() {
|
||||
override fun getChunk(x: Int, z: Int, leastStatus: ChunkStatus?, create: Boolean): Chunk {
|
||||
return EmptyChunk(world, ChunkPos(x,z), world.registryManager.get(RegistryKeys.BIOME).entryOf(BiomeKeys.PLAINS))
|
||||
}
|
||||
|
||||
override fun getWorld(): BlockView {
|
||||
return world
|
||||
}
|
||||
|
||||
override fun tick(shouldKeepTicking: BooleanSupplier?, tickChunks: Boolean) {
|
||||
}
|
||||
|
||||
override fun getDebugString(): String {
|
||||
return "FakeChunkManager"
|
||||
}
|
||||
|
||||
override fun getLoadedChunkCount(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun getLightingProvider(): LightingProvider {
|
||||
return FakeLightingProvider(this)
|
||||
}
|
||||
}
|
||||
|
||||
class FakeLightingProvider(chunkManager: FakeChunkManager) : LightingProvider(chunkManager, false, false)
|
||||
|
||||
override fun getChunkManager(): ChunkManager {
|
||||
return FakeChunkManager(this)
|
||||
}
|
||||
|
||||
override fun playSound(
|
||||
source: PlayerEntity?,
|
||||
x: Double,
|
||||
y: Double,
|
||||
z: Double,
|
||||
sound: RegistryEntry<SoundEvent>?,
|
||||
category: SoundCategory?,
|
||||
volume: Float,
|
||||
pitch: Float,
|
||||
seed: Long
|
||||
) {
|
||||
}
|
||||
|
||||
override fun syncWorldEvent(player: PlayerEntity?, eventId: Int, pos: BlockPos?, data: Int) {
|
||||
}
|
||||
|
||||
override fun emitGameEvent(event: GameEvent?, emitterPos: Vec3d?, emitter: GameEvent.Emitter?) {
|
||||
}
|
||||
|
||||
override fun updateListeners(pos: BlockPos?, oldState: BlockState?, newState: BlockState?, flags: Int) {
|
||||
}
|
||||
|
||||
override fun playSoundFromEntity(
|
||||
source: PlayerEntity?,
|
||||
entity: Entity?,
|
||||
sound: RegistryEntry<SoundEvent>?,
|
||||
category: SoundCategory?,
|
||||
volume: Float,
|
||||
pitch: Float,
|
||||
seed: Long
|
||||
) {
|
||||
}
|
||||
|
||||
override fun asString(): String {
|
||||
return "FakeWorld"
|
||||
}
|
||||
|
||||
override fun getEntityById(id: Int): Entity? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getTickManager(): TickManager {
|
||||
return TickManager()
|
||||
}
|
||||
|
||||
override fun getMapState(id: String?): MapState? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun putMapState(id: String?, state: MapState?) {
|
||||
}
|
||||
|
||||
override fun getNextMapId(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun setBlockBreakingInfo(entityId: Int, pos: BlockPos?, progress: Int) {
|
||||
}
|
||||
|
||||
override fun getScoreboard(): Scoreboard {
|
||||
return Scoreboard()
|
||||
}
|
||||
|
||||
override fun getRecipeManager(): RecipeManager {
|
||||
return RecipeManager()
|
||||
}
|
||||
|
||||
object FakeEntityLookup : EntityLookup<Entity> {
|
||||
override fun get(id: Int): Entity? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun get(uuid: UUID?): Entity? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun iterate(): MutableIterable<Entity> {
|
||||
return mutableListOf()
|
||||
}
|
||||
|
||||
override fun <U : Entity?> forEachIntersects(
|
||||
filter: TypeFilter<Entity, U>?,
|
||||
box: Box?,
|
||||
consumer: LazyIterationConsumer<U>?
|
||||
) {
|
||||
}
|
||||
|
||||
override fun forEachIntersects(box: Box?, action: Consumer<Entity>?) {
|
||||
}
|
||||
|
||||
override fun <U : Entity?> forEach(filter: TypeFilter<Entity, U>?, consumer: LazyIterationConsumer<U>?) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun getEntityLookup(): EntityLookup<Entity> {
|
||||
return FakeEntityLookup
|
||||
}
|
||||
}
|
||||
55
src/main/kotlin/moe/nea/firmament/gui/entity/GuiPlayer.kt
Normal file
55
src/main/kotlin/moe/nea/firmament/gui/entity/GuiPlayer.kt
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package moe.nea.firmament.gui.entity
|
||||
|
||||
import com.mojang.authlib.GameProfile
|
||||
import java.util.*
|
||||
import net.minecraft.client.network.AbstractClientPlayerEntity
|
||||
import net.minecraft.client.util.DefaultSkinHelper
|
||||
import net.minecraft.client.util.SkinTextures
|
||||
import net.minecraft.client.util.SkinTextures.Model
|
||||
import net.minecraft.client.world.ClientWorld
|
||||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.world.World
|
||||
|
||||
/**
|
||||
* @see moe.nea.firmament.init.EarlyRiser
|
||||
*/
|
||||
fun makeGuiPlayer(world: FakeWorld): GuiPlayer {
|
||||
val constructor = GuiPlayer::class.java.getDeclaredConstructor(
|
||||
World::class.java,
|
||||
BlockPos::class.java,
|
||||
Float::class.javaPrimitiveType,
|
||||
GameProfile::class.java
|
||||
)
|
||||
return constructor.newInstance(world, BlockPos.ORIGIN, 0F, GameProfile(UUID.randomUUID(), "Linnea"))
|
||||
}
|
||||
|
||||
class GuiPlayer(world: ClientWorld?, profile: GameProfile?) : AbstractClientPlayerEntity(world, profile) {
|
||||
override fun isSpectator(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun isCreative(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
var skinTexture: Identifier = DefaultSkinHelper.getSkinTextures(this.getUuid()).texture
|
||||
var capeTexture: Identifier? = null
|
||||
var model: Model = Model.WIDE
|
||||
override fun getSkinTextures(): SkinTextures {
|
||||
return SkinTextures(
|
||||
skinTexture,
|
||||
null,
|
||||
capeTexture,
|
||||
null,
|
||||
model,
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
30
src/main/kotlin/moe/nea/firmament/gui/entity/ModifyAge.kt
Normal file
30
src/main/kotlin/moe/nea/firmament/gui/entity/ModifyAge.kt
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package moe.nea.firmament.gui.entity
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import net.minecraft.entity.LivingEntity
|
||||
import net.minecraft.entity.decoration.ArmorStandEntity
|
||||
import net.minecraft.entity.mob.ZombieEntity
|
||||
import net.minecraft.entity.passive.PassiveEntity
|
||||
|
||||
object ModifyAge : EntityModifier {
|
||||
override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity {
|
||||
val isBaby = info["baby"]?.asBoolean ?: false
|
||||
if (entity is PassiveEntity) {
|
||||
entity.breedingAge = if (isBaby) -1 else 1
|
||||
} else if (entity is ZombieEntity) {
|
||||
entity.isBaby = isBaby
|
||||
} else if (entity is ArmorStandEntity) {
|
||||
entity.isSmall = isBaby
|
||||
} else {
|
||||
error("Cannot set age for $entity")
|
||||
}
|
||||
return entity
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package moe.nea.firmament.gui.entity
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import net.minecraft.entity.LivingEntity
|
||||
import net.minecraft.entity.mob.CreeperEntity
|
||||
|
||||
object ModifyCharged : EntityModifier {
|
||||
override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity {
|
||||
require(entity is CreeperEntity)
|
||||
entity.dataTracker.set(CreeperEntity.CHARGED, true)
|
||||
return entity
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package moe.nea.firmament.gui.entity
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import net.minecraft.entity.EquipmentSlot
|
||||
import net.minecraft.entity.LivingEntity
|
||||
import net.minecraft.item.DyeableArmorItem
|
||||
import net.minecraft.item.Item
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.item.Items
|
||||
import moe.nea.firmament.rei.SBItemStack
|
||||
import moe.nea.firmament.util.SkyblockId
|
||||
import moe.nea.firmament.util.item.setEncodedSkullOwner
|
||||
import moe.nea.firmament.util.item.zeroUUID
|
||||
|
||||
object ModifyEquipment : EntityModifier {
|
||||
val names = mapOf(
|
||||
"hand" to EquipmentSlot.MAINHAND,
|
||||
"helmet" to EquipmentSlot.HEAD,
|
||||
"chestplate" to EquipmentSlot.CHEST,
|
||||
"leggings" to EquipmentSlot.LEGS,
|
||||
"feet" to EquipmentSlot.FEET,
|
||||
)
|
||||
|
||||
override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity {
|
||||
names.forEach { (key, slot) ->
|
||||
info[key]?.let {
|
||||
entity.equipStack(slot, createItem(it.asString))
|
||||
}
|
||||
}
|
||||
return entity
|
||||
}
|
||||
|
||||
private fun createItem(item: String): ItemStack {
|
||||
val split = item.split("#")
|
||||
if (split.size != 2) return SBItemStack(SkyblockId(item)).asImmutableItemStack()
|
||||
val (type, data) = split
|
||||
return when (type) {
|
||||
"SKULL" -> ItemStack(Items.PLAYER_HEAD).also { it.setEncodedSkullOwner(zeroUUID, data) }
|
||||
"LEATHER_LEGGINGS" -> coloredLeatherArmor(Items.LEATHER_LEGGINGS, data)
|
||||
"LEATHER_BOOTS" -> coloredLeatherArmor(Items.LEATHER_BOOTS, data)
|
||||
"LEATHER_HELMET" -> coloredLeatherArmor(Items.LEATHER_HELMET, data)
|
||||
"LEATHER_CHESTPLATE" -> coloredLeatherArmor(Items.LEATHER_CHESTPLATE, data)
|
||||
else -> error("Unknown leather piece: $type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun coloredLeatherArmor(leatherArmor: Item, data: String): ItemStack {
|
||||
require(leatherArmor is DyeableArmorItem)
|
||||
val stack = ItemStack(leatherArmor)
|
||||
leatherArmor.setColor(stack, data.toInt(16))
|
||||
return stack
|
||||
}
|
||||
}
|
||||
66
src/main/kotlin/moe/nea/firmament/gui/entity/ModifyHorse.kt
Normal file
66
src/main/kotlin/moe/nea/firmament/gui/entity/ModifyHorse.kt
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package moe.nea.firmament.gui.entity
|
||||
|
||||
import com.google.gson.JsonNull
|
||||
import com.google.gson.JsonObject
|
||||
import kotlin.experimental.and
|
||||
import kotlin.experimental.inv
|
||||
import kotlin.experimental.or
|
||||
import net.minecraft.entity.EntityType
|
||||
import net.minecraft.entity.LivingEntity
|
||||
import net.minecraft.entity.passive.AbstractHorseEntity
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.item.Items
|
||||
import moe.nea.firmament.gui.entity.EntityRenderer.fakeWorld
|
||||
|
||||
object ModifyHorse : EntityModifier {
|
||||
override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity {
|
||||
require(entity is AbstractHorseEntity)
|
||||
var entity: AbstractHorseEntity = entity
|
||||
info["kind"]?.let {
|
||||
entity = when (it.asString) {
|
||||
"skeleton" -> EntityType.SKELETON_HORSE.create(fakeWorld)!!
|
||||
"zombie" -> EntityType.ZOMBIE_HORSE.create(fakeWorld)!!
|
||||
"mule" -> EntityType.MULE.create(fakeWorld)!!
|
||||
"donkey" -> EntityType.DONKEY.create(fakeWorld)!!
|
||||
"horse" -> EntityType.HORSE.create(fakeWorld)!!
|
||||
else -> error("Unknown horse kind $it")
|
||||
}
|
||||
}
|
||||
info["armor"]?.let {
|
||||
if (it is JsonNull) {
|
||||
entity.setHorseArmor(ItemStack.EMPTY)
|
||||
} else {
|
||||
when (it.asString) {
|
||||
"iron" -> entity.setHorseArmor(ItemStack(Items.IRON_HORSE_ARMOR))
|
||||
"golden" -> entity.setHorseArmor(ItemStack(Items.GOLDEN_HORSE_ARMOR))
|
||||
"diamond" -> entity.setHorseArmor(ItemStack(Items.DIAMOND_HORSE_ARMOR))
|
||||
else -> error("Unknown horse armor $it")
|
||||
}
|
||||
}
|
||||
}
|
||||
info["saddled"]?.let {
|
||||
entity.setIsSaddled(it.asBoolean)
|
||||
}
|
||||
return entity
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun AbstractHorseEntity.setIsSaddled(shouldBeSaddled: Boolean) {
|
||||
val oldFlag = dataTracker.get(AbstractHorseEntity.HORSE_FLAGS)
|
||||
dataTracker.set(
|
||||
AbstractHorseEntity.HORSE_FLAGS,
|
||||
if (shouldBeSaddled) oldFlag or AbstractHorseEntity.SADDLED_FLAG.toByte()
|
||||
else oldFlag and AbstractHorseEntity.SADDLED_FLAG.toByte().inv()
|
||||
)
|
||||
}
|
||||
|
||||
fun AbstractHorseEntity.setHorseArmor(itemStack: ItemStack) {
|
||||
items.setStack(1, itemStack)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package moe.nea.firmament.gui.entity
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import net.minecraft.entity.LivingEntity
|
||||
|
||||
object ModifyInvisible : EntityModifier {
|
||||
override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity {
|
||||
entity.isInvisible = info.get("invisible")?.asBoolean ?: true
|
||||
return entity
|
||||
}
|
||||
|
||||
}
|
||||
19
src/main/kotlin/moe/nea/firmament/gui/entity/ModifyName.kt
Normal file
19
src/main/kotlin/moe/nea/firmament/gui/entity/ModifyName.kt
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package moe.nea.firmament.gui.entity
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import net.minecraft.entity.LivingEntity
|
||||
import net.minecraft.text.Text
|
||||
|
||||
object ModifyName : EntityModifier {
|
||||
override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity {
|
||||
entity.customName = Text.literal(info.get("name").asString)
|
||||
return entity
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package moe.nea.firmament.gui.entity
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import net.minecraft.client.util.SkinTextures
|
||||
import net.minecraft.entity.LivingEntity
|
||||
import net.minecraft.util.Identifier
|
||||
|
||||
object ModifyPlayerSkin : EntityModifier {
|
||||
override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity {
|
||||
require(entity is GuiPlayer)
|
||||
info["cape"]?.let {
|
||||
entity.capeTexture = Identifier(it.asString)
|
||||
}
|
||||
info["skin"]?.let {
|
||||
entity.skinTexture = Identifier(it.asString)
|
||||
}
|
||||
info["slim"]?.let {
|
||||
entity.model = if (it.asBoolean) SkinTextures.Model.SLIM else SkinTextures.Model.WIDE
|
||||
}
|
||||
info["parts"]?.let {
|
||||
// TODO: support parts
|
||||
}
|
||||
return entity
|
||||
}
|
||||
|
||||
}
|
||||
20
src/main/kotlin/moe/nea/firmament/gui/entity/ModifyRiding.kt
Normal file
20
src/main/kotlin/moe/nea/firmament/gui/entity/ModifyRiding.kt
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package moe.nea.firmament.gui.entity
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import net.minecraft.entity.LivingEntity
|
||||
|
||||
object ModifyRiding : EntityModifier {
|
||||
override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity {
|
||||
val newEntity = EntityRenderer.constructEntity(info)
|
||||
require(newEntity != null)
|
||||
newEntity.startRiding(entity, true)
|
||||
return entity
|
||||
}
|
||||
|
||||
}
|
||||
25
src/main/kotlin/moe/nea/firmament/gui/entity/ModifyWither.kt
Normal file
25
src/main/kotlin/moe/nea/firmament/gui/entity/ModifyWither.kt
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package moe.nea.firmament.gui.entity
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import net.minecraft.entity.LivingEntity
|
||||
import net.minecraft.entity.boss.WitherEntity
|
||||
|
||||
object ModifyWither : EntityModifier {
|
||||
override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity {
|
||||
require(entity is WitherEntity)
|
||||
info["tiny"]?.let {
|
||||
entity.setInvulTimer(if (it.asBoolean) 800 else 0)
|
||||
}
|
||||
info["armored"]?.let {
|
||||
entity.health = if (it.asBoolean) 1F else entity.maxHealth
|
||||
}
|
||||
return entity
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user