Compare commits
24 Commits
023017a829
...
df6a14f2b1
Author | SHA1 | Date | |
---|---|---|---|
df6a14f2b1 | |||
89841beeb7 | |||
7ca6689bad | |||
3ce9081400 | |||
1bd5965ba1 | |||
eb772916dc | |||
421f80c9a1 | |||
fd7766b38c | |||
b574d8a3d4 | |||
96c48fa0db | |||
fe5eeb0364 | |||
b183e99b3c | |||
bdccd0c7f3 | |||
726a0337c5 | |||
0aee57947d | |||
336cab7de4 | |||
1d2d9ce4d8 | |||
bbef6cb342 | |||
314957eeb5 | |||
79b11498c1 | |||
730f6b9781 | |||
d5c9eb6b41 | |||
2f7c9a883a | |||
e917807adc |
@ -18,6 +18,7 @@ import quaedam.projection.SimpleProjectionUpdate
|
|||||||
import quaedam.projection.misc.NoiseProjection
|
import quaedam.projection.misc.NoiseProjection
|
||||||
import quaedam.projection.misc.SkylightProjection
|
import quaedam.projection.misc.SkylightProjection
|
||||||
import quaedam.projection.misc.SoundProjection
|
import quaedam.projection.misc.SoundProjection
|
||||||
|
import quaedam.projection.music.MusicProjection
|
||||||
import quaedam.projection.swarm.SwarmProjection
|
import quaedam.projection.swarm.SwarmProjection
|
||||||
import quaedam.projector.Projector
|
import quaedam.projector.Projector
|
||||||
import quaedam.shell.ProjectionShell
|
import quaedam.shell.ProjectionShell
|
||||||
@ -53,6 +54,7 @@ object Quaedam {
|
|||||||
SwarmProjection
|
SwarmProjection
|
||||||
SoundProjection
|
SoundProjection
|
||||||
NoiseProjection
|
NoiseProjection
|
||||||
|
MusicProjection
|
||||||
ProjectionCommand
|
ProjectionCommand
|
||||||
SimpleProjectionUpdate
|
SimpleProjectionUpdate
|
||||||
ProjectionShell
|
ProjectionShell
|
||||||
|
91
common/src/main/kotlin/quaedam/projection/music/Composer.kt
Normal file
91
common/src/main/kotlin/quaedam/projection/music/Composer.kt
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package quaedam.projection.music
|
||||||
|
|
||||||
|
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.random.Random
|
||||||
|
import kotlin.random.nextInt
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The composer for music.
|
||||||
|
* rhythmRandom is used for a better rhythm sync between different instruments.
|
||||||
|
*/
|
||||||
|
class Composer(val noteRandom: Random, val rhythmRandom: Random, val instrument: NoteBlockInstrument) {
|
||||||
|
|
||||||
|
data class Note(val note: Int, val volume: Float, val time: Int)
|
||||||
|
|
||||||
|
val baseTime = arrayOf(5, 5, 3, 3, 4, 4, 2, 2, 8).random(rhythmRandom)
|
||||||
|
val baseNote = noteRandom.nextInt(5..19)
|
||||||
|
|
||||||
|
val mayDropOut = instrument in arrayOf(
|
||||||
|
NoteBlockInstrument.BASEDRUM,
|
||||||
|
NoteBlockInstrument.HAT,
|
||||||
|
NoteBlockInstrument.SNARE,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun composeMusic(): List<Note> {
|
||||||
|
var note = (0..rhythmRandom.nextInt(4)).flatMap { composeSection() }
|
||||||
|
note = decorate(note)
|
||||||
|
if (mayDropOut && rhythmRandom.nextInt(6) != 0) {
|
||||||
|
val dropRate = arrayOf(2, 3, 3, 4, 4, 4, 4, 6).random(rhythmRandom)
|
||||||
|
note = note.chunked(dropRate).map {
|
||||||
|
val first = it.first()
|
||||||
|
Note(first.note, first.volume, it.sumOf { note -> note.time })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return note
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decorate(notes: List<Note>) = notes.map {
|
||||||
|
if (noteRandom.nextInt(4) == 0) {
|
||||||
|
doDecorate(it)
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun doDecorate(note: Note): Note {
|
||||||
|
var noteVal = note.note
|
||||||
|
if (noteRandom.nextInt(4) == 0) {
|
||||||
|
if (noteRandom.nextBoolean()) {
|
||||||
|
noteVal += 1
|
||||||
|
} else {
|
||||||
|
noteVal -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var volume = note.volume
|
||||||
|
if (noteRandom.nextInt(4) == 0) {
|
||||||
|
volume *= noteRandom.nextFloat() * 0.8f + 0.6f
|
||||||
|
}
|
||||||
|
return Note(noteVal, volume, note.time)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun composeSection(depth: Int = 0): List<Note> {
|
||||||
|
if (depth < 3 && rhythmRandom.nextBoolean()) {
|
||||||
|
val notes = (0..rhythmRandom.nextInt(3 - depth)).flatMap { composeSection(depth + 1) }
|
||||||
|
if (depth == 2) {
|
||||||
|
return (0..rhythmRandom.nextInt(3)).flatMap { notes }
|
||||||
|
} else {
|
||||||
|
return notes
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var notePointer = baseNote + noteRandom.nextInt(-3..3)
|
||||||
|
var direction = -1
|
||||||
|
var directionCounter = 0
|
||||||
|
return (0..rhythmRandom.nextInt(4..16)).map {
|
||||||
|
if (directionCounter == 0) {
|
||||||
|
// start new direction
|
||||||
|
directionCounter = rhythmRandom.nextInt(2..6)
|
||||||
|
direction = if (directionCounter % 2 == 0) {
|
||||||
|
rhythmRandom.nextInt(-2..2)
|
||||||
|
} else {
|
||||||
|
noteRandom.nextInt(-3..3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notePointer = abs(notePointer + direction) % 25
|
||||||
|
directionCounter--
|
||||||
|
Note(notePointer, 1.0f, baseTime + rhythmRandom.nextInt(-1..1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
128
common/src/main/kotlin/quaedam/projection/music/MusicPlayer.kt
Normal file
128
common/src/main/kotlin/quaedam/projection/music/MusicPlayer.kt
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
package quaedam.projection.music
|
||||||
|
|
||||||
|
import dev.architectury.utils.GameInstance
|
||||||
|
import net.minecraft.client.resources.sounds.SimpleSoundInstance
|
||||||
|
import net.minecraft.client.resources.sounds.SoundInstance
|
||||||
|
import net.minecraft.core.BlockPos
|
||||||
|
import net.minecraft.core.Holder
|
||||||
|
import net.minecraft.core.particles.ParticleTypes
|
||||||
|
import net.minecraft.nbt.CompoundTag
|
||||||
|
import net.minecraft.sounds.SoundEvent
|
||||||
|
import net.minecraft.sounds.SoundSource
|
||||||
|
import net.minecraft.util.RandomSource
|
||||||
|
import net.minecraft.world.level.Level
|
||||||
|
import net.minecraft.world.level.block.NoteBlock
|
||||||
|
import net.minecraft.world.level.block.entity.SkullBlockEntity
|
||||||
|
import net.minecraft.world.level.block.state.properties.BlockStateProperties
|
||||||
|
import quaedam.projector.Projector
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
class MusicPlayer(
|
||||||
|
val noteSeed: Long,
|
||||||
|
val rhythmSeed: Long,
|
||||||
|
val level: Level,
|
||||||
|
val pos: BlockPos,
|
||||||
|
val startedAt: Long = level.gameTime
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG_NOTE_SEED = "NoteSeed"
|
||||||
|
const val TAG_RHYTHM_SEED = "RhythmSeed"
|
||||||
|
const val TAG_STARTED_AT = "StartedAt"
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(tag: CompoundTag, level: Level, pos: BlockPos) : this(
|
||||||
|
tag.getLong(TAG_NOTE_SEED),
|
||||||
|
tag.getLong(TAG_RHYTHM_SEED),
|
||||||
|
level,
|
||||||
|
pos,
|
||||||
|
tag.getLong(TAG_STARTED_AT)
|
||||||
|
)
|
||||||
|
|
||||||
|
var notes = Composer(
|
||||||
|
noteRandom = Random(noteSeed),
|
||||||
|
rhythmRandom = Random(rhythmSeed),
|
||||||
|
instrument = level.getBlockState(pos).getValue(BlockStateProperties.NOTEBLOCK_INSTRUMENT)
|
||||||
|
).composeMusic().toMutableList()
|
||||||
|
val totalTime = notes.sumOf { it.time }.toLong()
|
||||||
|
var remainingTime = totalTime
|
||||||
|
val isEnd get() = remainingTime <= 0 || notes.isEmpty()
|
||||||
|
var noteTime = 0
|
||||||
|
|
||||||
|
init {
|
||||||
|
val currentRemaining = totalTime - (level.gameTime - startedAt)
|
||||||
|
while (remainingTime > currentRemaining && !isEnd) {
|
||||||
|
// seek to current position
|
||||||
|
remainingTime -= fetchNote().time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchNote() = notes.removeFirst()
|
||||||
|
|
||||||
|
fun tick() {
|
||||||
|
if (isEnd)
|
||||||
|
return
|
||||||
|
if (noteTime <= 0) {
|
||||||
|
// start new note
|
||||||
|
val note = fetchNote()
|
||||||
|
remainingTime -= note.time
|
||||||
|
noteTime = note.time
|
||||||
|
if (level.isClientSide) {
|
||||||
|
// play note
|
||||||
|
val projections = Projector.findNearbyProjections(level, pos, MusicProjection.effect.get())
|
||||||
|
.takeIf { it.isNotEmpty() } ?: listOf(MusicProjectionEffect())
|
||||||
|
val volume = 3.0f * projections.maxOf { it.volumeFactor } * note.volume
|
||||||
|
val particle = projections.any { it.particle }
|
||||||
|
val instrument = level.getBlockState(pos).getValue(BlockStateProperties.NOTEBLOCK_INSTRUMENT)
|
||||||
|
val pitch = if (instrument.isTunable) {
|
||||||
|
NoteBlock.getPitchFromNote(note.note)
|
||||||
|
} else {
|
||||||
|
1.0f
|
||||||
|
}
|
||||||
|
|
||||||
|
val holder = if (instrument.hasCustomSound()) {
|
||||||
|
val entity = level.getBlockEntity(pos.below())
|
||||||
|
(entity as? SkullBlockEntity)?.noteBlockSound?.let {
|
||||||
|
Holder.direct(SoundEvent.createVariableRangeEvent(it))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
} ?: instrument.soundEvent
|
||||||
|
|
||||||
|
if (particle) {
|
||||||
|
level.addParticle(
|
||||||
|
ParticleTypes.NOTE,
|
||||||
|
pos.x.toDouble() + 0.5,
|
||||||
|
pos.y.toDouble() + 1.2,
|
||||||
|
pos.z.toDouble() + 0.5,
|
||||||
|
note.time.toDouble() / 24.0,
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val instance = SimpleSoundInstance(
|
||||||
|
holder.value().location,
|
||||||
|
SoundSource.RECORDS,
|
||||||
|
volume,
|
||||||
|
pitch,
|
||||||
|
RandomSource.create(level.random.nextLong()),
|
||||||
|
false, 0, SoundInstance.Attenuation.LINEAR,
|
||||||
|
pos.x.toDouble() + 0.5,
|
||||||
|
pos.y.toDouble() + 0.5,
|
||||||
|
pos.z.toDouble() + 0.5,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
GameInstance.getClient().soundManager.play(instance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
noteTime--
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toTag() = CompoundTag().apply {
|
||||||
|
putLong(TAG_NOTE_SEED, noteSeed)
|
||||||
|
putLong(TAG_RHYTHM_SEED, rhythmSeed)
|
||||||
|
putLong(TAG_STARTED_AT, startedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
package quaedam.projection.music
|
||||||
|
|
||||||
|
import net.minecraft.nbt.CompoundTag
|
||||||
|
import net.minecraft.world.item.BlockItem
|
||||||
|
import net.minecraft.world.item.Item
|
||||||
|
import quaedam.Quaedam
|
||||||
|
import quaedam.projection.EntityProjectionBlock
|
||||||
|
import quaedam.projection.ProjectionEffect
|
||||||
|
import quaedam.projection.ProjectionEffectType
|
||||||
|
import quaedam.projection.SimpleProjectionEntity
|
||||||
|
import quaedam.shell.ProjectionEffectShell
|
||||||
|
import quaedam.shell.buildProjectionEffectShell
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
object MusicProjection {
|
||||||
|
|
||||||
|
const val ID = "music_projection"
|
||||||
|
const val SHORT_ID = "music"
|
||||||
|
|
||||||
|
val block = Quaedam.blocks.register(ID) { MusicProjectionBlock }!!
|
||||||
|
|
||||||
|
val item = Quaedam.items.register(ID) {
|
||||||
|
BlockItem(
|
||||||
|
MusicProjectionBlock, Item.Properties()
|
||||||
|
.`arch$tab`(Quaedam.creativeModeTab)
|
||||||
|
)
|
||||||
|
}!!
|
||||||
|
|
||||||
|
val effect = Quaedam.projectionEffects.register(SHORT_ID) {
|
||||||
|
ProjectionEffectType { MusicProjectionEffect() }
|
||||||
|
}!!
|
||||||
|
|
||||||
|
val blockEntity = Quaedam.blockEntities.register(ID) {
|
||||||
|
SimpleProjectionEntity.createBlockEntityType(block) { MusicProjectionEffect() }
|
||||||
|
}!!
|
||||||
|
|
||||||
|
init {
|
||||||
|
SmartInstrument
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object MusicProjectionBlock : EntityProjectionBlock<MusicProjectionEffect>(createProperties().lightLevel { 3 }) {
|
||||||
|
|
||||||
|
override val blockEntity = MusicProjection.blockEntity
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data class MusicProjectionEffect(var volumeFactor: Float = 1.0f, var particle: Boolean = true) : ProjectionEffect(),
|
||||||
|
ProjectionEffectShell.Provider {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG_VOLUME_FACTOR = "VolumeFactor"
|
||||||
|
const val TAG_PARTICLE = "Particle"
|
||||||
|
}
|
||||||
|
|
||||||
|
override val type
|
||||||
|
get() = MusicProjection.effect.get()!!
|
||||||
|
|
||||||
|
override fun toNbt(tag: CompoundTag) {
|
||||||
|
tag.putFloat(TAG_VOLUME_FACTOR, volumeFactor)
|
||||||
|
tag.putBoolean(TAG_PARTICLE, particle)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fromNbt(tag: CompoundTag, trusted: Boolean) {
|
||||||
|
volumeFactor = tag.getFloat(TAG_VOLUME_FACTOR)
|
||||||
|
particle = tag.getBoolean(TAG_PARTICLE)
|
||||||
|
if (!trusted) {
|
||||||
|
volumeFactor = min(volumeFactor, 5.0f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createShell() = buildProjectionEffectShell(this) {
|
||||||
|
floatSlider("quaedam.shell.music.volume_factor", ::volumeFactor, 0.0f..5.0f, 0.1f)
|
||||||
|
boolean("quaedam.shell.music.particle", ::particle)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,236 @@
|
|||||||
|
package quaedam.projection.music
|
||||||
|
|
||||||
|
import net.minecraft.core.BlockPos
|
||||||
|
import net.minecraft.nbt.CompoundTag
|
||||||
|
import net.minecraft.network.protocol.Packet
|
||||||
|
import net.minecraft.network.protocol.game.ClientGamePacketListener
|
||||||
|
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket
|
||||||
|
import net.minecraft.server.level.ServerLevel
|
||||||
|
import net.minecraft.util.RandomSource
|
||||||
|
import net.minecraft.world.InteractionHand
|
||||||
|
import net.minecraft.world.InteractionResult
|
||||||
|
import net.minecraft.world.entity.player.Player
|
||||||
|
import net.minecraft.world.item.BlockItem
|
||||||
|
import net.minecraft.world.item.Item
|
||||||
|
import net.minecraft.world.level.Level
|
||||||
|
import net.minecraft.world.level.block.Block
|
||||||
|
import net.minecraft.world.level.block.EntityBlock
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntity
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntityTicker
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntityType
|
||||||
|
import net.minecraft.world.level.block.state.BlockState
|
||||||
|
import net.minecraft.world.level.block.state.StateDefinition
|
||||||
|
import net.minecraft.world.level.block.state.properties.BlockStateProperties
|
||||||
|
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument
|
||||||
|
import net.minecraft.world.level.material.MapColor
|
||||||
|
import net.minecraft.world.phys.BlockHitResult
|
||||||
|
import quaedam.Quaedam
|
||||||
|
import quaedam.misc.causality.CausalityAnchor
|
||||||
|
import quaedam.projector.Projector
|
||||||
|
import quaedam.utils.getChunksNearby
|
||||||
|
import quaedam.utils.sendBlockUpdated
|
||||||
|
|
||||||
|
object SmartInstrument {
|
||||||
|
|
||||||
|
const val ID = "smart_instrument"
|
||||||
|
|
||||||
|
val block = Quaedam.blocks.register(ID) { SmartInstrumentBlock }!!
|
||||||
|
|
||||||
|
val item = Quaedam.items.register(ID) {
|
||||||
|
BlockItem(
|
||||||
|
SmartInstrumentBlock, Item.Properties()
|
||||||
|
.`arch$tab`(Quaedam.creativeModeTab)
|
||||||
|
)
|
||||||
|
}!!
|
||||||
|
|
||||||
|
val blockEntity = Quaedam.blockEntities.register(ID) {
|
||||||
|
BlockEntityType.Builder.of(::SmartInstrumentBlockEntity, block.get()).build(null)
|
||||||
|
}!!
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object SmartInstrumentBlock : Block(
|
||||||
|
Properties.of()
|
||||||
|
.strength(2.7f)
|
||||||
|
.requiresCorrectToolForDrops()
|
||||||
|
.mapColor(MapColor.COLOR_BROWN)
|
||||||
|
.randomTicks()
|
||||||
|
), EntityBlock {
|
||||||
|
|
||||||
|
init {
|
||||||
|
registerDefaultState(
|
||||||
|
defaultBlockState()
|
||||||
|
.setValue(BlockStateProperties.NOTEBLOCK_INSTRUMENT, NoteBlockInstrument.HARP)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newBlockEntity(pos: BlockPos, state: BlockState) = SmartInstrumentBlockEntity(pos, state)
|
||||||
|
|
||||||
|
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||||
|
super.createBlockStateDefinition(builder)
|
||||||
|
builder.add(BlockStateProperties.NOTEBLOCK_INSTRUMENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("OVERRIDE_DEPRECATION", "DEPRECATION")
|
||||||
|
override fun neighborChanged(
|
||||||
|
state: BlockState,
|
||||||
|
level: Level,
|
||||||
|
pos: BlockPos,
|
||||||
|
neighborBlock: Block,
|
||||||
|
neighborPos: BlockPos,
|
||||||
|
movedByPiston: Boolean
|
||||||
|
) {
|
||||||
|
super.neighborChanged(state, level, pos, neighborBlock, neighborPos, movedByPiston)
|
||||||
|
level.setBlock(
|
||||||
|
pos,
|
||||||
|
state.setValue(BlockStateProperties.NOTEBLOCK_INSTRUMENT, level.getBlockState(pos.below()).instrument()),
|
||||||
|
UPDATE_ALL
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("OVERRIDE_DEPRECATION", "DEPRECATION")
|
||||||
|
override fun onPlace(state: BlockState, level: Level, pos: BlockPos, oldState: BlockState, movedByPiston: Boolean) {
|
||||||
|
super.onPlace(state, level, pos, oldState, movedByPiston)
|
||||||
|
level.setBlock(
|
||||||
|
pos,
|
||||||
|
state.setValue(BlockStateProperties.NOTEBLOCK_INSTRUMENT, level.getBlockState(pos.below()).instrument()),
|
||||||
|
UPDATE_ALL
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("OVERRIDE_DEPRECATION")
|
||||||
|
override fun randomTick(
|
||||||
|
state: BlockState,
|
||||||
|
level: ServerLevel,
|
||||||
|
pos: BlockPos,
|
||||||
|
random: RandomSource
|
||||||
|
) {
|
||||||
|
if (Projector.findNearbyProjections(level, pos, MusicProjection.effect.get()).isNotEmpty()) {
|
||||||
|
val entity = level.getBlockEntity(pos) as SmartInstrumentBlockEntity
|
||||||
|
if (entity.player == null) {
|
||||||
|
entity.startMusic()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("OVERRIDE_DEPRECATION", "DEPRECATION")
|
||||||
|
override fun use(
|
||||||
|
state: BlockState,
|
||||||
|
level: Level,
|
||||||
|
pos: BlockPos,
|
||||||
|
player: Player,
|
||||||
|
hand: InteractionHand,
|
||||||
|
hit: BlockHitResult
|
||||||
|
): InteractionResult {
|
||||||
|
if (Projector.findNearbyProjections(level, pos, MusicProjection.effect.get()).isNotEmpty()
|
||||||
|
|| CausalityAnchor.checkEffect(level, pos)
|
||||||
|
) {
|
||||||
|
val entity = level.getBlockEntity(pos) as SmartInstrumentBlockEntity
|
||||||
|
if (entity.player == null) {
|
||||||
|
entity.startMusic()
|
||||||
|
}
|
||||||
|
return InteractionResult.SUCCESS
|
||||||
|
}
|
||||||
|
return super.use(state, level, pos, player, hand, hit)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T : BlockEntity?> getTicker(
|
||||||
|
level: Level,
|
||||||
|
state: BlockState,
|
||||||
|
blockEntityType: BlockEntityType<T>
|
||||||
|
): BlockEntityTicker<T> {
|
||||||
|
return BlockEntityTicker { _, _, _, entity ->
|
||||||
|
(entity as? SmartInstrumentBlockEntity)?.tick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class SmartInstrumentBlockEntity(pos: BlockPos, state: BlockState) :
|
||||||
|
BlockEntity(SmartInstrument.blockEntity.get(), pos, state) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG_MUSIC = "Music"
|
||||||
|
}
|
||||||
|
|
||||||
|
// delay MusicPlayer initialization until level is available
|
||||||
|
var playerData: CompoundTag? = null
|
||||||
|
var player: MusicPlayer? = null
|
||||||
|
|
||||||
|
override fun getUpdateTag(): CompoundTag = saveWithoutMetadata()
|
||||||
|
|
||||||
|
override fun getUpdatePacket(): Packet<ClientGamePacketListener> = ClientboundBlockEntityDataPacket.create(this)
|
||||||
|
|
||||||
|
override fun load(tag: CompoundTag) {
|
||||||
|
super.load(tag)
|
||||||
|
if (TAG_MUSIC in tag) {
|
||||||
|
try {
|
||||||
|
player = MusicPlayer(tag.getCompound(TAG_MUSIC), level!!, blockPos)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
playerData = tag.getCompound(TAG_MUSIC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveAdditional(tag: CompoundTag) {
|
||||||
|
super.saveAdditional(tag)
|
||||||
|
if (playerData != null) {
|
||||||
|
tag.put(TAG_MUSIC, playerData!!)
|
||||||
|
}
|
||||||
|
if (player != null) {
|
||||||
|
tag.put(TAG_MUSIC, player!!.toTag())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkProjections() =
|
||||||
|
Projector.findNearbyProjections(level!!, blockPos, MusicProjection.effect.get()).isNotEmpty()
|
||||||
|
|| CausalityAnchor.checkEffect(level!!, blockPos)
|
||||||
|
|
||||||
|
fun startMusic(force: Boolean = false, synced: Boolean = false) {
|
||||||
|
if ((player == null || force) && !level!!.isClientSide && checkProjections()) {
|
||||||
|
player = MusicPlayer(level!!.random.nextLong(), level!!.gameTime / 20, level!!, blockPos)
|
||||||
|
setChanged()
|
||||||
|
sendBlockUpdated()
|
||||||
|
if (!synced) {
|
||||||
|
// sync start to other instruments
|
||||||
|
level!!.getChunksNearby(blockPos, 1)
|
||||||
|
.flatMap {
|
||||||
|
it.blockEntities
|
||||||
|
.filterValues { entity -> entity is SmartInstrumentBlockEntity }
|
||||||
|
.filterKeys { pos -> pos.distSqr(blockPos) < 100 }
|
||||||
|
.values
|
||||||
|
}
|
||||||
|
.filterNot { it == this }
|
||||||
|
.filterIsInstance<SmartInstrumentBlockEntity>()
|
||||||
|
.forEach { it.startMusic(force = true, synced = true) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun tick() {
|
||||||
|
if (playerData != null) {
|
||||||
|
player = MusicPlayer(playerData!!, level!!, blockPos)
|
||||||
|
playerData = null
|
||||||
|
}
|
||||||
|
if (player != null) {
|
||||||
|
if (checkProjections()) {
|
||||||
|
player!!.tick()
|
||||||
|
if (!level!!.isClientSide) {
|
||||||
|
if (player!!.isEnd) {
|
||||||
|
player = null
|
||||||
|
setChanged()
|
||||||
|
sendBlockUpdated()
|
||||||
|
if (CausalityAnchor.checkEffect(level!!, blockPos) || level!!.random.nextInt(7) != 0) {
|
||||||
|
startMusic()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
player = null
|
||||||
|
setChanged()
|
||||||
|
sendBlockUpdated()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -28,9 +28,9 @@ class ProjectionEffectShell(val effect: ProjectionEffect) {
|
|||||||
|
|
||||||
fun text(key: String, value: Component) = row(key) { StringWidget(value, it.font) }
|
fun text(key: String, value: Component) = row(key) { StringWidget(value, it.font) }
|
||||||
|
|
||||||
fun doubleSlider(key: String, property: KMutableProperty0<Double>, range: ClosedRange<Double>, step: Double) {
|
fun doubleSlider(key: String, property: KMutableProperty0<Double>, range: ClosedRange<Double>, rawStep: Double) {
|
||||||
val len = range.endInclusive - range.start
|
val len = range.endInclusive - range.start
|
||||||
val step = step / len
|
val step = rawStep / len
|
||||||
row(key) {
|
row(key) {
|
||||||
object : AbstractSliderButton(
|
object : AbstractSliderButton(
|
||||||
0, 0, width, height,
|
0, 0, width, height,
|
||||||
@ -53,6 +53,35 @@ class ProjectionEffectShell(val effect: ProjectionEffect) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun floatSlider(key: String, property: KMutableProperty0<Float>, range: ClosedRange<Float>, rawStep: Float) {
|
||||||
|
val len = range.endInclusive - range.start
|
||||||
|
val step = rawStep / len
|
||||||
|
row(key) {
|
||||||
|
object : AbstractSliderButton(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
Component.literal(String.format("%.2f", property.get())),
|
||||||
|
(property.get() - range.start) / len.toDouble()
|
||||||
|
) {
|
||||||
|
override fun updateMessage() {
|
||||||
|
message = Component.literal(String.format("%.2f", property.get()))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun applyValue() {
|
||||||
|
val diff = value % step
|
||||||
|
if (diff < step * 0.5) {
|
||||||
|
value -= diff
|
||||||
|
} else {
|
||||||
|
value += (step - diff)
|
||||||
|
}
|
||||||
|
property.set(range.start + (value.toFloat() * len))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun intSlider(key: String, property: KMutableProperty0<Int>, range: IntProgression) {
|
fun intSlider(key: String, property: KMutableProperty0<Int>, range: IntProgression) {
|
||||||
val len = range.last - range.first
|
val len = range.last - range.first
|
||||||
val step = range.step.toDouble() / len
|
val step = range.step.toDouble() / len
|
||||||
@ -80,9 +109,25 @@ class ProjectionEffectShell(val effect: ProjectionEffect) {
|
|||||||
|
|
||||||
fun intCycle(key: String, property: KMutableProperty0<Int>, range: IntProgression) =
|
fun intCycle(key: String, property: KMutableProperty0<Int>, range: IntProgression) =
|
||||||
row(key) {
|
row(key) {
|
||||||
CycleButton.builder<Int> { Component.literal(it.toString()) }
|
CycleButton.builder<Int> {
|
||||||
|
property.set(it)
|
||||||
|
Component.literal(it.toString())
|
||||||
|
}
|
||||||
.displayOnlyValue()
|
.displayOnlyValue()
|
||||||
.withValues(range.toList())
|
.withValues(range.toList())
|
||||||
|
.withInitialValue(property.get())
|
||||||
|
.create(0, 0, width, height, Component.translatable(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun boolean(key: String, property: KMutableProperty0<Boolean>) =
|
||||||
|
row(key) {
|
||||||
|
CycleButton.builder<Boolean> {
|
||||||
|
property.set(it)
|
||||||
|
Component.translatable("$key.$it")
|
||||||
|
}
|
||||||
|
.displayOnlyValue()
|
||||||
|
.withValues(listOf(true, false))
|
||||||
|
.withInitialValue(property.get())
|
||||||
.create(0, 0, width, height, Component.translatable(key))
|
.create(0, 0, width, height, Component.translatable(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"variants": {
|
||||||
|
"": {
|
||||||
|
"model": "quaedam:block/music_projection"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"variants": {
|
||||||
|
"": {
|
||||||
|
"model": "quaedam:block/smart_instrument"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,8 @@
|
|||||||
"block.quaedam.swarm_projection": "Swarm Projection",
|
"block.quaedam.swarm_projection": "Swarm Projection",
|
||||||
"block.quaedam.sound_projection": "Sound Projection",
|
"block.quaedam.sound_projection": "Sound Projection",
|
||||||
"block.quaedam.noise_projection": "Noise Projection",
|
"block.quaedam.noise_projection": "Noise Projection",
|
||||||
|
"block.quaedam.music_projection": "Music Projection",
|
||||||
|
"block.quaedam.smart_instrument": "Smart Instrument",
|
||||||
"block.quaedam.causality_anchor": "Causality Anchor",
|
"block.quaedam.causality_anchor": "Causality Anchor",
|
||||||
"block.quaedam.reality_stabler": "Reality Stabler",
|
"block.quaedam.reality_stabler": "Reality Stabler",
|
||||||
"entity.quaedam.projected_person": "Virtual Person",
|
"entity.quaedam.projected_person": "Virtual Person",
|
||||||
@ -19,5 +21,9 @@
|
|||||||
"quaedam.shell.noise.rate": "Rate",
|
"quaedam.shell.noise.rate": "Rate",
|
||||||
"quaedam.shell.noise.amount": "Amount",
|
"quaedam.shell.noise.amount": "Amount",
|
||||||
"quaedam.shell.swarm.max_count": "Max Count",
|
"quaedam.shell.swarm.max_count": "Max Count",
|
||||||
"quaedam.shell.sound.rate": "Rate"
|
"quaedam.shell.sound.rate": "Rate",
|
||||||
|
"quaedam.shell.music.volume_factor": "Volume Factor",
|
||||||
|
"quaedam.shell.music.particle": "Particle",
|
||||||
|
"quaedam.shell.music.particle.true": "Display",
|
||||||
|
"quaedam.shell.music.particle.false": "Hidden"
|
||||||
}
|
}
|
@ -5,8 +5,10 @@
|
|||||||
"block.quaedam.swarm_projection": "人群投影",
|
"block.quaedam.swarm_projection": "人群投影",
|
||||||
"block.quaedam.sound_projection": "声音投影",
|
"block.quaedam.sound_projection": "声音投影",
|
||||||
"block.quaedam.noise_projection": "噪音投影",
|
"block.quaedam.noise_projection": "噪音投影",
|
||||||
|
"block.quaedam.music_projection": "音乐投影",
|
||||||
"block.quaedam.causality_anchor": "因果锚",
|
"block.quaedam.causality_anchor": "因果锚",
|
||||||
"block.quaedam.reality_stabler": "现实稳定器",
|
"block.quaedam.reality_stabler": "现实稳定器",
|
||||||
|
"block.quaedam.smart_instrument": "智能乐器",
|
||||||
"entity.quaedam.projected_person": "虚拟个体",
|
"entity.quaedam.projected_person": "虚拟个体",
|
||||||
"item.quaedam.projection_shell": "投影操作面板",
|
"item.quaedam.projection_shell": "投影操作面板",
|
||||||
"quaedam.screen.projection_shell": "投影操作",
|
"quaedam.screen.projection_shell": "投影操作",
|
||||||
@ -19,5 +21,9 @@
|
|||||||
"quaedam.shell.noise.rate": "速率",
|
"quaedam.shell.noise.rate": "速率",
|
||||||
"quaedam.shell.noise.amount": "数量",
|
"quaedam.shell.noise.amount": "数量",
|
||||||
"quaedam.shell.swarm.max_count": "最大数量",
|
"quaedam.shell.swarm.max_count": "最大数量",
|
||||||
"quaedam.shell.sound.rate": "速率"
|
"quaedam.shell.sound.rate": "速率",
|
||||||
|
"quaedam.shell.music.volume_factor": "响度因子",
|
||||||
|
"quaedam.shell.music.particle": "粒子效果",
|
||||||
|
"quaedam.shell.music.particle.true": "显示",
|
||||||
|
"quaedam.shell.music.particle.false": "隐藏"
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"parent": "quaedam:block/projection",
|
||||||
|
"textures": {
|
||||||
|
"ext": "quaedam:block/music_projection"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:block/note_block"
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"parent": "quaedam:block/music_projection"
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"parent": "quaedam:block/smart_instrument"
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 396 B |
@ -3,6 +3,7 @@
|
|||||||
"quaedam:noise_projection",
|
"quaedam:noise_projection",
|
||||||
"quaedam:sound_projection",
|
"quaedam:sound_projection",
|
||||||
"quaedam:skylight_projection",
|
"quaedam:skylight_projection",
|
||||||
"quaedam:swarm_projection"
|
"quaedam:swarm_projection",
|
||||||
|
"quaedam:music_projection"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user