feat: music player

This commit is contained in:
xtex 2023-07-29 21:33:08 +08:00
parent d5c9eb6b41
commit 730f6b9781
Signed by: xtex
GPG Key ID: B918086ED8045B91
8 changed files with 354 additions and 13 deletions

View File

@ -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>(
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),
)
}

View File

@ -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<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 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 <T : BlockEntity?> getTicker(
level: Level,
state: BlockState,
blockEntityType: BlockEntityType<T>
): BlockEntityTicker<T> {
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<ClientGamePacketListener> = 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()
}
}
}

View File

@ -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)
}
}

View File

@ -8,7 +8,6 @@ import quaedam.projection.EntityProjectionBlock
import quaedam.projection.ProjectionEffect import quaedam.projection.ProjectionEffect
import quaedam.projection.ProjectionEffectType import quaedam.projection.ProjectionEffectType
import quaedam.projection.SimpleProjectionEntity import quaedam.projection.SimpleProjectionEntity
import quaedam.projection.misc.SoundProjection
import quaedam.shell.ProjectionEffectShell import quaedam.shell.ProjectionEffectShell
import quaedam.shell.buildProjectionEffectShell import quaedam.shell.buildProjectionEffectShell
import kotlin.math.min import kotlin.math.min
@ -16,7 +15,7 @@ import kotlin.math.min
object MusicProjection { object MusicProjection {
const val ID = "music_projection" const val ID = "music_projection"
const val SHORT_ID = "projection" const val SHORT_ID = "music"
val block = Quaedam.blocks.register(ID) { MusicProjectionBlock }!! val block = Quaedam.blocks.register(ID) { MusicProjectionBlock }!!
@ -35,6 +34,10 @@ object MusicProjection {
SimpleProjectionEntity.createBlockEntityType(block) { MusicProjectionEffect() } SimpleProjectionEntity.createBlockEntityType(block) { MusicProjectionEffect() }
}!! }!!
init {
CyberInstrument
}
} }
object MusicProjectionBlock : EntityProjectionBlock<MusicProjectionEffect>(createProperties().lightLevel { 3 }) { object MusicProjectionBlock : EntityProjectionBlock<MusicProjectionEffect>(createProperties().lightLevel { 3 }) {
@ -43,12 +46,12 @@ object MusicProjectionBlock : EntityProjectionBlock<MusicProjectionEffect>(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 { ProjectionEffectShell.Provider {
companion object { companion object {
const val TAG_VOLUME_FACTOR = "VolumeFactor" const val TAG_VOLUME_FACTOR = "VolumeFactor"
const val TAG_MULTI_TRACKS = "MultiTracks" const val TAG_PARTICLE = "Particle"
} }
override val type override val type
@ -56,12 +59,12 @@ data class MusicProjectionEffect(var volumeFactor: Float = 1.0f, var multiTracks
override fun toNbt(tag: CompoundTag) { override fun toNbt(tag: CompoundTag) {
tag.putFloat(TAG_VOLUME_FACTOR, volumeFactor) tag.putFloat(TAG_VOLUME_FACTOR, volumeFactor)
tag.putBoolean(TAG_MULTI_TRACKS, multiTracks) tag.putBoolean(TAG_PARTICLE, particle)
} }
override fun fromNbt(tag: CompoundTag, trusted: Boolean) { override fun fromNbt(tag: CompoundTag, trusted: Boolean) {
volumeFactor = tag.getFloat(TAG_VOLUME_FACTOR) volumeFactor = tag.getFloat(TAG_VOLUME_FACTOR)
multiTracks = tag.getBoolean(TAG_MULTI_TRACKS) particle = tag.getBoolean(TAG_PARTICLE)
if (!trusted) { if (!trusted) {
volumeFactor = min(volumeFactor, 5.0f) volumeFactor = min(volumeFactor, 5.0f)
} }
@ -69,7 +72,7 @@ data class MusicProjectionEffect(var volumeFactor: Float = 1.0f, var multiTracks
override fun createShell() = buildProjectionEffectShell(this) { override fun createShell() = buildProjectionEffectShell(this) {
floatSlider("quaedam.shell.music.volume_factor", ::volumeFactor, 0.0f..1.0f, 0.1f) 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)
} }
} }

View File

@ -0,0 +1,7 @@
{
"variants": {
"": {
"model": "quaedam:block/cyber_instrument"
}
}
}

View File

@ -22,7 +22,7 @@
"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.volume_factor": "Volume Factor",
"quaedam.shell.music.multi_tracks": "Multi Tracks", "quaedam.shell.music.particle": "Particle",
"quaedam.shell.music.multi_tracks.true": "Enabled", "quaedam.shell.music.particle.true": "Display",
"quaedam.shell.music.multi_tracks.false": "Disabled" "quaedam.shell.music.particle.false": "Hidden"
} }

View File

@ -22,7 +22,7 @@
"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.volume_factor": "响度因子",
"quaedam.shell.music.multi_tracks": "多轨音乐", "quaedam.shell.music.particle": "粒子效果",
"quaedam.shell.music.multi_tracks.true": "开启", "quaedam.shell.music.particle.true": "显示",
"quaedam.shell.music.multi_tracks.false": "关闭" "quaedam.shell.music.particle.false": "隐藏"
} }

View File

@ -0,0 +1,3 @@
{
"parent": "minecraft:block/note_block"
}