From dfb8afb396cd8f069b82c22cbab9dda22b0c9f8f Mon Sep 17 00:00:00 2001 From: codingcat2468 Date: Fri, 30 May 2025 22:36:25 +0200 Subject: [PATCH 1/8] Add a NONE RespawnLocationType to PlayerRespawnEventEntry --- .../typewritermc/basic/entries/event/PlayerRespawnEventEntry.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/BasicExtension/src/main/kotlin/com/typewritermc/basic/entries/event/PlayerRespawnEventEntry.kt b/extensions/BasicExtension/src/main/kotlin/com/typewritermc/basic/entries/event/PlayerRespawnEventEntry.kt index c8e9272e37..49a5395116 100644 --- a/extensions/BasicExtension/src/main/kotlin/com/typewritermc/basic/entries/event/PlayerRespawnEventEntry.kt +++ b/extensions/BasicExtension/src/main/kotlin/com/typewritermc/basic/entries/event/PlayerRespawnEventEntry.kt @@ -39,6 +39,7 @@ class PlayerRespawnEventEntry( enum class RespawnLocationType { BED, ANCHOR, + NONE } enum class RespawnContextKeys(override val klass: KClass<*>) : EntryContextKey { @@ -60,6 +61,7 @@ fun onRespawn(event: PlayerRespawnEvent, query: Query) when (it) { RespawnLocationType.BED -> event.isBedSpawn RespawnLocationType.ANCHOR -> event.isAnchorSpawn + RespawnLocationType.NONE -> !event.isBedSpawn && !event.isAnchorSpawn } } }.toList() From 802dea87d70e3b5bf6933490e63a2492e196425c Mon Sep 17 00:00:00 2001 From: codingcat2468 Date: Fri, 30 May 2025 22:42:55 +0200 Subject: [PATCH 2/8] Add InvisibleData to ArmorStandEntity --- .../living/armorstand/InvisibleData.kt | 38 +++++++++++++++++++ .../entity/minecraft/ArmorStandEntity.kt | 1 + 2 files changed, 39 insertions(+) create mode 100644 extensions/EntityExtension/src/main/kotlin/com/typewritermc/entity/entries/data/minecraft/living/armorstand/InvisibleData.kt diff --git a/extensions/EntityExtension/src/main/kotlin/com/typewritermc/entity/entries/data/minecraft/living/armorstand/InvisibleData.kt b/extensions/EntityExtension/src/main/kotlin/com/typewritermc/entity/entries/data/minecraft/living/armorstand/InvisibleData.kt new file mode 100644 index 0000000000..5b32dbc5d7 --- /dev/null +++ b/extensions/EntityExtension/src/main/kotlin/com/typewritermc/entity/entries/data/minecraft/living/armorstand/InvisibleData.kt @@ -0,0 +1,38 @@ +package com.typewritermc.entity.entries.data.minecraft.living.armorstand + +import com.typewritermc.core.books.pages.Colors +import com.typewritermc.core.extension.annotations.Entry +import com.typewritermc.core.extension.annotations.Tags +import com.typewritermc.engine.paper.entry.entity.SinglePropertyCollectorSupplier +import com.typewritermc.engine.paper.entry.entries.EntityData +import com.typewritermc.engine.paper.entry.entries.EntityProperty +import com.typewritermc.engine.paper.extensions.packetevents.metas +import me.tofaa.entitylib.meta.other.ArmorStandMeta +import me.tofaa.entitylib.wrapper.WrapperEntity +import org.bukkit.entity.Player +import java.util.* +import kotlin.reflect.KClass + +@Entry("invisible_data", "Whether the armor stand itself is invisible", Colors.RED, "mdi:mirror-variant") +@Tags("invisible_data", "armor_stand_data") +class InvisibleData( + override val id: String = "", + override val name: String = "", + val isInvisible: Boolean = false, + override val priorityOverride: Optional = Optional.empty(), +) : EntityData { + override fun type(): KClass = InvisibleProperty::class + + override fun build(player: Player): InvisibleProperty = InvisibleProperty(isInvisible) +} + +data class InvisibleProperty(val isInvisible: Boolean) : EntityProperty { + companion object : SinglePropertyCollectorSupplier(InvisibleProperty::class, InvisibleProperty(false)) +} + +fun applyInvisibleData(entity: WrapperEntity, property: InvisibleProperty) { + entity.metas { + meta { isInvisible = property.isInvisible } + error("Could not apply InvisibleData to ${entity.entityType} entity.") + } +} \ No newline at end of file diff --git a/extensions/EntityExtension/src/main/kotlin/com/typewritermc/entity/entries/entity/minecraft/ArmorStandEntity.kt b/extensions/EntityExtension/src/main/kotlin/com/typewritermc/entity/entries/entity/minecraft/ArmorStandEntity.kt index 57520e6a04..a3669073ea 100644 --- a/extensions/EntityExtension/src/main/kotlin/com/typewritermc/entity/entries/entity/minecraft/ArmorStandEntity.kt +++ b/extensions/EntityExtension/src/main/kotlin/com/typewritermc/entity/entries/entity/minecraft/ArmorStandEntity.kt @@ -60,6 +60,7 @@ private class ArmorStandEntity(player: Player) : WrapperFakeEntity( is MarkerProperty -> applyMarkerData(entity, property) is RotationProperty -> applyRotationData(entity, property) is SmallProperty -> applySmallData(entity, property) + is InvisibleProperty -> applyInvisibleData(entity, property) else -> {} } if (applyGenericEntityData(entity, property)) return From c99e1b9756dade95ad4bc3cca1de55ea8e95131b Mon Sep 17 00:00:00 2001 From: codingcat2468 Date: Mon, 2 Jun 2025 20:47:18 +0200 Subject: [PATCH 3/8] Add a worldborder cinematic entry --- .../cinematic/WorldborderCinematicEntry.kt | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 extensions/BasicExtension/src/main/kotlin/com/typewritermc/basic/entries/cinematic/WorldborderCinematicEntry.kt diff --git a/extensions/BasicExtension/src/main/kotlin/com/typewritermc/basic/entries/cinematic/WorldborderCinematicEntry.kt b/extensions/BasicExtension/src/main/kotlin/com/typewritermc/basic/entries/cinematic/WorldborderCinematicEntry.kt new file mode 100644 index 0000000000..557bbfdd93 --- /dev/null +++ b/extensions/BasicExtension/src/main/kotlin/com/typewritermc/basic/entries/cinematic/WorldborderCinematicEntry.kt @@ -0,0 +1,79 @@ +package com.typewritermc.basic.entries.cinematic + +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerWorldBorderSize +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayWorldBorderLerpSize +import com.typewritermc.core.books.pages.Colors +import com.typewritermc.core.extension.annotations.Entry +import com.typewritermc.core.extension.annotations.Segments +import com.typewritermc.engine.paper.entry.Criteria +import com.typewritermc.engine.paper.entry.entries.CinematicAction +import com.typewritermc.engine.paper.entry.entries.PrimaryCinematicEntry +import com.typewritermc.engine.paper.entry.entries.Segment +import com.typewritermc.engine.paper.entry.temporal.SimpleCinematicAction +import com.typewritermc.engine.paper.extensions.packetevents.sendPacket +import com.typewritermc.engine.paper.extensions.packetevents.sendPacketTo +import org.bukkit.entity.Player +import java.time.Duration +import java.util.* + +@Entry("worldborder_cinematic", "A cinematic that resizes the worldborder over time", Colors.CYAN, "mdi:resize") +/** + * The `Worldborder cinematic` entry can animate the worldborder client-side. + * The border animation speed is not linked to the server's tick rate, since the animation fully runs on the client. + * + * ## How could this be used? + * + * This entry could be used to show the worldborder changing size during a cinematic, for example to emphasize a narrator talking about + * how the border could change on a server based on certain conditions. + */ +class WorldborderCinematicEntry( + override val id: String = "", + override val name: String = "", + override val criteria: List = emptyList(), + @Segments(icon = "mdi:resize") + val segments: List = emptyList(), +) : PrimaryCinematicEntry { + override fun create(player: Player): CinematicAction { + return WorldborderCinematicAction( + player, + this, + ) + } +} + +data class WorldborderSegment( + override val startFrame: Int = 0, + override val endFrame: Int = 0, + val newSize: Double = 0.0, + val oldSize: Optional = Optional.empty(), + val transitionTime: Duration = Duration.ofMillis(1000), + val fadeBack: Boolean = false +) : Segment + +class WorldborderCinematicAction( + private val player: Player, + entry: WorldborderCinematicEntry, +) : SimpleCinematicAction() { + + override val segments: List = entry.segments + + override suspend fun startSegment(segment: WorldborderSegment) { + super.startSegment(segment) + + val oldSize = segment.oldSize.orElseGet(this::borderSize) + val packet = WrapperPlayWorldBorderLerpSize(oldSize, segment.newSize, segment.transitionTime.toMillis()) + packet.sendPacketTo(player) + } + + private fun borderSize(): Double { + return player.world.worldBorder.size + } + + override suspend fun stopSegment(segment: WorldborderSegment) { + super.stopSegment(segment) + + val packet = if (!segment.fadeBack) WrapperPlayServerWorldBorderSize(borderSize()) + else WrapperPlayWorldBorderLerpSize(segment.newSize, borderSize(), segment.transitionTime.toMillis()) + player.sendPacket(packet) + } +} \ No newline at end of file From 7b6ade6556b4e7a302c714dae160bda515444553 Mon Sep 17 00:00:00 2001 From: codingcat2468 Date: Mon, 2 Jun 2025 21:47:42 +0200 Subject: [PATCH 4/8] Add frame range checks to InCinematicFactEntry --- .../paper/entry/temporal/TemporalInteraction.kt | 4 +++- .../basic/entries/fact/InCinematicFactEntry.kt | 13 +++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/engine/engine-paper/src/main/kotlin/com/typewritermc/engine/paper/entry/temporal/TemporalInteraction.kt b/engine/engine-paper/src/main/kotlin/com/typewritermc/engine/paper/entry/temporal/TemporalInteraction.kt index 3c30e90ef1..6be3affac5 100644 --- a/engine/engine-paper/src/main/kotlin/com/typewritermc/engine/paper/entry/temporal/TemporalInteraction.kt +++ b/engine/engine-paper/src/main/kotlin/com/typewritermc/engine/paper/entry/temporal/TemporalInteraction.kt @@ -35,7 +35,7 @@ class TemporalInteraction( internal var state = STARTING private set private var playTime = Duration.ofMillis(-1) - private val frame: Int get() = (playTime.toMillis() / 50).toInt() + val frame: Int get() = (playTime.toMillis() / 50).toInt() override val priority by lazy { Query.findPageById(pageId)?.priority ?: 0 } @@ -138,6 +138,8 @@ private val Player.temporalInteraction: TemporalInteraction? session?.interaction as? TemporalInteraction } +fun Player.currentTemporalFrame(): Int? = temporalInteraction?.frame + fun Player.isPlayingTemporal(pageId: String): Boolean = temporalInteraction?.pageId == pageId fun Player.isPlayingTemporal(): Boolean = temporalInteraction != null diff --git a/extensions/BasicExtension/src/main/kotlin/com/typewritermc/basic/entries/fact/InCinematicFactEntry.kt b/extensions/BasicExtension/src/main/kotlin/com/typewritermc/basic/entries/fact/InCinematicFactEntry.kt index 3c3ff2b4c2..0a2f35a23f 100644 --- a/extensions/BasicExtension/src/main/kotlin/com/typewritermc/basic/entries/fact/InCinematicFactEntry.kt +++ b/extensions/BasicExtension/src/main/kotlin/com/typewritermc/basic/entries/fact/InCinematicFactEntry.kt @@ -10,15 +10,19 @@ import com.typewritermc.core.extension.annotations.Help import com.typewritermc.core.extension.annotations.Page import com.typewritermc.engine.paper.entry.entries.GroupEntry import com.typewritermc.engine.paper.entry.entries.ReadableFactEntry +import com.typewritermc.engine.paper.entry.temporal.currentTemporalFrame import com.typewritermc.engine.paper.entry.temporal.isPlayingTemporal import com.typewritermc.engine.paper.facts.FactData import org.bukkit.entity.Player +import java.util.* @Entry("in_cinematic_fact", "If the player is in a cinematic", Colors.PURPLE, "eos-icons:storage-class") /** * The 'In Cinematic Fact' is a fact that returns 1 if the player has an active cinematic, and 0 if not. * * If no cinematic is referenced, it will filter based on if any cinematic is active. + * If the player has an active cinematic, this fact can optionally be used to check + * whether the player is within a certain range of frames. * * * @@ -30,10 +34,12 @@ class InCinematicFactEntry( override val name: String = "", override val comment: String = "", override val group: Ref = emptyRef(), - @Help("When not set, it will filter based when any cinematic is active.") + @Help("When not set, it will filter based on if any cinematic is active.") @Page(PageType.CINEMATIC) @SerializedName("cinematic") val pageId: String = "", + val beforeFrame: Optional = Optional.empty(), + val afterFrame: Optional = Optional.empty() ) : ReadableFactEntry { override fun readSinglePlayer(player: Player): FactData { val inCinematic = if (pageId.isNotBlank()) @@ -41,6 +47,9 @@ class InCinematicFactEntry( else player.isPlayingTemporal() - return FactData(inCinematic.toInt()) + val frame = player.currentTemporalFrame() ?: 0 + val isOutOfRange = (beforeFrame.isPresent && beforeFrame.get() <= frame) + || (afterFrame.isPresent && afterFrame.get() > frame) + return FactData((inCinematic && !isOutOfRange).toInt()) } } From d6f5250c14a334d0ad234938573a3674c15fdb37 Mon Sep 17 00:00:00 2001 From: codingcat2468 Date: Wed, 11 Jun 2025 16:39:18 +0200 Subject: [PATCH 5/8] Rename isInvisible property in InvisibleData to "invisible" --- .../data/minecraft/living/armorstand/InvisibleData.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/EntityExtension/src/main/kotlin/com/typewritermc/entity/entries/data/minecraft/living/armorstand/InvisibleData.kt b/extensions/EntityExtension/src/main/kotlin/com/typewritermc/entity/entries/data/minecraft/living/armorstand/InvisibleData.kt index 5b32dbc5d7..da7573963d 100644 --- a/extensions/EntityExtension/src/main/kotlin/com/typewritermc/entity/entries/data/minecraft/living/armorstand/InvisibleData.kt +++ b/extensions/EntityExtension/src/main/kotlin/com/typewritermc/entity/entries/data/minecraft/living/armorstand/InvisibleData.kt @@ -18,21 +18,21 @@ import kotlin.reflect.KClass class InvisibleData( override val id: String = "", override val name: String = "", - val isInvisible: Boolean = false, + val invisible: Boolean = false, override val priorityOverride: Optional = Optional.empty(), ) : EntityData { override fun type(): KClass = InvisibleProperty::class - override fun build(player: Player): InvisibleProperty = InvisibleProperty(isInvisible) + override fun build(player: Player): InvisibleProperty = InvisibleProperty(invisible) } -data class InvisibleProperty(val isInvisible: Boolean) : EntityProperty { +data class InvisibleProperty(val invisible: Boolean) : EntityProperty { companion object : SinglePropertyCollectorSupplier(InvisibleProperty::class, InvisibleProperty(false)) } fun applyInvisibleData(entity: WrapperEntity, property: InvisibleProperty) { entity.metas { - meta { isInvisible = property.isInvisible } + meta { isInvisible = property.invisible } error("Could not apply InvisibleData to ${entity.entityType} entity.") } } \ No newline at end of file From b1fce6ec2555ef9e9dbc774db729850cefe6cf5c Mon Sep 17 00:00:00 2001 From: codingcat2468 Date: Wed, 11 Jun 2025 16:44:17 +0200 Subject: [PATCH 6/8] Revert changes to InCinematicFactEntry --- .../basic/entries/fact/InCinematicFactEntry.kt | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/extensions/BasicExtension/src/main/kotlin/com/typewritermc/basic/entries/fact/InCinematicFactEntry.kt b/extensions/BasicExtension/src/main/kotlin/com/typewritermc/basic/entries/fact/InCinematicFactEntry.kt index 0a2f35a23f..a62c8fce07 100644 --- a/extensions/BasicExtension/src/main/kotlin/com/typewritermc/basic/entries/fact/InCinematicFactEntry.kt +++ b/extensions/BasicExtension/src/main/kotlin/com/typewritermc/basic/entries/fact/InCinematicFactEntry.kt @@ -10,19 +10,15 @@ import com.typewritermc.core.extension.annotations.Help import com.typewritermc.core.extension.annotations.Page import com.typewritermc.engine.paper.entry.entries.GroupEntry import com.typewritermc.engine.paper.entry.entries.ReadableFactEntry -import com.typewritermc.engine.paper.entry.temporal.currentTemporalFrame import com.typewritermc.engine.paper.entry.temporal.isPlayingTemporal import com.typewritermc.engine.paper.facts.FactData import org.bukkit.entity.Player -import java.util.* @Entry("in_cinematic_fact", "If the player is in a cinematic", Colors.PURPLE, "eos-icons:storage-class") /** * The 'In Cinematic Fact' is a fact that returns 1 if the player has an active cinematic, and 0 if not. * * If no cinematic is referenced, it will filter based on if any cinematic is active. - * If the player has an active cinematic, this fact can optionally be used to check - * whether the player is within a certain range of frames. * * * @@ -38,8 +34,6 @@ class InCinematicFactEntry( @Page(PageType.CINEMATIC) @SerializedName("cinematic") val pageId: String = "", - val beforeFrame: Optional = Optional.empty(), - val afterFrame: Optional = Optional.empty() ) : ReadableFactEntry { override fun readSinglePlayer(player: Player): FactData { val inCinematic = if (pageId.isNotBlank()) @@ -47,9 +41,6 @@ class InCinematicFactEntry( else player.isPlayingTemporal() - val frame = player.currentTemporalFrame() ?: 0 - val isOutOfRange = (beforeFrame.isPresent && beforeFrame.get() <= frame) - || (afterFrame.isPresent && afterFrame.get() > frame) - return FactData((inCinematic && !isOutOfRange).toInt()) + return FactData(inCinematic.toInt()) } } From 2b5f7ec8d5f3a27c55072af443196de7cc53259d Mon Sep 17 00:00:00 2001 From: codingcat2468 Date: Wed, 11 Jun 2025 18:07:24 +0200 Subject: [PATCH 7/8] Implement a new "cinematic section" fact --- .../entries/fact/CinematicSectionFactEntry.kt | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 extensions/BasicExtension/src/main/kotlin/com/typewritermc/basic/entries/fact/CinematicSectionFactEntry.kt diff --git a/extensions/BasicExtension/src/main/kotlin/com/typewritermc/basic/entries/fact/CinematicSectionFactEntry.kt b/extensions/BasicExtension/src/main/kotlin/com/typewritermc/basic/entries/fact/CinematicSectionFactEntry.kt new file mode 100644 index 0000000000..5070493f04 --- /dev/null +++ b/extensions/BasicExtension/src/main/kotlin/com/typewritermc/basic/entries/fact/CinematicSectionFactEntry.kt @@ -0,0 +1,69 @@ +package com.typewritermc.basic.entries.fact + +import com.google.gson.annotations.SerializedName +import com.typewritermc.core.books.pages.Colors +import com.typewritermc.core.books.pages.PageType +import com.typewritermc.core.entries.Ref +import com.typewritermc.core.entries.emptyRef +import com.typewritermc.core.extension.annotations.Entry +import com.typewritermc.core.extension.annotations.Help +import com.typewritermc.core.extension.annotations.Page +import com.typewritermc.engine.paper.entry.entries.GroupEntry +import com.typewritermc.engine.paper.entry.entries.ReadableFactEntry +import com.typewritermc.engine.paper.entry.temporal.currentTemporalFrame +import com.typewritermc.engine.paper.entry.temporal.isPlayingTemporal +import com.typewritermc.engine.paper.facts.FactData +import org.bukkit.entity.Player +import java.util.* + +@Entry( + "cinematic_section_fact", + "Whether the player is within a certain section of a cinematic", + Colors.PURPLE, + "mdi:camera-burst" +) +/** + * The 'Cinematic Section Fact' is a fact that can return different values depending + * on what part of a cinematic the player is in. + * + * The specified `sections` can be used to map a certain range of cinematic frames to a value + * that will then be returned by the fact if the player is in that range. If none of the ranges + * match or the player is not currently in a cinematic, the value from `default` will be returned instead. + * + * Optionally, a cinematic page can be specified to only check for the given sections + * in that page, instead of any active cinematic. + * + * + * + * + * ## How could this be used? + * With this fact, it is possible to make an entry act differently depending on + * what section of a cinematic the player is in. + */ +class CinematicSectionFactEntry( + override val id: String = "", + override val name: String = "", + override val comment: String = "", + override val group: Ref = emptyRef(), + @Page(PageType.CINEMATIC) + @SerializedName("cinematic") + val pageId: String = "", + val sections: List = emptyList(), + @Help("Will be returned when the player is not in a cinematic, or not in any of the given sections") + val default: Optional = Optional.empty() +) : ReadableFactEntry { + override fun readSinglePlayer(player: Player): FactData { + val inCinematic = if (pageId.isNotBlank()) player.isPlayingTemporal(pageId) else player.isPlayingTemporal() + val default = default.orElse(0) + if (!inCinematic) return FactData(default) + + val output = sections.firstOrNull { it.frameRange.contains(player.currentTemporalFrame() ?: -1) }?.value + return FactData(output ?: default) + } + + data class CinematicSection( + @Help("The range of cinematic frames the player has to be in") + val frameRange: ClosedRange = IntRange.EMPTY, + val value: Int = 0 + ) +} From 4c1b3d20472a946e13358d07c34aa12676923495 Mon Sep 17 00:00:00 2001 From: Gabber235 Date: Thu, 12 Jun 2025 05:34:47 +0200 Subject: [PATCH 8/8] Small tweaks --- .../basic/entries/fact/CinematicSectionFactEntry.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/extensions/BasicExtension/src/main/kotlin/com/typewritermc/basic/entries/fact/CinematicSectionFactEntry.kt b/extensions/BasicExtension/src/main/kotlin/com/typewritermc/basic/entries/fact/CinematicSectionFactEntry.kt index 5070493f04..03df9addeb 100644 --- a/extensions/BasicExtension/src/main/kotlin/com/typewritermc/basic/entries/fact/CinematicSectionFactEntry.kt +++ b/extensions/BasicExtension/src/main/kotlin/com/typewritermc/basic/entries/fact/CinematicSectionFactEntry.kt @@ -14,7 +14,6 @@ import com.typewritermc.engine.paper.entry.temporal.currentTemporalFrame import com.typewritermc.engine.paper.entry.temporal.isPlayingTemporal import com.typewritermc.engine.paper.facts.FactData import org.bukkit.entity.Player -import java.util.* @Entry( "cinematic_section_fact", @@ -50,14 +49,14 @@ class CinematicSectionFactEntry( val pageId: String = "", val sections: List = emptyList(), @Help("Will be returned when the player is not in a cinematic, or not in any of the given sections") - val default: Optional = Optional.empty() + val default: Int = 0, ) : ReadableFactEntry { override fun readSinglePlayer(player: Player): FactData { val inCinematic = if (pageId.isNotBlank()) player.isPlayingTemporal(pageId) else player.isPlayingTemporal() - val default = default.orElse(0) if (!inCinematic) return FactData(default) - val output = sections.firstOrNull { it.frameRange.contains(player.currentTemporalFrame() ?: -1) }?.value + val frame = player.currentTemporalFrame() ?: return FactData(default) + val output = sections.firstOrNull { it.frameRange.contains(frame) }?.value return FactData(output ?: default) }