1
0
mirror of https://github.com/chylex/Hardcore-Ender-Expansion-2.git synced 2025-02-27 15:46:01 +01:00

Implement Tomb Dungeon mob spawn triggers

This commit is contained in:
chylex 2021-01-26 18:22:12 +01:00
parent 5211731e62
commit 322c005648
18 changed files with 460 additions and 26 deletions

View File

@ -25,6 +25,7 @@ import chylex.hee.system.random.nextFloat
import chylex.hee.system.random.nextInt
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.entity.EntityType
import net.minecraft.item.BlockItemUseContext
import net.minecraft.item.ItemStack
import net.minecraft.state.StateContainer.Builder
@ -88,6 +89,12 @@ open class BlockGraveDirt(builder: BlockBuilder) : BlockSimpleShaped(builder, Ax
super.getShape(state, source, pos, context)
}
// Mobs
override fun canEntitySpawn(state: BlockState, worldIn: IBlockReader, pos: BlockPos, type: EntityType<*>): Boolean {
return true
}
// Explosions
override fun canDropFromExplosion(explosion: Explosion): Boolean {

View File

@ -56,11 +56,15 @@ import net.minecraft.block.BlockState
import net.minecraft.entity.CreatureAttribute
import net.minecraft.entity.Entity
import net.minecraft.entity.EntityType
import net.minecraft.entity.ILivingEntityData
import net.minecraft.entity.SharedMonsterAttributes.ATTACK_DAMAGE
import net.minecraft.entity.SharedMonsterAttributes.FOLLOW_RANGE
import net.minecraft.entity.SharedMonsterAttributes.MAX_HEALTH
import net.minecraft.entity.SharedMonsterAttributes.MOVEMENT_SPEED
import net.minecraft.entity.SpawnReason
import net.minecraft.entity.SpawnReason.SPAWNER
import net.minecraft.entity.ai.attributes.AttributeModifier
import net.minecraft.nbt.CompoundNBT
import net.minecraft.network.IPacket
import net.minecraft.network.datasync.DataSerializers
import net.minecraft.pathfinding.PathNavigator
@ -76,6 +80,8 @@ import net.minecraft.util.math.RayTraceResult.Type
import net.minecraft.util.math.Vec3d
import net.minecraft.world.Difficulty.HARD
import net.minecraft.world.Difficulty.NORMAL
import net.minecraft.world.DifficultyInstance
import net.minecraft.world.IWorld
import net.minecraft.world.IWorldReader
import net.minecraft.world.LightType.BLOCK
import net.minecraft.world.LightType.SKY
@ -213,7 +219,7 @@ class EntityMobSpiderling(type: EntityType<EntityMobSpiderling>, world: World) :
}
else if (wakeUpTimer > 0) {
if (--wakeUpTimer == 0) {
wakeUp(instant = false, preventSleep = false)
wakeUp(preventSleep = false)
}
}
else if (ticksExisted % 4 == 0) {
@ -291,10 +297,18 @@ class EntityMobSpiderling(type: EntityType<EntityMobSpiderling>, world: World) :
}
}
private fun wakeUp(instant: Boolean, preventSleep: Boolean) {
fun wakeUpInstantly(preventSleep: Boolean) {
wakeUp(preventSleep, aiDelayTicks = 1)
}
fun wakeUp(preventSleep: Boolean) {
wakeUp(preventSleep, aiDelayTicks = rand.nextInt(25, 40))
}
fun wakeUp(preventSleep: Boolean, aiDelayTicks: Int) {
if (isSleeping) {
isSleepingProp = false
wakeUpDelayAI = if (instant) 1 else rand.nextInt(25, 40)
wakeUpDelayAI = aiDelayTicks
if (preventSleep) {
canSleepAgain = false
@ -303,7 +317,7 @@ class EntityMobSpiderling(type: EntityType<EntityMobSpiderling>, world: World) :
}
override fun setFire(seconds: Int) {
wakeUp(instant = true, preventSleep = true)
wakeUpInstantly(preventSleep = true)
super.setFire(seconds)
}
@ -314,7 +328,7 @@ class EntityMobSpiderling(type: EntityType<EntityMobSpiderling>, world: World) :
// Behavior (Light)
override fun onLightStartled(): Boolean {
wakeUp(instant = false, preventSleep = true)
wakeUp(preventSleep = true)
if (world.totalTime < lightStartleResetTime) {
return false
@ -401,7 +415,7 @@ class EntityMobSpiderling(type: EntityType<EntityMobSpiderling>, world: World) :
return false
}
wakeUp(instant = true, preventSleep = true)
wakeUpInstantly(preventSleep = true)
if (!super.attackEntityFrom(source, if (source.isFireDamage) amount * 1.25F else amount)) {
return false
@ -445,6 +459,16 @@ class EntityMobSpiderling(type: EntityType<EntityMobSpiderling>, world: World) :
return true
}
// Spawning
override fun onInitialSpawn(world: IWorld, difficulty: DifficultyInstance, reason: SpawnReason, data: ILivingEntityData?, nbt: CompoundNBT?): ILivingEntityData? {
if (reason == SPAWNER) {
wakeUpInstantly(preventSleep = true)
}
return super.onInitialSpawn(world, difficulty, reason, data, nbt)
}
// Despawning
override fun isDespawnPeaceful(): Boolean {
@ -515,7 +539,7 @@ class EntityMobSpiderling(type: EntityType<EntityMobSpiderling>, world: World) :
val sleepState = getInt(SLEEP_STATE_TAG)
if (sleepState != 2) {
wakeUp(instant = true, preventSleep = sleepState != 1)
wakeUpInstantly(preventSleep = sleepState != 1)
}
lightStartleResetTime = getLong(LIGHT_STARTLE_RESET_TIME_TAG)

View File

@ -137,19 +137,19 @@ class EntityMobUndread(type: EntityType<EntityMobUndread>, world: World) : Entit
super.onDeathUpdate()
}
override fun playStepSound(pos: BlockPos, state: BlockState) {
public override fun playStepSound(pos: BlockPos, state: BlockState) {
playSound(Sounds.ENTITY_ZOMBIE_STEP, rand.nextFloat(0.4F, 0.5F), rand.nextFloat(0.9F, 1F))
}
override fun getHurtSound(source: DamageSource): SoundEvent {
public override fun getHurtSound(source: DamageSource): SoundEvent {
return ModSounds.MOB_UNDREAD_HURT
}
override fun getDeathSound(): SoundEvent {
public override fun getDeathSound(): SoundEvent {
return ModSounds.MOB_UNDREAD_DEATH
}
override fun getSoundPitch(): Float {
public override fun getSoundPitch(): Float {
return rand.nextFloat(0.8F, 1F)
}
}

View File

@ -9,6 +9,7 @@ import chylex.hee.game.world.feature.stronghold.piece.StrongholdRoom_Main_Portal
import chylex.hee.game.world.feature.stronghold.piece.StrongholdRoom_Trap_CornerHoles
import chylex.hee.game.world.feature.stronghold.piece.StrongholdRoom_Trap_Prison
import chylex.hee.game.world.feature.stronghold.piece.StrongholdRoom_Trap_TallIntersection
import chylex.hee.game.world.feature.tombdungeon.piece.TombDungeonRoom_Tomb
import chylex.hee.init.ModEntities
import chylex.hee.system.delegate.NotifyOnChange
import chylex.hee.system.serialization.TagCompound
@ -28,6 +29,10 @@ class EntityTechnicalTrigger(type: EntityType<EntityTechnicalTrigger>, world: Wo
this.type = type
}
constructor(world: World, type: Types, nbt: TagCompound) : this(world, type) {
handler.deserializeNBT(nbt)
}
private companion object {
private const val TYPE_TAG = "Type"
private const val DATA_TAG = "Data"
@ -62,6 +67,7 @@ class EntityTechnicalTrigger(type: EntityType<EntityTechnicalTrigger>, world: Wo
STRONGHOLD_TRAP_TALL_INTERSECTION({ StrongholdRoom_Trap_TallIntersection.Trigger }),
ENERGY_SHRINE_GENERATOR({ EnergyShrineGenerator.GeneratorTrigger }),
ENERGY_SHRINE_GLOBAL({ EnergyShrineRoom_Main_Start.Particles }),
TOMB_DUNGEON_UNDREAD_SPAWNER(TombDungeonRoom_Tomb::MobSpawnerTrigger),
OBSIDIAN_TOWER_TOP_GLOWSTONE(ObsidianTowerLevel_Top::GlowstoneTrigger),
OBSIDIAN_TOWER_DEATH_ANIMATION(ObsidianTowerLevel_Top::DeathAnimationTrigger)
}

View File

@ -1,5 +1,7 @@
package chylex.hee.game.world.feature.tombdungeon
import chylex.hee.game.world.feature.tombdungeon.TombDungeonLevel.MobAmount.HIGH
import chylex.hee.game.world.feature.tombdungeon.TombDungeonLevel.MobAmount.MEDIUM
import chylex.hee.game.world.feature.tombdungeon.TombDungeonPieces.PIECE_TOMB_RANDOM_MASS_5X_BASIC
import chylex.hee.game.world.feature.tombdungeon.TombDungeonPieces.PIECE_TOMB_RANDOM_MASS_5X_BORDER
import chylex.hee.game.world.feature.tombdungeon.TombDungeonPieces.PIECE_TOMB_RANDOM_MASS_5X_SPLIT
@ -19,6 +21,7 @@ import chylex.hee.game.world.feature.tombdungeon.piece.TombDungeonAbstractPiece
import chylex.hee.system.math.floorToInt
import chylex.hee.system.random.nextFloat
import chylex.hee.system.random.nextInt
import chylex.hee.system.random.nextRounded
import java.util.Random
import kotlin.math.max
import kotlin.math.min
@ -101,4 +104,46 @@ enum class TombDungeonLevel(val isFancy: Boolean, private val corridorFactor: In
}
}
}
fun pickUndreadAndSpiderlingSpawns(rand: Random, amount: MobAmount): Pair<Int, Int> {
val m = if (amount >= MEDIUM) 1 else 0
val h = if (amount >= HIGH) 1 else 0
return when(this) {
FIRST -> when(rand.nextInt(0, 3)) {
0 -> MOBS(undreads = rand.nextRounded(0.2F), spiderlings = 1 + m + rand.nextInt(0, h))
1 -> MOBS(undreads = rand.nextRounded(0.3F * m), spiderlings = 1 + h + rand.nextInt(0, m))
2 -> MOBS(undreads = rand.nextRounded(0.3F * h), spiderlings = 2 + m)
else -> MOBS(undreads = 0, spiderlings = 1 + rand.nextInt(0, 1 + m))
}
SECOND -> when(rand.nextInt(0, 3)) {
0 -> MOBS(undreads = rand.nextRounded(0.3F), spiderlings = 1 + m + rand.nextInt(0, h))
1 -> MOBS(undreads = rand.nextRounded(0.4F * m), spiderlings = 1 + rand.nextInt(0, 2 * m))
else -> MOBS(undreads = 0, spiderlings = rand.nextRounded(1.8F + (m * 0.55F) + (h * 0.55F)))
}
THIRD -> when(rand.nextInt(0, 2)) {
0 -> MOBS(undreads = rand.nextInt(0, 1 + h), spiderlings = rand.nextRounded(1.4F + (m * 0.5F)) + rand.nextInt(0, m))
1 -> MOBS(undreads = rand.nextInt(0, 1 + m), spiderlings = rand.nextRounded(1.2F) + rand.nextInt(0, 2 * h))
else -> MOBS(undreads = rand.nextInt(0, 1 + m), spiderlings = 2 + rand.nextInt(0, m + h))
}
FOURTH -> when(rand.nextInt(0, 5)) {
in 0..1 -> MOBS(undreads = 1 + rand.nextInt(h, 3 + h), spiderlings = rand.nextRounded(0.4F * m) + rand.nextInt(0, m + h))
else -> MOBS(undreads = 2 + rand.nextInt(m, 2 * m), spiderlings = rand.nextRounded(0.3F + (m * 0.1F) + (h * 0.3F)))
}
else -> when(rand.nextInt(0, 2)) {
0 -> MOBS(undreads = 1 + h + rand.nextInt(m, 1 + (m * 2)) + rand.nextInt(h, 1 + h), spiderlings = rand.nextInt(0, m))
else -> MOBS(undreads = 2 + h + rand.nextInt(m, 2 * m), spiderlings = rand.nextRounded(0.2F + (m * 0.2F)))
}
}
}
private fun MOBS(undreads: Int, spiderlings: Int) = undreads to spiderlings
enum class MobAmount {
LOW, MEDIUM, HIGH
}
}

View File

@ -1,10 +1,57 @@
package chylex.hee.game.world.feature.tombdungeon.piece
import chylex.hee.game.block.BlockGraveDirt
import chylex.hee.game.entity.living.EntityMobSpiderling
import chylex.hee.game.entity.living.EntityMobUndread
import chylex.hee.game.entity.posVec
import chylex.hee.game.entity.selectExistingEntities
import chylex.hee.game.entity.selectVulnerableEntities
import chylex.hee.game.entity.technical.EntityTechnicalTrigger
import chylex.hee.game.entity.technical.EntityTechnicalTrigger.ITriggerHandler
import chylex.hee.game.entity.technical.EntityTechnicalTrigger.Types.TOMB_DUNGEON_UNDREAD_SPAWNER
import chylex.hee.game.world.Pos
import chylex.hee.game.world.distanceSqTo
import chylex.hee.game.world.feature.tombdungeon.TombDungeonLevel
import chylex.hee.game.world.feature.tombdungeon.TombDungeonLevel.MobAmount
import chylex.hee.game.world.feature.tombdungeon.connection.TombDungeonConnection
import chylex.hee.game.world.feature.tombdungeon.connection.TombDungeonConnectionType.TOMB_ENTRANCE_INSIDE
import chylex.hee.game.world.getBlock
import chylex.hee.game.world.getState
import chylex.hee.game.world.isAir
import chylex.hee.game.world.offsetWhile
import chylex.hee.game.world.playClient
import chylex.hee.game.world.structure.IStructureWorld
import chylex.hee.game.world.structure.piece.IStructurePieceConnection
import chylex.hee.game.world.structure.trigger.EntityStructureTrigger
import chylex.hee.init.ModEntities
import chylex.hee.network.client.PacketClientFX
import chylex.hee.network.fx.FxVecData
import chylex.hee.network.fx.FxVecHandler
import chylex.hee.system.math.Vec
import chylex.hee.system.math.Vec3
import chylex.hee.system.math.addY
import chylex.hee.system.math.directionTowards
import chylex.hee.system.math.square
import chylex.hee.system.math.toYaw
import chylex.hee.system.migration.BlockWeb
import chylex.hee.system.migration.EntityLivingBase
import chylex.hee.system.migration.EntityPlayer
import chylex.hee.system.migration.Facing.DOWN
import chylex.hee.system.migration.Facing.NORTH
import chylex.hee.system.migration.Facing.SOUTH
import chylex.hee.system.random.nextFloat
import chylex.hee.system.random.nextInt
import chylex.hee.system.random.nextItem
import chylex.hee.system.serialization.TagCompound
import chylex.hee.system.serialization.use
import net.minecraft.entity.EntitySpawnPlacementRegistry
import net.minecraft.entity.SpawnReason.STRUCTURE
import net.minecraft.util.Direction.Axis
import net.minecraft.util.math.AxisAlignedBB
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Vec3d
import net.minecraft.world.World
import java.util.Random
abstract class TombDungeonRoom_Tomb(file: String, entranceY: Int, allowSecrets: Boolean, isFancy: Boolean) : TombDungeonRoom(file, isFancy) {
final override val secretAttachWeight = if (allowSecrets) 2 else 0
@ -13,4 +60,176 @@ abstract class TombDungeonRoom_Tomb(file: String, entranceY: Int, allowSecrets:
final override val connections = arrayOf<IStructurePieceConnection>(
TombDungeonConnection(TOMB_ENTRANCE_INSIDE, Pos(centerX, entranceY, maxZ), SOUTH)
)
override fun generate(world: IStructureWorld, instance: Instance) {
super.generate(world, instance)
generateSpawnerTrigger(world, instance)
}
protected open fun generateSpawnerTrigger(world: IStructureWorld, instance: Instance) {
val rand = world.rand
val level = instance.context ?: return
val mobAmount = getSpawnerTriggerMobAmount(rand, level) ?: return
val (undreads, spiderlings) = level.pickUndreadAndSpiderlingSpawns(rand, mobAmount)
MobSpawnerTrigger.place(world, entrance = connections.first().offset, width = maxX, depth = maxZ, undreads, spiderlings)
}
protected abstract fun getSpawnerTriggerMobAmount(rand: Random, level: TombDungeonLevel): MobAmount?
class MobSpawnerTrigger() : ITriggerHandler {
companion object {
private const val WIDTH_TAG = "Width"
private const val DEPTH_TAG = "Depth"
private const val UNDREADS_TAG = "Undreads"
private const val SPIDERLINGS_TAG = "Spiderlings"
fun place(world: IStructureWorld, entrance: BlockPos, width: Int, depth: Int, undreads: Int, spiderlings: Int) {
val nbt = MobSpawnerTrigger(width - 1.0, depth - 1.0, undreads, spiderlings).serializeNBT()
world.addTrigger(entrance, EntityStructureTrigger({ wrld ->
EntityTechnicalTrigger(wrld, TOMB_DUNGEON_UNDREAD_SPAWNER, nbt).apply { rotationYaw = NORTH.horizontalAngle }
}, yOffset = 0.5))
}
val FX_SPAWN_UNDREAD = object : FxVecHandler() {
override fun handle(world: World, rand: Random, vec: Vec3d) {
EntityMobUndread(world).apply {
setLocationAndAngles(vec.x, vec.y, vec.z, 0F, 0F)
spawnExplosionParticle()
deathSound.playClient(vec, soundCategory, volume = 1.2F, pitch = soundPitch * 0.7F)
}
}
}
val FX_SPAWN_SPIDERLING = object : FxVecHandler() {
override fun handle(world: World, rand: Random, vec: Vec3d) {
EntityMobSpiderling(world).apply {
setLocationAndAngles(vec.x, vec.y, vec.z, 0F, 0F)
spawnExplosionParticle()
ambientSound.playClient(vec, soundCategory, volume = 1F, pitch = soundPitch)
}
}
}
}
private var width = 0.0
private var depth = 0.0
private var undreads = 0
private var spiderlings = 0
constructor(width: Double, depth: Double, undreads: Int, spiderlings: Int) : this() {
this.width = width
this.depth = depth
this.undreads = undreads
this.spiderlings = spiderlings
}
override fun check(world: World): Boolean {
return !world.isRemote
}
override fun update(entity: EntityTechnicalTrigger) {
val world = entity.world
val facing = entity.horizontalFacing
val vecF = Vec3.fromYaw(facing.horizontalAngle)
val vecL = Vec3.fromYaw(facing.rotateYCCW().horizontalAngle)
val vecR = Vec3.fromYaw(facing.rotateY().horizontalAngle)
val aabb = AxisAlignedBB(
vecL.scale(width * 0.5).add(vecF.scale(0.3)),
vecR.scale(width * 0.5).add(vecF.scale(depth)).addY(2.5)
).offset(entity.posVec)
val nearbyPlayers = world.selectVulnerableEntities.inBox<EntityPlayer>(aabb)
if (nearbyPlayers.isEmpty()) {
return
}
val rand = world.rand
val minPlayerDist = ((depth * 0.4) + (width * 0.16)).coerceIn(1.5, 3.5)
for((entityCount, entityType, spawnParticle) in listOf(
Triple(undreads, ModEntities.UNDREAD, FX_SPAWN_UNDREAD),
Triple(spiderlings, ModEntities.SPIDERLING, FX_SPAWN_SPIDERLING)
)) {
repeat(entityCount) {
var bestPos: BlockPos? = null
for(attempt in 1..75) {
val pos = Pos(
rand.nextFloat(aabb.minX, aabb.maxX),
rand.nextFloat(aabb.minY, aabb.maxY) + 0.5,
rand.nextFloat(aabb.minZ, aabb.maxZ),
).offsetWhile(DOWN, 1..3) {
it.isAir(world) || it.getBlock(world) is BlockWeb
}
val collisionCheckAABB = entityType.getBoundingBoxWithSizeApplied(pos.x + 0.5, pos.y.toDouble(), pos.z + 0.5).grow(0.2, 0.0, 0.2)
if (EntitySpawnPlacementRegistry.func_223515_a(entityType, world, STRUCTURE, pos, rand) &&
world.hasNoCollisions(collisionCheckAABB) &&
world.selectExistingEntities.inBox<EntityLivingBase>(collisionCheckAABB.grow(0.4, 0.0, 0.4)).isEmpty() &&
world.players.none { pos.distanceSqTo(it) < square(minPlayerDist) }
) {
bestPos = pos
if (entityType === ModEntities.UNDREAD) {
if (rand.nextInt(5) == 0 || pos.down().getBlock(world) is BlockGraveDirt) {
break
}
}
else {
break
}
}
}
if (bestPos != null) {
val x = bestPos.x + rand.nextFloat(0.35, 0.65)
val y = bestPos.y + bestPos.down().getState(world).getCollisionShape(world, bestPos).getEnd(Axis.Y) - 1.0
val z = bestPos.z + rand.nextFloat(0.35, 0.65)
val vec = Vec(x, y, z)
val target = rand.nextItem(nearbyPlayers)
entityType.create(world)?.apply {
setLocationAndAngles(x, y, z, vec.directionTowards(target.posVec).toYaw(), 0F)
rotationYawHead = rotationYaw
attackTarget = target
onGround = true // allow instant pathfinding in MeleeAttackGoal
if (this is EntityMobSpiderling) {
wakeUp(preventSleep = true, aiDelayTicks = if (rand.nextInt(10) == 0) rand.nextInt(4, 6) else rand.nextInt(11, 18))
}
onInitialSpawn(world, world.getDifficultyForLocation(bestPos), STRUCTURE, null, null)
world.addEntity(this)
PacketClientFX(spawnParticle, FxVecData(vec)).sendToAllAround(this, 24.0)
}
}
}
}
entity.remove()
}
override fun nextTimer(rand: Random): Int {
return 3
}
override fun serializeNBT() = TagCompound().apply {
putDouble(WIDTH_TAG, width)
putDouble(DEPTH_TAG, depth)
putInt(UNDREADS_TAG, undreads)
putInt(SPIDERLINGS_TAG, spiderlings)
}
override fun deserializeNBT(nbt: TagCompound) = nbt.use {
width = getDouble(WIDTH_TAG)
depth = getDouble(DEPTH_TAG)
undreads = getInt(UNDREADS_TAG)
spiderlings = getInt(SPIDERLINGS_TAG)
}
}
}

View File

@ -2,9 +2,12 @@ package chylex.hee.game.world.feature.tombdungeon.piece
import chylex.hee.game.block.BlockGraveDirt
import chylex.hee.game.world.Pos
import chylex.hee.game.world.feature.tombdungeon.TombDungeonLevel
import chylex.hee.game.world.feature.tombdungeon.TombDungeonLevel.MobAmount
import chylex.hee.game.world.feature.tombdungeon.TombDungeonPieces
import chylex.hee.game.world.feature.tombdungeon.connection.TombDungeonConnection
import chylex.hee.game.world.feature.tombdungeon.connection.TombDungeonConnectionType.TOMB_ENTRANCE_INSIDE
import chylex.hee.game.world.feature.tombdungeon.piece.TombDungeonRoom_Tomb.MobSpawnerTrigger
import chylex.hee.game.world.generation.IBlockPicker.Single
import chylex.hee.game.world.generation.IBlockPicker.Single.Air
import chylex.hee.game.world.math.Size
@ -32,6 +35,8 @@ class TombDungeonRoom_Tomb_Mass(width: Int, depth: Int, private val border: Bool
super.generate(world, instance)
val rand = world.rand
val level = instance.context
val centerX = size.centerX
val maxX = size.maxX
val maxY = size.maxY
@ -56,8 +61,21 @@ class TombDungeonRoom_Tomb_Mass(width: Int, depth: Int, private val border: Bool
if (rand.nextInt(6) == 0 && (border || split)) {
placeJars(world, instance, listOf(Pos(centerX, 2, 1)))
if (level != null && rand.nextInt(9) != 0) {
placeSpawnerTrigger(world, level)
}
}
else if (level != null && rand.nextInt(3) != 0) {
placeSpawnerTrigger(world, level)
}
placeCobwebs(world, instance)
}
private fun placeSpawnerTrigger(world: IStructureWorld, level: TombDungeonLevel) {
val area = (size.x - 2) * (size.z - 2)
val (undreads, spiderlings) = level.pickUndreadAndSpiderlingSpawns(world.rand, if (area <= 30) MobAmount.LOW else MobAmount.MEDIUM)
MobSpawnerTrigger.place(world, entrance = connections.first().offset, width = size.maxX, depth = size.maxZ, undreads, spiderlings)
}
}

View File

@ -2,9 +2,11 @@ package chylex.hee.game.world.feature.tombdungeon.piece
import chylex.hee.game.world.Pos
import chylex.hee.game.world.feature.tombdungeon.TombDungeonLevel
import chylex.hee.game.world.feature.tombdungeon.TombDungeonLevel.MobAmount
import chylex.hee.game.world.structure.IStructureWorld
import chylex.hee.system.migration.Facing.NORTH
import chylex.hee.system.random.nextInt
import java.util.Random
class TombDungeonRoom_Tomb_MassSpacious(file: String, entranceY: Int, isFancy: Boolean) : TombDungeonRoom_Tomb(file, entranceY, allowSecrets = false, isFancy) {
override fun generate(world: IStructureWorld, instance: Instance) {
@ -32,4 +34,8 @@ class TombDungeonRoom_Tomb_MassSpacious(file: String, entranceY: Int, isFancy: B
placeJars(world, instance, listOf(Pos(jarX, 3, maxZ - 2)))
}
}
override fun getSpawnerTriggerMobAmount(rand: Random, level: TombDungeonLevel): MobAmount? {
return MobAmount.HIGH.takeIf { rand.nextInt(11) < (if (level <= TombDungeonLevel.SECOND) 7 else 4) }
}
}

View File

@ -2,11 +2,14 @@ package chylex.hee.game.world.feature.tombdungeon.piece
import chylex.hee.game.block.BlockGraveDirt
import chylex.hee.game.world.Pos
import chylex.hee.game.world.feature.tombdungeon.TombDungeonLevel
import chylex.hee.game.world.feature.tombdungeon.TombDungeonLevel.MobAmount
import chylex.hee.game.world.structure.IStructureWorld
import chylex.hee.system.migration.Facing.EAST
import chylex.hee.system.migration.Facing.WEST
import chylex.hee.system.random.nextInt
import chylex.hee.system.random.nextRounded
import java.util.Random
class TombDungeonRoom_Tomb_MultiDeep(file: String, private val tombsPerColumn: Int, entranceY: Int, isFancy: Boolean) : TombDungeonRoom_Tomb(file, entranceY, allowSecrets = false, isFancy) {
override fun generate(world: IStructureWorld, instance: Instance) {
@ -37,4 +40,15 @@ class TombDungeonRoom_Tomb_MultiDeep(file: String, private val tombsPerColumn: I
})
}
}
override fun getSpawnerTriggerMobAmount(rand: Random, level: TombDungeonLevel): MobAmount? {
if (rand.nextBoolean()) {
return null
}
return when {
tombsPerColumn <= 6 -> MobAmount.MEDIUM
else -> MobAmount.HIGH
}
}
}

View File

@ -1,7 +1,10 @@
package chylex.hee.game.world.feature.tombdungeon.piece
import chylex.hee.game.world.Pos
import chylex.hee.game.world.feature.tombdungeon.TombDungeonLevel
import chylex.hee.game.world.feature.tombdungeon.TombDungeonLevel.MobAmount
import chylex.hee.game.world.structure.IStructureWorld
import java.util.Random
class TombDungeonRoom_Tomb_MultiNarrow(file: String, private val tombsPerColumn: Int, entranceY: Int, isFancy: Boolean) : TombDungeonRoom_Tomb(file, entranceY, allowSecrets = false, isFancy) {
override fun generate(world: IStructureWorld, instance: Instance) {
@ -18,4 +21,16 @@ class TombDungeonRoom_Tomb_MultiNarrow(file: String, private val tombsPerColumn:
})
}
}
override fun getSpawnerTriggerMobAmount(rand: Random, level: TombDungeonLevel): MobAmount? {
if (rand.nextInt(10) >= if (level.isFancy) 3 else 5) {
return null
}
return when {
tombsPerColumn <= 4 -> MobAmount.LOW
tombsPerColumn <= 6 -> MobAmount.MEDIUM
else -> MobAmount.HIGH
}
}
}

View File

@ -2,11 +2,14 @@ package chylex.hee.game.world.feature.tombdungeon.piece
import chylex.hee.game.block.BlockGraveDirt
import chylex.hee.game.world.Pos
import chylex.hee.game.world.feature.tombdungeon.TombDungeonLevel
import chylex.hee.game.world.feature.tombdungeon.TombDungeonLevel.MobAmount
import chylex.hee.game.world.structure.IStructureWorld
import chylex.hee.system.migration.Facing.EAST
import chylex.hee.system.migration.Facing.WEST
import chylex.hee.system.random.nextInt
import chylex.hee.system.random.nextRounded
import java.util.Random
class TombDungeonRoom_Tomb_MultiSpacious(file: String, private val tombsPerColumn: Int, entranceY: Int, isFancy: Boolean) : TombDungeonRoom_Tomb(file, entranceY, allowSecrets = false, isFancy) {
override fun generate(world: IStructureWorld, instance: Instance) {
@ -37,4 +40,15 @@ class TombDungeonRoom_Tomb_MultiSpacious(file: String, private val tombsPerColum
})
}
}
override fun getSpawnerTriggerMobAmount(rand: Random, level: TombDungeonLevel): MobAmount? {
if (rand.nextInt(10) >= 6) {
return null
}
return when {
tombsPerColumn <= 6 -> MobAmount.MEDIUM
else -> MobAmount.HIGH
}
}
}

