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://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 {

View File

@@ -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 ) )
@@ -309,29 +341,82 @@ class TricksterKit : Kit(), Listener
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(
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