From ab8e5fc325c76cb5c0f6dd75fce666721a3ac9ca Mon Sep 17 00:00:00 2001 From: Gabber235 Date: Thu, 30 May 2024 12:06:43 +0200 Subject: [PATCH] Rework Entity Activities --- .../entries/activity/AudienceActivityPair.kt | 65 +++++++++ .../entries/activity/FixedLocationActivity.kt | 39 ----- .../entries/activity/GameTimeActivity.kt | 97 ++++++------- .../entries/activity/InDialogueActivity.kt | 92 ++++++------ .../entries/activity/LookCloseActivity.kt | 137 ++++++++++-------- .../activity/NavigationActivityTask.kt | 40 ++--- .../entries/activity/PatrolActivity.kt | 91 ++++++++---- .../entries/activity/PlayerCloseByActivity.kt | 68 ++++----- .../activity/RandomLookActivityEntry.kt | 76 ++++++++++ .../activity/TargetLocationActivity.kt | 105 +++++++++++--- .../entries/activity/TimedActivity.kt | 94 ++++++------ .../typewriter/entries/entity/custom/Npc.kt | 4 +- .../entries/entity/minecraft/CowEntity.kt | 3 +- .../entity/minecraft/EndermanEntity.kt | 3 +- .../entries/entity/minecraft/HoglinEntity.kt | 3 +- .../entries/entity/minecraft/HuskEntity.kt | 3 +- .../entity/minecraft/IronGolemEntity.kt | 3 +- .../entity/minecraft/ItemDisplayEntity.kt | 3 +- .../entity/minecraft/PiglinBruteEntity.kt | 3 +- .../entries/entity/minecraft/PiglinEntity.kt | 3 +- .../entries/entity/minecraft/PlayerEntity.kt | 3 +- .../entity/minecraft/SkeletonEntity.kt | 3 +- .../entity/minecraft/TextDisplayEntity.kt | 7 +- .../entity/minecraft/VillagerEntity.kt | 3 +- .../entries/entity/minecraft/WardenEntity.kt | 3 +- .../entries/entity/minecraft/WitchEntity.kt | 3 +- .../entries/entity/minecraft/ZombieEntity.kt | 3 +- .../instance/AdvancedEntityInstanceEntry.kt | 27 +++- plugin/build.gradle.kts | 4 +- .../entry/entity/ActivityEntityDisplay.kt | 3 +- .../entry/entity/ActivityManager.kt | 79 ++-------- .../typewriter/entry/entity/AdvancedEntity.kt | 87 +++++------ .../typewriter/entry/entity/DisplayEntity.kt | 2 +- .../typewriter/entry/entity/EntityActivity.kt | 108 +++++++++----- .../typewriter/entry/entity/EntityHandler.kt | 2 +- ....kt => IndividualActivityEntityDisplay.kt} | 26 ++-- ...play.kt => SharedActivityEntityDisplay.kt} | 16 +- .../typewriter/entry/entity/SimpleEntity.kt | 16 +- .../typewriter/entry/entries/EntityEntry.kt | 69 ++++++++- 39 files changed, 828 insertions(+), 568 deletions(-) create mode 100644 adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/AudienceActivityPair.kt delete mode 100644 adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/FixedLocationActivity.kt create mode 100644 adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/RandomLookActivityEntry.kt rename plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/{PlayerSpecificActivityEntityDisplay.kt => IndividualActivityEntityDisplay.kt} (75%) rename plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/{CommonActivityEntityDisplay.kt => SharedActivityEntityDisplay.kt} (80%) diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/AudienceActivityPair.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/AudienceActivityPair.kt new file mode 100644 index 0000000000..006d6a7324 --- /dev/null +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/AudienceActivityPair.kt @@ -0,0 +1,65 @@ +package me.gabber235.typewriter.entries.activity + +import me.gabber235.typewriter.adapters.Colors +import me.gabber235.typewriter.adapters.Entry +import me.gabber235.typewriter.adapters.modifiers.Help +import me.gabber235.typewriter.entry.Ref +import me.gabber235.typewriter.entry.emptyRef +import me.gabber235.typewriter.entry.entity.EntityActivity +import me.gabber235.typewriter.entry.entity.IndividualActivityContext +import me.gabber235.typewriter.entry.entity.LocationProperty +import me.gabber235.typewriter.entry.entity.SingleChildActivity +import me.gabber235.typewriter.entry.entries.AudienceEntry +import me.gabber235.typewriter.entry.entries.EntityActivityEntry +import me.gabber235.typewriter.entry.entries.IndividualEntityActivityEntry +import me.gabber235.typewriter.entry.inAudience +import java.util.* + +@Entry( + "audience_activity", + "Select activity based on the audience a player is in", + Colors.BLUE, + "fluent:people-audience-32-filled" +) +/** + * The `Audience Activity` is an activity that filters an audience based on the audience a player is in. + * The activity will go through the audiences in order and the first one + * where the player is part of the audience will have the activity selected. + * + * This can only be used on an individual entity instance. + * + * ## How could this be used? + * This could be used to make a bodyguard distracted by something and walk away just for the player. + */ +class AudienceActivityEntry( + override val id: String = "", + override val name: String = "", + val activities: List = emptyList(), + @Help("The activity that will be used when the player is not in any audience.") + val defaultActivity: Ref = emptyRef(), + override val priorityOverride: Optional = Optional.empty(), +) : IndividualEntityActivityEntry { + override fun create( + context: IndividualActivityContext, + currentLocation: LocationProperty + ): EntityActivity { + return AudienceActivity(activities, defaultActivity, currentLocation) + } +} + +class AudienceActivityPair( + val audience: Ref, + val activity: Ref, +) + +class AudienceActivity( + private val activities: List, + private val defaultActivity: Ref, + startLocation: LocationProperty, +) : SingleChildActivity(startLocation) { + override fun currentChild(context: IndividualActivityContext): Ref { + val player = context.viewer + + return activities.firstOrNull { player.inAudience(it.audience) }?.activity ?: defaultActivity + } +} \ No newline at end of file diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/FixedLocationActivity.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/FixedLocationActivity.kt deleted file mode 100644 index 03fe36db2d..0000000000 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/FixedLocationActivity.kt +++ /dev/null @@ -1,39 +0,0 @@ -package me.gabber235.typewriter.entries.activity - -import me.gabber235.typewriter.adapters.Colors -import me.gabber235.typewriter.adapters.Entry -import me.gabber235.typewriter.adapters.modifiers.Help -import me.gabber235.typewriter.entry.Ref -import me.gabber235.typewriter.entry.entity.* -import me.gabber235.typewriter.entry.entries.EntityActivityEntry -import me.gabber235.typewriter.entry.ref -import java.util.* - -@Entry("fixed_location_activity", "A fixed location activity", Colors.BLUE, "majesticons:map-marker-area") -/** -* The `FixedLocationActivityEntry` is an activity that freezes the entity in a specific location. - * - * ## How could this be used? - * This could be used to freeze an entity in a specific location. - */ -class FixedLocationActivityEntry( - override val id: String = "", - override val name: String = "", - override val priorityOverride: Optional = Optional.empty(), -) : EntityActivityEntry { - override fun create(context: TaskContext): EntityActivity = FixedLocationActivity(ref()) -} - -private class FixedLocationActivity(val ref: Ref) : EntityActivity { - override fun canActivate(context: TaskContext, currentLocation: LocationProperty): Boolean = ref canActivateFor context - - override fun currentTask(context: TaskContext, currentLocation: LocationProperty): EntityTask { - return FixedLocationActivityTask(currentLocation) - } -} - -private class FixedLocationActivityTask(override val location: LocationProperty) : EntityTask { - override fun tick(context: TaskContext) {} - override fun mayInterrupt(): Boolean = true - override fun isComplete(): Boolean = false -} \ No newline at end of file diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/GameTimeActivity.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/GameTimeActivity.kt index 2913125a05..62eb210df8 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/GameTimeActivity.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/GameTimeActivity.kt @@ -4,15 +4,14 @@ import lirand.api.extensions.server.server import me.gabber235.typewriter.adapters.Colors import me.gabber235.typewriter.adapters.Entry import me.gabber235.typewriter.entry.Ref -import me.gabber235.typewriter.entry.descendants -import me.gabber235.typewriter.entry.entity.* -import me.gabber235.typewriter.entry.entries.* -import me.gabber235.typewriter.entry.priority -import me.gabber235.typewriter.entry.ref -import me.gabber235.typewriter.logger -import org.bukkit.entity.Player -import org.bukkit.event.EventHandler -import org.bukkit.event.player.PlayerChangedWorldEvent +import me.gabber235.typewriter.entry.emptyRef +import me.gabber235.typewriter.entry.entity.ActivityContext +import me.gabber235.typewriter.entry.entity.EntityActivity +import me.gabber235.typewriter.entry.entity.LocationProperty +import me.gabber235.typewriter.entry.entity.SingleChildActivity +import me.gabber235.typewriter.entry.entries.EntityActivityEntry +import me.gabber235.typewriter.entry.entries.GenericEntityActivityEntry +import me.gabber235.typewriter.utils.logErrorIfNull import java.util.* @Entry("game_time_activity", "A game time activity", Colors.BLUE, "bi:clock-fill") @@ -38,21 +37,22 @@ import java.util.* class GameTimeActivityEntry( override val id: String = "", override val name: String = "", - override val children: List> = emptyList(), val world: String = "", - val activeTimes: List = emptyList(), + val activities: List = emptyList(), + val defaultActivity: Ref = emptyRef(), override val priorityOverride: Optional = Optional.empty(), -) : EntityActivityEntry { - override fun create(context: TaskContext): EntityActivity = GameTimeActivity( - ref(), - children - .descendants(EntityActivityEntry::class) - .mapNotNull { it.get() } - .sortedByDescending { it.priority } - .map { it.create(context) }, - ) - - override fun display(): AudienceFilter = GameTimeFilter(ref(), world, activeTimes) +) : GenericEntityActivityEntry { + override fun create( + context: ActivityContext, + currentLocation: LocationProperty + ): EntityActivity { + return GameTimeActivity( + world, + activities, + defaultActivity, + currentLocation, + ) + } } class GameTimeRange( @@ -64,43 +64,28 @@ class GameTimeRange( } } -class GameTimeFilter( - val ref: Ref, - private val world: String, - private val activeTimes: List, -) : AudienceFilter(ref), TickableDisplay { - override fun filter(player: Player): Boolean { - if (player.world.name != world) return false - val worldTime = player.world.time % 24000 - return activeTimes.any { worldTime in it } - } - - @EventHandler - private fun onWorldChange(event: PlayerChangedWorldEvent) { - event.player.refresh() - } - - override fun tick() { - val world = server.getWorld(world) - if (world == null) { - logger.warning("World '${this.world}' does not exist, $ref will not work.") - return - } - - val isActive = activeTimes.any { world.time % 24000 in it } - if (isActive && consideredPlayers.isNotEmpty() && players.isEmpty()) { - consideredPlayers.forEach { it.refresh() } - } else if (!isActive && players.isNotEmpty()) { - players.forEach { it.updateFilter(false) } - } +class GameTimedActivity( + val time: GameTimeRange, + val activity: Ref, +) { + operator fun contains(time: Long): Boolean { + return time in this.time } } class GameTimeActivity( - val ref: Ref, - children: List, -) : FilterActivity(children) { - override fun canActivate(context: TaskContext, currentLocation: LocationProperty): Boolean { - return ref.canActivateFor(context) + private val world: String, + private val activities: List, + private val defaultActivity: Ref, + startLocation: LocationProperty, +) : SingleChildActivity(startLocation) { + override fun currentChild(context: ActivityContext): Ref { + val world = + server.getWorld(world).logErrorIfNull("Could not find world '$world'") ?: return defaultActivity + + val worldTime = world.time % 24000 + return activities.firstOrNull { it.contains(worldTime) } + ?.activity + ?: defaultActivity } } \ No newline at end of file diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/InDialogueActivity.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/InDialogueActivity.kt index 47d9bfe4f5..ae273cfea5 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/InDialogueActivity.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/InDialogueActivity.kt @@ -4,14 +4,17 @@ import me.gabber235.typewriter.adapters.Colors import me.gabber235.typewriter.adapters.Entry import me.gabber235.typewriter.adapters.modifiers.Help import me.gabber235.typewriter.entry.Ref -import me.gabber235.typewriter.entry.descendants import me.gabber235.typewriter.entry.dialogue.currentDialogue -import me.gabber235.typewriter.entry.entity.* -import me.gabber235.typewriter.entry.entries.AudienceEntry +import me.gabber235.typewriter.entry.emptyRef +import me.gabber235.typewriter.entry.entity.ActivityContext +import me.gabber235.typewriter.entry.entity.EntityActivity +import me.gabber235.typewriter.entry.entity.LocationProperty +import me.gabber235.typewriter.entry.entity.SingleChildActivity import me.gabber235.typewriter.entry.entries.DialogueEntry import me.gabber235.typewriter.entry.entries.EntityActivityEntry +import me.gabber235.typewriter.entry.entries.GenericEntityActivityEntry import me.gabber235.typewriter.entry.priority -import me.gabber235.typewriter.entry.ref +import me.gabber235.typewriter.utils.logErrorIfNull import org.bukkit.entity.Player import java.time.Duration import java.util.* @@ -28,7 +31,6 @@ import java.util.* class InDialogueActivityEntry( override val id: String = "", override val name: String = "", - override val children: List> = emptyList(), @Help("When a player is considered to be idle in the same dialogue") /** * The duration a player can be idle in the same dialogue before the activity deactivates. @@ -41,49 +43,57 @@ class InDialogueActivityEntry( * */ val dialogueIdleDuration: Duration = Duration.ofSeconds(30), + @Help("The activity that will be used when the npc is in a dialogue") + val talkingActivity: Ref = emptyRef(), + @Help("The activity that will be used when the npc is not in a dialogue") + val idleActivity: Ref = emptyRef(), override val priorityOverride: Optional = Optional.empty(), -) : EntityActivityEntry { - override fun create(context: TaskContext): EntityActivity = InDialogueActivity( - ref(), - children - .descendants(EntityActivityEntry::class) - .mapNotNull { it.get() } - .sortedByDescending { it.priority } - .map { it.create(context) }, - dialogueIdleDuration, - priority, - ) +) : GenericEntityActivityEntry { + override fun create( + context: ActivityContext, + currentLocation: LocationProperty + ): EntityActivity { + return InDialogueActivity( + dialogueIdleDuration, + priority, + talkingActivity, + idleActivity, + currentLocation, + ) + } } class InDialogueActivity( - val ref: Ref, - children: List, private val dialogueIdleDuration: Duration, private val priority: Int, -) : FilterActivity(children) { - private var trackers = mutableMapOf() + private val talkingActivity: Ref, + private val idleActivity: Ref, + startLocation: LocationProperty, +) : SingleChildActivity(startLocation) { + private val trackers = mutableMapOf() - override fun canActivate(context: TaskContext, currentLocation: LocationProperty): Boolean { - if (!ref.canActivateFor(context)) { - return false - } - val definition = context.instanceRef.get()?.definition ?: return false - val trackingPlayers = context.viewers - .filter { it.currentDialogue?.speaker == definition } + override fun currentChild(context: ActivityContext): Ref { + val definition = + context.instanceRef.get()?.definition.logErrorIfNull("Could not find definition, this should not happen. Please report this on the TypeWriter Discord!") + ?: return idleActivity + val inDialogue = context.viewers.filter { it.currentDialogue?.speaker == definition } - val trackingPlayerIds = trackingPlayers.map { it.uniqueId } + trackers.keys.removeIf { uuid -> inDialogue.none { it.uniqueId == uuid } } - trackers.keys.removeAll { it !in trackingPlayerIds } + if (inDialogue.isEmpty()) { + return idleActivity + } - trackingPlayers.forEach { player -> - trackers.computeIfAbsent(player.uniqueId) { PlayerDialogueTracker(player.currentDialogue) } - .update(player) + inDialogue.forEach { player -> + trackers.computeIfAbsent(player.uniqueId) { PlayerDialogueTracker(player.currentDialogue) }.update(player) } - val canActivate = - !trackers.all { (_, playerLocation) -> playerLocation.canIgnore(dialogueIdleDuration) } && - super.canActivate(context, currentLocation) - return canActivate + val isTalking = trackers.any { (_, tracker) -> tracker.isActive(dialogueIdleDuration) } + return if (isTalking) { + talkingActivity + } else { + idleActivity + } } private inner class PlayerDialogueTracker( @@ -97,11 +107,11 @@ class InDialogueActivity( dialogue = currentDialogue } - fun canIgnore(maxIdleDuration: Duration): Boolean { - if (maxIdleDuration.isZero) return false - val dialogue = dialogue ?: return true - if (dialogue.priority > priority) return false - return System.currentTimeMillis() - lastInteraction > maxIdleDuration.toMillis() + fun isActive(maxIdleDuration: Duration): Boolean { + if (maxIdleDuration.isZero) return true + val dialogue = dialogue ?: return false + if (dialogue.priority > priority) return true + return System.currentTimeMillis() - lastInteraction < maxIdleDuration.toMillis() } } } \ No newline at end of file diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/LookCloseActivity.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/LookCloseActivity.kt index c9440d9110..9bc229099f 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/LookCloseActivity.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/LookCloseActivity.kt @@ -2,10 +2,8 @@ package me.gabber235.typewriter.entries.activity import me.gabber235.typewriter.adapters.Colors import me.gabber235.typewriter.adapters.Entry -import me.gabber235.typewriter.entry.Ref import me.gabber235.typewriter.entry.entity.* -import me.gabber235.typewriter.entry.entries.EntityActivityEntry -import me.gabber235.typewriter.entry.ref +import me.gabber235.typewriter.entry.entries.GenericEntityActivityEntry import me.gabber235.typewriter.snippets.snippet import org.bukkit.GameMode import org.bukkit.entity.Player @@ -29,57 +27,34 @@ class LookCloseActivityEntry( override val id: String = "", override val name: String = "", override val priorityOverride: Optional = Optional.empty(), -) : EntityActivityEntry { - override fun create(context: TaskContext): EntityActivity = LookCloseActivity(ref()) -} - -class LookCloseActivity(val ref: Ref) : EntityActivity { - override fun canActivate(context: TaskContext, currentLocation: LocationProperty): Boolean { - if (!ref.canActivateFor(context)) { - return false - } - - // Only if there is someone to look at - return context.viewers.any { player -> - val distance = currentLocation.distanceSqrt(player.location) ?: return@any false - distance <= playerCloseLookRange * playerCloseLookRange - } - } - - override fun currentTask(context: TaskContext, currentLocation: LocationProperty): EntityTask { - return LookCloseActivityTask(currentLocation) +) : GenericEntityActivityEntry { + override fun create( + context: ActivityContext, + currentLocation: LocationProperty + ): EntityActivity { + return LookCloseActivity(currentLocation) } } -class LookCloseActivityTask( - override var location: LocationProperty, -) : EntityTask { - inner class Target(val player: Player, private val lookupTime: Long = System.currentTimeMillis()) { - val shouldRefresh: Boolean - get() { - if (!player.isValid) return true - if (player.location.world.uid != this@LookCloseActivityTask.location.world) return true - if (this@LookCloseActivityTask.location.distanceSquared(player.location.toProperty()) > playerCloseLookRange * playerCloseLookRange) return true - return System.currentTimeMillis() - lookupTime > 1000 - } - - val location: LocationProperty - get() = player.location.toProperty() - } - +class LookCloseActivity( + override var currentLocation: LocationProperty, +) : EntityActivity { private var target: Target? = null private val yawVelocity = Velocity(0f) private val pitchVelocity = Velocity(0f) - private fun findNewTarget(context: TaskContext): Target? { + + override fun initialize(context: ActivityContext) {} + + private fun findNewTarget(context: ActivityContext): Target? { val closestTarget = context.viewers .filter { it.isValid && it.gameMode != GameMode.SPECTATOR && !it.isInvisible } - .minByOrNull { location.distanceSqrt(it.location) ?: Double.POSITIVE_INFINITY } + .minByOrNull { currentLocation.distanceSqrt(it.location) ?: Double.POSITIVE_INFINITY } if (closestTarget == null) { return null } - val distance = location.distanceSqrt(closestTarget.location) + val distance = currentLocation.distanceSqrt(closestTarget.location) if (distance == null || distance > playerCloseLookRange * playerCloseLookRange) { return null @@ -88,46 +63,82 @@ class LookCloseActivityTask( return Target(closestTarget) } - override fun tick(context: TaskContext) { + override fun tick(context: ActivityContext): TickResult { if (!context.isViewed) { this.target = null - return + return TickResult.CONSUMED } + if (target?.shouldRefresh == true) this.target = null + var target = target if (target == null) target = findNewTarget(context) - if (target == null) return - - if (target.shouldRefresh) { - this.target = null - return - } + if (target == null) return TickResult.IGNORED this.target = target - val direction = target.location.toVector().subtract(location.toVector()).normalize() + val direction = target.location.toVector().subtract(currentLocation.toVector()).normalize() val targetYaw = getLookYaw(direction.x, direction.z) val targetPitch = getLookPitch(direction.x, direction.y, direction.z) - val currentYaw = if (location.yaw - targetYaw > 180) { - location.yaw - 360 - } else if (location.yaw - targetYaw < -180) { - location.yaw + 360 - } else { - location.yaw - } - val currentPitch = location.pitch + val (yaw, pitch) = updateLookDirection( + LookDirection(currentLocation.yaw, currentLocation.pitch), + LookDirection(targetYaw, targetPitch), + yawVelocity, + pitchVelocity + ) + + currentLocation = + LocationProperty(currentLocation.world, currentLocation.x, currentLocation.y, currentLocation.z, yaw, pitch) + return TickResult.CONSUMED + } + + override fun dispose(context: ActivityContext) { + target = null + yawVelocity.value = 0f + pitchVelocity.value = 0f + } + + inner class Target(val player: Player, private val lookupTime: Long = System.currentTimeMillis()) { + val shouldRefresh: Boolean + get() { + if (!player.isValid) return true + if (player.location.world.uid != this@Target.location.world) return true + if (this@Target.location.distanceSquared(player.location.toProperty()) > playerCloseLookRange * playerCloseLookRange) return true + return System.currentTimeMillis() - lookupTime > 1000 + } - val yaw = smoothDamp(currentYaw, targetYaw, yawVelocity, 0.2f) - val pitch = smoothDamp(currentPitch, targetPitch, pitchVelocity, 0.2f) + val location: LocationProperty + get() = player.location.toProperty() + } + +} - location = LocationProperty(location.world, location.x, location.y, location.z, yaw, pitch) +data class LookDirection( + val yaw: Float, + val pitch: Float, +) + +fun updateLookDirection( + current: LookDirection, + target: LookDirection, + yawVelocity: Velocity, + pitchVelocity: Velocity, + smoothTime: Float = 0.2f +): Pair { + val correctedYaw = if (current.yaw - target.yaw > 180) { + current.yaw - 360 + } else if (current.yaw - target.yaw < -180) { + current.yaw + 360 + } else { + current.yaw } - override fun mayInterrupt(): Boolean = true + val yaw = smoothDamp(correctedYaw, target.yaw, yawVelocity, smoothTime) + val pitch = smoothDamp(current.pitch, target.pitch, pitchVelocity, smoothTime) - override fun isComplete(): Boolean = target == null + return yaw to pitch } class Velocity(var value: Float) diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/NavigationActivityTask.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/NavigationActivityTask.kt index 5495a65816..e22a73ba89 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/NavigationActivityTask.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/NavigationActivityTask.kt @@ -2,10 +2,7 @@ package me.gabber235.typewriter.entries.activity import kotlinx.coroutines.Job import kotlinx.coroutines.future.await -import me.gabber235.typewriter.entry.entity.EntityTask -import me.gabber235.typewriter.entry.entity.LocationProperty -import me.gabber235.typewriter.entry.entity.TaskContext -import me.gabber235.typewriter.entry.entity.toProperty +import me.gabber235.typewriter.entry.entity.* import me.gabber235.typewriter.entry.entries.roadNetworkMaxDistance import me.gabber235.typewriter.entry.roadnetwork.content.toLocation import me.gabber235.typewriter.entry.roadnetwork.content.toPathPosition @@ -23,28 +20,33 @@ import kotlin.math.cos import kotlin.math.sin -class NavigationActivityTask( +class NavigationActivity( gps: GPS, startLocation: LocationProperty, -) : EntityTask { +) : GenericEntityActivity { private var path: List? = null private var state: NavigationActivityTaskState = NavigationActivityTaskState.Searching(gps, startLocation) - override val location: LocationProperty + override val currentLocation: LocationProperty get() = state.location() - override fun tick(context: TaskContext) { + override fun initialize(context: ActivityContext) {} + + override fun tick(context: ActivityContext): TickResult { state.tick(context) if (state.isComplete()) { + if (path?.isEmpty() == true) { + return TickResult.IGNORED + } path = path?.subList(1, path?.size ?: 0) ?: (state as NavigationActivityTaskState.Searching).let { - it.path ?: return + it.path ?: return TickResult.IGNORED } - val currentEdge = path?.firstOrNull() ?: return + val currentEdge = path?.firstOrNull() ?: return TickResult.IGNORED state = when { currentEdge.isFastTravel -> NavigationActivityTaskState.FastTravel(currentEdge) - context.isViewed -> NavigationActivityTaskState.Walking(currentEdge, location) + context.isViewed -> NavigationActivityTaskState.Walking(currentEdge, currentLocation) else -> NavigationActivityTaskState.FakeNavigation(currentEdge) } @@ -59,15 +61,13 @@ class NavigationActivityTask( // And we switch back to fake navigation when the entity is not viewed if (state is NavigationActivityTaskState.Walking && !context.isViewed) { - this.state = NavigationActivityTaskState.FakeNavigation(state.edge, location) + this.state = NavigationActivityTaskState.FakeNavigation(state.edge, currentLocation) } - } - override fun mayInterrupt(): Boolean = true - - override fun isComplete(): Boolean = path?.isEmpty() == true && state.isComplete() + return TickResult.CONSUMED + } - override fun dispose() { + override fun dispose(context: ActivityContext) { state.dispose() } } @@ -76,7 +76,7 @@ class NavigationActivityTask( private sealed interface NavigationActivityTaskState { fun location(): LocationProperty fun isComplete(): Boolean = false - fun tick(context: TaskContext) {} + fun tick(context: ActivityContext) {} fun dispose() {} class Searching( @@ -118,7 +118,7 @@ private sealed interface NavigationActivityTaskState { override fun location(): LocationProperty = location - override fun tick(context: TaskContext) { + override fun tick(context: ActivityContext) { super.tick(context) ticks++ } @@ -199,7 +199,7 @@ private sealed interface NavigationActivityTaskState { override fun location(): LocationProperty = location - override fun tick(context: TaskContext) { + override fun tick(context: ActivityContext) { val path = path ?: return val targetPoint = path[pathIndex] diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/PatrolActivity.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/PatrolActivity.kt index 6755b9883d..c68997216c 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/PatrolActivity.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/PatrolActivity.kt @@ -5,11 +5,7 @@ import me.gabber235.typewriter.adapters.Entry import me.gabber235.typewriter.entry.Ref import me.gabber235.typewriter.entry.emptyRef import me.gabber235.typewriter.entry.entity.* -import me.gabber235.typewriter.entry.entries.EntityActivityEntry -import me.gabber235.typewriter.entry.entries.RoadNetworkEntry -import me.gabber235.typewriter.entry.entries.RoadNodeCollectionEntry -import me.gabber235.typewriter.entry.entries.RoadNodeId -import me.gabber235.typewriter.entry.ref +import me.gabber235.typewriter.entry.entries.* import me.gabber235.typewriter.entry.roadnetwork.RoadNetworkManager import me.gabber235.typewriter.entry.roadnetwork.gps.PointToPointGPS import org.koin.core.component.KoinComponent @@ -32,44 +28,83 @@ class PatrolActivityEntry( override val roadNetwork: Ref = emptyRef(), override val nodes: List = emptyList(), override val priorityOverride: Optional = Optional.empty(), -) : EntityActivityEntry, RoadNodeCollectionEntry { - override fun create(context: TaskContext): EntityActivity = PatrolActivity(ref(), roadNetwork, nodes) +) : GenericEntityActivityEntry, RoadNodeCollectionEntry { + override fun create( + context: ActivityContext, + currentLocation: LocationProperty + ): EntityActivity { + if (nodes.isEmpty()) return IdleActivity.create(context, currentLocation) + return PatrolActivity(roadNetwork, nodes, currentLocation) + } } private class PatrolActivity( - val ref: Ref, private val roadNetwork: Ref, private val nodes: List, -) : EntityActivity, KoinComponent { - override fun canActivate(context: TaskContext, currentLocation: LocationProperty): Boolean { - if (!ref.canActivateFor(context)) { - return false - } + private val startLocation: LocationProperty, +) : EntityActivity, KoinComponent { + private var currentLocationIndex = 0 + private var activity: EntityActivity? = null - return nodes.isNotEmpty() + fun refreshActivity(context: ActivityContext, network: RoadNetwork) { + val targetNodeId = nodes.getOrNull(currentLocationIndex) + ?: throw IllegalStateException("Could not find any node in the nodes list for the patrol activity.") + val targetNode = network.nodes.find { it.id == targetNodeId } + ?: throw IllegalStateException("Could not find any node in the nodes list for the patrol activity.") + + activity?.dispose(context) + activity = NavigationActivity(PointToPointGPS( + roadNetwork, + { currentLocation.toLocation() }) { + targetNode.location + }, currentLocation + ) + activity?.initialize(context) } - private var currentLocationIndex = 0 + override fun initialize(context: ActivityContext) = setup(context) - override fun currentTask(context: TaskContext, currentLocation: LocationProperty): EntityTask { + private fun setup(context: ActivityContext) { val network = KoinJavaComponent.get(RoadNetworkManager::class.java).getNetworkOrNull(roadNetwork) - ?: return IdleTask(currentLocation) + ?: return - val nodeId = nodes.getOrNull(currentLocationIndex) ?: return IdleTask(currentLocation) - val currentTargetNode = network.nodes.find { it.id == nodeId } - ?: return IdleTask(currentLocation) + // Get the closest node to the start location + val closestNode = network.nodes + .filter { it.id in nodes } + .minByOrNull { it.location.distanceSquared(startLocation.toLocation()) } + ?: throw IllegalStateException("Could not find any node in the nodes list for the patrol activity.") - // Only move to the next location if the entity is close to the current location - if (currentLocation.toLocation().distanceSquared(currentTargetNode.location) < 1.0) { - currentLocationIndex = (currentLocationIndex + 1) % nodes.size + val index = nodes.indexOf(closestNode.id) + currentLocationIndex = (index + 1) % nodes.size + refreshActivity(context, network) + } + + + override fun tick(context: ActivityContext): TickResult { + if (activity == null) { + setup(context) + return TickResult.CONSUMED } - val gps = PointToPointGPS( - roadNetwork, - { currentLocation.toLocation() }) { - it.nodes.find { node -> node.id == nodeId }?.location ?: currentLocation.toLocation() + val network = + KoinJavaComponent.get(RoadNetworkManager::class.java).getNetworkOrNull(roadNetwork) + ?: return TickResult.IGNORED + + val result = activity?.tick(context) + if (result == TickResult.IGNORED) { + currentLocationIndex = (currentLocationIndex + 1) % nodes.size + refreshActivity(context, network) } - return NavigationActivityTask(gps, currentLocation) + + return TickResult.CONSUMED } + + override fun dispose(context: ActivityContext) { + activity?.dispose(context) + activity = null + } + + override val currentLocation: LocationProperty + get() = activity?.currentLocation ?: startLocation } diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/PlayerCloseByActivity.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/PlayerCloseByActivity.kt index 716edaf0e8..6cc511d8d9 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/PlayerCloseByActivity.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/PlayerCloseByActivity.kt @@ -4,12 +4,10 @@ import me.gabber235.typewriter.adapters.Colors import me.gabber235.typewriter.adapters.Entry import me.gabber235.typewriter.adapters.modifiers.Help import me.gabber235.typewriter.entry.Ref -import me.gabber235.typewriter.entry.descendants +import me.gabber235.typewriter.entry.emptyRef import me.gabber235.typewriter.entry.entity.* -import me.gabber235.typewriter.entry.entries.AudienceEntry import me.gabber235.typewriter.entry.entries.EntityActivityEntry -import me.gabber235.typewriter.entry.priority -import me.gabber235.typewriter.entry.ref +import me.gabber235.typewriter.entry.entries.GenericEntityActivityEntry import java.time.Duration import java.util.* @@ -30,57 +28,51 @@ import java.util.* class PlayerCloseByActivityEntry( override val id: String = "", override val name: String = "", - override val children: List> = emptyList(), @Help("The range in which the player has to be close by to activate the activity.") val range: Double = 10.0, @Help("The maximum duration a player can be idle in the same range before the activity deactivates.") val maxIdleDuration: Duration = Duration.ofSeconds(30), + @Help("The activity that will be used when there is a player close by.") + val closeByActivity: Ref = emptyRef(), + @Help("The activity that will be used when there is no player close by.") + val idleActivity: Ref = emptyRef(), override val priorityOverride: Optional = Optional.empty(), -) : EntityActivityEntry { - override fun create(context: TaskContext): EntityActivity = - PlayerCloseByActivity( - ref(), - children - .descendants(EntityActivityEntry::class) - .mapNotNull { it.get() } - .sortedByDescending { it.priority } - .map { it.create(context) }, - range, - maxIdleDuration - ) +) : GenericEntityActivityEntry { + override fun create( + context: ActivityContext, + currentLocation: LocationProperty + ): EntityActivity { + return PlayerCloseByActivity(range, maxIdleDuration, closeByActivity, idleActivity, currentLocation) + } } class PlayerCloseByActivity( - val ref: Ref, - childActivities: List, private val range: Double, private val maxIdleDuration: Duration, -) : FilterActivity(childActivities) { + private val closeByActivity: Ref, + private val idleActivity: Ref, + startLocation: LocationProperty, +) : SingleChildActivity(startLocation) { private var trackers = mutableMapOf() - override fun canActivate(context: TaskContext, currentLocation: LocationProperty): Boolean { - if (!ref.canActivateFor(context)) { - return false - } - - val trackingPlayers = context.viewers + override fun currentChild(context: ActivityContext): Ref { + val closeByPlayers = context.viewers .filter { it.isValid } .filter { (it.location.toProperty().distanceSqrt(currentLocation) ?: Double.MAX_VALUE) < range * range } - val trackingPlayerIds = trackingPlayers.map { it.uniqueId } + trackers.keys.removeAll { uuid -> closeByPlayers.none { it.uniqueId == uuid } } - trackers.keys.removeAll { it !in trackingPlayerIds } - - trackingPlayers.forEach { player -> + closeByPlayers.forEach { player -> trackers.computeIfAbsent(player.uniqueId) { PlayerLocationTracker(player.location.toProperty()) } .update(player.location.toProperty()) } - val canActivate = - !trackers.all { (_, tracker) -> tracker.isIdle(maxIdleDuration) } && - super.canActivate(context, currentLocation) - - return canActivate + val isActive = trackers.any { (_, tracker) -> tracker.isActive(maxIdleDuration) } + return if (isActive) { + closeByActivity + } else { + idleActivity + } } private class PlayerLocationTracker( @@ -93,9 +85,9 @@ class PlayerCloseByActivity( lastSeen = System.currentTimeMillis() } - fun isIdle(maxIdleDuration: Duration): Boolean { - if (maxIdleDuration.isZero) return false - return System.currentTimeMillis() - lastSeen > maxIdleDuration.toMillis() + fun isActive(maxIdleDuration: Duration): Boolean { + if (maxIdleDuration.isZero) return true + return System.currentTimeMillis() - lastSeen < maxIdleDuration.toMillis() } } } \ No newline at end of file diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/RandomLookActivityEntry.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/RandomLookActivityEntry.kt new file mode 100644 index 0000000000..3ea9f429d5 --- /dev/null +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/RandomLookActivityEntry.kt @@ -0,0 +1,76 @@ +package me.gabber235.typewriter.entries.activity + +import me.gabber235.typewriter.adapters.Colors +import me.gabber235.typewriter.adapters.Entry +import me.gabber235.typewriter.adapters.modifiers.Help +import me.gabber235.typewriter.entry.entity.* +import me.gabber235.typewriter.entry.entries.GenericEntityActivityEntry +import java.time.Duration +import java.util.* +import kotlin.random.Random + +@Entry("random_look_activity", "A random look activity", Colors.BLUE, "fa6-solid:eye") +/** + * The `Random Look Activity` is used to make the entity look in random directions. + * + * ## How could this be used? + * This could be used to make the entity look distracted. + */ +class RandomLookActivityEntry( + override val id: String = "", + override val name: String = "", + val pitchRange: ClosedFloatingPointRange = -90f..90f, + val yawRange: ClosedFloatingPointRange = -180f..180f, + @Help("The duration between each look") + val duration: Duration = Duration.ofSeconds(2), + override val priorityOverride: Optional = Optional.empty(), +) : GenericEntityActivityEntry { + override fun create(context: ActivityContext, currentLocation: LocationProperty): EntityActivity { + return RandomLookActivity(pitchRange, yawRange, duration, currentLocation) + } +} + +class RandomLookActivity( + private val pitchRange: ClosedFloatingPointRange, + private val yawRange: ClosedFloatingPointRange, + private val duration: Duration, + override var currentLocation: LocationProperty, +) : GenericEntityActivity { + private var targetPitch: Float = pitchRange.random() + private var targetYaw: Float = yawRange.random() + private val pitchVelocity = Velocity(0f) + private val yawVelocity = Velocity(0f) + private var lastChangeTime = System.currentTimeMillis() + + + override fun initialize(context: ActivityContext) {} + + override fun tick(context: ActivityContext): TickResult { + val currentTime = System.currentTimeMillis() + if (currentTime - lastChangeTime > duration.toMillis()) { + targetPitch = pitchRange.random() + targetYaw = yawRange.random() + lastChangeTime = currentTime + } + + + val (yaw, pitch) = updateLookDirection( + LookDirection(currentLocation.yaw, currentLocation.pitch), + LookDirection(targetYaw, targetPitch), + yawVelocity, + pitchVelocity, + smoothTime = 0.5f, + ) + + currentLocation = + LocationProperty(currentLocation.world, currentLocation.x, currentLocation.y, currentLocation.z, yaw, pitch) + + return TickResult.CONSUMED + } + + override fun dispose(context: ActivityContext) {} +} + +fun ClosedFloatingPointRange.random(): Float { + return start + (endInclusive - start) * Random.nextFloat() +} \ No newline at end of file diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/TargetLocationActivity.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/TargetLocationActivity.kt index 009d98a612..7068aa4fae 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/TargetLocationActivity.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/TargetLocationActivity.kt @@ -2,12 +2,13 @@ package me.gabber235.typewriter.entries.activity import me.gabber235.typewriter.adapters.Colors import me.gabber235.typewriter.adapters.Entry +import me.gabber235.typewriter.adapters.modifiers.Help import me.gabber235.typewriter.entry.Ref import me.gabber235.typewriter.entry.emptyRef import me.gabber235.typewriter.entry.entity.* import me.gabber235.typewriter.entry.entries.EntityActivityEntry +import me.gabber235.typewriter.entry.entries.GenericEntityActivityEntry import me.gabber235.typewriter.entry.entries.RoadNetworkEntry -import me.gabber235.typewriter.entry.ref import me.gabber235.typewriter.entry.roadnetwork.gps.PointToPointGPS import me.gabber235.typewriter.snippets.snippet import org.bukkit.Location @@ -29,32 +30,100 @@ class TargetLocationActivityEntry( override val name: String = "", val roadNetwork: Ref = emptyRef(), val targetLocation: Location = Location(null, 0.0, 0.0, 0.0), + @Help("The activity that will be used when the entity is at the target location.") + val idleActivity: Ref = emptyRef(), override val priorityOverride: Optional = Optional.empty(), -) : EntityActivityEntry { - override fun create(context: TaskContext): EntityActivity = - TargetLocationActivity(ref(), roadNetwork, targetLocation) +) : GenericEntityActivityEntry { + override fun create( + context: ActivityContext, + currentLocation: LocationProperty + ): EntityActivity { + return TargetLocationActivity(roadNetwork, targetLocation, idleActivity, currentLocation) + } } class TargetLocationActivity( - val ref: Ref, private val network: Ref, private val targetLocation: Location, -) : EntityActivity { - override fun canActivate(context: TaskContext, currentLocation: LocationProperty): Boolean { - if (!ref.canActivateFor(context)) { - return false + private val idleActivity: Ref, + private val startLocation: LocationProperty, +) : GenericEntityActivity { + private var state: State = IdleState() + private var currentActivity: EntityActivity? = null + + + private interface State { + val isValid: Boolean + fun nextState(): State + fun createActivity( + context: ActivityContext, + currentLocation: LocationProperty + ): EntityActivity + } + + private inner class IdleState : State { + override val isValid: Boolean + get() { + val distance = currentLocation.distanceSqrt(targetLocation) ?: return false + return distance <= locationActivityRange * locationActivityRange + } + + override fun nextState(): State = NavigatingState() + + override fun createActivity( + context: ActivityContext, + currentLocation: LocationProperty + ): EntityActivity { + return (idleActivity.get() ?: IdleActivity).create(context, currentLocation) + } + } + + private inner class NavigatingState : State { + override val isValid: Boolean + get() { + val distance = currentLocation.distanceSqrt(targetLocation) ?: return true + return distance > locationActivityRange * locationActivityRange + } + + override fun nextState(): State = IdleState() + + override fun createActivity( + context: ActivityContext, + currentLocation: LocationProperty + ): EntityActivity { + return NavigationActivity(PointToPointGPS( + network, + { currentLocation.toLocation() }, + { targetLocation } + ), currentLocation) + } + } + + override fun initialize(context: ActivityContext) { + if (!state.isValid) { + state = state.nextState() } - // Only activate if outside the defined range - val distance = currentLocation.distanceSqrt(targetLocation.toProperty()) ?: return false - return distance > locationActivityRange * locationActivityRange + currentActivity = state.createActivity(context, currentLocation) + currentActivity?.initialize(context) } - override fun currentTask(context: TaskContext, currentLocation: LocationProperty): EntityTask { - return NavigationActivityTask(PointToPointGPS( - network, - { currentLocation.toLocation() }, - { targetLocation } - ), currentLocation) + override fun tick(context: ActivityContext): TickResult { + if (!state.isValid) { + currentActivity?.dispose(context) + state = state.nextState() + currentActivity = state.createActivity(context, currentLocation) + currentActivity?.initialize(context) + } + + return currentActivity?.tick(context) ?: TickResult.IGNORED } + + override fun dispose(context: ActivityContext) { + currentActivity?.dispose(context) + currentActivity = null + } + + override val currentLocation: LocationProperty + get() = currentActivity?.currentLocation ?: startLocation } \ No newline at end of file diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/TimedActivity.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/TimedActivity.kt index 32c25a4b91..ef47b032fb 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/TimedActivity.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/activity/TimedActivity.kt @@ -4,12 +4,13 @@ import me.gabber235.typewriter.adapters.Colors import me.gabber235.typewriter.adapters.Entry import me.gabber235.typewriter.adapters.modifiers.Help import me.gabber235.typewriter.entry.Ref -import me.gabber235.typewriter.entry.descendants -import me.gabber235.typewriter.entry.entity.* -import me.gabber235.typewriter.entry.entries.AudienceEntry +import me.gabber235.typewriter.entry.emptyRef +import me.gabber235.typewriter.entry.entity.ActivityContext +import me.gabber235.typewriter.entry.entity.EntityActivity +import me.gabber235.typewriter.entry.entity.LocationProperty +import me.gabber235.typewriter.entry.entity.SingleChildActivity import me.gabber235.typewriter.entry.entries.EntityActivityEntry -import me.gabber235.typewriter.entry.priority -import me.gabber235.typewriter.entry.ref +import me.gabber235.typewriter.entry.entries.GenericEntityActivityEntry import java.time.Duration import java.util.* @@ -31,70 +32,61 @@ import java.util.* class TimedActivityEntry( override val id: String = "", override val name: String = "", - override val children: List> = emptyList(), @Help("The duration child activities will be active for.") val duration: Duration = Duration.ofSeconds(10), @Help("The cooldown time before the activity can be activated again.") val cooldown: Duration = Duration.ofSeconds(1), + @Help("The activity that will be used when the duration is active.") + val activeActivity: Ref = emptyRef(), + @Help("The activity that will be used when it is on cooldown.") + val cooldownActivity: Ref = emptyRef(), override val priorityOverride: Optional = Optional.empty(), -) : EntityActivityEntry { - override fun create(context: TaskContext): EntityActivity { - return TimedActivity( - ref(), - children - .descendants(EntityActivityEntry::class) - .mapNotNull { it.get() } - .sortedByDescending { it.priority } - .map { it.create(context) }, - duration, - cooldown, - ) +) : GenericEntityActivityEntry { + override fun create( + context: ActivityContext, + currentLocation: LocationProperty + ): EntityActivity { + return TimedActivity(duration, cooldown, activeActivity, cooldownActivity, currentLocation) } } class TimedActivity( - val ref: Ref, - children: List, private val duration: Duration, private val cooldown: Duration, -) : FilterActivity(children) { - private var trackers = mutableMapOf() + private val activeActivity: Ref, + private val cooldownActivity: Ref, + startLocation: LocationProperty, +) : SingleChildActivity(startLocation) { + private var state: TimedActivityState = Active(System.currentTimeMillis()) - override fun canActivate(context: TaskContext, currentLocation: LocationProperty): Boolean { - if (!ref.canActivateFor(context)) { - return false + override fun currentChild(context: ActivityContext): Ref { + if (state.hasExpired) { + state = state.nextState } - if (!super.canActivate(context, currentLocation)) { - return false + return when (state) { + is Active -> activeActivity + is Cooldown -> cooldownActivity + else -> cooldownActivity } - - context.viewers.forEach { player -> - val tracker = trackers.computeIfAbsent(player.uniqueId) { - PlayerTimeTracker(duration, cooldown) - } - tracker.update() - } - - return !trackers.all { (_, tracker) -> tracker.isActive } } - private inner class PlayerTimeTracker( - private val duration: Duration, - private val cooldown: Duration, - ) { - private var lastStart = System.currentTimeMillis() - - val isActive: Boolean - get() = System.currentTimeMillis() - lastStart < duration.toMillis() + interface TimedActivityState { + val hasExpired: Boolean + val nextState: TimedActivityState + } - val isCooldown: Boolean - get() = System.currentTimeMillis() - lastStart < cooldown.toMillis() + duration.toMillis() + inner class Active(private val startTime: Long) : TimedActivityState { + override val hasExpired: Boolean + get() = System.currentTimeMillis() - startTime > duration.toMillis() + override val nextState: TimedActivityState + get() = Cooldown(System.currentTimeMillis()) + } - fun update() { - if (!isCooldown) { - lastStart = System.currentTimeMillis() - } - } + inner class Cooldown(private val startTime: Long) : TimedActivityState { + override val hasExpired: Boolean + get() = System.currentTimeMillis() - startTime > cooldown.toMillis() + override val nextState: TimedActivityState + get() = Active(System.currentTimeMillis()) } } \ No newline at end of file diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/custom/Npc.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/custom/Npc.kt index 4ea1acee64..0026d9e05d 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/custom/Npc.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/custom/Npc.kt @@ -72,8 +72,8 @@ class NpcInstance( override val definition: Ref = emptyRef(), override val spawnLocation: Location = Location(null, 0.0, 0.0, 0.0), @OnlyTags("generic_entity_data", "living_entity_data", "lines", "player_data") - override val data: List>>, - override val activities: List> = emptyList(), + override val data: List>> = emptyList(), + override val activity: Ref = emptyRef(), ) : SimpleEntityInstance class NpcEntity( diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/CowEntity.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/CowEntity.kt index 4b2d3a3258..684b02f733 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/CowEntity.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/CowEntity.kt @@ -18,6 +18,7 @@ import me.gabber235.typewriter.entry.entity.WrapperFakeEntity import me.gabber235.typewriter.entry.entries.EntityActivityEntry import me.gabber235.typewriter.entry.entries.EntityData import me.gabber235.typewriter.entry.entries.EntityProperty +import me.gabber235.typewriter.entry.entries.SharedEntityActivityEntry import me.gabber235.typewriter.utils.Sound import org.bukkit.Location import org.bukkit.entity.Player @@ -49,7 +50,7 @@ class CowInstance( override val spawnLocation: Location = Location(null, 0.0, 0.0, 0.0), @OnlyTags("generic_entity_data", "living_entity_data", "mob_data", "ageable_data", "cow_data") override val data: List>> = emptyList(), - override val activities: List> = emptyList(), + override val activity: Ref = emptyRef(), ) : SimpleEntityInstance private class CowEntity(player: Player) : WrapperFakeEntity( diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/EndermanEntity.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/EndermanEntity.kt index 1272d3aa93..a0e699dfb2 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/EndermanEntity.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/EndermanEntity.kt @@ -16,6 +16,7 @@ import me.gabber235.typewriter.entry.entity.WrapperFakeEntity import me.gabber235.typewriter.entry.entries.EntityActivityEntry import me.gabber235.typewriter.entry.entries.EntityData import me.gabber235.typewriter.entry.entries.EntityProperty +import me.gabber235.typewriter.entry.entries.SharedEntityActivityEntry import me.gabber235.typewriter.utils.Sound import org.bukkit.Location import org.bukkit.entity.Player @@ -47,7 +48,7 @@ class EndermanInstance( override val spawnLocation: Location = Location(null, 0.0, 0.0, 0.0), @OnlyTags("generic_entity_data", "living_entity_data", "mob_data") override val data: List>> = emptyList(), - override val activities: List> = emptyList(), + override val activity: Ref = emptyRef(), ) : SimpleEntityInstance private class EndermanEntity(player: Player) : WrapperFakeEntity( diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/HoglinEntity.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/HoglinEntity.kt index ccadc101e1..7923f7390a 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/HoglinEntity.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/HoglinEntity.kt @@ -16,6 +16,7 @@ import me.gabber235.typewriter.entry.entity.WrapperFakeEntity import me.gabber235.typewriter.entry.entries.EntityActivityEntry import me.gabber235.typewriter.entry.entries.EntityData import me.gabber235.typewriter.entry.entries.EntityProperty +import me.gabber235.typewriter.entry.entries.SharedEntityActivityEntry import me.gabber235.typewriter.utils.Sound import org.bukkit.Location import org.bukkit.entity.Player @@ -47,7 +48,7 @@ class HoglinInstance( override val spawnLocation: Location = Location(null, 0.0, 0.0, 0.0), @OnlyTags("generic_entity_data", "living_entity_data", "mob_data", "ageable_data", "trembling_data") override val data: List>> = emptyList(), - override val activities: List> = emptyList(), + override val activity: Ref = emptyRef(), ) : SimpleEntityInstance private class HoglinEntity(player: Player) : WrapperFakeEntity( diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/HuskEntity.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/HuskEntity.kt index 19b3da16cb..e8738eb6ae 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/HuskEntity.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/HuskEntity.kt @@ -16,6 +16,7 @@ import me.gabber235.typewriter.entry.entity.WrapperFakeEntity import me.gabber235.typewriter.entry.entries.EntityActivityEntry import me.gabber235.typewriter.entry.entries.EntityData import me.gabber235.typewriter.entry.entries.EntityProperty +import me.gabber235.typewriter.entry.entries.SharedEntityActivityEntry import me.gabber235.typewriter.utils.Sound import org.bukkit.Location import org.bukkit.entity.Player @@ -47,7 +48,7 @@ class HuskInstance( override val spawnLocation: Location = Location(null, 0.0, 0.0, 0.0), @OnlyTags("generic_entity_data", "living_entity_data", "mob_data", "ageable_data") override val data: List>> = emptyList(), - override val activities: List> = emptyList(), + override val activity: Ref = emptyRef(), ) : SimpleEntityInstance private class HuskEntity(player: Player) : WrapperFakeEntity( diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/IronGolemEntity.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/IronGolemEntity.kt index a9d21f048b..675914d2d9 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/IronGolemEntity.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/IronGolemEntity.kt @@ -16,6 +16,7 @@ import me.gabber235.typewriter.entry.entity.WrapperFakeEntity import me.gabber235.typewriter.entry.entries.EntityActivityEntry import me.gabber235.typewriter.entry.entries.EntityData import me.gabber235.typewriter.entry.entries.EntityProperty +import me.gabber235.typewriter.entry.entries.SharedEntityActivityEntry import me.gabber235.typewriter.utils.Sound import org.bukkit.Location import org.bukkit.entity.Player @@ -47,7 +48,7 @@ class IronGolemInstance( override val spawnLocation: Location = Location(null, 0.0, 0.0, 0.0), @OnlyTags("generic_entity_data", "living_entity_data", "mob_data") override val data: List>> = emptyList(), - override val activities: List> = emptyList(), + override val activity: Ref = emptyRef(), ) : SimpleEntityInstance private class IronGolemEntity(player: Player) : WrapperFakeEntity( diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/ItemDisplayEntity.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/ItemDisplayEntity.kt index 83b0c4d677..2682ea3dd8 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/ItemDisplayEntity.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/ItemDisplayEntity.kt @@ -20,6 +20,7 @@ import me.gabber235.typewriter.entry.entity.WrapperFakeEntity import me.gabber235.typewriter.entry.entries.EntityActivityEntry import me.gabber235.typewriter.entry.entries.EntityData import me.gabber235.typewriter.entry.entries.EntityProperty +import me.gabber235.typewriter.entry.entries.SharedEntityActivityEntry import me.gabber235.typewriter.utils.Sound import org.bukkit.Location import org.bukkit.entity.Player @@ -57,7 +58,7 @@ class ItemDisplayInstance( override val spawnLocation: Location = Location(null, 0.0, 0.0, 0.0), @OnlyTags("generic_entity_data", "display_data", "item_data") override val data: List>> = emptyList(), - override val activities: List> = emptyList(), + override val activity: Ref = emptyRef(), ) : SimpleEntityInstance open class ItemDisplayEntity(player: Player) : WrapperFakeEntity( diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/PiglinBruteEntity.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/PiglinBruteEntity.kt index bbf04910c0..63851ba05f 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/PiglinBruteEntity.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/PiglinBruteEntity.kt @@ -18,6 +18,7 @@ import me.gabber235.typewriter.entry.entity.WrapperFakeEntity import me.gabber235.typewriter.entry.entries.EntityActivityEntry import me.gabber235.typewriter.entry.entries.EntityData import me.gabber235.typewriter.entry.entries.EntityProperty +import me.gabber235.typewriter.entry.entries.SharedEntityActivityEntry import me.gabber235.typewriter.utils.Sound import org.bukkit.Location import org.bukkit.entity.Player @@ -49,7 +50,7 @@ class PiglinBruteInstance( override val spawnLocation: Location = Location(null, 0.0, 0.0, 0.0), @OnlyTags("generic_entity_data", "living_entity_data", "mob_data", "trembling_data") override val data: List>> = emptyList(), - override val activities: List> = emptyList(), + override val activity: Ref = emptyRef(), ) : SimpleEntityInstance private class PiglinBruteEntity(player: Player) : WrapperFakeEntity( diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/PiglinEntity.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/PiglinEntity.kt index fe4eca31b6..bacfc85609 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/PiglinEntity.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/PiglinEntity.kt @@ -18,6 +18,7 @@ import me.gabber235.typewriter.entry.entity.WrapperFakeEntity import me.gabber235.typewriter.entry.entries.EntityActivityEntry import me.gabber235.typewriter.entry.entries.EntityData import me.gabber235.typewriter.entry.entries.EntityProperty +import me.gabber235.typewriter.entry.entries.SharedEntityActivityEntry import me.gabber235.typewriter.utils.Sound import org.bukkit.Location import org.bukkit.entity.Player @@ -63,7 +64,7 @@ class PiglinInstance( "piglin_dancing_data" ) override val data: List>> = emptyList(), - override val activities: List> = emptyList(), + override val activity: Ref = emptyRef(), ) : SimpleEntityInstance private class PiglinEntity(player: Player) : WrapperFakeEntity( diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/PlayerEntity.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/PlayerEntity.kt index 800d5efe1c..1b76204a41 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/PlayerEntity.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/PlayerEntity.kt @@ -16,6 +16,7 @@ import me.gabber235.typewriter.entry.entity.* import me.gabber235.typewriter.entry.entries.EntityActivityEntry import me.gabber235.typewriter.entry.entries.EntityData import me.gabber235.typewriter.entry.entries.EntityProperty +import me.gabber235.typewriter.entry.entries.SharedEntityActivityEntry import me.gabber235.typewriter.extensions.packetevents.meta import me.gabber235.typewriter.extensions.packetevents.sendPacketTo import me.gabber235.typewriter.utils.Sound @@ -60,7 +61,7 @@ class PlayerInstance( override val spawnLocation: Location = Location(null, 0.0, 0.0, 0.0), @OnlyTags("generic_entity_data", "living_entity_data", "player_data") override val data: List>> = emptyList(), - override val activities: List> = emptyList(), + override val activity: Ref = emptyRef(), ) : SimpleEntityInstance class PlayerEntity( diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/SkeletonEntity.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/SkeletonEntity.kt index 59db0a9dcb..90460bb60c 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/SkeletonEntity.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/SkeletonEntity.kt @@ -16,6 +16,7 @@ import me.gabber235.typewriter.entry.entity.WrapperFakeEntity import me.gabber235.typewriter.entry.entries.EntityActivityEntry import me.gabber235.typewriter.entry.entries.EntityData import me.gabber235.typewriter.entry.entries.EntityProperty +import me.gabber235.typewriter.entry.entries.SharedEntityActivityEntry import me.gabber235.typewriter.utils.Sound import org.bukkit.Location import org.bukkit.entity.Player @@ -47,7 +48,7 @@ class SkeletonInstance( override val spawnLocation: Location = Location(null, 0.0, 0.0, 0.0), @OnlyTags("generic_entity_data", "living_entity_data", "mob_data") override val data: List>> = emptyList(), - override val activities: List> = emptyList(), + override val activity: Ref = emptyRef(), ) : SimpleEntityInstance private class SkeletonEntity(player: Player) : WrapperFakeEntity( diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/TextDisplayEntity.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/TextDisplayEntity.kt index 955c5a089c..f32a00f02d 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/TextDisplayEntity.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/TextDisplayEntity.kt @@ -14,10 +14,7 @@ import me.gabber235.typewriter.entry.entity.FakeEntity import me.gabber235.typewriter.entry.entity.SimpleEntityDefinition import me.gabber235.typewriter.entry.entity.SimpleEntityInstance import me.gabber235.typewriter.entry.entity.WrapperFakeEntity -import me.gabber235.typewriter.entry.entries.EntityActivityEntry -import me.gabber235.typewriter.entry.entries.EntityData -import me.gabber235.typewriter.entry.entries.EntityProperty -import me.gabber235.typewriter.entry.entries.LinesProperty +import me.gabber235.typewriter.entry.entries.* import me.gabber235.typewriter.extensions.packetevents.meta import me.gabber235.typewriter.utils.Sound import me.gabber235.typewriter.utils.asMini @@ -57,7 +54,7 @@ class TextDisplayInstance( override val spawnLocation: Location = Location(null, 0.0, 0.0, 0.0), @OnlyTags("generic_entity_data", "display_data", "lines", "text_display_data") override val data: List>> = emptyList(), - override val activities: List> = emptyList(), + override val activity: Ref = emptyRef(), ) : SimpleEntityInstance open class TextDisplayEntity(player: Player) : WrapperFakeEntity( diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/VillagerEntity.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/VillagerEntity.kt index e6485b7afe..87f8f08879 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/VillagerEntity.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/VillagerEntity.kt @@ -20,6 +20,7 @@ import me.gabber235.typewriter.entry.entity.WrapperFakeEntity import me.gabber235.typewriter.entry.entries.EntityActivityEntry import me.gabber235.typewriter.entry.entries.EntityData import me.gabber235.typewriter.entry.entries.EntityProperty +import me.gabber235.typewriter.entry.entries.SharedEntityActivityEntry import me.gabber235.typewriter.utils.Sound import org.bukkit.Location import org.bukkit.entity.Player @@ -52,7 +53,7 @@ class VillagerInstance( override val spawnLocation: Location = Location(null, 0.0, 0.0, 0.0), @OnlyTags("generic_entity_data", "living_entity_data", "mob_data", "ageable_data", "villager_data") override val data: List>> = emptyList(), - override val activities: List> = emptyList(), + override val activity: Ref = emptyRef(), ) : SimpleEntityInstance private class VillagerEntity(player: Player) : WrapperFakeEntity( diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/WardenEntity.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/WardenEntity.kt index 7bbc4f6d44..e1732fa7ce 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/WardenEntity.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/WardenEntity.kt @@ -18,6 +18,7 @@ import me.gabber235.typewriter.entry.entity.WrapperFakeEntity import me.gabber235.typewriter.entry.entries.EntityActivityEntry import me.gabber235.typewriter.entry.entries.EntityData import me.gabber235.typewriter.entry.entries.EntityProperty +import me.gabber235.typewriter.entry.entries.SharedEntityActivityEntry import me.gabber235.typewriter.utils.Sound import org.bukkit.Location import org.bukkit.entity.Player @@ -49,7 +50,7 @@ class WardenInstance( override val spawnLocation: Location = Location(null, 0.0, 0.0, 0.0), @OnlyTags("generic_entity_data", "living_entity_data", "mob_data") override val data: List>> = emptyList(), - override val activities: List> = emptyList(), + override val activity: Ref = emptyRef(), ) : SimpleEntityInstance private class WardenEntity(player: Player) : WrapperFakeEntity( diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/WitchEntity.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/WitchEntity.kt index 81b7f8d815..0e5ff4d308 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/WitchEntity.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/WitchEntity.kt @@ -16,6 +16,7 @@ import me.gabber235.typewriter.entry.entity.WrapperFakeEntity import me.gabber235.typewriter.entry.entries.EntityActivityEntry import me.gabber235.typewriter.entry.entries.EntityData import me.gabber235.typewriter.entry.entries.EntityProperty +import me.gabber235.typewriter.entry.entries.SharedEntityActivityEntry import me.gabber235.typewriter.utils.Sound import org.bukkit.Location import org.bukkit.entity.Player @@ -47,7 +48,7 @@ class WitchInstance( override val spawnLocation: Location = Location(null, 0.0, 0.0, 0.0), @OnlyTags("generic_entity_data", "living_entity_data", "mob_data") override val data: List>> = emptyList(), - override val activities: List> = emptyList(), + override val activity: Ref = emptyRef(), ) : SimpleEntityInstance private class WitchEntity(player: Player) : WrapperFakeEntity( diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/ZombieEntity.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/ZombieEntity.kt index 585c68a1f2..e5eb44bd77 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/ZombieEntity.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/minecraft/ZombieEntity.kt @@ -16,6 +16,7 @@ import me.gabber235.typewriter.entry.entity.WrapperFakeEntity import me.gabber235.typewriter.entry.entries.EntityActivityEntry import me.gabber235.typewriter.entry.entries.EntityData import me.gabber235.typewriter.entry.entries.EntityProperty +import me.gabber235.typewriter.entry.entries.SharedEntityActivityEntry import me.gabber235.typewriter.utils.Sound import org.bukkit.Location import org.bukkit.entity.Player @@ -47,7 +48,7 @@ class ZombieInstance( override val spawnLocation: Location = Location(null, 0.0, 0.0, 0.0), @OnlyTags("generic_entity_data", "living_entity_data", "mob_data", "ageable_data") override val data: List>> = emptyList(), - override val activities: List> = emptyList(), + override val activity: Ref = emptyRef(), ) : SimpleEntityInstance private class ZombieEntity(player: Player) : WrapperFakeEntity( diff --git a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/instance/AdvancedEntityInstanceEntry.kt b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/instance/AdvancedEntityInstanceEntry.kt index b89452ba2f..be321a2150 100644 --- a/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/instance/AdvancedEntityInstanceEntry.kt +++ b/adapters/EntityAdapter/src/main/kotlin/me/gabber235/typewriter/entries/instance/AdvancedEntityInstanceEntry.kt @@ -4,21 +4,40 @@ import me.gabber235.typewriter.adapters.Colors import me.gabber235.typewriter.adapters.Entry import me.gabber235.typewriter.entry.Ref import me.gabber235.typewriter.entry.emptyRef -import me.gabber235.typewriter.entry.entity.AdvancedEntityInstance +import me.gabber235.typewriter.entry.entity.IndividualAdvancedEntityInstance +import me.gabber235.typewriter.entry.entity.SharedAdvancedEntityInstance import me.gabber235.typewriter.entry.entries.AudienceEntry import me.gabber235.typewriter.entry.entries.EntityDefinitionEntry +import me.gabber235.typewriter.entry.entries.IndividualEntityActivityEntry +import me.gabber235.typewriter.entry.entries.SharedEntityActivityEntry import org.bukkit.Location @Entry( - "advanced_entity_instance", + "shared_advanced_entity_instance", "An advanced instance of an entity", Colors.YELLOW, "material-symbols:settings-account-box" ) -class AdvancedEntityInstanceEntry( +class SharedAdvancedEntityInstanceEntry( override val id: String = "", override val name: String = "", override val definition: Ref = emptyRef(), override val spawnLocation: Location = Location(null, 0.0, 0.0, 0.0), override val children: List> = emptyList(), -) : AdvancedEntityInstance \ No newline at end of file + override val activity: Ref = emptyRef(), +) : SharedAdvancedEntityInstance + +@Entry( + "individual_advanced_entity_instance", + "An advanced instance of an entity", + Colors.YELLOW, + "material-symbols:settings-account-box" +) +class IndividualAdvancedEntityInstanceEntry( + override val id: String = "", + override val name: String = "", + override val definition: Ref = emptyRef(), + override val spawnLocation: Location = Location(null, 0.0, 0.0, 0.0), + override val children: List> = emptyList(), + override val activity: Ref = emptyRef(), +) : IndividualAdvancedEntityInstance \ No newline at end of file diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 957cfea02e..ae0d2661c9 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -53,8 +53,8 @@ dependencies { api("com.github.Tofaa2.EntityLib:spigot:2.2.0-SNAPSHOT") api("com.github.shynixn.mccoroutine:mccoroutine-bukkit-api:2.11.0") api("com.github.shynixn.mccoroutine:mccoroutine-bukkit-core:2.11.0") - api("dev.jorel:commandapi-bukkit-shade:9.4.1") - api("dev.jorel:commandapi-bukkit-kotlin:9.4.1") + api("dev.jorel:commandapi-bukkit-shade:9.4.2") + api("dev.jorel:commandapi-bukkit-kotlin:9.4.2") // Doesn't want to load properly using the spigot api. implementation("io.ktor:ktor-server-core-jvm:2.3.6") diff --git a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/ActivityEntityDisplay.kt b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/ActivityEntityDisplay.kt index 923fd6a439..02623f6576 100644 --- a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/ActivityEntityDisplay.kt +++ b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/ActivityEntityDisplay.kt @@ -8,7 +8,8 @@ interface ActivityEntityDisplay { val creator: EntityCreator val definition: EntityDefinitionEntry? get() = creator as? EntityDefinitionEntry - fun playerHasEntity(playerId: UUID, entityId: Int): Boolean + + fun playerSeesEntity(playerId: UUID, entityId: Int): Boolean /** * The location of the entity for the player. diff --git a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/ActivityManager.kt b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/ActivityManager.kt index e6877ea374..95751c2b24 100644 --- a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/ActivityManager.kt +++ b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/ActivityManager.kt @@ -1,84 +1,25 @@ package me.gabber235.typewriter.entry.entity -import me.gabber235.typewriter.entry.Ref -import me.gabber235.typewriter.entry.entries.EntityInstanceEntry import me.gabber235.typewriter.entry.entries.EntityProperty -import org.bukkit.Location -class ActivityManager( - instanceRef: Ref, - private val activities: List, - spawnLocation: Location, +class ActivityManager( + private val activity: EntityActivity, ) { - private var activity: EntityActivity? = null - private var task: EntityTask = IdleTask(spawnLocation.toProperty()) - set(value) { - field.dispose() - field = value - } - - init { - findNewTask(EmptyTaskContext(instanceRef)) - } - val location: LocationProperty - get() = task.location + get() = activity.currentLocation val activeProperties: List - get() = listOf(location) + get() = activity.currentProperties - private fun findNewTask(context: TaskContext): Boolean { - val newActivity = activities.firstOrNull { - it.canActivate(context, location) - } - if (newActivity == null) { - activity = null - task = IdleTask(location) - return false - } - if (activity == null) { - activity = newActivity - task = newActivity.currentTask(context, location) - return true - } - - if (activity != newActivity) { - if (!task.mayInterrupt()) return false - activity = newActivity - task = newActivity.currentTask(context, location) - return true - } - - if (!task.isComplete()) return false - task = newActivity.currentTask(context, location) - return true + fun initialize(context: Context) { + activity.initialize(context) } - fun tick(context: TaskContext) { - findNewTask(context) - task.tick(context) - } - - fun dispose() { - } -} - -class IdleTask(override val location: LocationProperty) : EntityTask { - override fun tick(context: TaskContext) {} - override fun mayInterrupt(): Boolean = true - - override fun isComplete(): Boolean = true -} - -abstract class FilterActivity( - private val children: List, -) : EntityActivity { - override fun canActivate(context: TaskContext, currentLocation: LocationProperty): Boolean { - return children.any { it.canActivate(context, currentLocation) } + fun tick(context: Context) { + activity.tick(context) } - override fun currentTask(context: TaskContext, currentLocation: LocationProperty): EntityTask { - return children.firstOrNull { it.canActivate(context, currentLocation) }?.currentTask(context, currentLocation) - ?: IdleTask(currentLocation) + fun dispose(context: Context) { + activity.dispose(context) } } \ No newline at end of file diff --git a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/AdvancedEntity.kt b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/AdvancedEntity.kt index 6f5e42fe63..f0927865f7 100644 --- a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/AdvancedEntity.kt +++ b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/AdvancedEntity.kt @@ -1,5 +1,6 @@ package me.gabber235.typewriter.entry.entity +import me.gabber235.typewriter.adapters.Tags import me.gabber235.typewriter.entry.Ref import me.gabber235.typewriter.entry.descendants import me.gabber235.typewriter.entry.entries.* @@ -8,70 +9,58 @@ import me.gabber235.typewriter.entry.ref import me.gabber235.typewriter.logger import org.bukkit.Location -interface AdvancedEntityInstance : EntityInstanceEntry { +@Tags("shared_entity_instance") +interface SharedAdvancedEntityInstance : EntityInstanceEntry { + val activity: Ref + override fun display(): AudienceFilter { - val definition = definition.get() - if (definition == null) { - logger.warning("You must specify a definition for $name") - return PassThroughFilter(ref()) - } + val activityCreator = this.activity.get() ?: IdleActivity - return children.toAdvancedEntityDisplay( - ref(), - definition, - definition.data.withPriority(), - spawnLocation, + return toAdvancedEntityDisplay( + activityCreator, + ::SharedActivityEntityDisplay, ) } } -/** - * If the activities are only linked from the root with only other activities, we can use a `CommonActivityEntityDisplay`. - * Otherwise, we need to have a `PlayerSpecificActivityEntityDisplay`. - */ -fun List>.toAdvancedEntityDisplay( - ref: Ref, - creator: EntityCreator, - baseSuppliers: List, Int>> = emptyList(), - spawnLocation: Location, +@Tags("individual_entity_instance") +interface IndividualAdvancedEntityInstance : EntityInstanceEntry { + val activity: Ref + + override fun display(): AudienceFilter { + val activityCreator = this.activity.get() ?: IdleActivity + + return toAdvancedEntityDisplay( + activityCreator, + ::IndividualActivityEntityDisplay, + ) + } +} + +private fun EntityInstanceEntry.toAdvancedEntityDisplay( + activityCreator: ActivityCreator, + creator: (Ref, EntityDefinitionEntry, ActivityCreator, List, Int>>, Location) -> AudienceFilter, ): AudienceFilter { - val activityCreators = descendants(EntityActivityEntry::class) - .mapNotNull { it.get() } - .sortedByDescending { it.priority } + val definition = definition.get() + if (definition == null) { + logger.warning("You must specify a definition for $name") + return PassThroughFilter(ref()) + } + + val baseSuppliers = definition.data.withPriority() val maxBaseSupplier = baseSuppliers.maxOfOrNull { it.second } ?: 0 - val overrideSuppliers = descendants(EntityData::class) + val overrideSuppliers = children.descendants(EntityData::class) .mapNotNull { it.get() } .map { it to (it.priority + maxBaseSupplier + 1) } val suppliers = (baseSuppliers + overrideSuppliers) - val activitiesOnlyConnectedByActivities = activityOnlyConnectedByActivity(true) - - return if (activitiesOnlyConnectedByActivities) CommonActivityEntityDisplay( - ref, - creator, - activityCreators, + return creator( + ref(), + definition, + activityCreator, suppliers, spawnLocation, ) - else PlayerSpecificActivityEntityDisplay(ref, creator, activityCreators, suppliers, spawnLocation) -} - -private fun List>.activityOnlyConnectedByActivity( - seenOnlySeenActivities: Boolean, -): Boolean { - return all { ref -> - val entry = ref.get() ?: return@all true - when { - entry is EntityActivityEntry && seenOnlySeenActivities -> entry.children - .activityOnlyConnectedByActivity(true) - - entry is EntityActivityEntry -> false - entry is AudienceFilterEntry -> entry.children - .activityOnlyConnectedByActivity(false) - - else -> true - } - } } \ No newline at end of file diff --git a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/DisplayEntity.kt b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/DisplayEntity.kt index 93ba3b717c..bfb96a0ce3 100644 --- a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/DisplayEntity.kt +++ b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/DisplayEntity.kt @@ -11,7 +11,7 @@ import kotlin.reflect.full.companionObjectInstance internal class DisplayEntity( private val player: Player, creator: EntityCreator, - private val activityManager: ActivityManager, + private val activityManager: ActivityManager<*>, private val collectors: List>, ) { private val entity = creator.create(player) diff --git a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/EntityActivity.kt b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/EntityActivity.kt index ae88babcca..7601fe0add 100644 --- a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/EntityActivity.kt +++ b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/EntityActivity.kt @@ -1,70 +1,110 @@ package me.gabber235.typewriter.entry.entity import me.gabber235.typewriter.entry.Ref +import me.gabber235.typewriter.entry.emptyRef import me.gabber235.typewriter.entry.entries.EntityActivityEntry import me.gabber235.typewriter.entry.entries.EntityInstanceEntry -import me.gabber235.typewriter.entry.inAudience +import me.gabber235.typewriter.entry.entries.EntityProperty import org.bukkit.entity.Player - interface ActivityCreator { - fun create(context: TaskContext): EntityActivity + fun create(context: ActivityContext, currentLocation: LocationProperty): EntityActivity +} + +interface EntityActivity { + fun initialize(context: Context) + fun tick(context: Context): TickResult + fun dispose(context: Context) + + val currentLocation: LocationProperty + val currentProperties: List get() = listOf(currentLocation) } /** - * Class can be recreated at any time. Must not hold a disposable state. + * Indicates what the result of the tick is. * - * If the activity is from an entry, make sure that this checks if the context can be activated for the activity. + * Some activities may want to do fallback actions if the tick is ignored. */ -interface EntityActivity { - fun canActivate(context: TaskContext, currentLocation: LocationProperty): Boolean - fun currentTask(context: TaskContext, currentLocation: LocationProperty): EntityTask +enum class TickResult { + // The activity is done and everything is fine. + CONSUMED, + + // The activity got ignored and did not activate. + IGNORED, } -interface EntityTask { - val location: LocationProperty - fun tick(context: TaskContext) - fun mayInterrupt(): Boolean - fun isComplete(): Boolean +interface SharedEntityActivity : EntityActivity +interface IndividualEntityActivity : EntityActivity +interface GenericEntityActivity : EntityActivity + +class IdleActivity(override var currentLocation: LocationProperty) : GenericEntityActivity { + override fun initialize(context: ActivityContext) {} - fun dispose() {} + override fun tick(context: ActivityContext): TickResult = TickResult.IGNORED + + override fun dispose(context: ActivityContext) {} + + companion object : ActivityCreator { + override fun create(context: ActivityContext, currentLocation: LocationProperty): EntityActivity = IdleActivity(currentLocation) + } } -interface TaskContext { +abstract class SingleChildActivity( + private val startLocation: LocationProperty, +) : EntityActivity { + private var child: Ref = emptyRef() + private var currentActivity: EntityActivity? = null + + override fun initialize(context: Context) { + child = currentChild(context) + currentActivity = child.get()?.create(context, currentLocation) + currentActivity?.initialize(context) + } + + override fun tick(context: Context): TickResult { + val correctChild = currentChild(context) + if (child != correctChild) { + child = correctChild + val currentLocation = this.currentLocation + currentActivity?.dispose(context) + currentActivity = child.get()?.create(context, currentLocation) + currentActivity?.initialize(context) + } + return currentActivity?.tick(context) ?: TickResult.IGNORED + } + + override fun dispose(context: Context) { + currentActivity?.dispose(context) + currentActivity = null + child = emptyRef() + } + + override val currentLocation: LocationProperty + get() = currentActivity?.currentLocation ?: startLocation + + abstract fun currentChild(context: Context): Ref +} + +sealed interface ActivityContext { val instanceRef: Ref val isViewed: Boolean val viewers: List } -class GroupTaskContext( +class SharedActivityContext( override val instanceRef: Ref, override val viewers: List, -) : TaskContext { +) : ActivityContext { override val isViewed: Boolean get() = viewers.isNotEmpty() } -class IndividualTaskContext( +class IndividualActivityContext( override val instanceRef: Ref, val viewer: Player, override val isViewed: Boolean, -) : TaskContext { +) : ActivityContext { override val viewers: List get() = listOf(viewer) -} - -class EmptyTaskContext( - override val instanceRef: Ref, -) : TaskContext { - override val isViewed: Boolean = false - - override val viewers: List = emptyList() -} - -/** - * Can activate child activities if all the players in the context pass are in the audience for the activity. - */ -infix fun Ref.canActivateFor(context: TaskContext): Boolean { - return context.viewers.all { it.inAudience(this) } } \ No newline at end of file diff --git a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/EntityHandler.kt b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/EntityHandler.kt index 64d3d4690a..a0d50cb076 100644 --- a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/EntityHandler.kt +++ b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/EntityHandler.kt @@ -41,7 +41,7 @@ class EntityHandler : PacketListenerAbstract(), KoinComponent { val display = audienceManager .findDisplays(ActivityEntityDisplay::class) - .firstOrNull { it.playerHasEntity(event.user.uuid, entityId) } ?: return + .firstOrNull { it.playerSeesEntity(event.user.uuid, entityId) } ?: return val definition = display.definition ?: return AsyncEntityDefinitionInteract(player, entityId, definition, packet.hand, packet.action).callEvent() diff --git a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/PlayerSpecificActivityEntityDisplay.kt b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/IndividualActivityEntityDisplay.kt similarity index 75% rename from plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/PlayerSpecificActivityEntityDisplay.kt rename to plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/IndividualActivityEntityDisplay.kt index b33596578f..97a53acb3a 100644 --- a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/PlayerSpecificActivityEntityDisplay.kt +++ b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/IndividualActivityEntityDisplay.kt @@ -11,14 +11,14 @@ import org.bukkit.entity.Player import java.util.* import java.util.concurrent.ConcurrentHashMap -class PlayerSpecificActivityEntityDisplay( +class IndividualActivityEntityDisplay( private val ref: Ref, override val creator: EntityCreator, - private val activityCreators: List, + private val activityCreator: ActivityCreator, private val suppliers: List, Int>>, private val spawnLocation: Location, ) : AudienceFilter(ref), TickableDisplay, ActivityEntityDisplay { - private val activityManagers = ConcurrentHashMap() + private val activityManagers = ConcurrentHashMap>() private val entities = ConcurrentHashMap() override fun filter(player: Player): Boolean { @@ -30,11 +30,11 @@ class PlayerSpecificActivityEntityDisplay( override fun onPlayerAdd(player: Player) { activityManagers.computeIfAbsent(player.uniqueId) { - ActivityManager( - ref, - activityCreators.map { it.create(IndividualTaskContext(ref, player, false)) }, - spawnLocation - ) + val context = IndividualActivityContext(ref, player, false) + val activity = activityCreator.create(context, spawnLocation.toProperty()) + val activityManager = ActivityManager(activity) + activityManager.initialize(context) + activityManager } super.onPlayerAdd(player) } @@ -53,7 +53,7 @@ class PlayerSpecificActivityEntityDisplay( activityManagers.forEach { (pid, manager) -> val player = server.getPlayer(pid) ?: return@forEach val isViewing = pid in this - manager.tick(IndividualTaskContext(ref, player, isViewing)) + manager.tick(IndividualActivityContext(ref, player, isViewing)) } entities.values.forEach { it.tick() } } @@ -65,18 +65,20 @@ class PlayerSpecificActivityEntityDisplay( override fun onPlayerRemove(player: Player) { super.onPlayerRemove(player) - activityManagers.remove(player.uniqueId)?.dispose() + activityManagers.remove(player.uniqueId)?.dispose(IndividualActivityContext(ref, player, false)) } override fun dispose() { super.dispose() entities.values.forEach { it.dispose() } entities.clear() - activityManagers.values.forEach { it.dispose() } + activityManagers.entries.forEach { (playerId, activityManager) -> + activityManager.dispose(IndividualActivityContext(ref, server.getPlayer(playerId) ?: return@forEach, false)) + } activityManagers.clear() } - override fun playerHasEntity(playerId: UUID, entityId: Int): Boolean { + override fun playerSeesEntity(playerId: UUID, entityId: Int): Boolean { return entities[playerId]?.contains(entityId) ?: false } diff --git a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/CommonActivityEntityDisplay.kt b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/SharedActivityEntityDisplay.kt similarity index 80% rename from plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/CommonActivityEntityDisplay.kt rename to plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/SharedActivityEntityDisplay.kt index 7d4f4c055c..534c4d6b89 100644 --- a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/CommonActivityEntityDisplay.kt +++ b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/SharedActivityEntityDisplay.kt @@ -13,14 +13,14 @@ import java.util.concurrent.ConcurrentHashMap val entityShowRange by config("entity.show-range", 50.0, "The range at which entities are shown") -class CommonActivityEntityDisplay( +class SharedActivityEntityDisplay( private val ref: Ref, override val creator: EntityCreator, - private val activityCreators: List, + private val activityCreators: ActivityCreator, private val suppliers: List, Int>>, private val spawnLocation: Location, ) : AudienceFilter(ref), TickableDisplay, ActivityEntityDisplay { - private var activityManager: ActivityManager? = null + private var activityManager: ActivityManager? = null private val entities = ConcurrentHashMap() override fun filter(player: Player): Boolean { @@ -31,8 +31,10 @@ class CommonActivityEntityDisplay( override fun initialize() { super.initialize() + val context = SharedActivityContext(ref, players) activityManager = - ActivityManager(ref, activityCreators.map { it.create(GroupTaskContext(ref, players)) }, spawnLocation) + ActivityManager(activityCreators.create(context, spawnLocation.toProperty())) + activityManager?.initialize(context) } override fun onPlayerFilterAdded(player: Player) { @@ -46,7 +48,7 @@ class CommonActivityEntityDisplay( override fun tick() { consideredPlayers.forEach { it.refresh() } - activityManager?.tick(GroupTaskContext(ref, players)) + activityManager?.tick(SharedActivityContext(ref, players)) entities.values.forEach { it.tick() } } @@ -59,11 +61,11 @@ class CommonActivityEntityDisplay( super.dispose() entities.values.forEach { it.dispose() } entities.clear() - activityManager?.dispose() + activityManager?.dispose(SharedActivityContext(ref, players)) activityManager = null } - override fun playerHasEntity(playerId: UUID, entityId: Int): Boolean { + override fun playerSeesEntity(playerId: UUID, entityId: Int): Boolean { return entities[playerId]?.contains(entityId) ?: false } diff --git a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/SimpleEntity.kt b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/SimpleEntity.kt index 67331416bc..1e5b1fb533 100644 --- a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/SimpleEntity.kt +++ b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entity/SimpleEntity.kt @@ -1,5 +1,8 @@ package me.gabber235.typewriter.entry.entity +import me.gabber235.typewriter.adapters.Tags +import me.gabber235.typewriter.adapters.modifiers.Help +import me.gabber235.typewriter.adapters.modifiers.OnlyTags import me.gabber235.typewriter.entry.Ref import me.gabber235.typewriter.entry.entries.* import me.gabber235.typewriter.entry.priority @@ -8,19 +11,22 @@ import me.gabber235.typewriter.utils.logErrorIfNull interface SimpleEntityDefinition : EntityDefinitionEntry +@Tags("shared_entity_instance") interface SimpleEntityInstance : EntityInstanceEntry { override val definition: Ref val data: List>> - val activities: List> + @Help("What the entity will do.") + @OnlyTags("shared_entity_activity") + val activity: Ref override val children: List> - get() = data + activities + get() = data override fun display(): AudienceFilter { val definition = definition.get().logErrorIfNull("You must specify a definition for $name") ?: return PassThroughFilter(ref()) - val activities = this.activities.mapNotNull { it.get() }.sortedByDescending { it.priority } + val activity = this.activity.get() ?: IdleActivity val definitionData = definition.data.withPriority() @@ -31,10 +37,10 @@ interface SimpleEntityInstance : EntityInstanceEntry { data to (data.priority + maxDefinitionData + 1) } - return CommonActivityEntityDisplay( + return SharedActivityEntityDisplay( ref(), definition, - activities, + activity, (definitionData + instanceData), spawnLocation, ) diff --git a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entries/EntityEntry.kt b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entries/EntityEntry.kt index de20eb33a7..9c6e22214c 100644 --- a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entries/EntityEntry.kt +++ b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entries/EntityEntry.kt @@ -4,11 +4,11 @@ import me.gabber235.typewriter.adapters.Tags import me.gabber235.typewriter.adapters.modifiers.Help import me.gabber235.typewriter.adapters.modifiers.WithRotation import me.gabber235.typewriter.entry.* -import me.gabber235.typewriter.entry.entity.ActivityCreator -import me.gabber235.typewriter.entry.entity.EntityCreator +import me.gabber235.typewriter.entry.entity.* import me.gabber235.typewriter.utils.Sound import org.bukkit.Location import org.bukkit.entity.Player +import org.checkerframework.checker.units.qual.A import kotlin.reflect.KClass @Tags("speaker") @@ -73,10 +73,63 @@ interface EntityInstanceEntry : AudienceFilterEntry { } @Tags("entity_activity") -@ChildOnly -interface EntityActivityEntry : AudienceFilterEntry, ActivityCreator, PriorityEntry { - override val children: List> - get() = emptyList() +interface EntityActivityEntry : ActivityCreator, ManifestEntry, PriorityEntry + +@Tags("shared_entity_activity") +interface SharedEntityActivityEntry : EntityActivityEntry { + override fun create( + context: ActivityContext, + currentLocation: LocationProperty + ): EntityActivity { + if (context !is SharedActivityContext) throw WrongActivityContextException(context, SharedActivityContext::class, this) + return create(context, currentLocation) as EntityActivity + } + fun create(context: SharedActivityContext, currentLocation: LocationProperty): EntityActivity +} + +@Tags("individual_entity_activity") +interface IndividualEntityActivityEntry : EntityActivityEntry { + override fun create( + context: ActivityContext, + currentLocation: LocationProperty + ): EntityActivity { + if (context !is IndividualActivityContext) throw WrongActivityContextException(context, IndividualActivityContext::class, this) + return create(context, currentLocation) as EntityActivity + } + fun create(context: IndividualActivityContext, currentLocation: LocationProperty): EntityActivity +} + +@Tags("generic_entity_activity") +interface GenericEntityActivityEntry : SharedEntityActivityEntry, IndividualEntityActivityEntry { + override fun create( + context: ActivityContext, + currentLocation: LocationProperty + ): EntityActivity + + override fun create( + context: SharedActivityContext, + currentLocation: LocationProperty + ): EntityActivity { + return create(context as ActivityContext, currentLocation) as EntityActivity + } + + override fun create( + context: IndividualActivityContext, + currentLocation: LocationProperty + ): EntityActivity { + return create(context as ActivityContext, currentLocation) as EntityActivity + } +} - override fun display(): AudienceFilter = PassThroughFilter(ref()) -} \ No newline at end of file +class WrongActivityContextException(context: ActivityContext, expected: KClass, entry: EntityActivityEntry) : IllegalStateException(""" + |The activity context for ${entry.name} is not of the expected type. + |Expected: $expected + |Actual: $context + | + |This happens when you try to mix shared and individual activities. + |For example, you can't use a shared activity on an individual entity. + |And you can't use an individual activity on a shared entity. + | + |To fix this, you need to make sure that the activity matches the entity visibility. + |If you need more help, please join the TypeWriter Discord! https://discord.gg/gs5QYhfv9x +""".trimMargin()) \ No newline at end of file