feat: swarm projected entity

This commit is contained in:
xtex 2023-07-02 10:19:28 +08:00
parent b3743bf624
commit 23b30a1084
Signed by: xtex
GPG Key ID: B918086ED8045B91
10 changed files with 5290 additions and 13 deletions

7
Makefile Normal file
View File

@ -0,0 +1,7 @@
common/src/main/resources/data/quaedam/projected-person-names:
curl -Ls https://github.com/wainshine/Chinese-Names-Corpus/raw/master/English_Names_Corpus/English_Names_Corpus%EF%BC%882W%EF%BC%89.txt | tail -n +4 | shuf | head -n 2500 > $@.tmp
curl -Ls https://github.com/wainshine/Chinese-Names-Corpus/raw/master/Chinese_Names_Corpus/Chinese_Names_Corpus%EF%BC%88120W%EF%BC%89.txt | tail -n +4 | shuf | head -n 2500 >> $@.tmp
cat $@.tmp | tr -d '\015' > $@
rm $@.tmp
.PHONY: common/src/main/resources/data/quaedam/projected-person-names

View File

@ -8,6 +8,7 @@ import net.minecraft.network.chat.Component
import net.minecraft.world.item.CreativeModeTab import net.minecraft.world.item.CreativeModeTab
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items import net.minecraft.world.item.Items
import org.slf4j.LoggerFactory
import quaedam.projection.ProjectionEffectType import quaedam.projection.ProjectionEffectType
import quaedam.projection.SkylightProjection import quaedam.projection.SkylightProjection
import quaedam.projection.swarm.SwarmProjection import quaedam.projection.swarm.SwarmProjection
@ -17,10 +18,13 @@ object Quaedam {
const val ID = "quaedam" const val ID = "quaedam"
val logger = LoggerFactory.getLogger("Quaedam")
val creativeModeTabs = DeferredRegister.create(ID, Registries.CREATIVE_MODE_TAB)!! val creativeModeTabs = DeferredRegister.create(ID, Registries.CREATIVE_MODE_TAB)!!
val items = DeferredRegister.create(ID, Registries.ITEM)!! val items = DeferredRegister.create(ID, Registries.ITEM)!!
val blocks = DeferredRegister.create(ID, Registries.BLOCK)!! val blocks = DeferredRegister.create(ID, Registries.BLOCK)!!
val blockEntities = DeferredRegister.create(ID, Registries.BLOCK_ENTITY_TYPE)!! val blockEntities = DeferredRegister.create(ID, Registries.BLOCK_ENTITY_TYPE)!!
val entities = DeferredRegister.create(ID, Registries.ENTITY_TYPE)!!
val projectionEffects = DeferredRegister.create(ID, ProjectionEffectType.registryKey)!! val projectionEffects = DeferredRegister.create(ID, ProjectionEffectType.registryKey)!!
val creativeModeTab: RegistrySupplier<CreativeModeTab> = creativeModeTabs.register("quaedam") { val creativeModeTab: RegistrySupplier<CreativeModeTab> = creativeModeTabs.register("quaedam") {
@ -38,6 +42,7 @@ object Quaedam {
items.register() items.register()
blocks.register() blocks.register()
blockEntities.register() blockEntities.register()
entities.register()
projectionEffects.register() projectionEffects.register()
} }

View File

@ -28,8 +28,6 @@ abstract class ProjectionEffect {
open fun deactivate(level: Level, pos: BlockPos) {} open fun deactivate(level: Level, pos: BlockPos) {}
open fun update(level: Level, pos: BlockPos, old: ProjectionEffect) {}
open fun randomTick(level: ServerLevel, pos: BlockPos) {} open fun randomTick(level: ServerLevel, pos: BlockPos) {}
} }

View File

@ -0,0 +1,82 @@
package quaedam.projection.swarm
import dev.architectury.registry.client.level.entity.EntityRendererRegistry
import dev.architectury.registry.level.entity.EntityAttributeRegistry
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.syncher.EntityDataAccessor
import net.minecraft.network.syncher.EntityDataSerializers
import net.minecraft.network.syncher.SynchedEntityData
import net.minecraft.world.DifficultyInstance
import net.minecraft.world.entity.*
import net.minecraft.world.entity.ai.attributes.AttributeSupplier
import net.minecraft.world.entity.ai.attributes.Attributes
import net.minecraft.world.level.Level
import net.minecraft.world.level.ServerLevelAccessor
import quaedam.Quaedam
class ProjectedPersonEntity(entityType: EntityType<out PathfinderMob>, level: Level) :
PathfinderMob(entityType, level) {
companion object {
const val ID = "projected_person"
val entity = Quaedam.entities.register(ID) {
EntityType.Builder.of(::ProjectedPersonEntity, MobCategory.CREATURE)
.canSpawnFarFromPlayer()
.sized(2.0f, 2.0f)
.build("quaedam:$ID")
}!!
val dataShape =
SynchedEntityData.defineId(ProjectedPersonEntity::class.java, EntityDataSerializers.COMPOUND_TAG)
private fun createAttributes(): AttributeSupplier.Builder = Mob.createMobAttributes()
.add(Attributes.ATTACK_DAMAGE, 1.5)
.add(Attributes.MOVEMENT_SPEED, 0.11)
.add(Attributes.ATTACK_SPEED)
init {
EntityAttributeRegistry.register(entity, ::createAttributes)
EntityRendererRegistry.register(entity, ::ProjectedPersonRenderer)
ProjectedPersonShape
}
}
override fun finalizeSpawn(
serverLevelAccessor: ServerLevelAccessor,
difficultyInstance: DifficultyInstance,
mobSpawnType: MobSpawnType,
spawnGroupData: SpawnGroupData?,
compoundTag: CompoundTag?
): SpawnGroupData? {
shape = ProjectedPersonShape.create(serverLevelAccessor.random.nextLong())
return super.finalizeSpawn(serverLevelAccessor, difficultyInstance, mobSpawnType, spawnGroupData, compoundTag)
}
override fun defineSynchedData() {
super.defineSynchedData()
entityData.define(dataShape, CompoundTag())
}
private var shapeTag
get() = entityData.get(dataShape)
set(value) = entityData.set(dataShape, value)
var shape = ProjectedPersonShape()
set(value) {
field = value
shapeTag = shape.toTag()
}
override fun onSyncedDataUpdated(data: EntityDataAccessor<*>) {
if (data == dataShape) {
shape = ProjectedPersonShape.fromTag(shapeTag)
}
super.onSyncedDataUpdated(data)
}
override fun shouldShowName() = true
}

View File

@ -0,0 +1,30 @@
package quaedam.projection.swarm
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.model.PlayerModel
import net.minecraft.client.model.geom.ModelLayers
import net.minecraft.client.renderer.entity.EntityRendererProvider
import net.minecraft.client.renderer.entity.MobRenderer
import net.minecraft.client.renderer.entity.layers.CustomHeadLayer
import net.minecraft.client.renderer.entity.layers.ItemInHandLayer
class ProjectedPersonRenderer(context: EntityRendererProvider.Context) :
MobRenderer<ProjectedPersonEntity, PlayerModel<ProjectedPersonEntity>>(
context,
PlayerModel(context.bakeLayer(ModelLayers.PLAYER), false),
0.4f
) {
init {
addLayer(CustomHeadLayer(this, context.modelSet, context.itemInHandRenderer))
addLayer(ItemInHandLayer(this, context.itemInHandRenderer))
}
override fun getTextureLocation(entity: ProjectedPersonEntity) = ProjectedPersonShape.Skins[entity.shape.skin]
override fun scale(entity: ProjectedPersonEntity, poseStack: PoseStack, f: Float) {
poseStack.scale(entity.shape.scaleX, entity.shape.scaleY, entity.shape.scaleZ)
super.scale(entity, poseStack, f)
}
}

View File

@ -0,0 +1,145 @@
package quaedam.projection.swarm
import dev.architectury.registry.ReloadListenerRegistry
import net.minecraft.nbt.CompoundTag
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.packs.PackType
import net.minecraft.server.packs.resources.PreparableReloadListener
import net.minecraft.server.packs.resources.ResourceManager
import net.minecraft.util.profiling.ProfilerFiller
import quaedam.Quaedam
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executor
import java.util.function.IntFunction
import java.util.stream.Collectors
import kotlin.math.abs
import kotlin.random.Random
import kotlin.random.nextInt
data class ProjectedPersonShape(
val scaleX: Float = 1.0f,
val scaleY: Float = 1.0f,
val scaleZ: Float = 1.0f,
val name: String = "[DESYNC]",
val skin: Int = 0,
) {
companion object {
const val KEY_SCALE_X = "ScaleX"
const val KEY_SCALE_Y = "ScaleY"
const val KEY_SCALE_Z = "ScaleZ"
const val KEY_NAME = "Name"
const val KEY_SKIN = "Skin"
init {
Names
Skins
}
fun create(seed: Long) = create(Random(seed))
fun create(rand: Random) = ProjectedPersonShape(
scaleX = rand.nextInt(0..6) * 0.1f + 0.7f,
scaleY = rand.nextInt(0..6) * 0.1f + 0.7f,
scaleZ = rand.nextInt(0..2) * 0.1f + 0.9f,
name = Names.random(rand),
skin = Skins.random(rand),
)
fun fromTag(tag: CompoundTag) = ProjectedPersonShape(
scaleX = tag.getFloat(KEY_SCALE_X),
scaleY = tag.getFloat(KEY_SCALE_Y),
scaleZ = tag.getFloat(KEY_SCALE_Z),
name = tag.getString(KEY_NAME),
skin = tag.getInt(KEY_SKIN),
)
}
fun toTag() = CompoundTag().apply {
putFloat(KEY_SCALE_X, scaleX)
putFloat(KEY_SCALE_Y, scaleY)
putFloat(KEY_SCALE_Z, scaleZ)
putString(KEY_NAME, name)
putInt(KEY_SKIN, skin)
}
object Names {
val id = ResourceLocation("quaedam", "projected-person-names")
var names = emptySet<String>()
init {
ReloadListenerRegistry.register(PackType.SERVER_DATA, ReloadListener, id)
}
fun random(random: Random) = names.random(random)
private object ReloadListener : PreparableReloadListener {
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
override fun reload(
preparationBarrier: PreparableReloadListener.PreparationBarrier,
resourceManager: ResourceManager,
profilerFiller: ProfilerFiller,
profilerFiller2: ProfilerFiller,
executor: Executor,
executor2: Executor
): CompletableFuture<Void> = preparationBarrier.wait(null)
.thenAcceptAsync({
names = resourceManager.getResource(id).get().openAsReader().use { it.readLines() }
.filterNot { it.isBlank() }
.filterNot { it.startsWith("#") }
.toSet()
Quaedam.logger.info("Loaded ${names.size} unique projected person names")
}, executor2)
override fun getName() = "quaedam:projected_person_names"
}
}
object Skins {
val id = ResourceLocation("quaedam", "skins")
// only available on client
var skins = emptyList<ResourceLocation>()
init {
ReloadListenerRegistry.register(PackType.CLIENT_RESOURCES, ReloadListener, id)
}
operator fun get(index: Int) = skins[abs(index) % skins.size]
fun random(random: Random) = random.nextInt()
private object ReloadListener : PreparableReloadListener {
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
override fun reload(
preparationBarrier: PreparableReloadListener.PreparationBarrier,
resourceManager: ResourceManager,
profilerFiller: ProfilerFiller,
profilerFiller2: ProfilerFiller,
executor: Executor,
executor2: Executor
): CompletableFuture<Void> = preparationBarrier.wait(null)
.thenAcceptAsync({
val skins = mutableSetOf<ResourceLocation>()
skins.addAll(resourceManager.listResources("textures/entity/player/wide") { it.path.endsWith(".png") }.keys)
skins.addAll(resourceManager.listResources("textures/entity/projected_person") { it.namespace == "quaedam" }.keys)
Skins.skins = skins.toSet().toList().sorted()
Quaedam.logger.info("Loaded ${Skins.skins.size} unique projected person skins")
Quaedam.logger.debug("Projected person skins ring: $skins")
}, executor2)
override fun getName() = "quaedam:projected_person_skins"
}
}
}

View File

@ -23,4 +23,8 @@ object SwarmProjection {
ProjectionEffectType { SwarmProjectionEffect() } ProjectionEffectType { SwarmProjectionEffect() }
}!! }!!
init {
ProjectedPersonEntity
}
} }

