feat: music player
This commit is contained in:
parent
d5c9eb6b41
commit
730f6b9781
20
common/src/main/kotlin/quaedam/projection/music/Composer.kt
Normal file
20
common/src/main/kotlin/quaedam/projection/music/Composer.kt
Normal 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),
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
108
common/src/main/kotlin/quaedam/projection/music/MusicPlayer.kt
Normal file
108
common/src/main/kotlin/quaedam/projection/music/MusicPlayer.kt
Normal 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"variants": {
|
||||||
|
"": {
|
||||||
|
"model": "quaedam:block/cyber_instrument"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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"
|
||||||
}
|
}
|
@ -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": "隐藏"
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:block/note_block"
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user