From 730f6b97819c0fc003e2a37932a09999b3298841 Mon Sep 17 00:00:00 2001 From: xtex Date: Sat, 29 Jul 2023 21:33:08 +0800 Subject: [PATCH] feat: music player --- .../quaedam/projection/music/Composer.kt | 20 ++ .../projection/music/CyberInstrument.kt | 200 ++++++++++++++++++ .../quaedam/projection/music/MusicPlayer.kt | 108 ++++++++++ .../projection/music/MusicProjection.kt | 17 +- .../quaedam/blockstates/cyber_instrument.json | 7 + .../resources/assets/quaedam/lang/en_us.json | 6 +- .../resources/assets/quaedam/lang/zh_cn.json | 6 +- .../models/block/cyber_instrument.json | 3 + 8 files changed, 354 insertions(+), 13 deletions(-) create mode 100644 common/src/main/kotlin/quaedam/projection/music/Composer.kt create mode 100644 common/src/main/kotlin/quaedam/projection/music/CyberInstrument.kt create mode 100644 common/src/main/kotlin/quaedam/projection/music/MusicPlayer.kt create mode 100644 common/src/main/resources/assets/quaedam/blockstates/cyber_instrument.json create mode 100644 common/src/main/resources/assets/quaedam/models/block/cyber_instrument.json diff --git a/common/src/main/kotlin/quaedam/projection/music/Composer.kt b/common/src/main/kotlin/quaedam/projection/music/Composer.kt new file mode 100644 index 0000000..d6c1662 --- /dev/null +++ b/common/src/main/kotlin/quaedam/projection/music/Composer.kt @@ -0,0 +1,20 @@ +package quaedam.projection.music + +import kotlin.random.Random + +object Composer { + + data class Note(val note: Int, val volume: Float, val time: Int) + + fun composeMusic(random: Random) = listOf( + Note(0, 1.0f, 3), + Note(1, 1.0f, 3), + Note(2, 1.0f, 3), + Note(3, 1.0f, 3), + Note(4, 1.0f, 3), + Note(5, 1.0f, 3), + Note(6, 1.0f, 3), + Note(7, 1.0f, 3), + ) + +} \ No newline at end of file diff --git a/common/src/main/kotlin/quaedam/projection/music/CyberInstrument.kt b/common/src/main/kotlin/quaedam/projection/music/CyberInstrument.kt new file mode 100644 index 0000000..9febe42 --- /dev/null +++ b/common/src/main/kotlin/quaedam/projection/music/CyberInstrument.kt @@ -0,0 +1,200 @@ +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.item.context.BlockPlaceContext +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.projector.Projector + +object CyberInstrument { + + const val ID = "cyber_instrument" + + val block = Quaedam.blocks.register(ID) { CyberInstrumentBlock }!! + + val item = Quaedam.items.register(ID) { + BlockItem( + CyberInstrumentBlock, Item.Properties() + .`arch$tab`(Quaedam.creativeModeTab) + ) + }!! + + val blockEntity = Quaedam.blockEntities.register(ID) { + BlockEntityType.Builder.of(::CyberInstrumentBlockEntity, block.get()).build(null) + }!! + +} + +object CyberInstrumentBlock : 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) = CyberInstrumentBlockEntity(pos, state) + + override fun createBlockStateDefinition(builder: StateDefinition.Builder) { + 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 CyberInstrumentBlockEntity + 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()) { + val entity = level.getBlockEntity(pos) as CyberInstrumentBlockEntity + if (entity.player == null) { + entity.startMusic() + return InteractionResult.SUCCESS + } + } + return super.use(state, level, pos, player, hand, hit) + } + + override fun getTicker( + level: Level, + state: BlockState, + blockEntityType: BlockEntityType + ): BlockEntityTicker { + return BlockEntityTicker { _, _, _, entity -> + (entity as? CyberInstrumentBlockEntity)?.tick() + } + } + +} + +class CyberInstrumentBlockEntity(pos: BlockPos, state: BlockState) : + BlockEntity(CyberInstrument.blockEntity.get(), pos, state) { + + companion object { + const val TAG_MUSIC = "Music" + } + + var player: MusicPlayer? = null + + override fun getUpdateTag(): CompoundTag = saveWithoutMetadata() + + override fun getUpdatePacket(): Packet = ClientboundBlockEntityDataPacket.create(this) + + override fun load(tag: CompoundTag) { + super.load(tag) + if (TAG_MUSIC in tag) { + player = MusicPlayer(tag.getCompound(TAG_MUSIC), level!!, blockPos) + } + } + + override fun saveAdditional(tag: CompoundTag) { + super.saveAdditional(tag) + if (player != null) { + tag.put(TAG_MUSIC, player!!.toTag()) + } + } + + private fun checkProjections() = + Projector.findNearbyProjections(level!!, blockPos, MusicProjection.effect.get()).isNotEmpty() + + fun startMusic() { + if (player == null && !level!!.isClientSide && checkProjections()) { + player = MusicPlayer(level!!.random.nextLong(), level!!, blockPos) + setChanged() + } + } + + fun tick() { + if (checkProjections()) { + player?.tick() + if (!level!!.isClientSide) { + if (player?.isEnd == true) { + player = null + setChanged() + if (level!!.random.nextInt(7) != 0) { + startMusic() + } + } + } + } else { + player = null + setChanged() + } + } + +} diff --git a/common/src/main/kotlin/quaedam/projection/music/MusicPlayer.kt b/common/src/main/kotlin/quaedam/projection/music/MusicPlayer.kt new file mode 100644 index 0000000..3774e78 --- /dev/null +++ b/common/src/main/kotlin/quaedam/projection/music/MusicPlayer.kt @@ -0,0 +1,108 @@ +package quaedam.projection.music + +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.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 seed: Long, val level: Level, val pos: BlockPos, val startedAt: Long = level.gameTime) { + + companion object { + const val TAG_SEED = "Seed" + const val TAG_STARTED_AT = "StartedAt" + } + + constructor(tag: CompoundTag, level: Level, pos: BlockPos) : this( + tag.getLong(TAG_SEED), + level, + pos, + tag.getLong(TAG_STARTED_AT) + ) + + var notes = Composer.composeMusic(Random(seed)).toMutableList() + val totalTime = notes.sumOf { it.time }.toLong() + var remainingTime = totalTime + val isEnd get() = remainingTime <= 0 || notes.isEmpty() + var noteTime = 0 + + init { + var currentRemaining = totalTime - (level.gameTime - startedAt) + while (remainingTime > currentRemaining) { + // 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()) + 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 + ) + } + + level.playSeededSound( + null, + pos.x.toDouble() + 0.5, + pos.y.toDouble() + 0.5, + pos.z.toDouble() + 0.5, + holder, + SoundSource.RECORDS, + volume, + pitch, + level.random.nextLong() + ) + } + } + noteTime-- + } + + fun toTag() = CompoundTag().apply { + putLong(TAG_SEED, seed) + putLong(TAG_STARTED_AT, startedAt) + } + +} \ No newline at end of file diff --git a/common/src/main/kotlin/quaedam/projection/music/MusicProjection.kt b/common/src/main/kotlin/quaedam/projection/music/MusicProjection.kt index 7cce014..b063509 100644 --- a/common/src/main/kotlin/quaedam/projection/music/MusicProjection.kt +++ b/common/src/main/kotlin/quaedam/projection/music/MusicProjection.kt @@ -8,7 +8,6 @@ import quaedam.projection.EntityProjectionBlock import quaedam.projection.ProjectionEffect import quaedam.projection.ProjectionEffectType import quaedam.projection.SimpleProjectionEntity -import quaedam.projection.misc.SoundProjection import quaedam.shell.ProjectionEffectShell import quaedam.shell.buildProjectionEffectShell import kotlin.math.min @@ -16,7 +15,7 @@ import kotlin.math.min object MusicProjection { const val ID = "music_projection" - const val SHORT_ID = "projection" + const val SHORT_ID = "music" val block = Quaedam.blocks.register(ID) { MusicProjectionBlock }!! @@ -35,6 +34,10 @@ object MusicProjection { SimpleProjectionEntity.createBlockEntityType(block) { MusicProjectionEffect() } }!! + init { + CyberInstrument + } + } object MusicProjectionBlock : EntityProjectionBlock(createProperties().lightLevel { 3 }) { @@ -43,12 +46,12 @@ object MusicProjectionBlock : EntityProjectionBlock(creat } -data class MusicProjectionEffect(var volumeFactor: Float = 1.0f, var multiTracks: Boolean = true) : ProjectionEffect(), +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_MULTI_TRACKS = "MultiTracks" + const val TAG_PARTICLE = "Particle" } override val type @@ -56,12 +59,12 @@ data class MusicProjectionEffect(var volumeFactor: Float = 1.0f, var multiTracks override fun toNbt(tag: CompoundTag) { tag.putFloat(TAG_VOLUME_FACTOR, volumeFactor) - tag.putBoolean(TAG_MULTI_TRACKS, multiTracks) + tag.putBoolean(TAG_PARTICLE, particle) } override fun fromNbt(tag: CompoundTag, trusted: Boolean) { volumeFactor = tag.getFloat(TAG_VOLUME_FACTOR) - multiTracks = tag.getBoolean(TAG_MULTI_TRACKS) + particle = tag.getBoolean(TAG_PARTICLE) if (!trusted) { volumeFactor = min(volumeFactor, 5.0f) } @@ -69,7 +72,7 @@ data class MusicProjectionEffect(var volumeFactor: Float = 1.0f, var multiTracks override fun createShell() = buildProjectionEffectShell(this) { floatSlider("quaedam.shell.music.volume_factor", ::volumeFactor, 0.0f..1.0f, 0.1f) - boolean("quaedam.shell.music.multi_tracks", ::multiTracks) + boolean("quaedam.shell.music.particle", ::particle) } } diff --git a/common/src/main/resources/assets/quaedam/blockstates/cyber_instrument.json b/common/src/main/resources/assets/quaedam/blockstates/cyber_instrument.json new file mode 100644 index 0000000..3f0c676 --- /dev/null +++ b/common/src/main/resources/assets/quaedam/blockstates/cyber_instrument.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "quaedam:block/cyber_instrument" + } + } +} diff --git a/common/src/main/resources/assets/quaedam/lang/en_us.json b/common/src/main/resources/assets/quaedam/lang/en_us.json index 249f2bf..79e74bb 100644 --- a/common/src/main/resources/assets/quaedam/lang/en_us.json +++ b/common/src/main/resources/assets/quaedam/lang/en_us.json @@ -22,7 +22,7 @@ "quaedam.shell.swarm.max_count": "Max Count", "quaedam.shell.sound.rate": "Rate", "quaedam.shell.music.volume_factor": "Volume Factor", - "quaedam.shell.music.multi_tracks": "Multi Tracks", - "quaedam.shell.music.multi_tracks.true": "Enabled", - "quaedam.shell.music.multi_tracks.false": "Disabled" + "quaedam.shell.music.particle": "Particle", + "quaedam.shell.music.particle.true": "Display", + "quaedam.shell.music.particle.false": "Hidden" } \ No newline at end of file diff --git a/common/src/main/resources/assets/quaedam/lang/zh_cn.json b/common/src/main/resources/assets/quaedam/lang/zh_cn.json index 0e1cb76..04912c6 100644 --- a/common/src/main/resources/assets/quaedam/lang/zh_cn.json +++ b/common/src/main/resources/assets/quaedam/lang/zh_cn.json @@ -22,7 +22,7 @@ "quaedam.shell.swarm.max_count": "最大数量", "quaedam.shell.sound.rate": "速率", "quaedam.shell.music.volume_factor": "响度因子", - "quaedam.shell.music.multi_tracks": "多轨音乐", - "quaedam.shell.music.multi_tracks.true": "开启", - "quaedam.shell.music.multi_tracks.false": "关闭" + "quaedam.shell.music.particle": "粒子效果", + "quaedam.shell.music.particle.true": "显示", + "quaedam.shell.music.particle.false": "隐藏" } diff --git a/common/src/main/resources/assets/quaedam/models/block/cyber_instrument.json b/common/src/main/resources/assets/quaedam/models/block/cyber_instrument.json new file mode 100644 index 0000000..dd87334 --- /dev/null +++ b/common/src/main/resources/assets/quaedam/models/block/cyber_instrument.json @@ -0,0 +1,3 @@ +{ + "parent": "minecraft:block/note_block" +} \ No newline at end of file