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.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<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 {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "quaedam:block/cyber_instrument"
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
@ -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": "隐藏"
|
||||
}
|
||||
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"parent": "minecraft:block/note_block"
|
||||
}
|
Loading…
Reference in New Issue
Block a user