29 |
30 | abstract val dataDir: File
31 |
32 | abstract fun log(message: String)
33 |
34 | val settings = ReplayExtensionSettings()
35 |
36 | abstract fun registerSyncRepeatingTask(time: TickTime, action: () -> Unit): Any
37 |
38 | abstract fun cancelTask(tickerTask: Any)
39 |
40 | abstract fun getEntityType(replayEntity: E): String
41 | abstract fun getEntityData(replayEntity: E): BaseEntityData?
42 |
43 | abstract fun addToViewerTeam(p: P)
44 | abstract fun removeFromViewerTeam(player: P)
45 |
46 | abstract fun getWorldById(it: UUID): W
47 | abstract fun unregisterWorld(instance: W)
48 |
49 | abstract fun getEntityManager(replaySession: ReplaySession): IEntityManager
50 |
51 | abstract fun createReplaySession(
52 | replay: Replay,
53 | viewers: MutableList,
54 | instance: ReplayWorld? = null,
55 | tickTime: TickTime = TickTime(1L, TimeUnit.TICK)
56 | ): ReplaySession
57 |
58 | abstract fun getPlayerInventoryCopy(player: P): Any
59 |
60 | abstract fun loadPlayerInventoryCopy(player: P, inventory: Any)
61 | }
--------------------------------------------------------------------------------
/impl-abstraction/src/main/kotlin/io/github/openminigameserver/replay/recorder/PositionRecordType.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.recorder
2 |
3 | enum class PositionRecordType {
4 | /**
5 | * Group recorded positions into one Recordable
6 | */
7 | GROUP_ALL,
8 |
9 | /**
10 | * Split recorded positions into multiple Recordables
11 | */
12 | SEPARATE_ALL
13 | }
14 |
--------------------------------------------------------------------------------
/impl-abstraction/src/main/kotlin/io/github/openminigameserver/replay/recorder/RecorderOptions.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.recorder
2 |
3 | data class RecorderOptions(
4 | val positionRecordType: PositionRecordType = PositionRecordType.GROUP_ALL,
5 | val recordAllLocationChanges: Boolean = true,
6 | val recordInstanceChunks: Boolean = true
7 | )
8 |
--------------------------------------------------------------------------------
/impl-abstraction/src/main/kotlin/io/github/openminigameserver/replay/recorder/ReplayRecorder.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.recorder
2 |
3 | import io.github.openminigameserver.replay.ReplayManager
4 | import io.github.openminigameserver.replay.TickTime
5 | import io.github.openminigameserver.replay.TimeUnit
6 | import io.github.openminigameserver.replay.abstraction.ReplayEntity
7 | import io.github.openminigameserver.replay.abstraction.ReplayUser
8 | import io.github.openminigameserver.replay.abstraction.ReplayWorld
9 | import io.github.openminigameserver.replay.model.recordable.RecordablePosition
10 | import io.github.openminigameserver.replay.model.recordable.RecordablePositionAndVector
11 | import io.github.openminigameserver.replay.model.recordable.RecordedChunk
12 | import io.github.openminigameserver.replay.model.recordable.entity.RecordableEntity
13 | import io.github.openminigameserver.replay.model.recordable.impl.*
14 | import io.github.openminigameserver.replay.platform.ReplayExtension
15 | import io.github.openminigameserver.replay.platform.ReplayPlatform
16 | import io.github.openminigameserver.replay.recorder.PositionRecordType.GROUP_ALL
17 | import io.github.openminigameserver.replay.recorder.PositionRecordType.SEPARATE_ALL
18 | import java.util.*
19 |
20 | class ReplayRecorder(
21 | private val replayExtension: ReplayExtension,
22 | private val instance: ReplayWorld,
23 | private val options: RecorderOptions = RecorderOptions(),
24 | private val tickInterval: TickTime = TickTime(1L, TimeUnit.TICK)
25 | ) {
26 | private val replayManager: ReplayManager
27 | get() = replayExtension.replayManager
28 | private var tickerTask: Any
29 | val replay = replayManager.createEmptyReplay()
30 | private var isRecording = false
31 |
32 | init {
33 | tickerTask = buildTickerTask()
34 | }
35 |
36 | fun onHandSwing(entity: RecordableEntity, hand: Hand) {
37 | replay.addAction(RecPlayerHandAnimation(hand, entity))
38 | }
39 |
40 | fun onEntityRemove(entity: RecordableEntity, position: RecordablePosition) {
41 | replay.addAction(RecEntityRemove(position, entity))
42 | }
43 |
44 | fun onEntitySpawn(entity: RecordableEntity, positionAndVector: RecordablePositionAndVector) {
45 | replay.entities[entity.id] = entity
46 | replay.addAction(
47 | RecEntitySpawn(
48 | positionAndVector, entity
49 | )
50 | )
51 | }
52 |
53 | fun onEntityEquipmentChange(entity: ReplayEntity) {
54 | val replayEntity = replay.getEntityById(entity.id) ?: return
55 |
56 | replay.addAction(RecEntityEquipmentUpdate(replayEntity, entity.getEquipment()))
57 | }
58 |
59 | private fun buildTickerTask(): Any {
60 | val entityPositions = mutableMapOf()
61 |
62 | return replayExtension.platform.registerSyncRepeatingTask(tickInterval) {
63 | doEntityTick(entityPositions)
64 | }
65 | }
66 |
67 | private fun doEntityTick(entityPositions: MutableMap) {
68 | val recordedPositions = mutableMapOf()
69 |
70 | instance.entities.forEach { entity ->
71 | if (entity.instance != instance || !isRecording) return@forEach
72 | val currentPosition = entity.position
73 |
74 | val oldPosition = entityPositions[entity.uuid]
75 | val currentNewPosition =
76 | RecordablePositionAndVector(currentPosition, entity.velocity)
77 | if (oldPosition == null || oldPosition != currentNewPosition || options.recordAllLocationChanges) {
78 | val replayEntity = replay.getEntityById(entity.id) ?: return@forEach
79 | recordedPositions[replayEntity] = currentNewPosition
80 | entityPositions[entity.uuid] = currentNewPosition
81 | }
82 | }
83 |
84 | when (options.positionRecordType) {
85 | GROUP_ALL -> listOf(RecEntitiesPosition(recordedPositions))
86 | SEPARATE_ALL -> recordedPositions.map { RecEntityMove(it.value, it.key) }
87 | }.forEach {
88 | if (it !is RecEntitiesPosition || it.positions.isNotEmpty())
89 | replay.addAction(it)
90 | }
91 | }
92 |
93 | fun startRecording() {
94 | recordInstanceIfNeeded()
95 | instance.entities.forEach { entity ->
96 | //Save all entities
97 | replay.apply {
98 | @Suppress("UNCHECKED_CAST")
99 | entities[entity.id] =
100 | entity.toReplay(replayExtension.platform as ReplayPlatform)
101 | }
102 | }
103 | isRecording = true
104 | }
105 |
106 | private fun recordInstanceIfNeeded() {
107 | if (!options.recordInstanceChunks) return
108 |
109 | instance.chunks.forEach {
110 | replay.chunks.add(
111 | RecordedChunk(
112 | it.x,
113 | it.z,
114 | it.serializedData
115 | ?: throw Exception(
116 | "Unable to serialize chunk of type ${it.javaClass.name}.\n" +
117 | "Please make sure that your chunks can be serialized if you want to save them in the replay."
118 | )
119 | )
120 | )
121 |
122 | }
123 | }
124 |
125 | fun stopRecording() {
126 | replay.duration = replay.currentDuration
127 | isRecording = false
128 | replayExtension.platform.cancelTask(tickerTask)
129 | }
130 |
131 | fun notifyBlockChange(
132 | x: Int,
133 | y: Int,
134 | z: Int,
135 | newState: Short
136 | ) {
137 | replay.addAction(
138 | RecBlockStateUpdate(
139 | RecordablePosition(x.toDouble(), y.toDouble(), z.toDouble(), 0f, 0f),
140 | newState
141 | )
142 | )
143 | }
144 |
145 | }
146 |
147 |
148 |
--------------------------------------------------------------------------------
/impl-abstraction/src/main/kotlin/io/github/openminigameserver/replay/replayer/ActionPlayer.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer
2 |
3 | import io.github.openminigameserver.replay.abstraction.ReplayUser
4 | import io.github.openminigameserver.replay.abstraction.ReplayWorld
5 | import io.github.openminigameserver.replay.model.recordable.RecordableAction
6 |
7 | interface ActionPlayer {
8 | fun play(action: T, session: ReplaySession, instance: W, viewers: List)
9 | }
10 |
--------------------------------------------------------------------------------
/impl-abstraction/src/main/kotlin/io/github/openminigameserver/replay/replayer/ActionPlayerManager.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer
2 |
3 | import io.github.openminigameserver.replay.abstraction.ReplayEntity
4 | import io.github.openminigameserver.replay.abstraction.ReplayUser
5 | import io.github.openminigameserver.replay.abstraction.ReplayWorld
6 | import io.github.openminigameserver.replay.model.recordable.RecordableAction
7 | import io.github.openminigameserver.replay.platform.ReplayPlatform
8 |
9 | open class ActionPlayerManager(val platform: ReplayPlatform) {
10 | @PublishedApi
11 | internal val actionPlayers = mutableMapOf, ActionPlayer<*, W, P>>()
12 |
13 | /**
14 | * Register an action player for the specified action type
15 | */
16 | inline fun > registerActionPlayerGeneric(player: T) {
17 | actionPlayers[R::class.java] = player
18 | }
19 |
20 | /**
21 | * Register an action player for the specified action type
22 | */
23 | fun > registerActionPlayer(
24 | playerInstance: T,
25 | recordableClazz: Class
26 | ) {
27 | actionPlayers[recordableClazz] = playerInstance
28 | }
29 |
30 | /**
31 | * Get an action player for the given action
32 | */
33 | fun getActionPlayer(action: R): ActionPlayer {
34 | @Suppress("UNCHECKED_CAST")
35 | return actionPlayers[action.javaClass] as? ActionPlayer
36 | ?: throw Exception("Unable to find action player for ${action.javaClass.simpleName}")
37 | }
38 | }
--------------------------------------------------------------------------------
/impl-abstraction/src/main/kotlin/io/github/openminigameserver/replay/replayer/EntityActionPlayer.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer
2 |
3 | import io.github.openminigameserver.replay.abstraction.ReplayEntity
4 | import io.github.openminigameserver.replay.abstraction.ReplayUser
5 | import io.github.openminigameserver.replay.abstraction.ReplayWorld
6 | import io.github.openminigameserver.replay.model.recordable.EntityRecordableAction
7 | import io.github.openminigameserver.replay.model.recordable.entity.RecordableEntity
8 |
9 | abstract class EntityActionPlayer :
10 | ActionPlayer {
11 | @Suppress("UNCHECKED_CAST")
12 | override fun play(action: T, session: ReplaySession, instance: W, viewers: List) {
13 | val entity = session.entityManager.getNativeEntity(action.entity) as? E
14 | if (entity != null) {
15 | play(action, action.entity, entity, session, instance, viewers)
16 | }
17 | }
18 |
19 | abstract fun play(
20 | action: T,
21 | replayEntity: RecordableEntity,
22 | nativeEntity: E,
23 | session: ReplaySession,
24 | instance: W,
25 | viewers: List
26 | )
27 | }
--------------------------------------------------------------------------------
/impl-abstraction/src/main/kotlin/io/github/openminigameserver/replay/replayer/IEntityManager.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer
2 |
3 | import io.github.openminigameserver.replay.abstraction.ReplayEntity
4 | import io.github.openminigameserver.replay.abstraction.ReplayUser
5 | import io.github.openminigameserver.replay.model.recordable.RecordablePosition
6 | import io.github.openminigameserver.replay.model.recordable.RecordableVector
7 | import io.github.openminigameserver.replay.model.recordable.entity.RecordableEntity
8 | import kotlin.time.Duration
9 |
10 | interface IEntityManager
{
11 | var session: ReplaySession
12 | val entities: Collection
13 |
14 | //Replay entity id
15 | val replayEntities: MutableMap
16 | fun resetEntity(entity: RecordableEntity, startTime: Duration, targetReplayTime: Duration)
17 | fun spawnEntity(
18 | entity: RecordableEntity,
19 | position: RecordablePosition,
20 | velocity: RecordableVector = RecordableVector(0.0, 0.0, 0.0)
21 | )
22 |
23 | fun refreshPosition(
24 | minestomEntity: E,
25 | position: RecordablePosition
26 | )
27 |
28 | fun getNativeEntity(entity: RecordableEntity): E?
29 | fun removeEntity(entity: RecordableEntity)
30 | fun removeNativeEntity(entity: E)
31 | fun removeAllEntities()
32 | fun removeEntityViewer(player: P)
33 | }
--------------------------------------------------------------------------------
/impl-abstraction/src/main/kotlin/io/github/openminigameserver/replay/replayer/ReplaySession.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer
2 |
3 | import io.github.openminigameserver.replay.AbstractReplaySession
4 | import io.github.openminigameserver.replay.TickTime
5 | import io.github.openminigameserver.replay.TimeUnit
6 | import io.github.openminigameserver.replay.abstraction.ReplayEntity
7 | import io.github.openminigameserver.replay.abstraction.ReplayUser
8 | import io.github.openminigameserver.replay.abstraction.ReplayWorld
9 | import io.github.openminigameserver.replay.model.Replay
10 | import io.github.openminigameserver.replay.model.recordable.RecordableAction
11 | import io.github.openminigameserver.replay.platform.ReplayPlatform
12 | import io.github.openminigameserver.replay.replayer.statehelper.ReplaySessionPlayerStateHelper
13 | import kotlinx.datetime.Clock
14 | import kotlinx.datetime.Instant
15 | import net.kyori.adventure.text.Component
16 | import net.kyori.adventure.text.format.NamedTextColor
17 | import java.util.*
18 | import java.util.concurrent.CountDownLatch
19 | import kotlin.time.Duration
20 | import kotlin.time.seconds
21 |
22 | class ReplaySession constructor(
23 | internal val replayPlatform: ReplayPlatform,
24 | val world: ReplayWorld,
25 | override val replay: Replay,
26 | val viewers: MutableList,
27 | private val tickTime: TickTime = TickTime(1L, TimeUnit.TICK)
28 | ) : AbstractReplaySession() {
29 |
30 | val viewerCountDownLatch = CountDownLatch(if (replay.hasChunks) viewers.size else 0)
31 |
32 | var currentStepDuration = 10.seconds
33 | set(value) {
34 | field = value
35 | updateReplayStateToViewers()
36 | }
37 |
38 | override val hasEnded: Boolean
39 | get() = time == replay.duration
40 |
41 | val playerStateHelper = ReplaySessionPlayerStateHelper(this)
42 | private val playerTimeStepHelper = ReplaySessionTimeStepHelper(this)
43 | private val ticker: Runnable = ReplayTicker(this)
44 |
45 | init {
46 | resetActions()
47 | }
48 |
49 | private fun resetActions(targetDuration: Duration = Duration.ZERO) {
50 | actions.clear()
51 | actions.addAll(replay.actions.filter { it.timestamp >= targetDuration }.sortedByDescending { it.timestamp })
52 | }
53 |
54 | var speed: Double = 1.0
55 | set(value) {
56 | field = value
57 | updateReplayStateToViewers()
58 | }
59 |
60 | var paused = true
61 | set(value) {
62 | field = value
63 | updateReplayStateToViewers()
64 | }
65 |
66 | var hasSpawnedEntities = false
67 |
68 | /**
69 | * Last timestamp in milliseconds the [tick] method was called.
70 | */
71 | private var lastTickTime: Instant = Clock.System.now()
72 |
73 | /**
74 | * Current replay time.
75 | * Valid regardless of [paused].
76 | */
77 | override var time: Duration = Duration.ZERO
78 |
79 | private fun updateReplayStateToViewers() {
80 | playerStateHelper.updateReplayStateToViewers()
81 | }
82 |
83 | override fun init() {
84 | isInitialized = true
85 | viewers.forEach { p ->
86 | setupViewer(p)
87 | }
88 | Thread {
89 | viewerCountDownLatch.await()
90 | while (isInitialized) {
91 | ticker.run()
92 | Thread.sleep(tickTime.unit.toMilliseconds(tickTime.time))
93 | }
94 | }.start()
95 | }
96 |
97 | private val oldViewerInstanceMap = mutableMapOf()
98 | private fun setupViewer(p: ReplayUser) {
99 | replayPlatform.addToViewerTeam(p)
100 | if (p.instance != world) {
101 | p.instance?.uuid?.let { oldViewerInstanceMap[p.uuid] = it }
102 | p.setWorld(world)
103 | }
104 | }
105 |
106 | override fun unInit() {
107 | isInitialized = false
108 | entityManager.removeAllEntities()
109 | world.replaySession = null
110 | playerStateHelper.unInit()
111 | viewers.forEach { removeViewer(it) }
112 | if (replay.hasChunks) {
113 | replayPlatform.unregisterWorld(world)
114 | }
115 | }
116 |
117 | fun removeViewer(player: ReplayUser) {
118 | try {
119 | entityManager.removeEntityViewer(player)
120 | playerStateHelper.removeViewer(player)
121 |
122 | replayPlatform.removeFromViewerTeam(player)
123 |
124 | player.sendActionBar(Component.empty())
125 |
126 | val oldInstance =
127 | oldViewerInstanceMap[player.uuid]?.let { replayPlatform.getWorldById(it) }
128 | oldInstance?.let { player.setWorld(oldInstance) }
129 | } catch (e: Throwable) {
130 | e.printStackTrace()
131 | } finally {
132 | viewers.remove(player)
133 | if (viewers.isEmpty()) {
134 | unInit()
135 | }
136 | }
137 | }
138 |
139 | /**
140 | * The next action to be played.
141 | */
142 | private var nextAction: RecordableAction? = null
143 |
144 | /**
145 | * Update the current time and play actions accordingly.
146 | */
147 | internal var lastReplayTime = Duration.ZERO /* Used to detect if we're going backwards */
148 |
149 | override fun tick(forceTick: Boolean, isTimeStep: Boolean) {
150 | if (!hasSpawnedEntities) {
151 |
152 | replay.entities.values.filter { it.spawnOnStart }.forEach {
153 | entityManager.spawnEntity(it, it.spawnPosition!!.position, it.spawnPosition!!.velocity)
154 | }
155 |
156 | playerStateHelper.init()
157 |
158 | hasSpawnedEntities = true
159 | }
160 |
161 | val currentTime = Clock.System.now()
162 | if (!forceTick && paused) {
163 | lastTickTime = currentTime
164 | return
165 | }
166 |
167 | val timePassed = currentTime - lastTickTime
168 | val targetReplayTime = (this.time + (timePassed * speed))
169 |
170 | if (isTimeStep) {
171 | playerTimeStepHelper.performTimeStep(lastReplayTime, targetReplayTime)
172 | resetActions(targetReplayTime)
173 | nextAction = null
174 | lastTickTime = currentTime
175 | return
176 | }
177 |
178 | fun readNextAction() {
179 | nextAction = actions.takeIf { !it.empty() }?.pop()
180 | }
181 |
182 | while (true) {
183 | if (nextAction == null) {
184 | readNextAction()
185 | if (nextAction == null) {
186 | // If still null, then we reached end of replay
187 | time = replay.duration
188 | paused = true
189 |
190 | return
191 | }
192 | } else {
193 | if (nextAction!!.timestamp < targetReplayTime) {
194 | playAction(nextAction!!)
195 | this.time = nextAction?.timestamp ?: Duration.ZERO
196 | nextAction = null
197 | } else {
198 |
199 | this.time += timePassed * speed
200 | break
201 | }
202 | }
203 | }
204 | lastTickTime = currentTime
205 | }
206 |
207 | val entityManager = replayPlatform.getEntityManager(this)
208 | override fun playAction(action: RecordableAction) {
209 | try {
210 | replayPlatform.actionPlayerManager.getActionPlayer(action).play(action, this, world, viewers)
211 | } catch (e: Throwable) {
212 | e.printStackTrace()
213 |
214 | paused = true
215 | viewers.forEach {
216 | it.sendMessage(
217 | Component.text(
218 | "An error occurred while playing your replay. Please contact an administrator for support.",
219 | NamedTextColor.RED
220 | )
221 | )
222 | }
223 |
224 | //Unload everything
225 | unInit()
226 | }
227 | }
228 | }
--------------------------------------------------------------------------------
/impl-abstraction/src/main/kotlin/io/github/openminigameserver/replay/replayer/ReplaySessionTimeStepHelper.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer
2 |
3 | import io.github.openminigameserver.replay.model.recordable.RecordableAction
4 | import io.github.openminigameserver.replay.model.recordable.reverse.Reversible
5 | import kotlin.time.Duration
6 |
7 | class ReplaySessionTimeStepHelper(private val session: ReplaySession) {
8 | private val replay get() = session.replay
9 | private val entityManager get() = session.entityManager
10 |
11 | fun performTimeStep(currentTime: Duration, targetReplayTime: Duration) {
12 | val isForwardStep = targetReplayTime > currentTime
13 | val (start, end) = currentTime to targetReplayTime
14 |
15 |
16 | val reversibleActions: List =
17 | session.findManyActions(start, end) { it is Reversible }.groupBy { it.javaClass }
18 | .flatMap { it.value }.map { it as Reversible }
19 | .flatMap { entry ->
20 | if (!isForwardStep) entry.provideRevertedActions(start, end, session) else listOf(
21 | entry as RecordableAction
22 | )
23 | }
24 |
25 | val actionsToPlay = mutableListOf()
26 | actionsToPlay.addAll(reversibleActions.toMutableList())
27 |
28 | if (isForwardStep) actionsToPlay.sortBy { it.timestamp } else actionsToPlay.sortByDescending { it.timestamp }
29 |
30 | actionsToPlay.filter { it is Reversible && it.isAppliedInBatch }.groupBy { it.javaClass }
31 | .forEach { groupedEntry ->
32 | val first = groupedEntry.value.firstOrNull() as? Reversible ?: return@forEach
33 | actionsToPlay.removeAll(groupedEntry.value)
34 | first.batchActions(groupedEntry.value.let { it.sortedBy { it.timestamp } })
35 | ?.let { it1 -> actionsToPlay.add(it1) }
36 | }
37 |
38 | //Reset entities first, then play actions
39 | entityManager.entities.forEach {
40 | entityManager.resetEntity(it, start, end)
41 | }
42 |
43 | actionsToPlay.forEach {
44 | session.playAction(it)
45 | }
46 |
47 | }
48 | }
--------------------------------------------------------------------------------
/impl-abstraction/src/main/kotlin/io/github/openminigameserver/replay/replayer/ReplayTicker.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer
2 |
3 | class ReplayTicker(private val session: ReplaySession) : Runnable {
4 | override fun run() {
5 | session.tick()
6 | }
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/impl-abstraction/src/main/kotlin/io/github/openminigameserver/replay/replayer/statehelper/ControlItemAction.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer.statehelper
2 |
3 | enum class ControlItemAction {
4 | NONE,
5 |
6 | COOL_DOWN,
7 |
8 | STEP_BACKWARDS,
9 | PAUSE,
10 |
11 | RESUME,
12 | STEP_FORWARD,
13 |
14 | SPEED_UP,
15 |
16 | PLAY_AGAIN
17 | }
--------------------------------------------------------------------------------
/impl-abstraction/src/main/kotlin/io/github/openminigameserver/replay/replayer/statehelper/ReplaySessionPlayerStateHelper.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer.statehelper
2 |
3 | import io.github.openminigameserver.replay.TickTime
4 | import io.github.openminigameserver.replay.TimeUnit
5 | import io.github.openminigameserver.replay.abstraction.ReplayActionItemStack
6 | import io.github.openminigameserver.replay.abstraction.ReplayGameMode
7 | import io.github.openminigameserver.replay.abstraction.ReplayUser
8 | import io.github.openminigameserver.replay.model.recordable.entity.data.PlayerEntityData
9 | import io.github.openminigameserver.replay.replayer.ReplaySession
10 | import io.github.openminigameserver.replay.replayer.statehelper.constants.*
11 | import io.github.openminigameserver.replay.replayer.statehelper.utils.ReplayStatePlayerData
12 | import net.kyori.adventure.key.Key
13 | import net.kyori.adventure.sound.Sound
14 | import net.kyori.adventure.text.Component.empty
15 | import net.kyori.adventure.text.Component.text
16 | import net.kyori.adventure.text.format.NamedTextColor
17 | import java.util.*
18 | import kotlin.math.roundToInt
19 | import kotlin.time.Duration
20 | import kotlin.time.seconds
21 |
22 | class ReplaySessionPlayerStateHelper(val session: ReplaySession) {
23 | val replayPlatform = session.replayPlatform
24 |
25 | val viewers: List
26 | get() = session.viewers
27 |
28 | val host: ReplayUser?
29 | get() = viewers.firstOrNull()
30 |
31 | private var isInitialized = true
32 |
33 | private var tickerTask: Any? = null
34 | private fun getActionBarMessage() = if (!isInitialized) empty() else text {
35 | val spacing = " ".repeat(6)
36 | val (minutes, seconds) = formatTime(session.time)
37 | val (minutesFinal, secondsFinal) = formatTime(session.replay.duration)
38 |
39 | with(it) {
40 |
41 |
42 | append((if (session.paused) text("Paused", NamedTextColor.RED) else text("Playing", NamedTextColor.GREEN)))
43 | append(text(spacing))
44 |
45 | append(text("$minutes:$seconds", NamedTextColor.YELLOW))
46 | append(text(" / "))
47 | append(text("$minutesFinal:$secondsFinal", NamedTextColor.YELLOW))
48 |
49 | append(text(spacing))
50 | append(text("x", NamedTextColor.GOLD))
51 | append(
52 | text(
53 | (if (session.speed >= 0.5) "%.1f" else "%.2f").format(
54 | session.speed,
55 | Locale.ENGLISH
56 | ), NamedTextColor.GOLD
57 | )
58 | )
59 | append(text(" ".repeat(2)))
60 | }
61 | }
62 |
63 | private fun formatTime(time: Duration): Pair {
64 | val currentTime = time.inSeconds
65 | val minutes = formatResultToTime(currentTime / 60)
66 | val seconds = formatResultToTime(currentTime % 60)
67 | return Pair(minutes, seconds)
68 | }
69 |
70 | private fun formatResultToTime(currentTime: Double) = (currentTime).toInt().toString().padStart(2, '0')
71 |
72 | private val tickerTaskRunnable = Runnable {
73 | updateViewersActionBar(session.viewers.toMutableList())
74 | }
75 |
76 | private fun updateAllItems() {
77 | host?.let { updateItems(it) }
78 | }
79 |
80 | private fun updateViewersActionBar(viewers: MutableList) {
81 | viewers.forEach {
82 | it.sendActionBar(getActionBarMessage())
83 | }
84 | }
85 |
86 | fun init() {
87 | initializePlayerControlItems()
88 | teleportViewers()
89 | playLoadedSoundToViewers()
90 | tickerTask =
91 | replayPlatform.registerSyncRepeatingTask(TickTime(250, TimeUnit.MILLISECOND)) { tickerTaskRunnable.run() }
92 | }
93 |
94 | private fun playLoadedSoundToViewers() {
95 | viewers.forEach {
96 | try {
97 | it.playSound(Sound.sound(Key.key("entity.player.levelup"), Sound.Source.PLAYER, 1f, 1f))
98 | } catch (e: Throwable) {
99 | // e.printStackTrace()
100 | }
101 | }
102 | }
103 |
104 | private val oldData = mutableMapOf()
105 | fun removeViewer(player: ReplayUser) {
106 | oldData[player.uuid]?.apply(replayPlatform, player)
107 | }
108 |
109 | private fun initializePlayerControlItems() {
110 | session.viewers.forEach { p: ReplayUser ->
111 | oldData[p.uuid] = ReplayStatePlayerData(replayPlatform, p)
112 | p.clearInventory()
113 | p.gameMode = ReplayGameMode.ADVENTURE
114 | p.isAllowFlying = true
115 | p.isFlying = true
116 | }
117 |
118 | host?.let {
119 | it.setHeldItemSlot(SLOT_PLAY_PAUSE.toByte())
120 | updateItems(it)
121 | }
122 |
123 | }
124 |
125 | private fun updateItems(it: ReplayUser) {
126 | it.setItemStack(
127 | SLOT_DECREASE_SPEED,
128 | getItemStackOrAirIfReplayEnded(PlayerHeadsItems.getDecreaseSpeedItem())
129 | )
130 | it.setItemStack(
131 | SLOT_STEP_BACKWARDS,
132 | getItemStackOrAirIfReplayEnded(PlayerHeadsItems.getStepBackwardsItem(session.currentStepDuration))
133 | )
134 |
135 | it.setItemStack(SLOT_PLAY_PAUSE, PlayerHeadsItems.getPlayPauseItem(session.paused, session.hasEnded))
136 |
137 | it.setItemStack(
138 | SLOT_STEP_FORWARD,
139 | getItemStackOrAirIfReplayEnded(PlayerHeadsItems.getStepForwardItem(session.currentStepDuration))
140 | )
141 | it.setItemStack(
142 | SLOT_INCREASE_SPEED,
143 | getItemStackOrAirIfReplayEnded(PlayerHeadsItems.getIncreaseSpeedItem())
144 | )
145 | }
146 |
147 | private fun getItemStackOrAirIfReplayEnded(itemStack: ReplayActionItemStack) =
148 | if (session.hasEnded) ReplayActionItemStack.air else itemStack
149 |
150 | val skipSpeeds = arrayOf(1, 5, 10, 30, 60)
151 | private val speeds = arrayOf(0.25, 0.5, 1.0, 2.0, 4.0)
152 | fun handleItemAction(player: ReplayUser, action: ControlItemAction) {
153 | when (action) {
154 | ControlItemAction.COOL_DOWN -> {
155 | val previousSpeed = speeds[(speeds.indexOf(session.speed) - 1).coerceAtLeast(0)]
156 | session.speed = previousSpeed
157 | session.tick(true)
158 | }
159 | ControlItemAction.PAUSE -> {
160 | session.paused = true
161 | }
162 | ControlItemAction.RESUME -> {
163 | session.paused = false
164 | }
165 | ControlItemAction.PLAY_AGAIN -> {
166 | session.lastReplayTime = session.replay.duration
167 | session.time = Duration.ZERO
168 |
169 | session.tick(forceTick = action == ControlItemAction.PLAY_AGAIN, isTimeStep = true)
170 | session.paused = false
171 | }
172 | ControlItemAction.SPEED_UP -> {
173 | val nextSpeed = speeds[(speeds.indexOf(session.speed) + 1).coerceAtMost(speeds.size - 1)]
174 | session.speed = nextSpeed
175 | session.tick(true)
176 | }
177 | ControlItemAction.STEP_BACKWARDS -> {
178 | doStep(false)
179 | }
180 | ControlItemAction.STEP_FORWARD -> {
181 | doStep(true)
182 |
183 | }
184 | else -> TODO(action.name)
185 | }
186 | try {
187 | player.playSound(Sound.sound(Key.key("block.lever.click"), Sound.Source.BLOCK, 1f, 1f))
188 | } catch (e: Throwable) {
189 | // e.printStackTrace()
190 | }
191 | updateReplayStateToViewers()
192 | }
193 |
194 | private fun doStep(isForward: Boolean) {
195 | val oldPausedState = session.paused
196 | val duration = session.currentStepDuration * if (isForward) 1 else -1
197 | session.paused = true
198 |
199 | session.lastReplayTime = session.time
200 | session.time =
201 | (session.time + duration).coerceIn(Duration.ZERO, session.replay.duration)
202 | session.tick(forceTick = true, isTimeStep = true)
203 |
204 | session.paused = oldPausedState
205 | updateReplayStateToViewers()
206 | }
207 |
208 |
209 | private fun teleportViewers() {
210 | val entities = session.replay.entities.values
211 | val targetEntity =
212 | entities.firstOrNull { (it.entityData as? PlayerEntityData)?.userName == session.viewers.first().name }
213 | ?: entities.firstOrNull()
214 | val targetEntityMinestom = targetEntity?.let { session.entityManager.getNativeEntity(it) }
215 |
216 | if (targetEntityMinestom != null) {
217 | session.viewers.forEach { it.teleport(targetEntityMinestom.position) }
218 | }
219 | }
220 |
221 | fun unInit() {
222 | tickerTask?.let { replayPlatform.cancelTask(it) }
223 | tickerTask = null
224 | isInitialized = false
225 | updateViewersActionBar(session.viewers)
226 | }
227 |
228 | fun updateReplayStateToViewers() {
229 | updateViewersActionBar(session.viewers)
230 | updateAllItems()
231 | }
232 |
233 | fun handleItemSwing(player: ReplayUser, itemStack: ReplayActionItemStack) {
234 | val action = itemStack.action.takeUnless { it == ControlItemAction.NONE }
235 | if (action == ControlItemAction.STEP_BACKWARDS || action == ControlItemAction.STEP_FORWARD) {
236 | val duration = session.currentStepDuration.inSeconds.roundToInt()
237 | val currentSkipIndex = skipSpeeds.indexOf(duration).coerceAtLeast(0)
238 | var nextIndex = currentSkipIndex + 1
239 | if (nextIndex >= skipSpeeds.size) {
240 | nextIndex = 0
241 | }
242 |
243 | session.currentStepDuration = skipSpeeds[nextIndex].seconds
244 |
245 | }
246 | }
247 |
248 | }
--------------------------------------------------------------------------------
/impl-abstraction/src/main/kotlin/io/github/openminigameserver/replay/replayer/statehelper/constants/PlayerHeadsItems.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer.statehelper.constants
2 |
3 | import io.github.openminigameserver.replay.abstraction.ReplayActionItemStack
4 | import io.github.openminigameserver.replay.abstraction.ReplayHeadTextureSkin
5 | import io.github.openminigameserver.replay.replayer.statehelper.ControlItemAction
6 | import net.kyori.adventure.text.Component.text
7 | import net.kyori.adventure.text.format.NamedTextColor
8 | import kotlin.time.Duration
9 |
10 | const val actionData = "controlItem"
11 |
12 | object PlayerHeadsItems {
13 | fun getPlayPauseItem(paused: Boolean, finished: Boolean): ReplayActionItemStack {
14 | if (finished) {
15 | return ReplayActionItemStack(
16 | text("Play Recording Again", NamedTextColor.GREEN),
17 | ControlItemAction.PLAY_AGAIN
18 | )
19 | }
20 | return ReplayActionItemStack(
21 | text("Click to ${if (paused) "Resume" else "Pause"}", NamedTextColor.GREEN),
22 | if (paused) ControlItemAction.RESUME else ControlItemAction.PAUSE
23 | )
24 | }
25 |
26 | fun getDecreaseSpeedItem() =
27 | buildItemStack(PlayerHeadsTextureData.decreaseSpeed, "Decrease Speed", ControlItemAction.COOL_DOWN)
28 |
29 | fun getIncreaseSpeedItem() =
30 | buildItemStack(PlayerHeadsTextureData.increaseSpeed, "Increase Speed", ControlItemAction.SPEED_UP)
31 |
32 | private fun buildItemStack(skin: ReplayHeadTextureSkin, name: String, action: ControlItemAction) =
33 | ReplayActionItemStack(text(name, NamedTextColor.GREEN), action, skin)
34 |
35 | fun getStepBackwardsItem(skipSpeed: Duration): ReplayActionItemStack = buildItemStack(
36 | PlayerHeadsTextureData.backwards,
37 | "${skipSpeed.inSeconds.toInt()}s Backwards",
38 | ControlItemAction.STEP_BACKWARDS
39 | )
40 |
41 | fun getStepForwardItem(skipSpeed: Duration): ReplayActionItemStack = buildItemStack(
42 | PlayerHeadsTextureData.forwards,
43 | "${skipSpeed.inSeconds.toInt()}s Forward",
44 | ControlItemAction.STEP_FORWARD
45 | )
46 |
47 | }
--------------------------------------------------------------------------------
/impl-abstraction/src/main/kotlin/io/github/openminigameserver/replay/replayer/statehelper/constants/PlayerHeadsTextureData.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer.statehelper.constants
2 |
3 | import io.github.openminigameserver.replay.abstraction.ReplayHeadTextureSkin
4 |
5 | object PlayerHeadsTextureData {
6 | val decreaseSpeed = ReplayHeadTextureSkin(
7 | "eyJ0aW1lc3RhbXAiOjE1ODMyMjI0MDE2NDEsInByb2ZpbGVJZCI6Ijc1MTQ0NDgxOTFlNjQ1NDY4Yzk3MzlhNmUzOTU3YmViIiwicHJvZmlsZU5hbWUiOiJUaGFua3NNb2phbmciLCJzaWduYXR1cmVSZXF1aXJlZCI6dHJ1ZSwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2RjZDdjMTRiOTJjYjM3OTA5MjA4YTBkMjA0NzgwNDkzZjljOWNjNWY1NmQxMDE5YjczNjM0MTc5MDlmMWQ5NTYifX19",
8 | ""
9 | )
10 |
11 | val backwards = ReplayHeadTextureSkin(
12 | "eyJ0aW1lc3RhbXAiOjE1NTc5MjM4NDYyOTMsInByb2ZpbGVJZCI6IjU3MGIwNWJhMjZmMzRhOGViZmRiODBlY2JjZDdlNjIwIiwicHJvZmlsZU5hbWUiOiJMb3JkU29ubnkiLCJzaWduYXR1cmVSZXF1aXJlZCI6dHJ1ZSwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2E2ZTFjZDAwNjc4NTViNjdlMGZkNWI3ZWI3NDU3MjgxYzQxZDEzYmQ1YmM5MTU4YzRhODJmNTE4MTk4YTFkMjIifX19",
13 | ""
14 | )
15 |
16 | val forwards = ReplayHeadTextureSkin(
17 | "eyJ0aW1lc3RhbXAiOjE1NTc5MjM2NzA1MTMsInByb2ZpbGVJZCI6ImIwZDczMmZlMDBmNzQwN2U5ZTdmNzQ2MzAxY2Q5OGNhIiwicHJvZmlsZU5hbWUiOiJPUHBscyIsInNpZ25hdHVyZVJlcXVpcmVkIjp0cnVlLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZGIyZjMwNTAyYThmZTRjODBlODgzZDIzYjQ3Mzg5YjAzYTc4MThkOWJiYWQyYmE0ZGMxMGQ2NTNkM2ViNTJiMiJ9fX0=",
18 | ""
19 | )
20 |
21 | val increaseSpeed = ReplayHeadTextureSkin(
22 | "eyJ0aW1lc3RhbXAiOjE1ODMyMjIxMzI2NDUsInByb2ZpbGVJZCI6ImRkZWQ1NmUxZWY4YjQwZmU4YWQxNjI5MjBmN2FlY2RhIiwicHJvZmlsZU5hbWUiOiJEaXNjb3JkQXBwIiwic2lnbmF0dXJlUmVxdWlyZWQiOnRydWUsInRleHR1cmVzIjp7IlNLSU4iOnsidXJsIjoiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jZjM4MjFhYWIwYTVhYmZlN2Y0OTM3YWMyOGVjOGUzMWEzMzYwY2I1MTVjMTEwNDZmZjc1MGFlMmEwYTM5MWFmIn19fQ==",
23 | ""
24 | )
25 | }
--------------------------------------------------------------------------------
/impl-abstraction/src/main/kotlin/io/github/openminigameserver/replay/replayer/statehelper/constants/StateHelperConstants.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer.statehelper.constants
2 |
3 | const val SLOT_DECREASE_SPEED = 2
4 | const val SLOT_STEP_BACKWARDS = 3
5 | const val SLOT_PLAY_PAUSE = 4
6 | const val SLOT_STEP_FORWARD = 5
7 | const val SLOT_INCREASE_SPEED = 6
--------------------------------------------------------------------------------
/impl-abstraction/src/main/kotlin/io/github/openminigameserver/replay/replayer/statehelper/utils/ReplayStatePlayerData.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer.statehelper.utils
2 |
3 | import io.github.openminigameserver.replay.abstraction.ReplayGameMode
4 | import io.github.openminigameserver.replay.abstraction.ReplayUser
5 | import io.github.openminigameserver.replay.platform.ReplayPlatform
6 |
7 |
8 | class ReplayStatePlayerData(
9 | private val isAllowFlying: Boolean,
10 | private val isFlying: Boolean,
11 | private val gameMode: ReplayGameMode,
12 | private val heldSlot: Byte,
13 | private val exp: Float,
14 | private val inventory: Any
15 | ) {
16 |
17 | constructor(replayPlatform: ReplayPlatform<*, ReplayUser, *>, player: ReplayUser) : this(
18 | player.isAllowFlying,
19 | player.isFlying,
20 | player.gameMode,
21 | player.heldSlot,
22 | player.exp,
23 | replayPlatform.getPlayerInventoryCopy(player)
24 | )
25 |
26 | fun apply(replayPlatform: ReplayPlatform<*, ReplayUser, *>, player: ReplayUser) {
27 | player.isAllowFlying = isAllowFlying
28 | player.isFlying = isFlying
29 | player.gameMode = gameMode
30 | player.setHeldItemSlot(heldSlot)
31 | player.exp = exp
32 | replayPlatform.loadPlayerInventoryCopy(player, inventory)
33 | }
34 |
35 | }
36 |
37 |
38 |
--------------------------------------------------------------------------------
/impl-abstraction/src/template/kotlin/io/github/openminigameserver/replay/BuildInfo.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay;
2 |
3 | object BuildInfo {
4 | const val version: String = "${version}"
5 | }
--------------------------------------------------------------------------------
/impl-minestom/build.gradle.kts:
--------------------------------------------------------------------------------
1 | val minestomVersion = "f7ec45802f"
2 | val cloudVersion = "58e8fd76f3"
3 |
4 | dependencies {
5 | api(project(":impl-abstraction"))
6 | implementation("com.github.OpenMinigameServer:cloud-minestom:$cloudVersion")
7 | implementation("cloud.commandframework:cloud-annotations:1.4.0")
8 | implementation("com.github.mworzala:adventure-platform-minestom:d208f53200")
9 | compileOnly(minestom(minestomVersion))
10 | testImplementation(minestom(minestomVersion))
11 | }
12 |
13 | fun minestom(commit: String): String {
14 | return "com.github.Minestom:Minestom:$commit"
15 | }
16 |
17 | tasks {
18 | val templateContext = mapOf("version" to project.version.toString())
19 | processResources {
20 | expand(*templateContext.toList().toTypedArray())
21 | }
22 |
23 | create("generateKotlinBuildInfo") {
24 | inputs.properties(templateContext) // for gradle up-to-date check
25 | from("src/template/kotlin/")
26 | into("$buildDir/generated/kotlin/")
27 | expand(*templateContext.toList().toTypedArray())
28 | }
29 |
30 | kotlin.sourceSets["main"].kotlin.srcDir("$buildDir/generated/kotlin")
31 | compileKotlin.get().dependsOn(get("generateKotlinBuildInfo"))
32 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/java/io/github/openminigameserver/replay/mixins/InstanceMixin.java:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.mixins;
2 |
3 | import io.github.openminigameserver.replay.extensions.MinestomInteropExtensionsKt;
4 | import net.minestom.server.data.Data;
5 | import net.minestom.server.instance.Chunk;
6 | import net.minestom.server.instance.Instance;
7 | import net.minestom.server.instance.InstanceContainer;
8 | import net.minestom.server.instance.block.CustomBlock;
9 | import org.spongepowered.asm.mixin.Mixin;
10 | import org.spongepowered.asm.mixin.injection.At;
11 | import org.spongepowered.asm.mixin.injection.Inject;
12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
13 |
14 | @Mixin(InstanceContainer.class)
15 | public abstract class InstanceMixin {
16 |
17 | @Inject(method = "UNSAFE_setBlock", at = @At(value = "INVOKE", target = "Lnet/minestom/server/instance/Chunk;" +
18 | "UNSAFE_setBlock(IIISSLnet/minestom/server/data/Data;Z)V"))
19 | public void onSetBlock(Chunk chunk, int x, int y, int z, short blockStateId, CustomBlock customBlock, Data data,
20 | CallbackInfo ci) {
21 | //noinspection ConstantConditions
22 | if (!(((Object) this) instanceof Instance)) return;
23 | var instance = (Instance) (Object) this;
24 | var recorder = MinestomInteropExtensionsKt.getRecorder(instance);
25 | if (recorder == null) return;
26 |
27 | recorder.notifyBlockChange(x, y, z, blockStateId);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/impl-minestom/src/main/java/io/github/openminigameserver/replay/mixins/PacketUtilsMixin.java:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.mixins;
2 |
3 | import io.github.openminigameserver.replay.ReplayListener;
4 | import net.minestom.server.entity.Player;
5 | import net.minestom.server.network.packet.server.ServerPacket;
6 | import net.minestom.server.utils.PacketUtils;
7 | import net.minestom.server.utils.callback.validator.PlayerValidator;
8 | import org.spongepowered.asm.mixin.Mixin;
9 | import org.spongepowered.asm.mixin.injection.At;
10 | import org.spongepowered.asm.mixin.injection.Inject;
11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
12 |
13 | import java.util.Collection;
14 |
15 | @Mixin(PacketUtils.class)
16 | public class PacketUtilsMixin {
17 | @Inject(method = "sendGroupedPacket(Ljava/util/Collection;" +
18 | "Lnet/minestom/server/network/packet/server/ServerPacket;" +
19 | "Lnet/minestom/server/utils/callback/validator/PlayerValidator;)V", at = @At("HEAD"))
20 | private static void onSendGroupedPacket(Collection players, ServerPacket packet,
21 | PlayerValidator playerValidator, CallbackInfo ci) {
22 | ReplayListener.handleSentPacket(packet, players);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/impl-minestom/src/main/java/io/github/openminigameserver/replay/mixins/PlayerInventoryMixin.java:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.mixins;
2 |
3 | import io.github.openminigameserver.replay.MinestomReplayExtension;
4 | import io.github.openminigameserver.replay.extensions.MinestomInteropExtensionsKt;
5 | import io.github.openminigameserver.replay.platform.minestom.MinestomReplayPlatform;
6 | import net.minestom.server.entity.Player;
7 | import net.minestom.server.inventory.PlayerInventory;
8 | import org.spongepowered.asm.mixin.Final;
9 | import org.spongepowered.asm.mixin.Mixin;
10 | import org.spongepowered.asm.mixin.Shadow;
11 | import org.spongepowered.asm.mixin.injection.At;
12 | import org.spongepowered.asm.mixin.injection.Inject;
13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
14 |
15 | @Mixin(PlayerInventory.class)
16 | public class PlayerInventoryMixin {
17 | @Shadow
18 | @Final
19 | protected Player player;
20 |
21 | @Inject(method = "update", at = @At("TAIL"))
22 | public void onInventoryUpdate(CallbackInfo ci) {
23 | if (player.getInstance() != null) {
24 | var recorder = MinestomInteropExtensionsKt.getRecorder(player.getInstance());
25 | if (recorder != null) {
26 | recorder.onEntityEquipmentChange(((MinestomReplayPlatform) MinestomReplayExtension.extension.getPlatform()).getPlayer((Player) (Object) this));
27 | }
28 | }
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/impl-minestom/src/main/java/io/github/openminigameserver/replay/mixins/PlayerMixin.java:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.mixins;
2 |
3 | import io.github.openminigameserver.replay.MinestomReplayExtension;
4 | import io.github.openminigameserver.replay.extensions.MinestomInteropExtensionsKt;
5 | import io.github.openminigameserver.replay.platform.minestom.MinestomReplayPlatform;
6 | import net.minestom.server.entity.EntityType;
7 | import net.minestom.server.entity.LivingEntity;
8 | import net.minestom.server.entity.Player;
9 | import net.minestom.server.utils.Position;
10 | import org.jetbrains.annotations.NotNull;
11 | import org.spongepowered.asm.mixin.Mixin;
12 | import org.spongepowered.asm.mixin.injection.At;
13 | import org.spongepowered.asm.mixin.injection.Inject;
14 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
15 |
16 | @Mixin(Player.class)
17 | public abstract class PlayerMixin extends LivingEntity {
18 | public PlayerMixin(@NotNull EntityType entityType, @NotNull Position spawnPosition) {
19 | super(entityType, spawnPosition);
20 | }
21 |
22 | @Inject(method = "refreshHeldSlot", at = @At("RETURN"))
23 | public void onChangeHeld(byte slot, CallbackInfo ci) {
24 | if (getInstance() != null) {
25 | var recorder = MinestomInteropExtensionsKt.getRecorder(getInstance());
26 | if (recorder != null) {
27 | recorder.onEntityEquipmentChange(((MinestomReplayPlatform) MinestomReplayExtension.extension.getPlatform()).getPlayer((Player) (Object) this));
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/MinestomReplayExtension.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay
2 |
3 | import io.github.openminigameserver.replay.helpers.EntityHelper
4 | import io.github.openminigameserver.replay.platform.ReplayExtension
5 | import io.github.openminigameserver.replay.platform.minestom.MinestomReplayPlatform
6 | import net.minestom.server.MinecraftServer
7 | import net.minestom.server.extensions.Extension
8 | import net.minestom.server.extras.selfmodification.MinestomRootClassLoader
9 | import org.slf4j.Logger
10 | import java.io.File
11 |
12 | class MinestomReplayExtension : Extension() {
13 | val minestomLogger: Logger get() = this.logger
14 |
15 | companion object {
16 | @JvmStatic
17 | lateinit var dataFolder: File
18 |
19 | var hasLoadedPlatform = false
20 |
21 | lateinit var platform: MinestomReplayPlatform
22 |
23 | lateinit var extension: ReplayExtension
24 | }
25 |
26 | init {
27 | val classLoader = this.javaClass.classLoader
28 | if (classLoader is MinestomRootClassLoader) {
29 | classLoader.protectedClasses.add("io.github.openminigameserver.replay.AbstractReplaySession")
30 | classLoader.protectedPackages.add("io.github.openminigameserver.replay.model")
31 | }
32 | }
33 |
34 | override fun initialize() {
35 | dataFolder = File(
36 | MinecraftServer.getExtensionManager().extensionFolder,
37 | "Replay"
38 | ).also { it.mkdirs() }
39 | platform = MinestomReplayPlatform(this)
40 | hasLoadedPlatform = true
41 | extension = ReplayExtension(platform)
42 |
43 | extension.init()
44 |
45 | ReplayListener.platform = platform
46 | ReplayListener.registerListeners()
47 | logger.info("Initialized event listeners.")
48 |
49 | EntityHelper.init()
50 | logger.info("Initialized entity helpers.")
51 | }
52 |
53 | override fun terminate() {
54 |
55 | }
56 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/ReplayListener.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay
2 |
3 | import io.github.openminigameserver.replay.extensions.*
4 | import io.github.openminigameserver.replay.helpers.ReplayPlayerEntity
5 | import io.github.openminigameserver.replay.model.Replay
6 | import io.github.openminigameserver.replay.model.recordable.impl.*
7 | import io.github.openminigameserver.replay.platform.minestom.MinestomReplayPlatform
8 | import io.github.openminigameserver.replay.platform.minestom.controlItemAction
9 | import io.github.openminigameserver.replay.recorder.ReplayRecorder
10 | import io.github.openminigameserver.replay.replayer.statehelper.ControlItemAction
11 | import net.minestom.server.MinecraftServer
12 | import net.minestom.server.entity.Entity
13 | import net.minestom.server.entity.Player
14 | import net.minestom.server.entity.fakeplayer.FakePlayer
15 | import net.minestom.server.event.instance.AddEntityToInstanceEvent
16 | import net.minestom.server.event.inventory.InventoryPreClickEvent
17 | import net.minestom.server.event.player.PlayerDisconnectEvent
18 | import net.minestom.server.event.player.PlayerHandAnimationEvent
19 | import net.minestom.server.event.player.PlayerSwapItemEvent
20 | import net.minestom.server.event.player.PlayerUseItemEvent
21 | import net.minestom.server.network.packet.server.ServerPacket
22 | import net.minestom.server.network.packet.server.play.*
23 |
24 | object ReplayListener {
25 |
26 | lateinit var platform: MinestomReplayPlatform
27 | private val playerDisconnectHandler: (event: PlayerDisconnectEvent) -> Unit = event@{
28 | if (it.player !is FakePlayer && it.player !is ReplayPlayerEntity) {
29 | val replaySession = it.player.instance!!.replaySession ?: return@event
30 | replaySession.removeViewer(platform.getPlayer(it.player))
31 | }
32 | }
33 | private val viewerJoinedReplaySession: (event: AddEntityToInstanceEvent) -> Unit = event@{
34 | val player = it.entity as? Player ?: return@event
35 | val instance = it.instance
36 | val session = instance.replaySession ?: return@event
37 |
38 | val isViewer = session.viewers.any { it.uuid == player.uuid }
39 | if (!isViewer) return@event
40 |
41 | session.viewerCountDownLatch.countDown()
42 | }
43 |
44 | private val playerSwapItemEvent: (event: PlayerSwapItemEvent) -> Unit = {
45 | if (it.player.instance!!.replaySession != null) {
46 | it.isCancelled = true
47 | }
48 | }
49 |
50 | private val useItemHandler: (event: PlayerUseItemEvent) -> Unit = eventCallback@{
51 | val replaySession = it.player.instance!!.replaySession ?: return@eventCallback
52 |
53 | val action = it.itemStack.controlItemAction
54 | if (action != ControlItemAction.NONE) {
55 | runOnSeparateThread {
56 | replaySession.playerStateHelper.handleItemAction(platform.getPlayer(it.player), action)
57 | }
58 | }
59 | }
60 |
61 | private val inventoryPreClickHandler: (event: InventoryPreClickEvent) -> Unit = eventCallback@{
62 | it.player.instance!!.replaySession ?: return@eventCallback
63 |
64 | if (it.inventory == null)
65 | it.isCancelled = true
66 | }
67 |
68 | private val handAnimationHandler: (event: PlayerHandAnimationEvent) -> Unit =
69 | eventCallback@{ event: PlayerHandAnimationEvent ->
70 | val session = event.player.instance?.replaySession ?: return@eventCallback
71 |
72 | session.playerStateHelper.handleItemSwing(
73 | platform.getPlayer(event.player),
74 | platform.getItemStack(event.player.getItemInHand(event.hand))
75 | )
76 | }
77 |
78 | fun registerListeners() {
79 | val eventHandler = MinecraftServer.getGlobalEventHandler()
80 | eventHandler.addEventCallback(PlayerDisconnectEvent::class.java, playerDisconnectHandler)
81 | eventHandler.addEventCallback(AddEntityToInstanceEvent::class.java, viewerJoinedReplaySession)
82 | eventHandler.addEventCallback(PlayerSwapItemEvent::class.java, playerSwapItemEvent)
83 | eventHandler.addEventCallback(PlayerUseItemEvent::class.java, useItemHandler)
84 | eventHandler.addEventCallback(InventoryPreClickEvent::class.java, inventoryPreClickHandler)
85 |
86 | MinecraftServer.getGlobalEventHandler().addEventCallback(
87 | PlayerHandAnimationEvent::class.java,
88 | handAnimationHandler
89 | )
90 | }
91 |
92 | @JvmStatic
93 | fun handleSentPacket(
94 | packet: ServerPacket,
95 | players: Collection
96 | ) {
97 | if (!MinestomReplayExtension.hasLoadedPlatform) return
98 | when (packet) {
99 | is SoundEffectPacket -> {
100 | handleRecording(players) { replay ->
101 | replay.addAction(packet.run {
102 | RecSoundEffect(soundId, SoundCategory.valueOf(soundCategory.name), x, y, z, volume, pitch)
103 | })
104 | }
105 | }
106 | is EntityMetaDataPacket -> {
107 | val entity = Entity.getEntity(packet.entityId) as? Player
108 | handleRecording(entity?.let { players.plus(entity) } ?: players) { replay ->
109 | val metadataArray = packet.getMetadataArray()
110 |
111 | replay.getEntityById(packet.entityId)?.let {
112 | replay.addAction(RecEntityMetadata(metadataArray, it))
113 | }
114 | }
115 | }
116 | is EntityEquipmentPacket -> {
117 | val entity = Entity.getEntity(packet.entityId) ?: return
118 | val instance = entity.instance ?: return
119 |
120 | val recorder: ReplayRecorder = instance.recorder ?: return
121 |
122 | recorder.onEntityEquipmentChange(platform.getEntity(entity))
123 | }
124 | is EffectPacket -> {
125 | handleEffectPacket(players, packet)
126 | }
127 | is ParticlePacket -> {
128 | handleRecording(players) { replay ->
129 | replay.addAction(
130 | RecParticleEffect(
131 | packet.particleId,
132 | packet.longDistance,
133 | packet.x,
134 | packet.y,
135 | packet.z,
136 | packet.offsetX,
137 | packet.offsetY,
138 | packet.offsetZ,
139 | packet.particleData,
140 | packet.particleCount,
141 | packet.dataConsumer?.getMetadataArray()
142 | )
143 | )
144 | }
145 | }
146 | }
147 | }
148 |
149 | private fun handleEffectPacket(
150 | players: Collection,
151 | packet: EffectPacket
152 | ) {
153 | handleRecording(players) { replay ->
154 | replay.addAction(
155 | RecBlockEffect(
156 | packet.effectId,
157 | packet.position.toPosition().toReplay(),
158 | packet.data,
159 | packet.disableRelativeVolume
160 | )
161 | )
162 | }
163 | }
164 |
165 | private inline fun handleRecording(players: Collection, code: (Replay) -> Unit) {
166 | players.filter { it.instance != null }.groupBy { it.instance?.uniqueId }.forEach {
167 | val player = it.value.first()
168 | val replay = player.instance?.recorder?.replay ?: return@forEach
169 |
170 | code.invoke(replay)
171 | }
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/extensions/MinestomInteropExtensions.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.extensions
2 |
3 | import com.google.common.cache.Cache
4 | import com.google.common.cache.CacheBuilder
5 | import io.github.openminigameserver.replay.MinestomReplayExtension
6 | import io.github.openminigameserver.replay.abstraction.ReplayWorld
7 | import io.github.openminigameserver.replay.model.Replay
8 | import io.github.openminigameserver.replay.model.recordable.RecordableItemStack
9 | import io.github.openminigameserver.replay.model.recordable.RecordablePosition
10 | import io.github.openminigameserver.replay.model.recordable.RecordablePositionAndVector
11 | import io.github.openminigameserver.replay.model.recordable.RecordableVector
12 | import io.github.openminigameserver.replay.model.recordable.entity.EntityEquipmentSlot
13 | import io.github.openminigameserver.replay.model.recordable.entity.RecordableEntity
14 | import io.github.openminigameserver.replay.model.recordable.entity.data.BaseEntityData
15 | import io.github.openminigameserver.replay.model.recordable.entity.data.PlayerEntityData
16 | import io.github.openminigameserver.replay.model.recordable.entity.data.PlayerSkinData
17 | import io.github.openminigameserver.replay.recorder.ReplayRecorder
18 | import io.github.openminigameserver.replay.replayer.ReplaySession
19 | import kotlinx.coroutines.CoroutineScope
20 | import kotlinx.coroutines.runBlocking
21 | import net.minestom.server.entity.Entity
22 | import net.minestom.server.entity.Player
23 | import net.minestom.server.entity.PlayerSkin
24 | import net.minestom.server.instance.Instance
25 | import net.minestom.server.inventory.EquipmentHandler
26 | import net.minestom.server.item.ItemStack
27 | import net.minestom.server.network.packet.server.play.EntityEquipmentPacket
28 | import net.minestom.server.network.packet.server.play.EntityMetaDataPacket
29 | import net.minestom.server.utils.NBTUtils
30 | import net.minestom.server.utils.Position
31 | import net.minestom.server.utils.Vector
32 | import net.minestom.server.utils.binary.BinaryReader
33 | import net.minestom.server.utils.binary.BinaryWriter
34 | import java.util.*
35 | import java.util.concurrent.TimeUnit
36 | import java.util.function.Consumer
37 |
38 | fun Position.toReplay(): RecordablePosition = RecordablePosition(x, y, z, yaw, pitch)
39 | fun RecordablePosition.toMinestom(): Position = Position(x, y, z, yaw, pitch)
40 |
41 | fun Vector.toReplay(): RecordableVector = RecordableVector(x, y, z)
42 | fun RecordableVector.toMinestom(): Vector = Vector(x, y, z)
43 |
44 | fun ItemStack.toReplay(): RecordableItemStack =
45 | RecordableItemStack(BinaryWriter().also { NBTUtils.writeItemStack(it, this) }.toByteArray())
46 |
47 | fun RecordableItemStack.toMinestom(): ItemStack =
48 | BinaryReader(nbtValue).let { NBTUtils.readItemStack(it) ?: ItemStack.getAirItem() }
49 |
50 | const val REPLAY_RECORDER_DATA = "replay:recorder"
51 | const val REPLAY_REPLAYER_DATA = "replay:replayer"
52 |
53 | var Player.recorder: ReplayRecorder?
54 | get() = instance!!.recorder
55 | set(value) {
56 | instance!!.recorder = value
57 | }
58 |
59 | var Instance.recorder: ReplayRecorder?
60 | get() = replayWorld.recorder
61 | set(value) {
62 | replayWorld.recorder = value
63 | }
64 |
65 | val Instance.replayWorld: ReplayWorld
66 | get() = MinestomReplayExtension.platform.getWorldById(uniqueId)
67 |
68 | var Instance.replaySession: ReplaySession?
69 | get() = replayWorld.replaySession
70 | set(value) {
71 | replayWorld.replaySession = value
72 | }
73 |
74 | internal fun runOnSeparateThread(code: suspend CoroutineScope.() -> Unit) {
75 | Thread {
76 | runBlocking(block = code)
77 | }.start()
78 | }
79 |
80 | internal val profileCache: Cache =
81 | CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.MINUTES).build()
82 |
83 | internal fun Entity.toReplay(spawnOnStart: Boolean = true): RecordableEntity {
84 | var data: BaseEntityData? = null
85 | if (this is Player) {
86 | val skin = skin
87 |
88 | data = PlayerEntityData(username, skin?.toReplay(), metadataPacket.getMetadataArray(), getEquipmentForEntity())
89 | }
90 | return RecordableEntity(
91 | entityId,
92 | entityType.namespaceID,
93 | RecordablePositionAndVector(position.toReplay(), velocity.toReplay()),
94 | data
95 | ).apply {
96 | this.spawnOnStart =
97 | spawnOnStart
98 | }
99 | }
100 |
101 | internal fun PlayerSkin.toReplay(): PlayerSkinData {
102 | val decoder = Base64.getDecoder()
103 | return PlayerSkinData(decoder.decode(textures), decoder.decode(signature))
104 | }
105 |
106 | fun PlayerSkinData.toMinestom(): PlayerSkin {
107 | val encoder = Base64.getEncoder()
108 | return PlayerSkin(encoder.encodeToString(textures), encoder.encodeToString(signature))
109 | }
110 |
111 |
112 | fun Replay.getEntity(entity: Entity): RecordableEntity? {
113 | return getEntityById(entity.entityId)
114 | }
115 |
116 | internal fun Consumer.getMetadataArray(): ByteArray {
117 | return BinaryWriter().use { accept(it); it.toByteArray() }
118 | }
119 |
120 | internal fun EntityMetaDataPacket.getMetadataArray(): ByteArray {
121 | val binaryWriter = BinaryWriter()
122 | write(binaryWriter)
123 | return binaryWriter.toByteArray()
124 | }
125 |
126 | internal fun EquipmentHandler.getEquipmentForEntity(): Map =
127 | EntityEquipmentPacket.Slot.values().map {
128 | EntityEquipmentSlot.valueOf(it.name) to getEquipment(it).toReplay()
129 | }.toMap()
130 |
131 | internal fun EquipmentHandler.setEquipmentForEntity(equipment: Map) =
132 | equipment.forEach {
133 | val item = it.value.toMinestom()
134 | when (it.key) {
135 | EntityEquipmentSlot.MAIN_HAND -> itemInMainHand = item
136 | EntityEquipmentSlot.OFF_HAND -> itemInOffHand = item
137 | EntityEquipmentSlot.BOOTS -> boots = item
138 | EntityEquipmentSlot.LEGGINGS -> leggings = item
139 | EntityEquipmentSlot.CHESTPLATE -> chestplate = item
140 | EntityEquipmentSlot.HELMET -> helmet = item
141 | }
142 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/helpers/EntityHelper.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.helpers
2 |
3 | import io.github.openminigameserver.replay.extensions.setEquipmentForEntity
4 | import io.github.openminigameserver.replay.extensions.toMinestom
5 | import io.github.openminigameserver.replay.model.recordable.entity.data.BaseEntityData
6 | import io.github.openminigameserver.replay.model.recordable.entity.data.EquipmentEntityData
7 | import io.github.openminigameserver.replay.model.recordable.entity.data.PlayerEntityData
8 | import net.minestom.server.entity.*
9 | import net.minestom.server.entity.type.animal.*
10 | import net.minestom.server.entity.type.decoration.EntityArmorStand
11 | import net.minestom.server.entity.type.decoration.EntityItemFrame
12 | import net.minestom.server.entity.type.monster.*
13 | import net.minestom.server.entity.type.other.EntityAreaEffectCloud
14 | import net.minestom.server.entity.type.other.EntityEndCrystal
15 | import net.minestom.server.entity.type.other.EntityIronGolem
16 | import net.minestom.server.entity.type.other.EntitySnowman
17 | import net.minestom.server.entity.type.projectile.EntityEyeOfEnder
18 | import net.minestom.server.entity.type.projectile.EntityPotion
19 | import net.minestom.server.entity.type.vehicle.EntityBoat
20 | import net.minestom.server.inventory.EquipmentHandler
21 | import net.minestom.server.utils.Position
22 | import org.apache.commons.lang3.reflect.ConstructorUtils
23 | import java.util.*
24 |
25 | internal object EntityHelper {
26 |
27 | private val entityTypeMap = mutableMapOf>()
28 |
29 | @JvmStatic
30 | fun init() {
31 | entityTypeMap[EntityType.POTION] = EntityPotion::class.java
32 | entityTypeMap[EntityType.CAVE_SPIDER] = EntityCaveSpider::class.java
33 | entityTypeMap[EntityType.SILVERFISH] = EntitySilverfish::class.java
34 | entityTypeMap[EntityType.COW] = EntityCow::class.java
35 | entityTypeMap[EntityType.BLAZE] = EntityBlaze::class.java
36 | entityTypeMap[EntityType.ZOMBIFIED_PIGLIN] = EntityZombifiedPiglin::class.java
37 | entityTypeMap[EntityType.PANDA] = EntityPanda::class.java
38 | entityTypeMap[EntityType.ARMOR_STAND] = EntityArmorStand::class.java
39 | entityTypeMap[EntityType.GIANT] = EntityGiant::class.java
40 | entityTypeMap[EntityType.PHANTOM] = EntityPhantom::class.java
41 | entityTypeMap[EntityType.GHAST] = EntityGhast::class.java
42 | entityTypeMap[EntityType.BEE] = EntityBee::class.java
43 | entityTypeMap[EntityType.SPIDER] = EntitySpider::class.java
44 | entityTypeMap[EntityType.EXPERIENCE_ORB] = ExperienceOrb::class.java
45 | entityTypeMap[EntityType.ITEM] = ItemEntity::class.java
46 | entityTypeMap[EntityType.ITEM_FRAME] = EntityItemFrame::class.java
47 | entityTypeMap[EntityType.END_CRYSTAL] = EntityEndCrystal::class.java
48 | entityTypeMap[EntityType.SNOW_GOLEM] = EntitySnowman::class.java
49 | entityTypeMap[EntityType.RABBIT] = EntityRabbit::class.java
50 | entityTypeMap[EntityType.WITCH] = EntityWitch::class.java
51 | entityTypeMap[EntityType.ENDERMITE] = EntityEndermite::class.java
52 | entityTypeMap[EntityType.GUARDIAN] = EntityGuardian::class.java
53 | entityTypeMap[EntityType.EYE_OF_ENDER] = EntityEyeOfEnder::class.java
54 | entityTypeMap[EntityType.POLAR_BEAR] = EntityPolarBear::class.java
55 | entityTypeMap[EntityType.OCELOT] = EntityOcelot::class.java
56 | entityTypeMap[EntityType.CAT] = EntityCat::class.java
57 | entityTypeMap[EntityType.CHICKEN] = EntityChicken::class.java
58 | entityTypeMap[EntityType.IRON_GOLEM] = EntityIronGolem::class.java
59 | entityTypeMap[EntityType.BOAT] = EntityBoat::class.java
60 | entityTypeMap[EntityType.AREA_EFFECT_CLOUD] = EntityAreaEffectCloud::class.java
61 | entityTypeMap[EntityType.ZOMBIE] = EntityZombie::class.java
62 | entityTypeMap[EntityType.DOLPHIN] = EntityDolphin::class.java
63 | entityTypeMap[EntityType.FOX] = EntityFox::class.java
64 | entityTypeMap[EntityType.PIG] = EntityPig::class.java
65 | entityTypeMap[EntityType.LLAMA] = EntityLlama::class.java
66 | entityTypeMap[EntityType.CREEPER] = EntityCreeper::class.java
67 | entityTypeMap[EntityType.ARMOR_STAND] = EntityArmorStand::class.java
68 | entityTypeMap[EntityType.SLIME] = EntitySlime::class.java
69 | entityTypeMap[EntityType.MOOSHROOM] = EntityMooshroom::class.java
70 | }
71 |
72 | fun createEntity(
73 | type: EntityType,
74 | spawnPosition: Position,
75 | entityData: BaseEntityData?,
76 | isAutoViewable: Boolean
77 | ): Entity =
78 | (if (type == EntityType.PLAYER && entityData is PlayerEntityData) {
79 | ReplayPlayerEntity(UUID.randomUUID(), entityData.userName, entityData.metadata).also {
80 | it.skin = entityData.skin?.toMinestom()
81 | }
82 | } else {
83 | val entityTypeClazz = entityTypeMap[type]
84 |
85 | if (entityTypeClazz != null) {
86 | ConstructorUtils.invokeConstructor(entityTypeClazz, spawnPosition)
87 | } else {
88 | object : EntityCreature(type, spawnPosition) {
89 | override fun update(time: Long) {
90 | }
91 |
92 | override fun spawn() {
93 | }
94 | }
95 | }
96 |
97 | }).also {
98 | it.isAutoViewable = isAutoViewable
99 | it.setNoGravity(true)
100 | if (it is LivingEntity) {
101 | it.health = it.maxHealth
102 | }
103 |
104 | if (it is EquipmentHandler && entityData is EquipmentEntityData) {
105 | it.setEquipmentForEntity(entityData.equipment)
106 | }
107 | }
108 |
109 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/helpers/EntityManager.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.helpers
2 |
3 | import io.github.openminigameserver.replay.extensions.toMinestom
4 | import io.github.openminigameserver.replay.extensions.toReplay
5 | import io.github.openminigameserver.replay.model.recordable.RecordablePosition
6 | import io.github.openminigameserver.replay.model.recordable.RecordableVector
7 | import io.github.openminigameserver.replay.model.recordable.entity.RecordableEntity
8 | import io.github.openminigameserver.replay.model.recordable.impl.RecEntitiesPosition
9 | import io.github.openminigameserver.replay.model.recordable.impl.RecEntityMove
10 | import io.github.openminigameserver.replay.platform.minestom.MinestomReplayEntity
11 | import io.github.openminigameserver.replay.platform.minestom.MinestomReplayPlatform
12 | import io.github.openminigameserver.replay.platform.minestom.MinestomReplayUser
13 | import io.github.openminigameserver.replay.platform.minestom.MinestomReplayWorld
14 | import io.github.openminigameserver.replay.replayer.IEntityManager
15 | import io.github.openminigameserver.replay.replayer.ReplaySession
16 | import net.minestom.server.entity.Player
17 | import net.minestom.server.registry.Registries
18 | import net.minestom.server.utils.Vector
19 | import kotlin.time.Duration
20 |
21 | class EntityManager(val platform: MinestomReplayPlatform, override var session: ReplaySession) :
22 | IEntityManager {
23 | override val entities: Collection
24 | get() = replayEntities.keys.mapNotNull { session.replay.getEntityById(it) }
25 |
26 | //Replay entity id
27 | override val replayEntities = mutableMapOf()
28 |
29 | override fun resetEntity(entity: RecordableEntity, startTime: Duration, targetReplayTime: Duration) {
30 | getNativeEntity(entity)?.let { e ->
31 | val nativeEntity = e.entity
32 | nativeEntity.remove()
33 | nativeEntity.askSynchronization()
34 |
35 | //Check if Entity (has been spawned at start) or (has been spawned somewhere before and has not been removed before)
36 | val shouldSpawn = true
37 |
38 | //Find actual position
39 | var finalPos = entity.spawnPosition?.position
40 | session.findActionsForEntity(startTime, entity, targetReplayTime)
41 | ?.let { finalPos = it.data.position }
42 | session.findLastAction(
43 | startTime,
44 | targetReplayTime
45 | ) { it.positions.containsKey(entity) }
46 | ?.let { finalPos = it.positions[entity]!!.position }
47 |
48 | nativeEntity.velocity = Vector(0.0, 0.0, 0.0)
49 | finalPos?.let { previousLoc ->
50 | if (shouldSpawn) {
51 | this.spawnEntity(entity, previousLoc)
52 | }
53 | }
54 | }
55 | }
56 |
57 | override fun spawnEntity(
58 | entity: RecordableEntity,
59 | position: RecordablePosition,
60 | velocity: RecordableVector
61 | ) {
62 | replayEntities[entity.id]?.takeIf { !it.entity.isRemoved }?.entity?.remove()
63 | val spawnPosition = position.toMinestom()
64 | val minestomEntity =
65 | MinestomReplayEntity(
66 | platform,
67 | EntityHelper.createEntity(
68 | Registries.getEntityType(entity.type)!!,
69 | spawnPosition,
70 | entity.entityData,
71 | session.replay.hasChunks
72 | )
73 | )
74 | replayEntities[entity.id] = minestomEntity
75 |
76 | if (minestomEntity.instance != session.world)
77 | minestomEntity.entity.setInstance((session.world as MinestomReplayWorld).instance)
78 |
79 | refreshPosition(minestomEntity, spawnPosition.toReplay())
80 | minestomEntity.velocity = velocity
81 |
82 | session.viewers.forEach {
83 | minestomEntity.entity.addViewer((it as MinestomReplayUser).player)
84 | }
85 | }
86 |
87 | override fun refreshPosition(entity: MinestomReplayEntity, position: RecordablePosition) {
88 | val minestomEntity = entity.entity
89 | minestomEntity.velocity = Vector(0.0, 0.0, 0.0)
90 | minestomEntity.refreshPosition(position.toMinestom())
91 | minestomEntity.refreshView(position.yaw, position.pitch)
92 | minestomEntity.askSynchronization()
93 | }
94 |
95 | override fun getNativeEntity(entity: RecordableEntity): MinestomReplayEntity? {
96 | return replayEntities[entity.id]
97 | }
98 |
99 | override fun removeEntity(entity: RecordableEntity) {
100 | getNativeEntity(entity)?.entity?.remove()
101 | replayEntities.remove(entity.id)
102 | }
103 |
104 | override fun removeNativeEntity(entity: MinestomReplayEntity) {
105 | entity.entity.remove()
106 | replayEntities.remove(entity.id)
107 | }
108 |
109 | override fun removeAllEntities() {
110 | replayEntities.forEach {
111 | val entity = it.value.entity
112 | if (entity !is Player || entity is ReplayPlayerEntity)
113 | entity.remove()
114 | }
115 | replayEntities.clear()
116 | }
117 |
118 | override fun removeEntityViewer(player: MinestomReplayUser) {
119 | replayEntities.forEach {
120 | it.value.entity.removeViewer(player.player)
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/helpers/ReplayPlayerEntity.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.helpers
2 |
3 | import io.netty.channel.local.LocalAddress
4 | import net.minestom.server.entity.Player
5 | import net.minestom.server.network.packet.server.ServerPacket
6 | import net.minestom.server.network.packet.server.play.EntityMetaDataPacket
7 | import net.minestom.server.network.player.PlayerConnection
8 | import net.minestom.server.utils.binary.BinaryWriter
9 | import java.net.SocketAddress
10 | import java.util.*
11 | import java.util.concurrent.atomic.AtomicInteger
12 |
13 |
14 | class ReplayPlayerEntity(uuid: UUID, username: String, private val firstMetadata: ByteArray) :
15 | Player(uuid, "$username§r".take(16), ReplayPlayerConnection()) {
16 | init {
17 | settings.refresh(Locale.ENGLISH.toLanguageTag(), 0, ChatMode.ENABLED, true, 127, MainHand.RIGHT)
18 | }
19 |
20 | private var isFirstMetadata = true
21 |
22 | override fun getMetadataPacket(): EntityMetaDataPacket {
23 | if (isFirstMetadata) {
24 | isFirstMetadata = false
25 | return object : EntityMetaDataPacket() {
26 | override fun write(writer: BinaryWriter) {
27 | writer.writeVarInt(this@ReplayPlayerEntity.entityId)
28 | writer.write(firstMetadata)
29 | writer.writeByte(0xFF.toByte())
30 | }
31 | }
32 | }
33 | return super.getMetadataPacket()
34 | }
35 | }
36 |
37 |
38 | class ReplayPlayerConnection : PlayerConnection() {
39 | private val id = counter.getAndIncrement()
40 |
41 | companion object {
42 | private val counter = AtomicInteger()
43 | }
44 |
45 | override fun sendPacket(serverPacket: ServerPacket) {
46 | }
47 |
48 | /**
49 | * Gets the remote address of the client.
50 | *
51 | * @return the remote address
52 | */
53 | override fun getRemoteAddress(): SocketAddress {
54 | return LocalAddress("replay-player$id")
55 | }
56 |
57 | /**
58 | * Forcing the player to disconnect.
59 | */
60 | override fun disconnect() {
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/platform/minestom/ItemStackExtensions.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.platform.minestom
2 |
3 | import io.github.openminigameserver.replay.replayer.statehelper.ControlItemAction
4 | import net.minestom.server.data.DataImpl
5 | import net.minestom.server.item.ItemStack
6 |
7 | const val actionData = "controlItem"
8 |
9 | var ItemStack.controlItemAction: ControlItemAction
10 | get() = data?.get(actionData) ?: ControlItemAction.NONE
11 | set(value) {
12 | if (data == null) {
13 | data = DataImpl()
14 | }
15 | data?.set(actionData, value)
16 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/platform/minestom/MinestomActionPlayerManager.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.platform.minestom
2 |
3 | import io.github.openminigameserver.replay.platform.ReplayPlatform
4 | import io.github.openminigameserver.replay.replayer.ActionPlayerManager
5 | import io.github.openminigameserver.replay.replayer.impl.*
6 |
7 | class MinestomActionPlayerManager(platform: ReplayPlatform) :
8 | ActionPlayerManager(platform) {
9 |
10 | init {
11 | registerActionPlayerGeneric(RecBlockBreakAnimationPlayer)
12 | registerActionPlayerGeneric(RecBlockEffectPlayer)
13 | registerActionPlayerGeneric(RecBlockStateUpdatePlayer)
14 | registerActionPlayerGeneric(RecEntitiesPositionPlayer)
15 | registerActionPlayerGeneric(RecEntityEquipmentUpdatePlayer)
16 | registerActionPlayerGeneric(RecEntityMetadataPlayer)
17 | registerActionPlayerGeneric(RecEntityMovePlayer)
18 | registerActionPlayerGeneric(RecEntityRemovePlayer)
19 | registerActionPlayerGeneric(RecEntitySpawnPlayer)
20 | registerActionPlayerGeneric(RecParticleEffectPlayer)
21 | registerActionPlayerGeneric(RecPlayerHandAnimationPlayer)
22 | registerActionPlayerGeneric(RecSoundEffectPlayer)
23 | registerActionPlayerGeneric(RecBlockStateBatchUpdatePlayer)
24 | }
25 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/platform/minestom/MinestomReplayChunk.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.platform.minestom
2 |
3 | import io.github.openminigameserver.replay.abstraction.ReplayChunk
4 | import net.minestom.server.instance.Chunk
5 |
6 | class MinestomReplayChunk(private val chunk: Chunk) : ReplayChunk {
7 | override val x: Int
8 | get() = chunk.chunkX
9 | override val z: Int
10 | get() = chunk.chunkZ
11 | override val serializedData: ByteArray?
12 | get() = chunk.serializedData
13 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/platform/minestom/MinestomReplayEntity.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.platform.minestom
2 |
3 | import io.github.openminigameserver.replay.abstraction.ReplayEntity
4 | import io.github.openminigameserver.replay.abstraction.ReplayWorld
5 | import io.github.openminigameserver.replay.extensions.getEquipmentForEntity
6 | import io.github.openminigameserver.replay.extensions.toMinestom
7 | import io.github.openminigameserver.replay.extensions.toReplay
8 | import io.github.openminigameserver.replay.model.recordable.RecordableItemStack
9 | import io.github.openminigameserver.replay.model.recordable.RecordablePosition
10 | import io.github.openminigameserver.replay.model.recordable.RecordableVector
11 | import io.github.openminigameserver.replay.model.recordable.entity.EntityEquipmentSlot
12 | import net.minestom.server.entity.Entity
13 | import net.minestom.server.inventory.EquipmentHandler
14 | import java.util.*
15 |
16 | class MinestomReplayEntity(private val replayPlatform: MinestomReplayPlatform, val entity: Entity) : ReplayEntity {
17 | override val id: Int
18 | get() = entity.entityId
19 | override val uuid: UUID
20 | get() = entity.uuid
21 | override val position: RecordablePosition
22 | get() = entity.position.toReplay()
23 | override var velocity: RecordableVector
24 | get() = entity.velocity.toReplay()
25 | set(value) {
26 | entity.velocity = value.toMinestom()
27 | }
28 | override val world: ReplayWorld?
29 | get() = entity.instance?.uniqueId?.let { replayPlatform.getWorldById(it) }
30 |
31 | override fun getEquipment(): Map {
32 | if (entity is EquipmentHandler) {
33 | return entity.getEquipmentForEntity()
34 | }
35 | return emptyMap()
36 | }
37 |
38 | override fun teleport(position: RecordablePosition) {
39 | entity.teleport(position.toMinestom())
40 | }
41 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/platform/minestom/MinestomReplayPlatform.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.platform.minestom
2 |
3 | import cloud.commandframework.CommandManager
4 | import cloud.commandframework.annotations.AnnotationParser
5 | import io.github.openminigameserver.replay.MinestomReplayExtension
6 | import io.github.openminigameserver.replay.TickTime
7 | import io.github.openminigameserver.replay.abstraction.*
8 | import io.github.openminigameserver.replay.helpers.EntityManager
9 | import io.github.openminigameserver.replay.model.Replay
10 | import io.github.openminigameserver.replay.model.recordable.entity.data.BaseEntityData
11 | import io.github.openminigameserver.replay.platform.IdHelperContainer
12 | import io.github.openminigameserver.replay.platform.ReplayPlatform
13 | import io.github.openminigameserver.replay.platform.minestom.replayer.getPlayerInventoryCopy
14 | import io.github.openminigameserver.replay.platform.minestom.replayer.loadAllItems
15 | import io.github.openminigameserver.replay.replayer.ActionPlayerManager
16 | import io.github.openminigameserver.replay.replayer.IEntityManager
17 | import io.github.openminigameserver.replay.replayer.ReplayChunkLoader
18 | import io.github.openminigameserver.replay.replayer.ReplaySession
19 | import net.kyori.adventure.platform.minestom.MinestomComponentSerializer
20 | import net.kyori.adventure.text.Component
21 | import net.minestom.server.MinecraftServer
22 | import net.minestom.server.chat.ChatColor
23 | import net.minestom.server.chat.ColoredText
24 | import net.minestom.server.data.DataImpl
25 | import net.minestom.server.entity.Entity
26 | import net.minestom.server.entity.Player
27 | import net.minestom.server.item.ItemStack
28 | import net.minestom.server.item.metadata.PlayerHeadMeta
29 | import net.minestom.server.network.packet.server.play.TeamsPacket
30 | import net.minestom.server.scoreboard.Team
31 | import net.minestom.server.timer.Task
32 | import net.minestom.server.utils.time.TimeUnit
33 | import org.jglrxavpok.hephaistos.nbt.NBTCompound
34 | import org.jglrxavpok.hephaistos.nbt.NBTList
35 | import java.io.File
36 | import java.util.*
37 |
38 | class MinestomReplayPlatform(private val replayExtension: MinestomReplayExtension) :
39 | ReplayPlatform() {
40 | override val name: String
41 | get() = "Minestom"
42 | override val version: String
43 | get() = MinecraftServer.VERSION_NAME
44 |
45 | override val commandManager: CommandManager = ReplayCommandManager(this)
46 | override val commandAnnotationParser: AnnotationParser =
47 | AnnotationParser(commandManager, ReplayUser::class.java) { commandManager.createDefaultCommandMeta() }
48 | override val dataDir: File
49 | get() = MinestomReplayExtension.dataFolder
50 |
51 | override fun log(message: String) {
52 | replayExtension.minestomLogger.info(message)
53 | }
54 |
55 | override fun cancelTask(tickerTask: Any) {
56 | val task = tickerTask as? Task ?: return
57 | task.cancel()
58 | }
59 |
60 | override fun registerSyncRepeatingTask(time: TickTime, action: () -> Unit): Any {
61 | return MinecraftServer.getSchedulerManager().buildTask(action)
62 | .repeat(time.unit.toMilliseconds(time.time), TimeUnit.MILLISECOND).schedule()
63 | }
64 |
65 | override fun getEntityType(replayEntity: MinestomReplayEntity): String {
66 | return replayEntity.entity.entityType.namespaceID
67 | }
68 |
69 | override fun getEntityData(replayEntity: MinestomReplayEntity): BaseEntityData? {
70 | return null
71 | }
72 |
73 | override val actionPlayerManager: ActionPlayerManager =
74 | MinestomActionPlayerManager(this)
75 |
76 | private val viewerTeam: Team = MinecraftServer.getTeamManager().createBuilder("ReplayViewers")
77 | .prefix(ColoredText.of(ChatColor.GRAY, "[Viewer] "))
78 | .collisionRule(TeamsPacket.CollisionRule.NEVER)
79 | .teamColor(ChatColor.GRAY)
80 | .build()
81 |
82 | override fun addToViewerTeam(p: MinestomReplayUser) {
83 | viewerTeam.addMember(p.player.username)
84 | }
85 |
86 | override fun removeFromViewerTeam(player: MinestomReplayUser) {
87 | viewerTeam.removeMember(player.player.username)
88 | }
89 |
90 | override fun unregisterWorld(instance: MinestomReplayWorld) {
91 | MinecraftServer.getInstanceManager().unregisterInstance(instance.instance)
92 | }
93 |
94 | override fun getWorldById(it: UUID): MinestomReplayWorld {
95 | return worlds.getOrCompute(it)
96 | }
97 |
98 | override fun getEntityManager(replaySession: ReplaySession): IEntityManager {
99 | return EntityManager(MinestomReplayExtension.platform, replaySession)
100 | }
101 |
102 | private fun createEmptyReplayInstance(replay: Replay) =
103 | (
104 | MinecraftServer.getInstanceManager().createInstanceContainer().apply {
105 | enableAutoChunkLoad(true)
106 | data = DataImpl()
107 | chunkLoader = ReplayChunkLoader(replay)
108 | }
109 | ).let { worlds.getOrCompute(it.uniqueId) }
110 |
111 | override fun createReplaySession(
112 | replay: Replay,
113 | viewers: MutableList,
114 | instance: ReplayWorld?,
115 | tickTime: TickTime
116 | ): ReplaySession {
117 | val hasChunks = replay.hasChunks
118 | val finalInstance =
119 | if (hasChunks) createEmptyReplayInstance(replay) else instance!!
120 |
121 | return ReplaySession(
122 | MinestomReplayExtension.platform as ReplayPlatform,
123 | finalInstance,
124 | replay,
125 | viewers,
126 | tickTime
127 | )
128 | }
129 |
130 | override fun getPlayerInventoryCopy(player: MinestomReplayUser): Any {
131 | return getPlayerInventoryCopy(player.player)
132 | }
133 |
134 | override fun loadPlayerInventoryCopy(player: MinestomReplayUser, inventory: Any) {
135 | @Suppress("UNCHECKED_CAST")
136 | (inventory as? NBTList)?.let { loadAllItems(it, player.player.inventory) }
137 | }
138 |
139 | fun getPlayer(player: Player): ReplayUser {
140 | return entities.getOrCompute(player.entityId) as ReplayUser
141 | }
142 |
143 | fun getItemStack(itemInHand: ItemStack): ReplayActionItemStack {
144 | val action = itemInHand.controlItemAction
145 | val skin = (itemInHand.itemMeta as? PlayerHeadMeta)?.playerSkin?.let {
146 | ReplayHeadTextureSkin(
147 | it.textures,
148 | it.signature
149 | )
150 | }
151 |
152 | return ReplayActionItemStack(itemInHand.displayName?.let {
153 | MinestomComponentSerializer.get().deserialize(
154 | it
155 | )
156 | } ?: Component.empty(), action, skin)
157 | }
158 |
159 | fun getEntity(entity: Entity): ReplayEntity {
160 | return entities.getOrCompute(entity.entityId)
161 | }
162 |
163 | override val worlds: IdHelperContainer =
164 | IdHelperContainer { MinestomReplayWorld(MinecraftServer.getInstanceManager().getInstance(this)!!) }
165 |
166 | override val entities: IdHelperContainer = IdHelperContainer {
167 | val entity: Entity = Player.getEntity(this) ?: Entity.getEntity(this)!!
168 | if (entity is Player) MinestomReplayUser(this@MinestomReplayPlatform, entity) else
169 | MinestomReplayEntity(this@MinestomReplayPlatform, entity)
170 | }
171 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/platform/minestom/MinestomReplayUser.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.platform.minestom
2 |
3 | import io.github.openminigameserver.replay.abstraction.*
4 | import io.github.openminigameserver.replay.replayer.statehelper.ControlItemAction
5 | import net.kyori.adventure.audience.Audience
6 | import net.kyori.adventure.platform.minestom.MinestomAudiences
7 | import net.kyori.adventure.platform.minestom.MinestomComponentSerializer
8 | import net.kyori.adventure.text.Component.text
9 | import net.minestom.server.entity.GameMode
10 | import net.minestom.server.entity.Player
11 | import net.minestom.server.entity.PlayerSkin
12 | import net.minestom.server.item.ItemStack
13 | import net.minestom.server.item.Material
14 | import net.minestom.server.item.metadata.PlayerHeadMeta
15 | import java.util.*
16 |
17 | internal val audiences = MinestomAudiences.create()
18 |
19 | class MinestomReplayUser(val replayPlatform: MinestomReplayPlatform, val player: Player) : ReplayUser(),
20 | ReplayEntity by MinestomReplayEntity(replayPlatform, player) {
21 | override var exp: Float
22 | get() = player.exp
23 | set(value) {
24 | player.exp = value
25 | }
26 | override val heldSlot: Byte
27 | get() = player.heldSlot
28 | override var isFlying: Boolean
29 | get() = player.isFlying
30 | set(value) {
31 | player.isFlying = value
32 | }
33 | override var isAllowFlying: Boolean
34 | get() = player.isFlying
35 | set(value) {
36 | player.isFlying = value
37 | }
38 | override var gameMode: ReplayGameMode
39 | get() = ReplayGameMode.valueOf(player.gameMode.name)
40 | set(value) {
41 | player.gameMode = GameMode.valueOf(value.name)
42 | }
43 | override val audience: Audience = audiences.player(player)
44 | override val name: String
45 | get() = player.username
46 |
47 | override fun setWorld(instance: ReplayWorld) {
48 | (instance as? MinestomReplayWorld)?.instance?.let { player.setInstance(it) }
49 | }
50 |
51 | override fun clearInventory() {
52 | player.inventory.clear()
53 | }
54 |
55 | override fun setHeldItemSlot(slot: Byte) {
56 | player.setHeldItemSlot(slot)
57 | }
58 |
59 | override fun setItemStack(slot: Int, itemStack: ReplayActionItemStack) {
60 | player.inventory.setItemStack(slot, itemStack.toMinestom())
61 | }
62 | }
63 |
64 | private fun ReplayActionItemStack.toMinestom(): ItemStack {
65 | val material = when (action) {
66 | ControlItemAction.NONE -> Material.AIR
67 |
68 | ControlItemAction.PAUSE -> Material.PINK_DYE
69 | ControlItemAction.RESUME -> Material.GRAY_DYE
70 | ControlItemAction.PLAY_AGAIN -> Material.LIME_DYE
71 |
72 | else -> Material.PLAYER_HEAD
73 | }
74 |
75 | return ItemStack(material, 1).apply {
76 | val itemSkin = skin
77 | if (itemSkin != null) {
78 | itemMeta = PlayerHeadMeta().apply {
79 | setSkullOwner(UUID.randomUUID())
80 | setPlayerSkin(PlayerSkin(itemSkin.value, itemSkin.signature))
81 | }
82 | }
83 |
84 | displayName = MinestomComponentSerializer.get().serialize(text("§r").append(title))
85 | controlItemAction = action
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/platform/minestom/MinestomReplayWorld.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.platform.minestom
2 |
3 | import io.github.openminigameserver.replay.MinestomReplayExtension
4 | import io.github.openminigameserver.replay.abstraction.ReplayChunk
5 | import io.github.openminigameserver.replay.abstraction.ReplayEntity
6 | import io.github.openminigameserver.replay.abstraction.ReplayWorld
7 | import net.minestom.server.instance.Instance
8 | import java.util.*
9 |
10 | class MinestomReplayWorld(val instance: Instance) : ReplayWorld() {
11 | override val uuid: UUID
12 | get() = instance.uniqueId
13 | override val entities: Iterable
14 | get() = instance.entities.map { MinestomReplayExtension.platform.entities.getOrCompute(it.entityId) }
15 | override val chunks: Iterable
16 | get() = instance.chunks.map { MinestomReplayChunk(it) }
17 |
18 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/platform/minestom/ReplayCommandManager.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.platform.minestom
2 |
3 | import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator
4 | import io.github.openminigameserver.cloudminestom.MinestomCommandManager
5 | import io.github.openminigameserver.replay.abstraction.ReplayUser
6 | import java.util.function.Function
7 |
8 | class ReplayCommandManager(replayPlatform: MinestomReplayPlatform) : MinestomCommandManager(
9 | AsynchronousCommandExecutionCoordinator.newBuilder().withAsynchronousParsing().build(),
10 | Function {
11 | replayPlatform.entities.getOrCompute(it.asPlayer().entityId) as ReplayUser
12 | }, Function {
13 | (it as MinestomReplayUser).player
14 | }
15 | )
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/platform/minestom/replayer/MinestomActionPlayer.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.platform.minestom.replayer
2 |
3 | import io.github.openminigameserver.replay.model.recordable.RecordableAction
4 | import io.github.openminigameserver.replay.platform.minestom.MinestomReplayUser
5 | import io.github.openminigameserver.replay.platform.minestom.MinestomReplayWorld
6 | import io.github.openminigameserver.replay.replayer.ActionPlayer
7 | import io.github.openminigameserver.replay.replayer.ReplaySession
8 | import net.minestom.server.entity.Player
9 | import net.minestom.server.instance.Instance
10 |
11 | interface MinestomActionPlayer : ActionPlayer {
12 | fun play(action: T, session: ReplaySession, instance: Instance, viewers: List)
13 |
14 | override fun play(
15 | action: T,
16 | session: ReplaySession,
17 | instance: MinestomReplayWorld,
18 | viewers: List
19 | ) {
20 | play(action, session, instance.instance, viewers.map { it.player })
21 | }
22 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/platform/minestom/replayer/MinestomEntityActionPlayer.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.platform.minestom.replayer
2 |
3 | import io.github.openminigameserver.replay.model.recordable.EntityRecordableAction
4 | import io.github.openminigameserver.replay.model.recordable.entity.RecordableEntity
5 | import io.github.openminigameserver.replay.platform.minestom.MinestomReplayEntity
6 | import io.github.openminigameserver.replay.platform.minestom.MinestomReplayUser
7 | import io.github.openminigameserver.replay.platform.minestom.MinestomReplayWorld
8 | import io.github.openminigameserver.replay.replayer.EntityActionPlayer
9 | import io.github.openminigameserver.replay.replayer.ReplaySession
10 | import net.minestom.server.entity.Entity
11 | import net.minestom.server.entity.Player
12 | import net.minestom.server.instance.Instance
13 |
14 | abstract class MinestomEntityActionPlayer :
15 | EntityActionPlayer() {
16 | override fun play(
17 | action: T,
18 | replayEntity: RecordableEntity,
19 | nativeEntity: MinestomReplayEntity,
20 | session: ReplaySession,
21 | instance: MinestomReplayWorld,
22 | viewers: List
23 | ) {
24 | play(action, replayEntity, nativeEntity.entity, session, instance.instance, viewers.map { it.player })
25 | }
26 |
27 | abstract fun play(
28 | action: T,
29 | replayEntity: RecordableEntity,
30 | nativeEntity: Entity,
31 | session: ReplaySession,
32 | instance: Instance,
33 | viewers: List
34 | )
35 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/platform/minestom/replayer/PlayerInventoryHelper.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.platform.minestom.replayer
2 |
3 | import net.minestom.server.entity.Player
4 | import net.minestom.server.inventory.PlayerInventory
5 | import net.minestom.server.item.ItemStack
6 | import net.minestom.server.registry.Registries
7 | import net.minestom.server.utils.NBTUtils
8 | import org.jglrxavpok.hephaistos.nbt.NBTCompound
9 | import org.jglrxavpok.hephaistos.nbt.NBTList
10 | import org.jglrxavpok.hephaistos.nbt.NBTTypes
11 |
12 | internal fun getPlayerInventoryCopy(player: Player) = NBTList(NBTTypes.TAG_Compound).also {
13 | saveAllItems(
14 | it,
15 | player.inventory
16 | )
17 | }
18 |
19 | internal fun loadAllItems(items: NBTList, destination: PlayerInventory) {
20 | destination.clear()
21 | for (tag in items) {
22 | val item = Registries.getMaterial(tag.getString("id"))
23 | val stack = ItemStack(item, tag.getByte("Count")!!)
24 | if (tag.containsKey("tag")) {
25 | NBTUtils.loadDataIntoItem(stack, tag.getCompound("tag")!!)
26 | }
27 | destination.setItemStack(tag.getByte("Slot")!!.toInt(), stack)
28 | }
29 | destination.update()
30 | }
31 |
32 | internal fun saveAllItems(list: NBTList, inventory: PlayerInventory) {
33 | for (i in 0 until inventory.size) {
34 | val stack = inventory.getItemStack(i)
35 | val nbt = NBTCompound()
36 | val tag = NBTCompound()
37 | NBTUtils.saveDataIntoNBT(stack, tag)
38 | nbt["tag"] = tag
39 | nbt.setByte("Slot", i.toByte())
40 | nbt.setByte("Count", stack.amount)
41 | nbt.setString("id", stack.material.getName())
42 | list.add(nbt)
43 | }
44 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/replayer/ReplayChunkLoader.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer
2 |
3 | import com.google.common.collect.HashBasedTable
4 | import io.github.openminigameserver.replay.model.Replay
5 | import io.github.openminigameserver.replay.model.recordable.RecordedChunk
6 | import net.minestom.server.instance.*
7 | import net.minestom.server.utils.binary.BinaryReader
8 | import net.minestom.server.utils.chunk.ChunkCallback
9 | import net.minestom.server.world.biomes.Biome
10 | import java.util.*
11 |
12 | class ReplayChunkLoader(val replay: Replay) : IChunkLoader {
13 | private val chunksMap: HashBasedTable = HashBasedTable.create()
14 |
15 | init {
16 | replay.chunks.forEach {
17 | chunksMap.put(it.chunkX, it.chunkZ, it)
18 | }
19 | }
20 |
21 | override fun loadChunk(instance: Instance, chunkX: Int, chunkZ: Int, callback: ChunkCallback?): Boolean {
22 | val biomes = arrayOfNulls(Chunk.BIOME_COUNT).also { Arrays.fill(it, Biome.PLAINS) }
23 | val chunk = if (instance is InstanceContainer) instance.chunkSupplier.createChunk(
24 | biomes,
25 | chunkX,
26 | chunkZ
27 | ) else DynamicChunk(biomes, chunkX, chunkZ)
28 |
29 | val savedChunk = chunksMap.get(chunkX, chunkZ)
30 | if (savedChunk != null) {
31 | val reader = BinaryReader(savedChunk.data)
32 | chunk.readChunk(reader, callback)
33 | } else {
34 | callback?.accept(chunk)
35 | }
36 | return true
37 |
38 | }
39 |
40 | override fun saveChunk(chunk: Chunk, callback: Runnable?) {
41 | callback?.run()
42 | }
43 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/replayer/impl/RecBlockBreakAnimationPlayer.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer.impl
2 |
3 | import io.github.openminigameserver.replay.extensions.toMinestom
4 | import io.github.openminigameserver.replay.model.recordable.entity.RecordableEntity
5 | import io.github.openminigameserver.replay.model.recordable.impl.RecBlockBreakAnimation
6 | import io.github.openminigameserver.replay.platform.minestom.replayer.MinestomEntityActionPlayer
7 | import io.github.openminigameserver.replay.replayer.ReplaySession
8 | import net.minestom.server.entity.Entity
9 | import net.minestom.server.entity.Player
10 | import net.minestom.server.instance.Instance
11 | import net.minestom.server.network.packet.server.play.BlockBreakAnimationPacket
12 | import net.minestom.server.utils.PacketUtils
13 |
14 | object RecBlockBreakAnimationPlayer : MinestomEntityActionPlayer() {
15 | override fun play(
16 | action: RecBlockBreakAnimation,
17 | replayEntity: RecordableEntity,
18 | nativeEntity: Entity,
19 | session: ReplaySession,
20 | instance: Instance,
21 | viewers: List
22 | ) {
23 | PacketUtils.sendGroupedPacket(
24 | viewers,
25 | BlockBreakAnimationPacket(
26 | nativeEntity.entityId,
27 | action.position.toMinestom().toBlockPosition(),
28 | action.destroyStage
29 | )
30 | )
31 | }
32 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/replayer/impl/RecBlockEffectPlayer.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer.impl
2 |
3 | import io.github.openminigameserver.replay.extensions.toMinestom
4 | import io.github.openminigameserver.replay.model.recordable.impl.RecBlockEffect
5 | import io.github.openminigameserver.replay.platform.minestom.replayer.MinestomActionPlayer
6 | import io.github.openminigameserver.replay.replayer.ReplaySession
7 | import net.minestom.server.entity.Player
8 | import net.minestom.server.instance.Instance
9 | import net.minestom.server.network.packet.server.play.EffectPacket
10 | import net.minestom.server.utils.PacketUtils
11 |
12 | object RecBlockEffectPlayer : MinestomActionPlayer {
13 | override fun play(action: RecBlockEffect, session: ReplaySession, instance: Instance, viewers: List) {
14 | PacketUtils.sendGroupedPacket(viewers, EffectPacket().apply {
15 | effectId = action.effectId
16 | this.data = action.data
17 | this.position = action.position.toMinestom().toBlockPosition()
18 | this.disableRelativeVolume = action.disableRelativeVolume
19 | })
20 | }
21 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/replayer/impl/RecBlockStateBatchUpdatePlayer.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer.impl
2 |
3 | import io.github.openminigameserver.replay.extensions.toMinestom
4 | import io.github.openminigameserver.replay.model.recordable.impl.RecBlockStateBatchUpdate
5 | import io.github.openminigameserver.replay.platform.minestom.replayer.MinestomActionPlayer
6 | import io.github.openminigameserver.replay.replayer.ReplaySession
7 | import net.minestom.server.entity.Player
8 | import net.minestom.server.instance.Instance
9 |
10 | object RecBlockStateBatchUpdatePlayer : MinestomActionPlayer {
11 | override fun play(
12 | action: RecBlockStateBatchUpdate,
13 | session: ReplaySession,
14 | instance: Instance,
15 | viewers: List
16 | ) {
17 | val batch = instance.createBlockBatch()
18 | action.actions.forEach {
19 | batch.setBlockStateId(it.position.toMinestom().toBlockPosition(), it.newState)
20 | }
21 | batch.flush {}
22 | }
23 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/replayer/impl/RecBlockStateUpdatePlayer.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer.impl
2 |
3 | import io.github.openminigameserver.replay.extensions.toMinestom
4 | import io.github.openminigameserver.replay.model.recordable.impl.RecBlockStateUpdate
5 | import io.github.openminigameserver.replay.platform.minestom.replayer.MinestomActionPlayer
6 | import io.github.openminigameserver.replay.replayer.ReplaySession
7 | import net.minestom.server.entity.Player
8 | import net.minestom.server.instance.Instance
9 |
10 | object RecBlockStateUpdatePlayer : MinestomActionPlayer {
11 | override fun play(action: RecBlockStateUpdate, session: ReplaySession, instance: Instance, viewers: List) {
12 | instance.setBlockStateId(action.position.toMinestom().toBlockPosition(), action.newState)
13 | }
14 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/replayer/impl/RecEntitiesPositionPlayer.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer.impl
2 |
3 | import io.github.openminigameserver.replay.extensions.toMinestom
4 | import io.github.openminigameserver.replay.model.recordable.impl.RecEntitiesPosition
5 | import io.github.openminigameserver.replay.platform.minestom.MinestomReplayEntity
6 | import io.github.openminigameserver.replay.platform.minestom.replayer.MinestomActionPlayer
7 | import io.github.openminigameserver.replay.replayer.ReplaySession
8 | import net.minestom.server.entity.Player
9 | import net.minestom.server.instance.Instance
10 |
11 | object RecEntitiesPositionPlayer : MinestomActionPlayer {
12 | override fun play(action: RecEntitiesPosition, session: ReplaySession, instance: Instance, viewers: List) {
13 | action.positions.forEach {
14 | val entity =
15 | (session.entityManager.getNativeEntity(it.key) as? MinestomReplayEntity)?.entity ?: return@forEach
16 |
17 | val data = it.value
18 | val position = data.position.toMinestom()
19 | val velocity = data.velocity.toMinestom()
20 | entity.refreshPosition(position)
21 | entity.refreshView(position.yaw, position.pitch)
22 | entity.askSynchronization()
23 | entity.velocity = velocity
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/replayer/impl/RecEntityEquipmentUpdatePlayer.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer.impl
2 |
3 | import io.github.openminigameserver.replay.extensions.setEquipmentForEntity
4 | import io.github.openminigameserver.replay.model.recordable.entity.RecordableEntity
5 | import io.github.openminigameserver.replay.model.recordable.impl.RecEntityEquipmentUpdate
6 | import io.github.openminigameserver.replay.platform.minestom.replayer.MinestomEntityActionPlayer
7 | import io.github.openminigameserver.replay.replayer.ReplaySession
8 | import net.minestom.server.entity.Entity
9 | import net.minestom.server.entity.Player
10 | import net.minestom.server.instance.Instance
11 | import net.minestom.server.inventory.EquipmentHandler
12 |
13 | object RecEntityEquipmentUpdatePlayer : MinestomEntityActionPlayer() {
14 | override fun play(
15 | action: RecEntityEquipmentUpdate,
16 | replayEntity: RecordableEntity,
17 | nativeEntity: Entity,
18 | session: ReplaySession,
19 | instance: Instance,
20 | viewers: List
21 | ) {
22 | if (nativeEntity is EquipmentHandler) {
23 | nativeEntity.setEquipmentForEntity(action.equipment)
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/replayer/impl/RecEntityMetadataPlayer.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer.impl
2 |
3 | import io.github.openminigameserver.replay.model.recordable.entity.RecordableEntity
4 | import io.github.openminigameserver.replay.model.recordable.impl.RecEntityMetadata
5 | import io.github.openminigameserver.replay.platform.minestom.replayer.MinestomEntityActionPlayer
6 | import io.github.openminigameserver.replay.replayer.ReplaySession
7 | import net.minestom.server.entity.Entity
8 | import net.minestom.server.entity.Player
9 | import net.minestom.server.instance.Instance
10 | import net.minestom.server.network.packet.server.play.EntityMetaDataPacket
11 | import net.minestom.server.utils.PacketUtils
12 | import net.minestom.server.utils.binary.BinaryWriter
13 |
14 | object RecEntityMetadataPlayer : MinestomEntityActionPlayer() {
15 | override fun play(
16 | action: RecEntityMetadata,
17 | replayEntity: RecordableEntity,
18 | nativeEntity: Entity,
19 | session: ReplaySession,
20 | instance: Instance,
21 | viewers: List
22 | ) {
23 | PacketUtils.sendGroupedPacket(viewers, object : EntityMetaDataPacket() {
24 | override fun write(writer: BinaryWriter) {
25 | writer.writeVarInt(nativeEntity.entityId)
26 | writer.write(action.metadata)
27 | writer.writeByte(0xFF.toByte())
28 | }
29 | })
30 | }
31 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/replayer/impl/RecEntityMovePlayer.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer.impl
2 |
3 | import io.github.openminigameserver.replay.extensions.toMinestom
4 | import io.github.openminigameserver.replay.model.recordable.entity.RecordableEntity
5 | import io.github.openminigameserver.replay.model.recordable.impl.RecEntityMove
6 | import io.github.openminigameserver.replay.platform.minestom.replayer.MinestomEntityActionPlayer
7 | import io.github.openminigameserver.replay.replayer.ReplaySession
8 | import net.minestom.server.entity.Entity
9 | import net.minestom.server.entity.Player
10 | import net.minestom.server.instance.Instance
11 |
12 | object RecEntityMovePlayer : MinestomEntityActionPlayer() {
13 | override fun play(
14 | action: RecEntityMove,
15 | replayEntity: RecordableEntity,
16 | nativeEntity: Entity,
17 | session: ReplaySession,
18 | instance: Instance,
19 | viewers: List
20 | ) {
21 | val data = action.data
22 | val position = data.position.toMinestom()
23 | val velocity = data.velocity.toMinestom()
24 | nativeEntity.refreshPosition(position)
25 | nativeEntity.refreshView(position.yaw, position.pitch)
26 | nativeEntity.askSynchronization()
27 | nativeEntity.velocity = velocity
28 | }
29 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/replayer/impl/RecEntityRemovePlayer.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer.impl
2 |
3 | import io.github.openminigameserver.replay.model.recordable.entity.RecordableEntity
4 | import io.github.openminigameserver.replay.model.recordable.impl.RecEntityRemove
5 | import io.github.openminigameserver.replay.platform.minestom.replayer.MinestomEntityActionPlayer
6 | import io.github.openminigameserver.replay.replayer.ReplaySession
7 | import net.minestom.server.entity.Entity
8 | import net.minestom.server.entity.Player
9 | import net.minestom.server.instance.Instance
10 |
11 | object RecEntityRemovePlayer : MinestomEntityActionPlayer() {
12 | override fun play(
13 | action: RecEntityRemove,
14 | replayEntity: RecordableEntity,
15 | nativeEntity: Entity,
16 | session: ReplaySession,
17 | instance: Instance,
18 | viewers: List
19 | ) {
20 | session.entityManager.removeEntity(replayEntity)
21 | }
22 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/replayer/impl/RecEntitySpawnPlayer.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer.impl
2 |
3 | import io.github.openminigameserver.replay.model.recordable.impl.RecEntitySpawn
4 | import io.github.openminigameserver.replay.platform.minestom.replayer.MinestomActionPlayer
5 | import io.github.openminigameserver.replay.replayer.ReplaySession
6 | import net.minestom.server.entity.Player
7 | import net.minestom.server.instance.Instance
8 |
9 | object RecEntitySpawnPlayer : MinestomActionPlayer {
10 |
11 | override fun play(action: RecEntitySpawn, session: ReplaySession, instance: Instance, viewers: List) {
12 | session.entityManager.spawnEntity(
13 | action.entity,
14 | action.positionAndVelocity.position,
15 | action.positionAndVelocity.velocity
16 | )
17 | }
18 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/replayer/impl/RecParticleEffectPlayer.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer.impl
2 |
3 | import io.github.openminigameserver.replay.model.recordable.impl.RecParticleEffect
4 | import io.github.openminigameserver.replay.platform.minestom.replayer.MinestomActionPlayer
5 | import io.github.openminigameserver.replay.replayer.ReplaySession
6 | import net.minestom.server.entity.Player
7 | import net.minestom.server.instance.Instance
8 | import net.minestom.server.network.packet.server.play.ParticlePacket
9 | import net.minestom.server.utils.PacketUtils
10 | import java.util.function.Consumer
11 |
12 | object RecParticleEffectPlayer : MinestomActionPlayer {
13 | override fun play(action: RecParticleEffect, session: ReplaySession, instance: Instance, viewers: List) {
14 | PacketUtils.sendGroupedPacket(viewers, ParticlePacket().apply {
15 | this.particleId = action.particleId
16 | this.particleCount = action.particleCount
17 | this.particleData = action.particleData
18 | this.x = action.x
19 | this.y = action.y
20 | this.z = action.z
21 | this.offsetX = action.offsetX
22 | this.offsetY = action.offsetY
23 | this.offsetZ = action.offsetZ
24 | this.longDistance = action.longDistance
25 | this.dataConsumer = action.extraData?.let { bytes -> Consumer { it.writeBytes(bytes) } }
26 | })
27 | }
28 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/replayer/impl/RecPlayerHandAnimationPlayer.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer.impl
2 |
3 | import io.github.openminigameserver.replay.model.recordable.entity.RecordableEntity
4 | import io.github.openminigameserver.replay.model.recordable.impl.Hand
5 | import io.github.openminigameserver.replay.model.recordable.impl.RecPlayerHandAnimation
6 | import io.github.openminigameserver.replay.platform.minestom.replayer.MinestomEntityActionPlayer
7 | import io.github.openminigameserver.replay.replayer.ReplaySession
8 | import net.minestom.server.entity.Entity
9 | import net.minestom.server.entity.Player
10 | import net.minestom.server.instance.Instance
11 |
12 | object RecPlayerHandAnimationPlayer : MinestomEntityActionPlayer() {
13 | override fun play(
14 | action: RecPlayerHandAnimation,
15 | replayEntity: RecordableEntity,
16 | nativeEntity: Entity,
17 | session: ReplaySession,
18 | instance: Instance,
19 | viewers: List
20 | ) {
21 | if (nativeEntity !is Player) return
22 | when (action.hand) {
23 | Hand.MAIN -> nativeEntity.swingMainHand()
24 | Hand.OFF -> nativeEntity.swingOffHand()
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/kotlin/io/github/openminigameserver/replay/replayer/impl/RecSoundEffectPlayer.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.replayer.impl
2 |
3 | import io.github.openminigameserver.replay.model.recordable.impl.RecSoundEffect
4 | import io.github.openminigameserver.replay.platform.minestom.replayer.MinestomActionPlayer
5 | import io.github.openminigameserver.replay.replayer.ReplaySession
6 | import net.minestom.server.entity.Player
7 | import net.minestom.server.instance.Instance
8 | import net.minestom.server.network.packet.server.play.SoundEffectPacket
9 | import net.minestom.server.sound.SoundCategory
10 | import net.minestom.server.utils.PacketUtils
11 |
12 | object RecSoundEffectPlayer : MinestomActionPlayer {
13 | override fun play(
14 | @Suppress("DuplicatedCode") action: RecSoundEffect,
15 | session: ReplaySession,
16 | instance: Instance,
17 | viewers: List
18 | ) {
19 | PacketUtils.sendGroupedPacket(viewers, SoundEffectPacket().apply {
20 | this.soundId = action.soundId
21 | this.soundCategory = action.soundCategory?.let { SoundCategory.valueOf(it.name) }
22 | this.pitch = action.pitch
23 | this.volume = action.volume
24 | this.x = action.x
25 | this.y = action.y
26 | this.z = action.z
27 | })
28 | }
29 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/resources/extension.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Replay",
3 | "entrypoint": "io.github.openminigameserver.replay.MinestomReplayExtension",
4 | "version": "${version}",
5 | "authors": [
6 | "NickAcPT"
7 | ],
8 | "mixinConfig": "mixins.replay.json"
9 | }
--------------------------------------------------------------------------------
/impl-minestom/src/main/resources/mixins.replay.json:
--------------------------------------------------------------------------------
1 | {
2 | "required": true,
3 | "minVersion": "0.8",
4 | "package": "io.github.openminigameserver.replay.mixins",
5 | "compatibilityLevel": "JAVA_11",
6 | "mixins": [
7 | "InstanceMixin",
8 | "PacketUtilsMixin",
9 | "PlayerInventoryMixin",
10 | "PlayerMixin"
11 | ]
12 | }
--------------------------------------------------------------------------------
/impl-minestom/src/template/kotlin/io/github/openminigameserver/replay/BuildInfo.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay;
2 |
3 | object BuildInfo {
4 | const val version: String = "${version}"
5 | }
--------------------------------------------------------------------------------
/impl-minestom/src/test/kotlin/io/github/openminigameserver/replay/test/EntityCommand.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.test
2 |
3 | /*
4 | object EntityCommand : Command("entity") {
5 |
6 | val entities = mutableListOf()
7 |
8 | init {
9 | addSyntax({ sender: CommandSender, args: Arguments ->
10 | if (sender !is Player) return@addSyntax
11 | val type = args.getEntityType("type")
12 |
13 | val lastEntity = EntityHelper.createEntity(type, sender.position, null, false)
14 | lastEntity.isAutoViewable = true
15 | lastEntity.setInstance(sender.instance!!)
16 | entities.add(lastEntity)
17 |
18 | if (lastEntity is EntityCreature) {
19 | val targetGoal = FollowTargetGoal(lastEntity, UpdateOption(1, TimeUnit.TICK))
20 | lastEntity.target = sender
21 | lastEntity.goalSelectors.add(targetGoal)
22 | }
23 |
24 | }, ArgumentWord("of").from("living"), ArgumentEntityType("type"))
25 | addSyntax({ sender: CommandSender, args: Arguments ->
26 | entities.forEach {
27 | if (it is EntityCreature) {
28 | it.currentGoalSelector?.end()
29 | it.goalSelectors.clear()
30 | }
31 | it.remove()
32 | }
33 | entities.clear()
34 | }, ArgumentWord("of").from("delete"))
35 | }
36 | }*/
37 |
--------------------------------------------------------------------------------
/impl-minestom/src/test/kotlin/io/github/openminigameserver/replay/test/MyChunkGenerator.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.test
2 |
3 | import net.minestom.server.instance.Chunk
4 | import net.minestom.server.instance.ChunkGenerator
5 | import net.minestom.server.instance.ChunkPopulator
6 | import net.minestom.server.instance.batch.ChunkBatch
7 | import net.minestom.server.instance.block.Block
8 | import net.minestom.server.world.biomes.Biome
9 | import java.util.*
10 |
11 | class MyChunkGenerator : ChunkGenerator {
12 | override fun generateChunkData(batch: ChunkBatch, chunkX: Int, chunkZ: Int) {
13 | // Set chunk blocks
14 | for (x in 0 until Chunk.CHUNK_SIZE_X) for (z in 0 until Chunk.CHUNK_SIZE_Z) {
15 | for (y in 0..39) {
16 | batch.setBlock(x, y, z, Block.STONE)
17 | }
18 | }
19 | }
20 |
21 | override fun fillBiomes(biomes: Array, chunkX: Int, chunkZ: Int) {
22 | Arrays.fill(biomes, Biome.PLAINS)
23 | }
24 |
25 | override fun getPopulators(): List? {
26 | return null
27 | }
28 | }
--------------------------------------------------------------------------------
/impl-minestom/src/test/kotlin/io/github/openminigameserver/replay/test/PlayerInit.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.test
2 |
3 | import net.minestom.server.MinecraftServer
4 | import net.minestom.server.data.DataImpl
5 | import net.minestom.server.entity.GameMode
6 | import net.minestom.server.event.EventCallback
7 | import net.minestom.server.event.player.PlayerLoginEvent
8 | import net.minestom.server.instance.InstanceManager
9 | import net.minestom.server.utils.Position
10 |
11 |
12 | class PlayerInit : EventCallback {
13 |
14 | private val instanceManager: InstanceManager = MinecraftServer.getInstanceManager()
15 |
16 | // Create the instance
17 | private val instanceContainer by lazy {
18 | instanceManager.createInstanceContainer().apply {
19 | chunkGenerator = MyChunkGenerator()
20 | data = DataImpl()
21 | enableAutoChunkLoad(true)
22 | }
23 | }
24 |
25 | override fun run(event: PlayerLoginEvent) {
26 | val player = event.player
27 | player.data = DataImpl()
28 | player.gameMode = GameMode.CREATIVE
29 | player.isAllowFlying = true
30 | player.isFlying = true
31 |
32 | event.setSpawningInstance(instanceContainer)
33 | player.respawnPoint = Position(0.0, 42.0, 0.0)
34 | }
35 | }
36 |
37 |
38 |
--------------------------------------------------------------------------------
/impl-minestom/src/test/kotlin/io/github/openminigameserver/replay/test/Replay.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.test
2 |
3 | import io.github.openminigameserver.replay.extensions.profileCache
4 | import net.minestom.server.MinecraftServer
5 | import net.minestom.server.entity.PlayerSkin
6 | import net.minestom.server.event.player.PlayerLoginEvent
7 | import net.minestom.server.event.player.PlayerSkinInitEvent
8 | import net.minestom.server.extras.MojangAuth
9 | import net.minestom.server.extras.PlacementRules
10 | import net.minestom.server.extras.optifine.OptifineSupport
11 | import net.minestom.server.extras.selfmodification.MinestomRootClassLoader
12 |
13 | fun main(args: Array) {
14 | MinestomRootClassLoader.getInstance().protectedPackages.addAll(
15 | arrayOf(
16 | "org.reactivestreams",
17 | "io.leangen.geantyref",
18 | "kotlinx"
19 | )
20 | )
21 |
22 | val server = MinecraftServer.init()
23 | MojangAuth.init()
24 | OptifineSupport.enable()
25 | PlacementRules.init()
26 | MinecraftServer.setGroupedPacket(false)
27 |
28 |
29 | MinecraftServer.getGlobalEventHandler().addEventCallback(PlayerSkinInitEvent::class.java) {
30 | it.apply {
31 | kotlin.runCatching {
32 | skin = profileCache.get(this.player.uuid) {
33 | kotlin.runCatching { PlayerSkin.fromUuid(this.player.uuid.toString()) }.getOrNull()
34 | }
35 | }
36 | }
37 | }
38 | MinecraftServer.getGlobalEventHandler().addEventCallback(PlayerLoginEvent::class.java, PlayerInit())
39 |
40 |
41 | // MinecraftServer.getCommandManager().register(EntityCommand)
42 | server.start(
43 | "0.0.0.0", 25566
44 | ) { _, responseData ->
45 | responseData.apply {
46 | responseData.setDescription("Replay")
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/impl-minestom/src/test/kotlin/io/github/openminigameserver/replay/test/ReplayBootstrapper.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.test
2 |
3 | import net.minestom.server.Bootstrap
4 |
5 | fun main(args: Array) {
6 | System.setProperty("minestom.extension.indevfolder.classes", "../impl-minestom/build/classes/java")
7 | System.setProperty("minestom.extension.indevfolder.resources", "../impl-minestom/build/resources/main/")
8 |
9 | Bootstrap.bootstrap("io.github.openminigameserver.replay.test.ReplayKt", args)
10 | }
--------------------------------------------------------------------------------
/jitpack.yml:
--------------------------------------------------------------------------------
1 | jdk:
2 | - openjdk11
--------------------------------------------------------------------------------
/model/build.gradle.kts:
--------------------------------------------------------------------------------
1 | dependencies {
2 | implementation("com.github.luben:zstd-jni:1.4.8-1")
3 | implementation("com.esotericsoftware:kryo:5.0.3")
4 | }
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/AbstractReplaySession.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay
2 |
3 | import io.github.openminigameserver.replay.model.Replay
4 | import io.github.openminigameserver.replay.model.recordable.EntityRecordableAction
5 | import io.github.openminigameserver.replay.model.recordable.RecordableAction
6 | import io.github.openminigameserver.replay.model.recordable.entity.RecordableEntity
7 | import java.util.*
8 | import kotlin.time.Duration
9 |
10 | abstract class AbstractReplaySession {
11 | abstract val replay: Replay
12 | var isInitialized = false
13 | abstract val hasEnded: Boolean
14 | protected val actions = Stack()
15 |
16 | /**
17 | * Current replay time.
18 | */
19 | abstract var time: Duration
20 |
21 | fun findManyActions(
22 | startDuration: Duration = time,
23 | targetDuration: Duration = Duration.ZERO,
24 | condition: (RecordableAction) -> Boolean = { true }
25 | ): List {
26 | var start = startDuration
27 | var end = targetDuration
28 | val isReverse = start > end
29 |
30 | if (isReverse) {
31 | start = end.also { end = start }
32 | }
33 |
34 | val actions = replay.actions.filter { it.timestamp in start..end }
35 | return actions.filter { condition(it) }
36 | .let { action -> if (isReverse) action.sortedByDescending { it.timestamp } else action.sortedBy { it.timestamp } }
37 | }
38 |
39 |
40 | inline fun findLastAction(
41 | startDuration: Duration = time,
42 | targetDuration: Duration = Duration.ZERO,
43 | condition: (T) -> Boolean = { true }
44 | ): T? {
45 | var start = startDuration
46 | var end = targetDuration
47 | val isReverse = start > end
48 |
49 | if (isReverse) {
50 | start = end.also { end = start }
51 | }
52 |
53 | return replay.actions.filter { it.timestamp in start..end }
54 | .let { action -> if (isReverse) action.sortedByDescending { it.timestamp } else action.sortedBy { it.timestamp } }
55 | .lastOrNull { it is T && condition(it) } as? T
56 | }
57 |
58 |
59 | inline fun findManyActionsGeneric(
60 | startDuration: Duration = time,
61 | targetDuration: Duration = Duration.ZERO,
62 | crossinline condition: (T) -> Boolean = { true }
63 | ): List {
64 | return findManyActions(startDuration, targetDuration) {
65 | it is T && condition(it)
66 | }.map { it as T }
67 | }
68 |
69 | inline fun findActionsForEntity(
70 | startDuration: Duration = time,
71 | entity: RecordableEntity,
72 | targetDuration: Duration = Duration.ZERO,
73 | condition: (T) -> Boolean = { true }
74 | ): T? {
75 | return findLastAction(startDuration, targetDuration) { it.entity == entity && condition(it) }
76 | }
77 |
78 | abstract fun init()
79 |
80 | abstract fun unInit()
81 |
82 | abstract fun tick(forceTick: Boolean = false, isTimeStep: Boolean = false)
83 |
84 | abstract fun playAction(action: RecordableAction)
85 | }
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/io/ReplayFile.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.io
2 |
3 | import com.esotericsoftware.kryo.io.Input
4 | import com.esotericsoftware.kryo.io.Output
5 | import com.github.luben.zstd.ZstdInputStream
6 | import com.github.luben.zstd.ZstdOutputStream
7 | import io.github.openminigameserver.replay.io.format.readHeader
8 | import io.github.openminigameserver.replay.io.format.readReplayData
9 | import io.github.openminigameserver.replay.io.format.writeHeader
10 | import io.github.openminigameserver.replay.io.format.writeReplayData
11 | import io.github.openminigameserver.replay.model.Replay
12 | import java.io.File
13 |
14 | class ReplayFile(private val file: File, var replay: Replay? = null, private val isCompressed: Boolean = true) {
15 |
16 | fun loadReplay() {
17 | replay = Replay().apply {
18 | Input(getInputStream().readAllBytes()).use {
19 | it.readHeader(this)
20 | it.readReplayData(this)
21 | }
22 | }
23 | }
24 |
25 | fun saveReplay() {
26 | Output(getOutputStream()).use {
27 | it.writeHeader(replay!!)
28 | it.writeReplayData(replay!!)
29 | }
30 | }
31 |
32 | private fun getInputStream() = if (isCompressed) ZstdInputStream(file.inputStream()) else file.inputStream()
33 |
34 | private fun getOutputStream() = if (isCompressed) ZstdOutputStream(file.outputStream()) else file.outputStream()
35 |
36 | }
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/io/format/ReadByteBufExtensions.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.io.format
2 |
3 | import com.esotericsoftware.kryo.io.Input
4 | import io.github.openminigameserver.replay.model.Replay
5 | import io.github.openminigameserver.replay.model.ReplayHeader
6 | import io.github.openminigameserver.replay.model.recordable.RecordableAction
7 | import io.github.openminigameserver.replay.model.recordable.entity.RecordableEntity
8 | import kotlinx.datetime.Instant
9 | import java.util.*
10 | import kotlin.time.Duration
11 | import kotlin.time.milliseconds
12 |
13 | fun Input.readUUID(): UUID {
14 | return UUID(readLong(), readLong())
15 | }
16 |
17 | fun Input.readInstant(): Instant {
18 | return Instant.fromEpochSeconds(readLong())
19 | }
20 |
21 | fun Input.readObject(): Any {
22 | return kryo.readClassAndObject(this)
23 | }
24 |
25 | fun Input.readMap(): Map {
26 | val result = mutableMapOf()
27 | repeat(readInt()) {
28 | result[readObject()] = readObject()
29 | }
30 | return result
31 | }
32 |
33 | fun Input.readDuration(): Duration {
34 | return readLong().milliseconds
35 | }
36 |
37 | inline fun Input.readToCollection(obj: MutableCollection) {
38 | repeat(readInt()) {
39 | kryo.readObjectOrNull(this, C::class.java)?.let { it1 -> obj.add(it1) }
40 | }
41 | }
42 |
43 | fun Input.readHeader(destination: ReplayHeader) {
44 | destination.apply {
45 | version = readInt()
46 | id = readUUID()
47 | recordStartTime = readInstant()
48 | metadata = readMap() as MutableMap
49 | duration = readDuration()
50 | if (version > 3) {
51 | readToCollection(chunks)
52 | }
53 | }
54 | }
55 |
56 |
57 | fun Input.readReplayData(replay: Replay) {
58 | replay.apply {
59 | repeat(readInt()) {
60 | entities[readInt()] = readObject() as RecordableEntity
61 | }
62 |
63 | repeat(readInt()) {
64 | (readObject() as? RecordableAction?).also { actions.add(it) }
65 | }
66 | }
67 | }
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/io/format/WriteByteBufExtensions.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.io.format
2 |
3 | import com.esotericsoftware.kryo.Kryo
4 | import com.esotericsoftware.kryo.io.Output
5 | import com.esotericsoftware.kryo.util.DefaultInstantiatorStrategy
6 | import io.github.openminigameserver.replay.model.Replay
7 | import io.github.openminigameserver.replay.model.ReplayHeader
8 | import kotlinx.datetime.Instant
9 | import org.objenesis.strategy.StdInstantiatorStrategy
10 | import java.util.*
11 | import kotlin.time.Duration
12 |
13 |
14 | var kryo = Kryo().apply {
15 | isRegistrationRequired = false
16 | instantiatorStrategy = DefaultInstantiatorStrategy(StdInstantiatorStrategy())
17 | }
18 |
19 | fun Output.writeUUID(id: UUID) {
20 | writeLong(id.mostSignificantBits)
21 | writeLong(id.leastSignificantBits)
22 | }
23 |
24 | fun Output.writeInstant(instant: Instant) {
25 | writeLong(instant.epochSeconds)
26 | }
27 |
28 | fun Output.writeDuration(duration: Duration) {
29 | writeLong(duration.toLongMilliseconds())
30 | }
31 |
32 | fun Output.writeObject(obj: Any) {
33 | kryo.writeClassAndObject(this, obj)
34 | }
35 |
36 | inline fun Output.writeCollection(obj: Collection) {
37 | writeInt(obj.size)
38 | obj.forEach { kryo.writeObjectOrNull(this, it, C::class.java) }
39 | }
40 |
41 | fun Output.writeMap(map: Map) {
42 | writeInt(map.size)
43 | map.forEach { (k, v) ->
44 | writeObject(k)
45 | writeObject(v)
46 | }
47 | }
48 |
49 | fun Output.writeHeader(header: ReplayHeader) {
50 | header.apply {
51 | writeInt(version)
52 | writeUUID(id)
53 | writeInstant(recordStartTime)
54 | writeMap(metadata)
55 | writeDuration(duration)
56 | if (version > 3) {
57 | writeCollection(chunks)
58 | }
59 | }
60 | }
61 |
62 | fun Output.writeReplayData(replay: Replay) {
63 | replay.apply {
64 | writeInt(entities.size)
65 | entities.forEach { (id, entity) ->
66 | writeInt(id)
67 | writeObject(entity)
68 | }
69 |
70 | writeInt(replay.actions.size)
71 | replay.actions.forEach {
72 | writeObject(it)
73 | }
74 | }
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/Replay.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model
2 |
3 | import io.github.openminigameserver.replay.model.recordable.RecordableAction
4 | import io.github.openminigameserver.replay.model.recordable.entity.RecordableEntity
5 | import kotlinx.datetime.Clock.System.now
6 | import kotlinx.datetime.Instant
7 | import java.util.*
8 | import java.util.concurrent.ConcurrentLinkedDeque
9 | import kotlin.time.Duration
10 |
11 | data class Replay(
12 | override var version: Int = 4,
13 | override var id: UUID = UUID.randomUUID(),
14 | override var recordStartTime: Instant = now(),
15 | val entities: MutableMap = mutableMapOf(),
16 | val actions: ConcurrentLinkedDeque = ConcurrentLinkedDeque()
17 | ) : ReplayHeader(version, id, recordStartTime) {
18 |
19 | val currentDuration: Duration
20 | get() {
21 | return now().minus(recordStartTime)
22 | }
23 |
24 | fun addAction(action: RecordableAction) {
25 | action.timestamp = currentDuration
26 | actions.add(action)
27 | }
28 |
29 | fun getEntityById(id: Int): RecordableEntity? {
30 | return entities[id]
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/ReplayHeader.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model
2 |
3 | import io.github.openminigameserver.replay.model.recordable.RecordedChunk
4 | import kotlinx.datetime.Clock
5 | import kotlinx.datetime.Instant
6 | import java.util.*
7 | import kotlin.time.Duration
8 |
9 | open class ReplayHeader(
10 | open var version: Int = 4,
11 | open var id: UUID = UUID.randomUUID(),
12 | open var recordStartTime: Instant = Clock.System.now()
13 | ) {
14 | var metadata: MutableMap = mutableMapOf()
15 | var duration: Duration = Duration.ZERO
16 | var chunks = mutableListOf()
17 |
18 | val hasChunks get() = chunks.isNotEmpty()
19 |
20 | operator fun get(name: String): T? {
21 | return metadata[name] as? T
22 | }
23 |
24 | operator fun set(name: String, value: T) {
25 | metadata[name] = value
26 | }
27 | }
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/EntityRecordableAction.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable
2 |
3 | import io.github.openminigameserver.replay.model.recordable.entity.RecordableEntity
4 |
5 | abstract class EntityRecordableAction(open val entity: RecordableEntity) : RecordableAction() {
6 | }
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/RecordableAction.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable
2 |
3 | import kotlin.time.Duration
4 |
5 | abstract class RecordableAction {
6 | var timestamp: Duration = Duration.ZERO
7 | }
8 |
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/RecordableItemStack.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable
2 |
3 | data class RecordableItemStack(val nbtValue: ByteArray) {
4 | override fun equals(other: Any?): Boolean {
5 | if (this === other) return true
6 | if (javaClass != other?.javaClass) return false
7 |
8 | other as RecordableItemStack
9 |
10 | if (!nbtValue.contentEquals(other.nbtValue)) return false
11 |
12 | return true
13 | }
14 |
15 | override fun hashCode(): Int {
16 | return nbtValue.contentHashCode()
17 | }
18 | }
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/RecordablePosition.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable
2 |
3 | data class RecordablePosition(
4 | val x: Double = 0.0,
5 | val y: Double = 0.0,
6 | val z: Double = 0.0,
7 | val yaw: Float = 0f,
8 | val pitch: Float = 0f
9 | )
10 |
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/RecordablePositionAndVector.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable
2 |
3 | data class RecordablePositionAndVector(val position: RecordablePosition, val velocity: RecordableVector)
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/RecordableVector.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable
2 |
3 | data class RecordableVector(val x: Double = 0.0, val y: Double = 0.0, val z: Double = 0.0)
4 |
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/RecordedChunk.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable
2 |
3 | data class RecordedChunk(val chunkX: Int, val chunkZ: Int, val data: ByteArray) {
4 | override fun equals(other: Any?): Boolean {
5 | if (this === other) return true
6 | if (javaClass != other?.javaClass) return false
7 |
8 | other as RecordedChunk
9 |
10 | if (chunkX != other.chunkX) return false
11 | if (chunkZ != other.chunkZ) return false
12 | if (!data.contentEquals(other.data)) return false
13 |
14 | return true
15 | }
16 |
17 | override fun hashCode(): Int {
18 | var result = chunkX
19 | result = 31 * result + chunkZ
20 | result = 31 * result + data.contentHashCode()
21 | return result
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/entity/EntityEquipmentSlot.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable.entity
2 |
3 | enum class EntityEquipmentSlot {
4 | MAIN_HAND,
5 | OFF_HAND,
6 | BOOTS,
7 | LEGGINGS,
8 | CHESTPLATE,
9 | HELMET
10 | }
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/entity/RecordableEntity.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable.entity
2 |
3 | import io.github.openminigameserver.replay.model.recordable.RecordablePositionAndVector
4 | import io.github.openminigameserver.replay.model.recordable.entity.data.BaseEntityData
5 |
6 | data class RecordableEntity(
7 | val id: Int,
8 | val type: String,
9 | val spawnPosition: RecordablePositionAndVector?,
10 | val entityData: BaseEntityData? = null
11 | ) {
12 | var spawnOnStart: Boolean = spawnPosition != null
13 |
14 | override fun equals(other: Any?): Boolean {
15 | if (this === other) return true
16 | if (javaClass != other?.javaClass) return false
17 |
18 | other as RecordableEntity
19 |
20 | if (id != other.id) return false
21 |
22 | return true
23 | }
24 |
25 | override fun hashCode(): Int {
26 | return id
27 | }
28 | }
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/entity/data/BaseEntityData.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable.entity.data
2 |
3 | abstract class BaseEntityData
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/entity/data/EquipmentEntityData.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable.entity.data
2 |
3 | import io.github.openminigameserver.replay.model.recordable.RecordableItemStack
4 | import io.github.openminigameserver.replay.model.recordable.entity.EntityEquipmentSlot
5 |
6 | interface EquipmentEntityData {
7 | val equipment: Map
8 | }
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/entity/data/PlayerEntityData.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable.entity.data
2 |
3 | import io.github.openminigameserver.replay.model.recordable.RecordableItemStack
4 | import io.github.openminigameserver.replay.model.recordable.entity.EntityEquipmentSlot
5 |
6 | data class PlayerEntityData(val userName: String, val skin: PlayerSkinData?, val metadata: ByteArray,
7 | override val equipment: Map
8 | ) :
9 | BaseEntityData(), EquipmentEntityData {
10 | override fun equals(other: Any?): Boolean {
11 | if (this === other) return true
12 | if (javaClass != other?.javaClass) return false
13 |
14 | other as PlayerEntityData
15 |
16 | if (userName != other.userName) return false
17 | if (skin != other.skin) return false
18 | if (!metadata.contentEquals(other.metadata)) return false
19 |
20 | return true
21 | }
22 |
23 | override fun hashCode(): Int {
24 | var result = userName.hashCode()
25 | result = 31 * result + (skin?.hashCode() ?: 0)
26 | result = 31 * result + metadata.contentHashCode()
27 | return result
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/entity/data/PlayerSkinData.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable.entity.data
2 |
3 | data class PlayerSkinData(val textures: ByteArray, val signature: ByteArray) {
4 | override fun equals(other: Any?): Boolean {
5 | if (this === other) return true
6 | if (javaClass != other?.javaClass) return false
7 |
8 | other as PlayerSkinData
9 |
10 | if (!textures.contentEquals(other.textures)) return false
11 | if (!signature.contentEquals(other.signature)) return false
12 |
13 | return true
14 | }
15 |
16 | override fun hashCode(): Int {
17 | var result = textures.contentHashCode()
18 | result = 31 * result + signature.contentHashCode()
19 | return result
20 | }
21 | }
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/impl/RecBlockBreakAnimation.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable.impl
2 |
3 | import io.github.openminigameserver.replay.model.recordable.EntityRecordableAction
4 | import io.github.openminigameserver.replay.model.recordable.RecordablePosition
5 | import io.github.openminigameserver.replay.model.recordable.entity.RecordableEntity
6 |
7 | class RecBlockBreakAnimation(entity: RecordableEntity, val position: RecordablePosition, val destroyStage: Byte) :
8 | EntityRecordableAction(entity)
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/impl/RecBlockEffect.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable.impl
2 |
3 | import io.github.openminigameserver.replay.model.recordable.RecordableAction
4 | import io.github.openminigameserver.replay.model.recordable.RecordablePosition
5 |
6 | class RecBlockEffect(
7 | val effectId: Int,
8 | val position: RecordablePosition,
9 | val data: Int,
10 | val disableRelativeVolume: Boolean
11 | ) : RecordableAction()
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/impl/RecBlockStateBatchUpdate.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable.impl
2 |
3 | import io.github.openminigameserver.replay.model.recordable.RecordableAction
4 |
5 |
6 | data class RecBlockStateBatchUpdate(val actions: List) : RecordableAction()
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/impl/RecBlockStateUpdate.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable.impl
2 |
3 | import io.github.openminigameserver.replay.model.recordable.RecordableAction
4 | import io.github.openminigameserver.replay.model.recordable.RecordablePosition
5 | import io.github.openminigameserver.replay.model.recordable.reverse.DefaultStateReversible
6 |
7 | data class RecBlockStateUpdate(val position: RecordablePosition, val newState: Short) :
8 | RecordableAction(), DefaultStateReversible {
9 | override val isAppliedInBatch: Boolean
10 | get() = true
11 |
12 | override fun batchActions(value: List): RecordableAction {
13 | return RecBlockStateBatchUpdate(value.mapNotNull { it as? RecBlockStateUpdate })
14 | }
15 |
16 | override fun provideDefaultState(): RecordableAction {
17 | return RecBlockStateUpdate(position, 0)
18 | }
19 |
20 | override fun equals(other: Any?): Boolean {
21 | if (this === other) return true
22 | if (javaClass != other?.javaClass) return false
23 |
24 | other as RecBlockStateUpdate
25 |
26 | if (position != other.position) return false
27 |
28 | return true
29 | }
30 |
31 | override fun hashCode(): Int {
32 | return position.hashCode()
33 | }
34 | }
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/impl/RecEntitiesPosition.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable.impl
2 |
3 | import io.github.openminigameserver.replay.model.recordable.RecordableAction
4 | import io.github.openminigameserver.replay.model.recordable.RecordablePositionAndVector
5 | import io.github.openminigameserver.replay.model.recordable.entity.RecordableEntity
6 |
7 | class RecEntitiesPosition(
8 | val positions: MutableMap
9 | ) : RecordableAction()
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/impl/RecEntityEquipmentUpdate.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable.impl
2 |
3 | import io.github.openminigameserver.replay.model.recordable.EntityRecordableAction
4 | import io.github.openminigameserver.replay.model.recordable.RecordableAction
5 | import io.github.openminigameserver.replay.model.recordable.RecordableItemStack
6 | import io.github.openminigameserver.replay.model.recordable.entity.EntityEquipmentSlot
7 | import io.github.openminigameserver.replay.model.recordable.entity.RecordableEntity
8 | import io.github.openminigameserver.replay.model.recordable.entity.data.EquipmentEntityData
9 | import io.github.openminigameserver.replay.model.recordable.reverse.ApplyLastReversible
10 | import io.github.openminigameserver.replay.model.recordable.reverse.DefaultStateReversible
11 |
12 |
13 | data class RecEntityEquipmentUpdate(override val entity: RecordableEntity, val equipment: Map) :
14 | EntityRecordableAction(entity), DefaultStateReversible, ApplyLastReversible {
15 |
16 | override fun provideDefaultState(): RecordableAction {
17 | return RecEntityEquipmentUpdate(
18 | entity,
19 | (entity.entityData as EquipmentEntityData).equipment
20 | )
21 | }
22 |
23 | override fun equals(other: Any?): Boolean {
24 | if (this === other) return true
25 | if (javaClass != other?.javaClass) return false
26 |
27 | other as RecEntityEquipmentUpdate
28 |
29 | if (entity != other.entity) return false
30 |
31 | return true
32 | }
33 |
34 | override fun hashCode(): Int {
35 | return entity.hashCode()
36 | }
37 |
38 |
39 | }
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/impl/RecEntityMetadata.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable.impl
2 |
3 | import io.github.openminigameserver.replay.model.recordable.EntityRecordableAction
4 | import io.github.openminigameserver.replay.model.recordable.entity.RecordableEntity
5 |
6 | class RecEntityMetadata(val metadata: ByteArray, entity: RecordableEntity) : EntityRecordableAction(entity)
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/impl/RecEntityMove.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable.impl
2 |
3 | import io.github.openminigameserver.replay.model.recordable.EntityRecordableAction
4 | import io.github.openminigameserver.replay.model.recordable.RecordablePositionAndVector
5 | import io.github.openminigameserver.replay.model.recordable.entity.RecordableEntity
6 |
7 | class RecEntityMove(val data: RecordablePositionAndVector, entity: RecordableEntity) : EntityRecordableAction(entity)
8 |
9 |
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/impl/RecEntityRemove.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable.impl
2 |
3 | import io.github.openminigameserver.replay.AbstractReplaySession
4 | import io.github.openminigameserver.replay.model.recordable.*
5 | import io.github.openminigameserver.replay.model.recordable.entity.RecordableEntity
6 | import io.github.openminigameserver.replay.model.recordable.reverse.Reversible
7 | import kotlin.time.Duration
8 |
9 | class RecEntityRemove(
10 | val position: RecordablePosition,
11 | entity: RecordableEntity
12 | ) : EntityRecordableAction(entity), Reversible {
13 | override fun provideRevertedActions(start: Duration, end: Duration, session: AbstractReplaySession): List = listOf(RecEntitySpawn(
14 | RecordablePositionAndVector(position, RecordableVector()), entity))
15 | }
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/impl/RecEntitySpawn.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable.impl
2 |
3 | import io.github.openminigameserver.replay.AbstractReplaySession
4 | import io.github.openminigameserver.replay.model.recordable.EntityRecordableAction
5 | import io.github.openminigameserver.replay.model.recordable.RecordableAction
6 | import io.github.openminigameserver.replay.model.recordable.RecordablePositionAndVector
7 | import io.github.openminigameserver.replay.model.recordable.entity.RecordableEntity
8 | import io.github.openminigameserver.replay.model.recordable.reverse.Reversible
9 | import kotlin.time.Duration
10 |
11 | class RecEntitySpawn constructor(
12 | val positionAndVelocity: RecordablePositionAndVector, entity: RecordableEntity
13 | ) : EntityRecordableAction(entity), Reversible {
14 |
15 | override fun provideRevertedActions(start: Duration, end: Duration, session: AbstractReplaySession): List = listOf(RecEntityRemove(positionAndVelocity.position, entity))
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/impl/RecParticleEffect.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable.impl
2 |
3 | import io.github.openminigameserver.replay.model.recordable.RecordableAction
4 |
5 | class RecParticleEffect(
6 | val particleId: Int = 0,
7 | val longDistance: Boolean = false,
8 | val x: Double = 0.0,
9 | var y: kotlin.Double = 0.0,
10 | var z: kotlin.Double = 0.0,
11 | val offsetX: Float = 0f,
12 | var offsetY: kotlin.Float = 0f,
13 | var offsetZ: kotlin.Float = 0f,
14 | val particleData: Float = 0f,
15 | val particleCount: Int = 0,
16 | val extraData: ByteArray? = null
17 | ) : RecordableAction()
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/impl/RecPlayerHandAnimation.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable.impl
2 |
3 | import io.github.openminigameserver.replay.model.recordable.EntityRecordableAction
4 | import io.github.openminigameserver.replay.model.recordable.entity.RecordableEntity
5 |
6 | enum class Hand {
7 | MAIN, OFF
8 | }
9 |
10 | class RecPlayerHandAnimation(val hand: Hand, entity: RecordableEntity) : EntityRecordableAction(entity)
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/impl/RecSoundEffect.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable.impl
2 |
3 | import io.github.openminigameserver.replay.model.recordable.RecordableAction
4 |
5 | enum class SoundCategory {
6 | MASTER, MUSIC, RECORDS, WEATHER, BLOCKS, HOSTILE, NEUTRAL, PLAYERS, AMBIENT, VOICE
7 | }
8 |
9 | class RecSoundEffect(
10 | var soundId: Int = 0,
11 | var soundCategory: SoundCategory? = null,
12 | var x: Int = 0,
13 | var y: Int = 0,
14 | var z: Int = 0,
15 | var volume: Float = 0f,
16 | var pitch: Float = 0f,
17 | ) : RecordableAction()
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/reverse/ApplyLastReversible.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable.reverse
2 |
3 | import io.github.openminigameserver.replay.model.recordable.RecordableAction
4 |
5 |
6 | /**
7 | * Helper class implementing a batch that will only return the last value of this group
8 | */
9 | interface ApplyLastReversible : Reversible {
10 |
11 | override val isAppliedInBatch: Boolean
12 | get() = true
13 |
14 | override fun batchActions(value: List): RecordableAction? {
15 | return value.last()
16 | }
17 | }
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/reverse/DefaultStateReversible.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable.reverse
2 |
3 | import io.github.openminigameserver.replay.AbstractReplaySession
4 | import io.github.openminigameserver.replay.model.recordable.RecordableAction
5 | import kotlin.time.Duration
6 | import kotlin.time.milliseconds
7 |
8 | /**
9 | * Helper class that will lookup a previous instance of itself or the default state
10 | */
11 | interface DefaultStateReversible : Reversible {
12 |
13 | @JvmDefault
14 | override fun provideRevertedActions(
15 | start: Duration,
16 | end: Duration,
17 | session: AbstractReplaySession
18 | ): List {
19 | val forwardStep = end > start
20 | return session.findManyActions(
21 | Duration.ZERO,
22 | end.let { if (!forwardStep) it - 1.milliseconds else it }
23 | ) {
24 | it is DefaultStateReversible && isMatch(it)
25 | }.takeIf { it.isNotEmpty() } ?: listOf(provideDefaultState())
26 | }
27 |
28 | /**
29 | * Provide the default state of this action
30 | */
31 | fun provideDefaultState(): RecordableAction
32 |
33 | /**
34 | * Check whether this action is a match to the given action
35 | */
36 | fun isMatch(other: RecordableAction): Boolean {
37 | return this == other
38 | }
39 |
40 | }
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/recordable/reverse/Reversible.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.recordable.reverse
2 |
3 | import io.github.openminigameserver.replay.AbstractReplaySession
4 | import io.github.openminigameserver.replay.model.recordable.RecordableAction
5 | import kotlin.time.Duration
6 |
7 | /**
8 | * Represents an action that is reversible
9 | */
10 | interface Reversible {
11 |
12 | /**
13 | * States if actions like this are applied in batch
14 | */
15 | val isAppliedInBatch: Boolean
16 | get() = false
17 |
18 | /**
19 | * Batch multiple actions of this type into a single action
20 | */
21 | fun batchActions(value: List): RecordableAction? = null
22 |
23 | /**
24 | * Provide a list of actions that, when run, revert this action
25 | */
26 | fun provideRevertedActions(start: Duration, end: Duration, session: AbstractReplaySession): List
27 |
28 | }
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/storage/CompletableFutureReplayStorageSystem.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.storage
2 |
3 | import io.github.openminigameserver.replay.model.Replay
4 | import kotlinx.coroutines.future.await
5 | import java.util.*
6 | import java.util.concurrent.CompletableFuture
7 |
8 | abstract class CompletableFutureReplayStorageSystem : ReplayStorageSystem {
9 |
10 | abstract fun getReplaysForPlayerCompletable(player: UUID): CompletableFuture>
11 | abstract fun loadReplayCompletable(uuid: UUID): CompletableFuture
12 | abstract fun saveReplayCompletable(replay: Replay): CompletableFuture
13 |
14 | override suspend fun getReplaysForPlayer(player: UUID): List {
15 | return getReplaysForPlayerCompletable(player).await()
16 | }
17 |
18 | override suspend fun loadReplay(uuid: UUID): Replay? {
19 | return loadReplayCompletable(uuid).await()
20 | }
21 |
22 | override suspend fun saveReplay(replay: Replay) {
23 | saveReplayCompletable(replay).await()
24 | }
25 | }
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/storage/FileReplayStorageSystem.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.storage
2 |
3 | import io.github.openminigameserver.replay.io.ReplayFile
4 | import io.github.openminigameserver.replay.model.Replay
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.withContext
7 | import java.io.File
8 | import java.util.*
9 |
10 | class FileReplayStorageSystem(dataFolder: File) : ReplayStorageSystem {
11 |
12 | private val replaysFolder = File(dataFolder, "replays").also { it.mkdirs() }
13 |
14 | override suspend fun getReplaysForPlayer(player: UUID): List {
15 | return replaysFolder.listFiles()
16 | ?.mapNotNull { kotlin.runCatching { UUID.fromString(it.nameWithoutExtension) }.getOrNull() } ?: emptyList()
17 | }
18 |
19 | override suspend fun loadReplay(uuid: UUID): Replay? {
20 | return File(replaysFolder, "$uuid.$replayExtension").takeIf { it.exists() }?.let {
21 | withContext(Dispatchers.IO) {
22 | ReplayFile(it).let { it.loadReplay(); it.replay }
23 | }
24 | }
25 | }
26 |
27 | override suspend fun saveReplay(replay: Replay) {
28 | val targetFile = File(replaysFolder, "${replay.id}.$replayExtension")
29 |
30 | withContext(Dispatchers.IO) {
31 | ReplayFile(targetFile, replay).also { it.saveReplay() }
32 | }
33 |
34 |
35 | }
36 |
37 | companion object {
38 | const val replayExtension = "osmrp"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/storage/MemoryReplayStorageSystem.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.storage
2 |
3 | import io.github.openminigameserver.replay.model.Replay
4 | import java.util.*
5 |
6 | object MemoryReplayStorageSystem : ReplayStorageSystem {
7 | private val replays = mutableMapOf()
8 |
9 | override suspend fun getReplaysForPlayer(player: UUID): List {
10 | return replays.keys.toList()
11 | }
12 |
13 | override suspend fun loadReplay(uuid: UUID): Replay? {
14 | return replays[uuid]
15 | }
16 |
17 | override suspend fun saveReplay(replay: Replay) {
18 | replays[replay.id] = replay
19 | }
20 | }
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/storage/ReplayStorageSystem.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.storage
2 |
3 | import io.github.openminigameserver.replay.model.Replay
4 | import java.util.*
5 |
6 | interface ReplayStorageSystem {
7 |
8 | suspend fun getReplaysForPlayer(player: UUID): List
9 |
10 | suspend fun loadReplay(uuid: UUID): Replay?
11 |
12 | suspend fun saveReplay(replay: Replay)
13 |
14 | }
15 |
16 |
17 |
--------------------------------------------------------------------------------
/model/src/main/kotlin/io/github/openminigameserver/replay/model/storage/ReplayStorageSystemUtils.kt:
--------------------------------------------------------------------------------
1 | package io.github.openminigameserver.replay.model.storage
2 |
3 | import io.github.openminigameserver.replay.model.Replay
4 | import kotlinx.coroutines.runBlocking
5 | import java.util.*
6 | import java.util.concurrent.CompletableFuture
7 |
8 | object ReplayStorageSystemUtils {
9 | @JvmStatic
10 | fun ReplayStorageSystem.getReplaysForPlayerAsCompletable(player: UUID): CompletableFuture> {
11 | return CompletableFuture.supplyAsync {
12 | return@supplyAsync runBlocking { getReplaysForPlayer(player) }
13 | }
14 | }
15 |
16 | @JvmStatic
17 | fun ReplayStorageSystem.loadReplayAsCompletable(uuid: UUID): CompletableFuture {
18 | return CompletableFuture.supplyAsync {
19 | return@supplyAsync runBlocking { loadReplay(uuid) }
20 | }
21 | }
22 |
23 | @JvmStatic
24 | fun ReplayStorageSystem.saveReplayAsCompletable(replay: Replay): CompletableFuture {
25 | return CompletableFuture.runAsync {
26 | return@runAsync runBlocking { saveReplay(replay) }
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "Replay"
2 | include("model")
3 | include("impl-minestom")
4 | include("impl-abstraction")
5 |
--------------------------------------------------------------------------------