Update citizens version to 2.0.35 for 1.21.1 support

This commit is contained in:
TDSTOS
2026-04-17 03:28:31 +02:00
parent 9a34be8f8f
commit a79afee6e3
2 changed files with 111 additions and 26 deletions

View File

@@ -20,6 +20,7 @@ repositories {
maven("https://repo.lunarclient.dev") maven("https://repo.lunarclient.dev")
maven("https://maven.enginehub.org/repo/") maven("https://maven.enginehub.org/repo/")
maven("https://repo.citizensnpcs.co/") maven("https://repo.citizensnpcs.co/")
maven("https://repo.alessiodp.com/releases/")
} }
dependencies { dependencies {
@@ -41,7 +42,7 @@ dependencies {
compileOnly("io.papermc.paper:paper-api:1.21.1-R0.1-SNAPSHOT") 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-core:7.2.17-SNAPSHOT")
compileOnly("com.sk89q.worldedit:worldedit-bukkit: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 { tasks {

View File

@@ -106,6 +106,7 @@ class TricksterKit : Kit(), Listener
data class DecoySession( data class DecoySession(
val npc: NPC, val npc: NPC,
var expiryTask: BukkitTask, var expiryTask: BukkitTask,
val chaseTask: BukkitTask,
val activatedAt: Long, val activatedAt: Long,
val playstyle: Playstyle, val playstyle: Playstyle,
val tricksterUUID: UUID val tricksterUUID: UUID
@@ -134,6 +135,12 @@ class TricksterKit : Kit(), Listener
/** 3 seconds in ticks — Slowness I on explosion victims. */ /** 3 seconds in ticks — Slowness I on explosion victims. */
const val SLOWNESS_DURATION_TICKS = 60 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 ───────────────────────────────────────────────── // ── Live config accessors ─────────────────────────────────────────────────
@@ -162,6 +169,12 @@ class TricksterKit : Kit(), Listener
private val slownessDurationTicks: Int private val slownessDurationTicks: Int
get() = override().getInt( "slowness_duration_ticks" ) ?: SLOWNESS_DURATION_TICKS 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) ──────────── // ── Cached ability instances (avoid allocating per event call) ────────────
private val aggressiveActive = AggressiveActive() private val aggressiveActive = AggressiveActive()
@@ -226,7 +239,7 @@ class TricksterKit : Kit(), Listener
player: Player player: Player
) { ) {
cooldowns.remove( player.uniqueId ) cooldowns.remove( player.uniqueId )
terminateDecoy( player.uniqueId, silent = true ) terminateDecoy( player.uniqueId)
cachedItems.remove( player.uniqueId )?.forEach { player.inventory.remove( it ) } 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 if ( !plugin.gameManager.alivePlayers.contains( attacker.uniqueId ) ) return
val trickster = Bukkit.getPlayer( session.tricksterUUID ) ?: run { val trickster = Bukkit.getPlayer( session.tricksterUUID ) ?: run {
terminateDecoy( session.tricksterUUID, silent = true ) terminateDecoy( session.tricksterUUID)
return return
} }
@@ -296,11 +309,30 @@ class TricksterKit : Kit(), Listener
val registry = CitizensAPI.getNPCRegistry() val registry = CitizensAPI.getNPCRegistry()
val npc = registry.createNPC( EntityType.PLAYER, player.name ) val npc = registry.createNPC( EntityType.PLAYER, player.name )
// Mirror the player's skin by name — Citizens fetches it from Mojang // ── Skin: direkt aus dem gecachten Paper-PlayerProfile lesen ──────────
val skinTrait = npc.getOrAddTrait( SkinTrait::class.java ) // Paper speichert die Textur-Property des letzten bekannten Mojang-Profils
skinTrait.setSkinName( player.name, false ) // 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 ) val equipment = npc.getOrAddTrait( Equipment::class.java )
equipment.set( EquipmentSlot.HELMET, player.inventory.helmet ?: ItemStack( Material.AIR ) ) equipment.set( EquipmentSlot.HELMET, player.inventory.helmet ?: ItemStack( Material.AIR ) )
equipment.set( EquipmentSlot.CHESTPLATE, player.inventory.chestplate ?: ItemStack( Material.AIR ) ) equipment.set( EquipmentSlot.CHESTPLATE, player.inventory.chestplate ?: ItemStack( Material.AIR ) )
@@ -309,29 +341,82 @@ class TricksterKit : Kit(), Listener
npc.spawn( location ) npc.spawn( location )
val now = System.currentTimeMillis() 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( val session = DecoySession(
npc = npc, npc = npc,
expiryTask = Bukkit.getScheduler().runTaskLater( plugin, { -> }, 1L ), expiryTask = expiryTask,
chaseTask = chaseTask,
activatedAt = now, activatedAt = now,
playstyle = playstyle, playstyle = playstyle,
tricksterUUID = player.uniqueId tricksterUUID = tricksterUUID
) )
// Replace placeholder with the real expiry task activeDecoys[ tricksterUUID ] = session
session.expiryTask.cancel() return session
val capturedDurationTicks = decoyDurationTicks.toLong() }
val expiryTask = Bukkit.getScheduler().runTaskLater( plugin, { -> /**
if ( !activeDecoys.containsKey( player.uniqueId ) ) return@runTaskLater * Starts a repeating task that steers the Citizens NPC toward the nearest
onDecoyExpired( player, session ) * alive enemy (excluding the Trickster themselves) every [NPC_CHASE_UPDATE_TICKS].
}, capturedDurationTicks ) *
* 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 ) // Self-cancel if the session is gone or the NPC was destroyed
activeDecoys[ player.uniqueId ] = finalSession if ( !activeDecoys.containsKey( tricksterUUID ) || !npc.isSpawned )
return finalSession {
// 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. * Removes the active decoy for [tricksterUUID] without any gameplay effect.
* Used in [onRemove] and emergency cleanup paths. * Used in [onRemove] and emergency cleanup paths.
* *
* @param silent If true, no ActionBar is sent to the player.
*/ */
private fun terminateDecoy( private fun terminateDecoy(
tricksterUUID: UUID, tricksterUUID: UUID
silent: Boolean
) { ) {
val session = activeDecoys.remove( tricksterUUID ) ?: return val session = activeDecoys.remove( tricksterUUID ) ?: return
session.expiryTask.cancel() session.expiryTask.cancel()
session.chaseTask.cancel()
destroyNPC( session.npc ) destroyNPC( session.npc )
} }
@@ -413,7 +497,7 @@ class TricksterKit : Kit(), Listener
) { ) {
val npcLoc = session.npc.storedLocation ?: trickster.location val npcLoc = session.npc.storedLocation ?: trickster.location
terminateDecoy( trickster.uniqueId, silent = true ) terminateDecoy( trickster.uniqueId)
triggerFakeExplosion( trickster, npcLoc ) triggerFakeExplosion( trickster, npcLoc )
@@ -431,7 +515,7 @@ class TricksterKit : Kit(), Listener
trickster: Player, trickster: Player,
session: DecoySession session: DecoySession
) { ) {
terminateDecoy( trickster.uniqueId, silent = true ) terminateDecoy( trickster.uniqueId)
// Cooldown reduction: shift lastUse forward so remaining wait is halved. // 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 // If the full cooldown is 25 s and 3 s have elapsed, the player would normally