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://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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user