Skip to content

Commit

Permalink
Rework Entity Activities
Browse files Browse the repository at this point in the history
  • Loading branch information
gabber235 committed May 30, 2024
1 parent efe266c commit ab8e5fc
Show file tree
Hide file tree
Showing 39 changed files with 828 additions and 568 deletions.
Original file line number Diff line number Diff line change
@@ -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<AudienceActivityPair> = emptyList(),
@Help("The activity that will be used when the player is not in any audience.")
val defaultActivity: Ref<out EntityActivityEntry> = emptyRef(),
override val priorityOverride: Optional<Int> = Optional.empty(),
) : IndividualEntityActivityEntry {
override fun create(
context: IndividualActivityContext,
currentLocation: LocationProperty
): EntityActivity<IndividualActivityContext> {
return AudienceActivity(activities, defaultActivity, currentLocation)
}
}

class AudienceActivityPair(
val audience: Ref<out AudienceEntry>,
val activity: Ref<out IndividualEntityActivityEntry>,
)

class AudienceActivity(
private val activities: List<AudienceActivityPair>,
private val defaultActivity: Ref<out EntityActivityEntry>,
startLocation: LocationProperty,
) : SingleChildActivity<IndividualActivityContext>(startLocation) {
override fun currentChild(context: IndividualActivityContext): Ref<out EntityActivityEntry> {
val player = context.viewer

return activities.firstOrNull { player.inAudience(it.audience) }?.activity ?: defaultActivity
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -38,21 +37,22 @@ import java.util.*
class GameTimeActivityEntry(
override val id: String = "",
override val name: String = "",
override val children: List<Ref<out AudienceEntry>> = emptyList(),
val world: String = "",
val activeTimes: List<GameTimeRange> = emptyList(),
val activities: List<GameTimedActivity> = emptyList(),
val defaultActivity: Ref<out EntityActivityEntry> = emptyRef(),
override val priorityOverride: Optional<Int> = 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<ActivityContext> {
return GameTimeActivity(
world,
activities,
defaultActivity,
currentLocation,
)
}
}

class GameTimeRange(
Expand All @@ -64,43 +64,28 @@ class GameTimeRange(
}
}

class GameTimeFilter(
val ref: Ref<out AudienceFilterEntry>,
private val world: String,
private val activeTimes: List<GameTimeRange>,
) : 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<out EntityActivityEntry>,
) {
operator fun contains(time: Long): Boolean {
return time in this.time
}
}

class GameTimeActivity(
val ref: Ref<out GameTimeActivityEntry>,
children: List<EntityActivity>,
) : FilterActivity(children) {
override fun canActivate(context: TaskContext, currentLocation: LocationProperty): Boolean {
return ref.canActivateFor(context)
private val world: String,
private val activities: List<GameTimedActivity>,
private val defaultActivity: Ref<out EntityActivityEntry>,
startLocation: LocationProperty,
) : SingleChildActivity<ActivityContext>(startLocation) {
override fun currentChild(context: ActivityContext): Ref<out EntityActivityEntry> {
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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand All @@ -28,7 +31,6 @@ import java.util.*
class InDialogueActivityEntry(
override val id: String = "",
override val name: String = "",
override val children: List<Ref<out AudienceEntry>> = 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.
Expand All @@ -41,49 +43,57 @@ class InDialogueActivityEntry(
* </Admonition>
*/
val dialogueIdleDuration: Duration = Duration.ofSeconds(30),
@Help("The activity that will be used when the npc is in a dialogue")
val talkingActivity: Ref<out EntityActivityEntry> = emptyRef(),
@Help("The activity that will be used when the npc is not in a dialogue")
val idleActivity: Ref<out EntityActivityEntry> = emptyRef(),
override val priorityOverride: Optional<Int> = 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<in ActivityContext> {
return InDialogueActivity(
dialogueIdleDuration,
priority,
talkingActivity,
idleActivity,
currentLocation,
)
}
}

class InDialogueActivity(
val ref: Ref<InDialogueActivityEntry>,
children: List<EntityActivity>,
private val dialogueIdleDuration: Duration,
private val priority: Int,
) : FilterActivity(children) {
private var trackers = mutableMapOf<UUID, PlayerDialogueTracker>()
private val talkingActivity: Ref<out EntityActivityEntry>,
private val idleActivity: Ref<out EntityActivityEntry>,
startLocation: LocationProperty,
) : SingleChildActivity<ActivityContext>(startLocation) {
private val trackers = mutableMapOf<UUID, PlayerDialogueTracker>()

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<out EntityActivityEntry> {
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(
Expand All @@ -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()
}
}
}
Loading

0 comments on commit ab8e5fc

Please sign in to comment.