Skip to content

Commit 6f3607f

Browse files
Merge pull request #28 from SLNE-Development/feat/add-npc-poses
Feat/add npc poses
2 parents 4c195cf + f5ef784 commit 6f3607f

File tree

14 files changed

+345
-32
lines changed

14 files changed

+345
-32
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
kotlin.code.style=official
22
kotlin.stdlib.default.dependency=false
33
org.gradle.parallel=true
4-
version=1.21.7-1.5.0-SNAPSHOT
4+
version=1.21.7-1.5.1-SNAPSHOT

surf-npc-api/src/main/kotlin/dev/slne/surf/npc/api/npc/Npc.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,21 @@ interface Npc {
5858
*/
5959
val viewers: ObjectSet<UUID>?
6060

61+
/**
62+
* Represents the unique identifier for the NPC's sitting state.
63+
*
64+
* This identifier corresponds to the NPC's sitting entity, such as an invisible armor stand.
65+
* It is used to manage and reference the sitting state of the NPC.
66+
*/
67+
val npcSittingId: Int
68+
69+
/**
70+
* Represents the unique identifier (UUID) associated with an NPC's sitting entity.
71+
* This UUID is primarily used to manage and interact with the sitting entity of the NPC,
72+
* such as spawning or assigning specific behaviors or properties.
73+
*/
74+
val npcSittingUuid: UUID
75+
6176
/**
6277
* Spawns the NPC for a specific player.
6378
*
@@ -231,4 +246,11 @@ interface Npc {
231246
* @param animationType The type of animation to play.
232247
*/
233248
fun playAnimation(animationType: NpcAnimationType)
249+
250+
/**
251+
* Updates the pose of the NPC to the specified pose.
252+
*
253+
* @param pose The new pose to set for the NPC. Acceptable values are defined in the [NpcPose] enum.
254+
*/
255+
fun setPose(pose: NpcPose)
234256
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package dev.slne.surf.npc.api.npc
2+
3+
enum class NpcPose(val id: Int, val usable: Boolean = false) {
4+
STANDING(0, true),
5+
FALL_FLYING(1, true),
6+
SLEEPING(2, true),
7+
SWIMMING(3, true),
8+
9+
@Deprecated("No effect")
10+
SPIN_ATTACK(4),
11+
SNEAKING(5, true),
12+
13+
@Deprecated("No effect")
14+
LONG_JUMPING(6),
15+
16+
@Deprecated("No effect")
17+
DYING(7),
18+
19+
@Deprecated("No effect")
20+
CROAKING(8),
21+
22+
@Deprecated("No effect")
23+
USING_TONGUE(9),
24+
25+
SITTING(10, true),
26+
27+
@Deprecated("No effect")
28+
ROARING(11),
29+
30+
@Deprecated("No effect")
31+
SNIFFING(12),
32+
33+
@Deprecated("No effect")
34+
EMERGING(13),
35+
36+
@Deprecated("No effect")
37+
DIGGING(14),
38+
39+
@Deprecated("No effect")
40+
SLIDING(15),
41+
42+
@Deprecated("No effect")
43+
SHOOTING(16),
44+
45+
@Deprecated("No effect")
46+
INHALING(17);
47+
48+
companion object {
49+
private val BY_ID = entries.associateBy(NpcPose::id)
50+
51+
fun fromId(id: Int): NpcPose? = BY_ID[id]
52+
operator fun get(id: Int): NpcPose? = fromId(id)
53+
}
54+
}

surf-npc-bukkit/src/main/kotlin/dev/slne/surf/npc/bukkit/BukkitPackets.kt

Lines changed: 96 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@ import com.github.retrooper.packetevents.protocol.player.GameMode
77
import com.github.retrooper.packetevents.protocol.player.UserProfile
88
import com.github.retrooper.packetevents.util.Vector3d
99
import com.github.retrooper.packetevents.wrapper.play.server.*
10+
import dev.slne.surf.npc.api.npc.Npc
11+
import dev.slne.surf.npc.api.npc.NpcPose
1012
import dev.slne.surf.npc.api.npc.animation.NpcAnimationType
13+
import dev.slne.surf.npc.bukkit.util.toEntityPose
1114
import io.github.retrooper.packetevents.util.SpigotConversionUtil
1215
import net.kyori.adventure.text.Component
1316
import net.kyori.adventure.text.format.NamedTextColor
14-
import org.bukkit.Location
1517
import java.util.*
18+
import com.github.retrooper.packetevents.protocol.world.Location as PacketLocation
19+
import org.bukkit.Location as BukkitLocation
1620

1721
fun createPlayerInfoPacket(profile: UserProfile, displayName: Component, listed: Boolean = false) =
1822
WrapperPlayServerPlayerInfoUpdate(
@@ -27,25 +31,76 @@ fun createPlayerInfoPacket(profile: UserProfile, displayName: Component, listed:
2731
)
2832
)
2933

30-
fun createEntityMetadataPacket(npcEntityId: Int, skinParts: Byte = 0x7F.toByte()) = WrapperPlayServerEntityMetadata(
34+
fun createEntityMetadataPacket(npcEntityId: Int, skinParts: Byte = 0x7F.toByte()) =
35+
WrapperPlayServerEntityMetadata(
36+
npcEntityId,
37+
listOf(
38+
EntityData(17, EntityDataTypes.BYTE, skinParts),
39+
EntityData(0, EntityDataTypes.BYTE, 0x02.toByte()),
40+
)
41+
)
42+
43+
fun createSpawnSittingArmorStandPacket(npc: Npc, npcLocation: BukkitLocation) =
44+
WrapperPlayServerSpawnEntity(
45+
npc.npcSittingId,
46+
npc.npcSittingUuid,
47+
EntityTypes.ARMOR_STAND,
48+
SpigotConversionUtil.fromBukkitLocation(npcLocation.clone().subtract(0.0, 2.0, 0.0)),
49+
0f,
50+
0,
51+
null
52+
)
53+
54+
fun createCorrectNameTagPacket(nameTagId: Int, npcLocation: BukkitLocation, npcPose: NpcPose) =
55+
WrapperPlayServerEntityTeleport(
56+
nameTagId,
57+
SpigotConversionUtil.fromBukkitLocation(
58+
calculateNametagLocation(npcPose, npcLocation).add(
59+
0.0,
60+
2.0,
61+
0.0
62+
)
63+
),
64+
false
65+
)
66+
67+
fun createSittingArmorStandMetadataPacket(npc: Npc) = WrapperPlayServerEntityMetadata(
68+
npc.npcSittingId,
69+
listOf(
70+
EntityData(0, EntityDataTypes.BYTE, 0x20.toByte()),
71+
)
72+
)
73+
74+
fun createMountSittingArmorStandPacket(npc: Npc) = WrapperPlayServerSetPassengers(
75+
npc.npcSittingId,
76+
intArrayOf(npc.id)
77+
)
78+
79+
fun createDestroySittingArmorStandPacket(npc: Npc) =
80+
WrapperPlayServerDestroyEntities(npc.npcSittingId)
81+
82+
fun createPoseChangePacket(npcEntityId: Int, pose: NpcPose) = WrapperPlayServerEntityMetadata(
3183
npcEntityId,
3284
listOf(
33-
EntityData(17, EntityDataTypes.BYTE, skinParts),
34-
EntityData(0, EntityDataTypes.BYTE, 0x02.toByte()),
85+
EntityData(
86+
6,
87+
EntityDataTypes.ENTITY_POSE,
88+
pose.toEntityPose()
89+
)
3590
)
3691
)
3792

3893
fun createPlayerSpawnPacket(
3994
entityId: Int,
4095
uuid: UUID,
41-
location: Location,
96+
location: BukkitLocation,
4297
yaw: Float,
4398
pitch: Float
4499
) = WrapperPlayServerSpawnEntity(
45100
entityId,
46101
uuid,
47102
EntityTypes.PLAYER,
48-
com.github.retrooper.packetevents.protocol.world.Location(
103+
PacketLocation(
49104
Vector3d(
50105
location.x,
51106
location.y,
@@ -60,12 +115,12 @@ fun createPlayerSpawnPacket(
60115
fun createNametagSpawnPacket(
61116
entityId: Int,
62117
uuid: UUID,
63-
location: Location
118+
location: BukkitLocation
64119
) = WrapperPlayServerSpawnEntity(
65120
entityId,
66121
uuid,
67122
EntityTypes.TEXT_DISPLAY,
68-
com.github.retrooper.packetevents.protocol.world.Location(
123+
PacketLocation(
69124
Vector3d(
70125
location.x,
71126
location.y + 2,
@@ -122,21 +177,43 @@ fun createRotationPackets(entityId: Int, yaw: Float, pitch: Float) = Pair(
122177
WrapperPlayServerEntityHeadLook(entityId, yaw)
123178
)
124179

125-
fun createTeleportPacket(entityId: Int, location: Location, onGround: Boolean = false) =
180+
fun createTeleportPacket(entityId: Int, location: BukkitLocation, onGround: Boolean = false) =
126181
WrapperPlayServerEntityTeleport(
127182
entityId,
128183
SpigotConversionUtil.fromBukkitLocation(location),
129184
onGround
130185
)
131186

132-
fun createEntityAnimation(entityId: Int, animation: NpcAnimationType) = WrapperPlayServerEntityAnimation(
133-
entityId,
134-
when (animation) {
135-
NpcAnimationType.SWING_ARM_MAIN -> WrapperPlayServerEntityAnimation.EntityAnimationType.SWING_MAIN_ARM
136-
NpcAnimationType.SWING_ARM_OFF -> WrapperPlayServerEntityAnimation.EntityAnimationType.SWING_OFF_HAND
137-
NpcAnimationType.GET_DAMAGE -> WrapperPlayServerEntityAnimation.EntityAnimationType.HURT
138-
NpcAnimationType.LEAVE_BED -> WrapperPlayServerEntityAnimation.EntityAnimationType.WAKE_UP
139-
NpcAnimationType.HIT_CRITICAL -> WrapperPlayServerEntityAnimation.EntityAnimationType.CRITICAL_HIT
140-
NpcAnimationType.HIT_MAGIC -> WrapperPlayServerEntityAnimation.EntityAnimationType.MAGIC_CRITICAL_HIT
187+
fun createEntityAnimation(entityId: Int, animation: NpcAnimationType) =
188+
WrapperPlayServerEntityAnimation(
189+
entityId,
190+
when (animation) {
191+
NpcAnimationType.SWING_ARM_MAIN -> WrapperPlayServerEntityAnimation.EntityAnimationType.SWING_MAIN_ARM
192+
NpcAnimationType.SWING_ARM_OFF -> WrapperPlayServerEntityAnimation.EntityAnimationType.SWING_OFF_HAND
193+
NpcAnimationType.GET_DAMAGE -> WrapperPlayServerEntityAnimation.EntityAnimationType.HURT
194+
NpcAnimationType.LEAVE_BED -> WrapperPlayServerEntityAnimation.EntityAnimationType.WAKE_UP
195+
NpcAnimationType.HIT_CRITICAL -> WrapperPlayServerEntityAnimation.EntityAnimationType.CRITICAL_HIT
196+
NpcAnimationType.HIT_MAGIC -> WrapperPlayServerEntityAnimation.EntityAnimationType.MAGIC_CRITICAL_HIT
197+
}
198+
)
199+
200+
201+
private fun calculateNametagLocation(
202+
npcPose: NpcPose,
203+
npcLocation: BukkitLocation
204+
): BukkitLocation =
205+
when (npcPose) {
206+
NpcPose.SNEAKING -> {
207+
npcLocation.clone().subtract(0.0, 0.2, 0.0)
208+
}
209+
210+
NpcPose.SITTING -> {
211+
npcLocation.clone().subtract(0.0, 0.62, 0.0)
212+
}
213+
214+
NpcPose.SWIMMING, NpcPose.FALL_FLYING, NpcPose.SLEEPING -> {
215+
npcLocation.clone().subtract(0.0, 1.62, 0.0)
216+
}
217+
218+
else -> npcLocation.clone()
141219
}
142-
)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package dev.slne.surf.npc.bukkit.command.argument
2+
3+
import dev.jorel.commandapi.CommandAPICommand
4+
import dev.jorel.commandapi.arguments.Argument
5+
import dev.jorel.commandapi.arguments.ArgumentSuggestions
6+
import dev.jorel.commandapi.arguments.CustomArgument
7+
import dev.jorel.commandapi.arguments.StringArgument
8+
import dev.slne.surf.npc.api.npc.NpcPose
9+
10+
class NpcPoseArgument(nodeName: String) :
11+
CustomArgument<NpcPose, String>(StringArgument(nodeName), { info ->
12+
NpcPose.valueOf(info.input.uppercase())
13+
}) {
14+
init {
15+
replaceSuggestions(ArgumentSuggestions.stringCollection {
16+
NpcPose.entries.filter { it.usable }.map { it.name.lowercase() }
17+
})
18+
}
19+
}
20+
21+
inline fun CommandAPICommand.npcPoseArgument(
22+
nodeName: String,
23+
optional: Boolean = false,
24+
block: Argument<*>.() -> Unit = {}
25+
): CommandAPICommand =
26+
withArguments(NpcPoseArgument(nodeName).setOptional(optional).apply(block))

surf-npc-bukkit/src/main/kotlin/dev/slne/surf/npc/bukkit/command/sub/edit/NpcEditCommand.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ fun CommandAPICommand.npcEditCommand() = subcommand("edit") {
1010
npcEditRotationCommand()
1111
npcEditSkinCommand()
1212
npcEditDisplayNameCommand()
13+
npcEditPoseCommand()
1314
}

surf-npc-bukkit/src/main/kotlin/dev/slne/surf/npc/bukkit/command/sub/edit/NpcEditDisplayNameCommand.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import dev.slne.surf.npc.core.property.propertyTypeRegistry
1616
import dev.slne.surf.surfapi.core.api.messages.adventure.sendText
1717

1818
fun CommandAPICommand.npcEditDisplayNameCommand() = subcommand("displayname") {
19-
withPermission(PermissionRegistry.COMMAND_NPC_DISPLAYNAME)
19+
withPermission(PermissionRegistry.COMMAND_NPC_EDIT_DISPLAYNAME)
2020
npcArgument("npc")
2121
textArgument("displayName")
2222
playerExecutor { player, args ->
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package dev.slne.surf.npc.bukkit.command.sub.edit
2+
3+
import dev.jorel.commandapi.CommandAPICommand
4+
import dev.jorel.commandapi.kotlindsl.getValue
5+
import dev.jorel.commandapi.kotlindsl.playerExecutor
6+
import dev.jorel.commandapi.kotlindsl.subcommand
7+
import dev.slne.surf.npc.api.npc.Npc
8+
import dev.slne.surf.npc.api.npc.NpcPose
9+
import dev.slne.surf.npc.bukkit.command.argument.npcArgument
10+
import dev.slne.surf.npc.bukkit.command.argument.npcPoseArgument
11+
import dev.slne.surf.npc.bukkit.util.PermissionRegistry
12+
import dev.slne.surf.surfapi.core.api.messages.adventure.sendText
13+
14+
fun CommandAPICommand.npcEditPoseCommand() = subcommand("pose") {
15+
withPermission(PermissionRegistry.COMMAND_NPC_EDIT_POSE)
16+
npcArgument("npc")
17+
npcPoseArgument("pose")
18+
19+
playerExecutor { player, args ->
20+
val npc: Npc by args
21+
val pose: NpcPose by args
22+
23+
if (npc.isFromPlugin()) {
24+
player.sendText {
25+
appendPrefix()
26+
error("Der Npc wurde von einem Plugin erstellt und kann daher nicht bearbeitet werden.")
27+
}
28+
return@playerExecutor
29+
}
30+
31+
npc.setPose(pose)
32+
33+
player.sendText {
34+
appendPrefix()
35+
success("Die Pose des Npcs ")
36+
variableValue(npc.uniqueName)
37+
success(" wurde auf ")
38+
variableValue(pose.name)
39+
success(" gesetzt.")
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)