Skip to content
This repository was archived by the owner on Nov 28, 2025. It is now read-only.

Commit 7657806

Browse files
authored
Merge pull request #150 from DockyardMC/feature/permission-system
Add permission system
2 parents d27c133 + 201a98c commit 7657806

File tree

12 files changed

+423
-18
lines changed

12 files changed

+423
-18
lines changed

.idea/inspectionProfiles/Project_Default.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main/kotlin/Main.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@ fun main() {
3838

3939
Events.on<PlayerJoinEvent> { event ->
4040
val player = event.player
41-
player.permissions.add("dockyard.admin")
42-
player.permissions.add("dockyard.*")
4341
player.gameMode.value = GameMode.CREATIVE
4442
DebugSidebar.sidebar.viewers.add(player)
4543
player.canFly.value = true

src/main/kotlin/io/github/dockyardmc/entity/Entity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ abstract class Entity(open var location: Location, open var world: World) : Disp
273273
}
274274

275275
fun playDeathAnimation() {
276-
val packet = ClientboundEntityEventPacket(this, EntityEvent.ENTITY_DIE)
276+
val packet = ClientboundEntityEventPacket(this, EntityEvent.LIVING_ENTITY_PLAY_DEATH_SOUND)
277277
pose.value = EntityPose.DYING
278278
viewers.sendPacket(packet)
279279
passengers.clear(false)

src/main/kotlin/io/github/dockyardmc/extentions/ExtendedMutableList.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,27 @@ import io.github.dockyardmc.scroll.Component
1717
import io.github.dockyardmc.scroll.extensions.toComponent
1818
import java.util.UUID
1919

20+
fun <T> MutableList<T>.addAllNonDuplicates(other: Collection<T>) {
21+
val nonDuplicates = other.filter { item -> !this.contains(item) }
22+
this.addAll(nonDuplicates)
23+
}
24+
25+
fun Collection<Player>.filterByPermission(permission: String): Collection<Player> {
26+
return this.filter { player -> player.hasPermission(permission) }
27+
}
28+
29+
fun Collection<Player>.addPermission(permission: String) {
30+
this.forEach { player ->
31+
player.permissions.add(permission)
32+
}
33+
}
34+
35+
fun Collection<Player>.removePermission(permission: String) {
36+
this.forEach { player ->
37+
player.permissions.remove(permission)
38+
}
39+
}
40+
2041
fun Collection<Player>.teleport(location: Location) {
2142
this.forEach { player -> player.teleport(location) }
2243
}

src/main/kotlin/io/github/dockyardmc/player/Player.kt

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import io.github.dockyardmc.maths.vectors.Vector3d
2020
import io.github.dockyardmc.maths.vectors.Vector3f
2121
import io.github.dockyardmc.particles.BlockParticleData
2222
import io.github.dockyardmc.particles.spawnParticle
23+
import io.github.dockyardmc.player.permissions.PermissionSystem
2324
import io.github.dockyardmc.player.systems.*
2425
import io.github.dockyardmc.protocol.PlayerNetworkManager
2526
import io.github.dockyardmc.protocol.packets.ClientboundPacket
@@ -116,6 +117,7 @@ class Player(
116117
val gameModeSystem = GameModeSystem(this)
117118
val playerInfoSystem = PlayerInfoSystem(this)
118119
val entityViewSystem = EntityViewSystem(this)
120+
val permissionSystem = PermissionSystem(this, permissions)
119121
val attributes = PlayerAttributes(this)
120122

121123
val decoupledEntityViewSystemTicking = DockyardServer.scheduler.runRepeating(1.ticks) {
@@ -165,9 +167,6 @@ class Player(
165167

166168
fovModifier.valueChanged { this.sendPacket(ClientboundPlayerAbilitiesPacket(isFlying.value, isInvulnerable, canFly.value, flySpeed.value, it.newValue)) }
167169

168-
permissions.itemAdded { rebuildCommandNodeGraph() }
169-
permissions.itemRemoved { rebuildCommandNodeGraph() }
170-
171170
health.valueChanged { sendHealthUpdatePacket() }
172171
food.valueChanged { sendHealthUpdatePacket() }
173172
saturation.valueChanged { sendHealthUpdatePacket() }
@@ -363,9 +362,7 @@ class Player(
363362
}
364363

365364
fun hasPermission(permission: String): Boolean {
366-
if (permission.isEmpty()) return true
367-
if (permissions.values.contains("dockyard.all") || permissions.values.contains("dockyard.*")) return true
368-
return permissions.values.contains(permission)
365+
return permissionSystem.hasPermission(permission)
369366
}
370367

371368
fun sendSelfMetadataPacket() {
@@ -471,7 +468,7 @@ class Player(
471468
val totem = ItemStack(Items.TOTEM_OF_UNDYING).withCustomModelData(customModelData)
472469
inventory[heldSlotIndex.value] = totem
473470
}
474-
val packet = ClientboundEntityEventPacket(this, EntityEvent.PLAYER_PLAY_TOTEM_ANIMATION)
471+
val packet = ClientboundEntityEventPacket(this, EntityEvent.LIVING_ENTITY_PLAY_TOTEM_UNDYING_ANIMATION)
475472
sendPacket(packet)
476473

477474
if (customModelData != null) {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.github.dockyardmc.player.permissions
2+
3+
data class PermissionGroup(val id: String, private val permissions: List<String>) : PermissionHolder() {
4+
5+
init {
6+
buildPermissionCache(permissions)
7+
}
8+
9+
class Builder {
10+
var id: String? = null
11+
val permissions: MutableList<String> = mutableListOf()
12+
13+
fun withId(id: String) {
14+
this.id = id.lowercase()
15+
}
16+
17+
fun withPermissions(permissions: List<String>) {
18+
this.permissions.addAll(permissions)
19+
}
20+
21+
fun withPermissions(vararg permissions: String) {
22+
this.permissions.addAll(permissions.toList())
23+
}
24+
}
25+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package io.github.dockyardmc.player.permissions
2+
3+
import io.github.dockyardmc.extentions.addAllNonDuplicates
4+
5+
abstract class PermissionHolder {
6+
7+
protected val cachedPermissions: MutableList<String> = mutableListOf()
8+
9+
// includes inherited permissions
10+
@JvmName("getCachedPermissionsFunction")
11+
fun getCachedPermissions(): List<String> {
12+
return cachedPermissions
13+
}
14+
15+
16+
fun buildPermissionCache(permissions: Collection<String>) {
17+
cachedPermissions.clear()
18+
19+
permissions.forEach { permission ->
20+
if (!permission.startsWith("group")) {
21+
cachedPermissions.add(permission)
22+
} else {
23+
24+
val inheritedGroupId = permission.split(".").getOrNull(1) ?: throw IllegalArgumentException("Group inheritance permissions string need be in the following format: `group.<group id>`")
25+
val inheritedGroup = PermissionManager[inheritedGroupId]
26+
27+
cachedPermissions.addAllNonDuplicates(inheritedGroup.getCachedPermissions())
28+
}
29+
}
30+
}
31+
32+
fun hasPermission(permission: String): Boolean {
33+
if(permission.isEmpty()) return true
34+
if (cachedPermissions.contains(permission)) return true
35+
36+
cachedPermissions.forEach { loopPermission ->
37+
if (loopPermission == "*") return true
38+
39+
if (loopPermission.endsWith(".*")) {
40+
val prefix = loopPermission.removeSuffix(".*")
41+
if (permission.startsWith("$prefix.")) {
42+
return true
43+
}
44+
}
45+
}
46+
47+
return false
48+
}
49+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package io.github.dockyardmc.player.permissions
2+
3+
object PermissionManager {
4+
private val groups: MutableMap<String, PermissionGroup> = mutableMapOf<String, PermissionGroup>()
5+
6+
fun getOrNull(id: String): PermissionGroup? {
7+
val indexId = if (id.contains("group.")) id.split(".").getOrNull(1)!! else id
8+
return groups[indexId]
9+
}
10+
11+
operator fun get(id: String): PermissionGroup {
12+
return getOrNull(id) ?: throw IllegalArgumentException("Permissions group with id `$id` is not registered")
13+
}
14+
15+
fun addGroup(group: PermissionGroup.Builder.() -> Unit): PermissionGroup {
16+
val builder = PermissionGroup.Builder()
17+
group.invoke(builder)
18+
19+
if (builder.id == null) throw IllegalArgumentException("Id of a permissions group cannot be empty")
20+
val lowercaseId = builder.id!!.lowercase()
21+
22+
if (lowercaseId.contains(".")) throw IllegalArgumentException("Permissions group ids cannot contain dots")
23+
if (groups.containsKey(lowercaseId)) throw IllegalArgumentException("Permission group with the id of $lowercaseId already exists")
24+
25+
builder.id = lowercaseId
26+
val newGroup = PermissionGroup(lowercaseId, builder.permissions)
27+
groups[lowercaseId] = newGroup
28+
29+
return newGroup
30+
}
31+
32+
fun removeGroup(group: PermissionGroup) {
33+
groups.remove(group.id)
34+
}
35+
36+
fun removeGroup(id: String) {
37+
groups.remove(id)
38+
}
39+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package io.github.dockyardmc.player.permissions
2+
3+
import cz.lukynka.bindables.BindableList
4+
import io.github.dockyardmc.player.Player
5+
import io.github.dockyardmc.player.systems.PlayerSystem
6+
import io.github.dockyardmc.protocol.packets.play.clientbound.ClientboundEntityEventPacket
7+
import io.github.dockyardmc.protocol.packets.play.clientbound.EntityEvent
8+
9+
class PermissionSystem(val player: Player, val bindable: BindableList<String>): PermissionHolder(), PlayerSystem {
10+
11+
init {
12+
bindable.listUpdated {
13+
buildPermissionCache(bindable.values)
14+
player.rebuildCommandNodeGraph()
15+
16+
if(bindable.contains("*")) {
17+
player.sendPacket(ClientboundEntityEventPacket(player, EntityEvent.PLAYER_SET_OP_PERMISSION_LEVEL_4))
18+
} else {
19+
player.sendPacket(ClientboundEntityEventPacket(player, EntityEvent.PLAYER_SET_OP_PERMISSION_LEVEL_0))
20+
}
21+
}
22+
}
23+
24+
}

src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundEntityEventPacket.kt

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,42 @@ class ClientboundEntityEventPacket(entity: Entity, event: EntityEvent) : Clientb
1313
}
1414

1515
enum class EntityEvent(val id: Int) {
16-
ENTITY_DIE(3),
17-
WARDEN_ATTACK(4),
18-
RAVAGER_ATTACK_ANIMATION(4),
19-
RAVAGER_STUNNED(39),
16+
SPAWNS_HONEY_BLOCK_PARTICLES(53),
17+
18+
ARROW_SPAWN_TIPPED_ARROW_PARTICLE(0),
19+
20+
LIVING_ENTITY_PLAY_DEATH_SOUND(3),
21+
LIVING_ENTITY_SHIELD_BLOCK_SOUND(29),
22+
LIVING_ENTITY_SHIELD_BREAK_SOUND(30),
23+
LIVING_ENTITY_PLAY_TOTEM_UNDYING_ANIMATION(35),
24+
LIVING_ENTITY_SWAP_HAND_ITEMS(55),
25+
LIVING_ENTITY_SPAWN_DEATH_SMOKE_PARTICLES(60),
26+
2027
PLAYER_ITEM_USE_FINISHED(9),
21-
PLAYER_ENABLE_DEBUG_SCREEN(23),
22-
PLAYER_DISABLE_DEBUG_SCREEN(22),
28+
PLAYER_ENABLE_DEBUG_SCREEN(22),
29+
PLAYER_DISABLE_DEBUG_SCREEN(23),
2330
PLAYER_SET_OP_PERMISSION_LEVEL_0(24),
2431
PLAYER_SET_OP_PERMISSION_LEVEL_1(25),
2532
PLAYER_SET_OP_PERMISSION_LEVEL_2(26),
2633
PLAYER_SET_OP_PERMISSION_LEVEL_3(27),
2734
PLAYER_SET_OP_PERMISSION_LEVEL_4(28),
28-
PLAYER_PLAY_TOTEM_ANIMATION(35),
35+
PLAY_SPAWN_CLOUD_PARTICLES(43),
36+
37+
ANIMAL_SPAWN_LOVE_MODE_PARTICLES(18),
38+
39+
OCELOT_SPAWN_SMOKE_PARTICLES(40),
40+
OCELOT_SPAWN_HEART_PARTICLE(41),
41+
42+
RABBIT_JUMP_ANIMATION(1),
43+
44+
SHEEP_EAT_GRASS(10),
45+
46+
SNIFFER_PLAY_DIGGING_SOUND(63),
47+
48+
RAVAGER_ATTACK_ANIMATION(4),
49+
RAVAGER_STUNNED(39),
50+
51+
WARDEN_ATTACK(4),
2952
WARDEN_TENDRIL_SHAKING(61),
3053
WARDEN_SONIC_BOOM(62),
3154
}

0 commit comments

Comments
 (0)