View File

@ -0,0 +1,26 @@
package chylex.hee.game.world.feature.tombdungeon.piece
import chylex.hee.game.world.Pos
import chylex.hee.game.world.feature.tombdungeon.TombDungeonLevel
import chylex.hee.game.world.feature.tombdungeon.TombDungeonLevel.MobAmount
import chylex.hee.game.world.structure.IStructureWorld
import chylex.hee.system.migration.Facing.SOUTH
import java.util.Random
abstract class TombDungeonRoom_Tomb_Single(file: String, entranceY: Int, isFancy: Boolean) : TombDungeonRoom_Tomb(file, entranceY, allowSecrets = false, isFancy) {
override fun generate(world: IStructureWorld, instance: Instance) {
super.generate(world, instance)
if (world.rand.nextInt(10) < 3) {
placeChest(world, instance, Pos(centerX, 1, maxZ - 4), SOUTH)
}
}
final override fun getSpawnerTriggerMobAmount(rand: Random, level: TombDungeonLevel): MobAmount? {
return null
}
protected fun placeSingleTombUndreadSpawner(world: IStructureWorld) {
MobSpawnerTrigger.place(world, entrance = connections.first().offset, width = maxX, depth = maxZ, undreads = 1, spiderlings = 0)
}
}

View File

@ -1,15 +1,13 @@
package chylex.hee.game.world.feature.tombdungeon.piece
import chylex.hee.game.world.Pos
import chylex.hee.game.world.structure.IStructureWorld
import chylex.hee.system.migration.Facing.SOUTH
open class TombDungeonRoom_Tomb_SingleNarrow(file: String, entranceY: Int, isFancy: Boolean) : TombDungeonRoom_Tomb(file, entranceY, allowSecrets = false, isFancy) {
class TombDungeonRoom_Tomb_SingleNarrow(file: String, entranceY: Int, isFancy: Boolean) : TombDungeonRoom_Tomb_Single(file, entranceY, isFancy) {
override fun generate(world: IStructureWorld, instance: Instance) {
super.generate(world, instance)
if (world.rand.nextInt(10) < 3) {
placeChest(world, instance, Pos(centerX, 1, maxZ - 4), SOUTH)
if (world.rand.nextInt(5) == 0) {
placeSingleTombUndreadSpawner(world)
}
}
}

View File

@ -3,7 +3,7 @@ package chylex.hee.game.world.feature.tombdungeon.piece
import chylex.hee.game.world.Pos
import chylex.hee.game.world.structure.IStructureWorld
class TombDungeonRoom_Tomb_SingleSpacious(file: String, entranceY: Int, isFancy: Boolean) : TombDungeonRoom_Tomb_SingleNarrow(file, entranceY, isFancy) {
class TombDungeonRoom_Tomb_SingleSpacious(file: String, entranceY: Int, isFancy: Boolean) : TombDungeonRoom_Tomb_Single(file, entranceY, isFancy) {
override fun generate(world: IStructureWorld, instance: Instance) {
super.generate(world, instance)
@ -14,6 +14,11 @@ class TombDungeonRoom_Tomb_SingleSpacious(file: String, entranceY: Int, isFancy:
Pos(centerX - 2, 4, if (rand.nextBoolean()) maxZ - 3 else maxZ - 4),
Pos(centerX + 2, 4, if (rand.nextBoolean()) maxZ - 3 else maxZ - 4),
))
placeSingleTombUndreadSpawner(world)
}
else if (rand.nextInt(10) < 4) {
placeSingleTombUndreadSpawner(world)
}
}
}

View File

@ -28,6 +28,7 @@ import chylex.hee.game.item.ItemTableLink
import chylex.hee.game.mechanics.scorching.ScorchingHelper
import chylex.hee.game.mechanics.table.TableParticleHandler
import chylex.hee.game.potion.PotionBanishment
import chylex.hee.game.world.feature.tombdungeon.piece.TombDungeonRoom_Tomb
import chylex.hee.network.BaseClientPacket
import chylex.hee.network.fx.IFxData
import chylex.hee.network.fx.IFxHandler
@ -79,7 +80,9 @@ class PacketClientFX<T : IFxData>() : BaseClientPacket() {
EnderEyeSpawnerParticles.FX_PARTICLE,
EndermanTeleportHandler.FX_TELEPORT_FAIL,
EndermanTeleportHandler.FX_TELEPORT_OUT_OF_WORLD,
PotionBanishment.FX_BANISH
PotionBanishment.FX_BANISH,
TombDungeonRoom_Tomb.MobSpawnerTrigger.FX_SPAWN_UNDREAD,
TombDungeonRoom_Tomb.MobSpawnerTrigger.FX_SPAWN_SPIDERLING,
)
}