View File

@ -12,6 +12,7 @@ import net.minecraft.world.level.ChunkPos
import net.minecraft.world.level.block.entity.BlockEntity import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.levelgen.structure.BoundingBox import net.minecraft.world.level.levelgen.structure.BoundingBox
import quaedam.Quaedam
import quaedam.projection.ProjectionEffect import quaedam.projection.ProjectionEffect
import quaedam.projection.ProjectionEffectType import quaedam.projection.ProjectionEffectType
import quaedam.projection.ProjectionProvider import quaedam.projection.ProjectionProvider
@ -62,7 +63,7 @@ class ProjectorBlockEntity(pos: BlockPos, state: BlockState) :
} }
} }
} }
updateEffects(effects) updateEffects(effects, notify = false)
} }
override fun getUpdateTag(): CompoundTag = saveWithoutMetadata() override fun getUpdateTag(): CompoundTag = saveWithoutMetadata()
@ -89,17 +90,22 @@ class ProjectorBlockEntity(pos: BlockPos, state: BlockState) :
fun updateEffects(effects: Map<ProjectionEffectType<*>, ProjectionEffect>, notify: Boolean = true) { fun updateEffects(effects: Map<ProjectionEffectType<*>, ProjectionEffect>, notify: Boolean = true) {
if (effects != this.effects) { if (effects != this.effects) {
val oldEffects = this.effects val oldEffects = this.effects
val level = level!!
this.effects = effects this.effects = effects
if (!level.isClientSide) { if (level != null) {
sendBlockUpdated() val level = level!!
if (level.isClientSide && notify) {
sendBlockUpdated()
}
val addedEffects = effects.filterKeys { it !in oldEffects }
val removedEffects = oldEffects.filterKeys { it !in effects }
val updatedEffects = effects.filter { (k, v) -> oldEffects[k] != null && oldEffects[k] != v }
addedEffects.values.forEach { it.activate(level, blockPos) }
removedEffects.values.forEach { it.deactivate(level, blockPos) }
updatedEffects.forEach { (k, v) ->
oldEffects[k]!!.deactivate(level, blockPos)
v.activate(level, blockPos)
}
} }
val addedEffects = effects.filterKeys { it !in oldEffects }
val removedEffects = oldEffects.filterKeys { it !in effects }
val updatedEffects = effects.filter { (k, v) -> oldEffects[k] != v }
addedEffects.values.forEach { it.activate(level, blockPos) }
removedEffects.values.forEach { it.deactivate(level, blockPos) }
updatedEffects.forEach { (k, v) -> v.update(level, blockPos, oldEffects[k]!!) }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@ architectury {
loom { loom {
accessWidenerPath.set(project(":common").loom.accessWidenerPath) accessWidenerPath.set(project(":common").loom.accessWidenerPath)
forge.apply { forge {
convertAccessWideners.set(true) convertAccessWideners.set(true)
extraAccessWideners.add(loom.accessWidenerPath.get().asFile.name) extraAccessWideners.add(loom.accessWidenerPath.get().asFile.name)