Update citizens version to 2.0.35 for 1.21.1 support
This commit is contained in:
@@ -20,6 +20,7 @@ repositories {
|
||||
maven("https://repo.lunarclient.dev")
|
||||
maven("https://maven.enginehub.org/repo/")
|
||||
maven("https://repo.citizensnpcs.co/")
|
||||
maven("https://repo.alessiodp.com/releases/")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -41,7 +42,7 @@ dependencies {
|
||||
compileOnly("io.papermc.paper:paper-api:1.21.1-R0.1-SNAPSHOT")
|
||||
compileOnly("com.sk89q.worldedit:worldedit-core:7.2.17-SNAPSHOT")
|
||||
compileOnly("com.sk89q.worldedit:worldedit-bukkit:7.2.17-SNAPSHOT")
|
||||
compileOnly("net.citizensnpcs:citizens-main:2.0.36-SNAPSHOT")
|
||||
compileOnly("net.citizensnpcs:citizens-main:2.0.35-SNAPSHOT")
|
||||
}
|
||||
|
||||
tasks {
|
||||
|
||||
@@ -106,6 +106,7 @@ class TricksterKit : Kit(), Listener
|
||||
data class DecoySession(
|
||||
val npc: NPC,
|
||||
var expiryTask: BukkitTask,
|
||||
val chaseTask: BukkitTask,
|
||||
val activatedAt: Long,
|
||||
val playstyle: Playstyle,
|
||||
val tricksterUUID: UUID
|
||||
@@ -134,6 +135,12 @@ class TricksterKit : Kit(), Listener
|
||||
|
||||
/** 3 seconds in ticks — Slowness I on explosion victims. */
|
||||
const val SLOWNESS_DURATION_TICKS = 60
|
||||
|
||||
/** How often (in ticks) the NPC re-targets the nearest enemy. */
|
||||
const val NPC_CHASE_UPDATE_TICKS = 10L
|
||||
|
||||
/** Walk speed passed to the Citizens navigator. 0.5 = roughly player walking pace. */
|
||||
const val NPC_WALK_SPEED = 0.5
|
||||
}
|
||||
|
||||
// ── Live config accessors ─────────────────────────────────────────────────
|
||||
@@ -162,6 +169,12 @@ class TricksterKit : Kit(), Listener
|
||||
private val slownessDurationTicks: Int
|
||||
get() = override().getInt( "slowness_duration_ticks" ) ?: SLOWNESS_DURATION_TICKS
|
||||
|
||||
private val npcChaseUpdateTicks: Long
|
||||
get() = override().getLong( "npc_chase_update_ticks" ) ?: NPC_CHASE_UPDATE_TICKS
|
||||
|
||||
private val npcWalkSpeed: Double
|
||||
get() = override().getDouble( "npc_walk_speed" ) ?: NPC_WALK_SPEED
|
||||
|
||||
// ── Cached ability instances (avoid allocating per event call) ────────────
|
||||
|
||||
private val aggressiveActive = AggressiveActive()
|
||||
@@ -226,7 +239,7 @@ class TricksterKit : Kit(), Listener
|
||||
player: Player
|
||||
) {
|
||||
cooldowns.remove( player.uniqueId )
|
||||
terminateDecoy( player.uniqueId, silent = true )
|
||||
terminateDecoy( player.uniqueId)
|
||||
cachedItems.remove( player.uniqueId )?.forEach { player.inventory.remove( it ) }
|
||||
}
|
||||
|
||||
@@ -263,7 +276,7 @@ class TricksterKit : Kit(), Listener
|
||||
if ( !plugin.gameManager.alivePlayers.contains( attacker.uniqueId ) ) return
|
||||
|
||||
val trickster = Bukkit.getPlayer( session.tricksterUUID ) ?: run {
|
||||
terminateDecoy( session.tricksterUUID, silent = true )
|
||||
terminateDecoy( session.tricksterUUID)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -296,11 +309,30 @@ class TricksterKit : Kit(), Listener
|
||||
val registry = CitizensAPI.getNPCRegistry()
|
||||
val npc = registry.createNPC( EntityType.PLAYER, player.name )
|
||||
|
||||
// Mirror the player's skin by name — Citizens fetches it from Mojang
|
||||
val skinTrait = npc.getOrAddTrait( SkinTrait::class.java )
|
||||
skinTrait.setSkinName( player.name, false )
|
||||
// ── Skin: direkt aus dem gecachten Paper-PlayerProfile lesen ──────────
|
||||
// Paper speichert die Textur-Property des letzten bekannten Mojang-Profils
|
||||
// im PlayerProfile — kein Netzwerkaufruf nötig.
|
||||
val profile = player.playerProfile
|
||||
val textureProperty = profile.properties
|
||||
.firstOrNull { it.name == "textures" }
|
||||
|
||||
// Mirror the player's worn armour for visual authenticity
|
||||
val skinTrait = npc.getOrAddTrait( SkinTrait::class.java )
|
||||
|
||||
if ( textureProperty != null )
|
||||
{
|
||||
// Synchron setzen — kein Mojang-Lookup, erscheint sofort korrekt
|
||||
skinTrait.setTexture(
|
||||
textureProperty.value,
|
||||
textureProperty.signature ?: ""
|
||||
)
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: name-basierter Lookup (nur wenn Paper-Profil leer ist)
|
||||
skinTrait.setSkinName( player.name, false )
|
||||
}
|
||||
|
||||
// ── Rüstung spiegeln ──────────────────────────────────────────────────
|
||||
val equipment = npc.getOrAddTrait( Equipment::class.java )
|
||||
equipment.set( EquipmentSlot.HELMET, player.inventory.helmet ?: ItemStack( Material.AIR ) )
|
||||
equipment.set( EquipmentSlot.CHESTPLATE, player.inventory.chestplate ?: ItemStack( Material.AIR ) )
|
||||
@@ -310,28 +342,81 @@ class TricksterKit : Kit(), Listener
|
||||
npc.spawn( location )
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
val tricksterUUID = player.uniqueId
|
||||
|
||||
// ── Chase-Task: NPC läuft auf nächsten Feind zu ───────────────────────
|
||||
val chaseTask = startNPCChaseTask( npc, tricksterUUID )
|
||||
|
||||
// ── Expiry-Task ───────────────────────────────────────────────────────
|
||||
val expiryTask = Bukkit.getScheduler().runTaskLater( plugin, { ->
|
||||
if ( activeDecoys.containsKey( tricksterUUID ) )
|
||||
onDecoyExpired( player, activeDecoys[ tricksterUUID ]!! )
|
||||
}, decoyDurationTicks.toLong() )
|
||||
|
||||
// Placeholder task — replaced immediately below; needed for data class construction
|
||||
val session = DecoySession(
|
||||
npc = npc,
|
||||
expiryTask = Bukkit.getScheduler().runTaskLater( plugin, { -> }, 1L ),
|
||||
expiryTask = expiryTask,
|
||||
chaseTask = chaseTask,
|
||||
activatedAt = now,
|
||||
playstyle = playstyle,
|
||||
tricksterUUID = player.uniqueId
|
||||
tricksterUUID = tricksterUUID
|
||||
)
|
||||
|
||||
// Replace placeholder with the real expiry task
|
||||
session.expiryTask.cancel()
|
||||
val capturedDurationTicks = decoyDurationTicks.toLong()
|
||||
activeDecoys[ tricksterUUID ] = session
|
||||
return session
|
||||
}
|
||||
|
||||
val expiryTask = Bukkit.getScheduler().runTaskLater( plugin, { ->
|
||||
if ( !activeDecoys.containsKey( player.uniqueId ) ) return@runTaskLater
|
||||
onDecoyExpired( player, session )
|
||||
}, capturedDurationTicks )
|
||||
/**
|
||||
* Starts a repeating task that steers the Citizens NPC toward the nearest
|
||||
* alive enemy (excluding the Trickster themselves) every [NPC_CHASE_UPDATE_TICKS].
|
||||
*
|
||||
* Citizens' navigator is only available after the NPC is spawned, so the task
|
||||
* reads `npc.navigator` safely on the main thread each iteration.
|
||||
*
|
||||
* The task cancels itself if the NPC is no longer spawned or if the session
|
||||
* has already been removed from [activeDecoys].
|
||||
*/
|
||||
private fun startNPCChaseTask(
|
||||
npc: NPC,
|
||||
tricksterUUID: UUID
|
||||
): BukkitTask
|
||||
{
|
||||
return Bukkit.getScheduler().runTaskTimer( plugin, { ->
|
||||
|
||||
val finalSession = session.copy( expiryTask = expiryTask )
|
||||
activeDecoys[ player.uniqueId ] = finalSession
|
||||
return finalSession
|
||||
// Self-cancel if the session is gone or the NPC was destroyed
|
||||
if ( !activeDecoys.containsKey( tricksterUUID ) || !npc.isSpawned )
|
||||
{
|
||||
// Cancellation happens via the task reference stored in DecoySession;
|
||||
// we can't call cancel() on ourselves here without storing a ref first,
|
||||
// so we rely on terminateDecoy() which cancels the task externally.
|
||||
return@runTaskTimer
|
||||
}
|
||||
|
||||
val npcEntity = npc.entity ?: return@runTaskTimer
|
||||
val npcLoc = npcEntity.location
|
||||
|
||||
// Find the nearest alive player that is not the Trickster
|
||||
val target = plugin.gameManager.alivePlayers
|
||||
.asSequence()
|
||||
.filter { it != tricksterUUID }
|
||||
.mapNotNull { Bukkit.getPlayer( it ) }
|
||||
.filter { it.world == npcLoc.world }
|
||||
.minByOrNull { it.location.distanceSquared( npcLoc ) }
|
||||
|
||||
if ( target == null )
|
||||
{
|
||||
npc.navigator.cancelNavigation()
|
||||
return@runTaskTimer
|
||||
}
|
||||
|
||||
// Update navigator destination — Citizens handles pathfinding internally
|
||||
val params = npc.navigator.localParameters
|
||||
params.speedModifier( npcWalkSpeed.toFloat() )
|
||||
params.range( 64f )
|
||||
|
||||
npc.navigator.setTarget( target, false )
|
||||
|
||||
}, 0L, npcChaseUpdateTicks )
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -389,14 +474,13 @@ class TricksterKit : Kit(), Listener
|
||||
* Removes the active decoy for [tricksterUUID] without any gameplay effect.
|
||||
* Used in [onRemove] and emergency cleanup paths.
|
||||
*
|
||||
* @param silent If true, no ActionBar is sent to the player.
|
||||
*/
|
||||
private fun terminateDecoy(
|
||||
tricksterUUID: UUID,
|
||||
silent: Boolean
|
||||
tricksterUUID: UUID
|
||||
) {
|
||||
val session = activeDecoys.remove( tricksterUUID ) ?: return
|
||||
session.expiryTask.cancel()
|
||||
session.chaseTask.cancel()
|
||||
destroyNPC( session.npc )
|
||||
}
|
||||
|
||||
@@ -413,7 +497,7 @@ class TricksterKit : Kit(), Listener
|
||||
) {
|
||||
val npcLoc = session.npc.storedLocation ?: trickster.location
|
||||
|
||||
terminateDecoy( trickster.uniqueId, silent = true )
|
||||
terminateDecoy( trickster.uniqueId)
|
||||
|
||||
triggerFakeExplosion( trickster, npcLoc )
|
||||
|
||||
@@ -431,7 +515,7 @@ class TricksterKit : Kit(), Listener
|
||||
trickster: Player,
|
||||
session: DecoySession
|
||||
) {
|
||||
terminateDecoy( trickster.uniqueId, silent = true )
|
||||
terminateDecoy( trickster.uniqueId)
|
||||
|
||||
// Cooldown reduction: shift lastUse forward so remaining wait is halved.
|
||||
// If the full cooldown is 25 s and 3 s have elapsed, the player would normally
|
||||
|
||||
Reference in New Issue
Block a user