aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/moe/nea/firmament/gui
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2024-03-01 21:31:48 +0100
committerLinnea Gräf <nea@nea.moe>2024-03-02 19:43:00 +0100
commit3bfec3033e9d905514d5c1c6c62953c2a1646af0 (patch)
treeafee0b3c20d0bdef9780e5fddb368c9d58fb3c70 /src/main/kotlin/moe/nea/firmament/gui
parentf28dee0ef3a0dd4a0819a3d3a1c800a83a0f07f5 (diff)
downloadFirmament-3bfec3033e9d905514d5c1c6c62953c2a1646af0.tar.gz
Firmament-3bfec3033e9d905514d5c1c6c62953c2a1646af0.tar.bz2
Firmament-3bfec3033e9d905514d5c1c6c62953c2a1646af0.zip
Add mob drop viewer to item list
Diffstat (limited to 'src/main/kotlin/moe/nea/firmament/gui')
-rw-r--r--src/main/kotlin/moe/nea/firmament/gui/entity/EntityModifier.kt14
-rw-r--r--src/main/kotlin/moe/nea/firmament/gui/entity/EntityRenderer.kt202
-rw-r--r--src/main/kotlin/moe/nea/firmament/gui/entity/EntityWidget.kt40
-rw-r--r--src/main/kotlin/moe/nea/firmament/gui/entity/FakeWorld.kt491
-rw-r--r--src/main/kotlin/moe/nea/firmament/gui/entity/GuiPlayer.kt55
-rw-r--r--src/main/kotlin/moe/nea/firmament/gui/entity/ModifyAge.kt30
-rw-r--r--src/main/kotlin/moe/nea/firmament/gui/entity/ModifyCharged.kt19
-rw-r--r--src/main/kotlin/moe/nea/firmament/gui/entity/ModifyEquipment.kt59
-rw-r--r--src/main/kotlin/moe/nea/firmament/gui/entity/ModifyHorse.kt66
-rw-r--r--src/main/kotlin/moe/nea/firmament/gui/entity/ModifyInvisible.kt18
-rw-r--r--src/main/kotlin/moe/nea/firmament/gui/entity/ModifyName.kt19
-rw-r--r--src/main/kotlin/moe/nea/firmament/gui/entity/ModifyPlayerSkin.kt32
-rw-r--r--src/main/kotlin/moe/nea/firmament/gui/entity/ModifyRiding.kt20
-rw-r--r--src/main/kotlin/moe/nea/firmament/gui/entity/ModifyWither.kt25
14 files changed, 1090 insertions, 0 deletions
diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/EntityModifier.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/EntityModifier.kt
new file mode 100644
index 0000000..fae3a4b
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/gui/entity/EntityModifier.kt
@@ -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
+}
diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/EntityRenderer.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/EntityRenderer.kt
new file mode 100644
index 0000000..d645e5b
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/gui/entity/EntityRenderer.kt
@@ -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()
+ }
+
+
+}
diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/EntityWidget.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/EntityWidget.kt
new file mode 100644
index 0000000..42fa485
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/gui/entity/EntityWidget.kt
@@ -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))
+ }
+}
diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/FakeWorld.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/FakeWorld.kt
new file mode 100644
index 0000000..4cdfc45
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/gui/entity/FakeWorld.kt
@@ -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
+ }
+}
diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/GuiPlayer.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/GuiPlayer.kt
new file mode 100644
index 0000000..5f88098
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/gui/entity/GuiPlayer.kt
@@ -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
+ )
+ }
+}
diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyAge.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyAge.kt
new file mode 100644
index 0000000..7b80e88
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyAge.kt
@@ -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
+ }
+
+}
diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyCharged.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyCharged.kt
new file mode 100644
index 0000000..225a8ca
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyCharged.kt
@@ -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
+ }
+}
diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyEquipment.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyEquipment.kt
new file mode 100644
index 0000000..e438f59
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyEquipment.kt
@@ -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
+ }
+}
diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyHorse.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyHorse.kt
new file mode 100644
index 0000000..4c49510
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyHorse.kt
@@ -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)
+}
diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyInvisible.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyInvisible.kt
new file mode 100644
index 0000000..07c6617
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyInvisible.kt
@@ -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
+ }
+
+}
diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyName.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyName.kt
new file mode 100644
index 0000000..c74e2e5
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyName.kt
@@ -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
+ }
+
+}
diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyPlayerSkin.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyPlayerSkin.kt
new file mode 100644
index 0000000..886a17e
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyPlayerSkin.kt
@@ -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
+ }
+
+}
diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyRiding.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyRiding.kt
new file mode 100644
index 0000000..b9c462e
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyRiding.kt
@@ -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
+ }
+
+}
diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyWither.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyWither.kt
new file mode 100644
index 0000000..a0ddcdd
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyWither.kt
@@ -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
+ }
+
+}