View File

@ -0,0 +1,12 @@
package chylex.hee.network.fx
import chylex.hee.system.serialization.use
import chylex.hee.system.serialization.writeVec
import net.minecraft.network.PacketBuffer
import net.minecraft.util.math.Vec3d
class FxVecData(private val vec: Vec3d) : IFxData {
override fun write(buffer: PacketBuffer) = buffer.use {
writeVec(vec)
}
}

View File

@ -0,0 +1,16 @@
package chylex.hee.network.fx
import chylex.hee.system.serialization.readVec
import chylex.hee.system.serialization.use
import net.minecraft.network.PacketBuffer
import net.minecraft.util.math.Vec3d
import net.minecraft.world.World
import java.util.Random
abstract class FxVecHandler : IFxHandler<FxVecData> {
override fun handle(buffer: PacketBuffer, world: World, rand: Random) = buffer.use {
handle(world, rand, readVec())
}
abstract fun handle(world: World, rand: Random, vec: Vec3d)
}

View File

@ -2,6 +2,7 @@ package chylex.hee.test.main
import chylex.hee.game.world.feature.tombdungeon.TombDungeonLevel
import chylex.hee.game.world.feature.tombdungeon.TombDungeonLevel.MobAmount
import java.util.Locale.ROOT
import java.util.Random
import kotlin.math.roundToInt
@ -11,19 +12,24 @@ fun main() {
for(level in TombDungeonLevel.values()) {
val reps = 100000
println("\n")
println("-".repeat(level.name.length))
println(level.name)
println("-".repeat(level.name.length))
println(MobAmount.values().joinToString("\n") { amount ->
"\n " + amount.name + "\n\n" + (1..reps)
println()
println("== ${level.name} ${"=".repeat(24 - level.name.length)}")
for(amount in MobAmount.values()) {
println()
println(" ${amount.name}")
println()
val results = (1..reps)
.map { level.pickUndreadAndSpiderlingSpawns(rand, amount) }
.groupingBy { it }
.eachCount()
.entries
.map { ((it.value * 100.0) / reps).roundToInt() to it.key }
.sortedWith(compareBy({ -it.first }, { -it.second.first }, { -it.second.second }))
.joinToString("\n") { " U = ${it.second.first}, S = ${it.second.second} ... ${it.first} %" }
})
println(results.joinToString("\n") { " U = ${it.second.first} S = ${it.second.second} ${it.first.toString().padStart(2)} %" })
println(" U ~ ${"%.1f".format(ROOT, results.sumBy { it.second.first * it.first } * 0.01)} S ~ ${"%.1f".format(ROOT, results.sumBy { it.second.second * it.first } * 0.01)}")
}
}
}