Skip to content
This repository was archived by the owner on Nov 28, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d34403e
fix: writeOptional uses T instead of Float
p1k0chu Mar 26, 2025
f307d50
minimal packet stuff that works
p1k0chu Mar 26, 2025
6177488
advancement api
p1k0chu Mar 27, 2025
07bb299
Send only updated progress and now can remove advancements
p1k0chu Mar 27, 2025
8ba251a
showToast extension function
p1k0chu Mar 27, 2025
6d8a1f2
feat: add AdvancementBuilder for advancements
p1k0chu Apr 4, 2025
cc46a71
feat: add background from block in AdvancementBuilder
p1k0chu Apr 4, 2025
40ce325
fix the AdvancementDisplayTest
p1k0chu Apr 5, 2025
e52adae
refactor: move select tab action class and remove ByteBuf.write(Netwo…
p1k0chu Apr 5, 2025
96f69af
advancement revoking, selecting tab, refactor, style
p1k0chu Apr 7, 2025
bda30f0
feat: advancements are viewable now
p1k0chu Apr 11, 2025
c0380a8
Fix removal of advancements with children
p1k0chu Apr 15, 2025
3cc7c70
Add a getter for advancement progress
p1k0chu Apr 15, 2025
d6f6204
Add icon background to advancements in a builder
p1k0chu Apr 15, 2025
5af5b17
Make selectedTab a bindable
p1k0chu Apr 15, 2025
a6fbe63
Just send packets to client instead of juggling
p1k0chu Apr 15, 2025
39e180a
Fix sending of advancement progress
p1k0chu Apr 15, 2025
1149d05
feat: add viewer to advancements recursively
p1k0chu Apr 15, 2025
3ce6cad
Make advancements mutable and update when modified
p1k0chu Apr 15, 2025
d4be2c0
Fix advancement test
p1k0chu Apr 16, 2025
a5a84ac
chmod +x gradlew
p1k0chu Apr 21, 2025
d0aef0f
move makePacket for advancements out of class
p1k0chu Apr 21, 2025
4417f5b
use event to remove the player from viewers when they disconnect
p1k0chu Apr 21, 2025
a03842d
Add advancement disposal
p1k0chu Apr 21, 2025
0bfe2f1
oopsie
p1k0chu Apr 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified gradlew
100644 → 100755
Empty file.
188 changes: 188 additions & 0 deletions src/main/kotlin/io/github/dockyardmc/advancement/Advancement.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package io.github.dockyardmc.advancement

import cz.lukynka.bindables.BindableList
import io.github.dockyardmc.events.EventPool
import io.github.dockyardmc.events.PlayerLeaveEvent
import io.github.dockyardmc.extentions.writeString
import io.github.dockyardmc.extentions.writeStringArray
import io.github.dockyardmc.extentions.writeTextComponent
import io.github.dockyardmc.extentions.writeVarInt
import io.github.dockyardmc.item.ItemStack
import io.github.dockyardmc.player.Player
import io.github.dockyardmc.protocol.NetworkWritable
import io.github.dockyardmc.protocol.writeOptional
import io.github.dockyardmc.registry.Items
import io.github.dockyardmc.utils.Disposable
import io.github.dockyardmc.utils.Viewable
import io.netty.buffer.ByteBuf
import kotlin.properties.Delegates

class Advancement(
id: String,
parent: Advancement?,

title: String,
description: String,
icon: ItemStack,
frame: AdvancementFrame,
showToast: Boolean,
isHidden: Boolean,
background: String?,
x: Float,
y: Float,

requirements: List<List<String>>,
) : NetworkWritable, Viewable(), Disposable {

var id by Delegates.observable(id) { _, _, _ -> update() }
var parent by Delegates.observable(parent) { _, _, _ -> update() }
val requirements = BindableList(requirements)

var title by Delegates.observable(title) { _, _, _ -> update() }
var description by Delegates.observable(description) { _, _, _ -> update() }
var icon by Delegates.observable(icon) { _, _, _ -> update() }
var frame by Delegates.observable(frame) { _, _, _ -> update() }
var showToast by Delegates.observable(showToast) { _, _, _ -> update() }
var isHidden by Delegates.observable(isHidden) { _, _, _ -> update() }
var background by Delegates.observable(background) { _, _, _ -> update() }
var x by Delegates.observable(x) { _, _, _ -> update() }
var y by Delegates.observable(y) { _, _, _ -> update() }

private val innerChildren = mutableListOf<Advancement>()
val children
get() = synchronized(innerChildren) {
innerChildren.toList()
}

override var autoViewable: Boolean = false

private val eventPool = EventPool()

init {
if (icon.material == Items.AIR) throw IllegalArgumentException("advancement icon can't be air")

parent?.innerChildren?.let {
synchronized(it) {
it.add(this)
}
}

this.requirements.listUpdated { update() }

eventPool.on<PlayerLeaveEvent> { event ->
removeViewer(event.player)
}
}

fun update() {
viewers.forEach { player ->
player.advancementTracker.onAdvancementRemoved(this)
player.advancementTracker.onAdvancementAdded(this)
}

children.forEach(Advancement::update)
}

override fun write(buffer: ByteBuf) {
buffer.writeOptional(parent?.id, ByteBuf::writeString)

// advancement display is present
buffer.writeBoolean(true)

buffer.writeTextComponent(title)
buffer.writeTextComponent(description)
icon.write(buffer)
buffer.writeVarInt(frame.ordinal)
buffer.writeInt(getFlags())
background?.let(buffer::writeString)
buffer.writeFloat(x)
buffer.writeFloat(y)

buffer.writeVarInt(requirements.size)
requirements.values.forEach(buffer::writeStringArray)

buffer.writeBoolean(false) // that's 'Sends telemetry' field
}

/**
* Adds the player as a viewer to this advancement
* and all parents, all the way to root
*/
override fun addViewer(player: Player) {
if (viewers.contains(player)) return

// parents first
this.parent?.addViewer(player)

player.advancementTracker.onAdvancementAdded(this)
viewers.add(player)
}

/**
* Adds the player as a viewer
* to this advancement and ALL children
* recursively
*/
fun addAll(player: Player) {
addViewer(player)

children.forEach { child ->
child.addAll(player)
}
}

/**
* Removes the player-viewer from this advancement
* and all children
*/
override fun removeViewer(player: Player) {
if (!viewers.contains(player)) return

// children first
synchronized(this.innerChildren) {
this.innerChildren.forEach { child ->
child.removeViewer(player)
}
}

player.advancementTracker.onAdvancementRemoved(this)
viewers.remove(player)
}

fun getFlags(): Int {
var flags = 0x0
if (background != null) {
flags = flags or HAS_BACKGROUND_TEXTURE
}
if (showToast) {
flags = flags or SHOW_TOAST
}
if (isHidden) {
flags = flags or HIDDEN
}
return flags
}

companion object {
const val HAS_BACKGROUND_TEXTURE = 0x01
const val SHOW_TOAST = 0x02
const val HIDDEN = 0x04
}

/**
* Deletes this advancement and its children
*/
override fun dispose() {
this.parent?.innerChildren?.remove(this)

while (viewers.isNotEmpty()) {
removeViewer(viewers.first())
}

while (innerChildren.isNotEmpty()) {
innerChildren.removeFirstOrNull()?.parent = null
}

this.parent = null
}
}
113 changes: 113 additions & 0 deletions src/main/kotlin/io/github/dockyardmc/advancement/AdvancementBuilder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package io.github.dockyardmc.advancement

import io.github.dockyardmc.item.ItemStack
import io.github.dockyardmc.maths.vectors.Vector2f
import io.github.dockyardmc.registry.Items
import io.github.dockyardmc.registry.registries.Item
import io.github.dockyardmc.registry.registries.RegistryBlock

/**
* Build the [Advancement]
*/
class AdvancementBuilder(val id: String) {
var parent: Advancement? = null
val requirements = mutableListOf<List<String>>()

var title: String = ""
var description: String = ""
var icon: ItemStack = Items.PAPER.toItemStack()
var frame: AdvancementFrame = AdvancementFrame.TASK
var showToast: Boolean = true
var isHidden: Boolean = false
var background: String? = null

var x: Float = 0f
var y: Float = 0f

fun withParent(parent: Advancement) {
this.parent = parent
}

fun withTitle(title: String) {
this.title = title
}

fun withDescription(description: String) {
this.description = description
}

fun withIcon(icon: ItemStack) {
this.icon = icon
}

fun withIcon(icon: Item) {
this.icon = icon.toItemStack()
}

fun withFrame(frame: AdvancementFrame) {
this.frame = frame
}

fun useToast(showToast: Boolean) {
this.showToast = showToast
}

fun withHidden(isHidden: Boolean) {
this.isHidden = isHidden
}

/**
* @param background path to a texture in minecraft resource pack
*
* Some examples:
* - `minecraft:textures/item/stick.png`
* - `minecraft:textures/block/netherrack.png`
* - `minecraft:textures/gui/book.png` (looks bad but works)
*/
fun withBackground(background: String?) {
this.background = background
}

fun withBackground(block: RegistryBlock) {
val blockId = block.identifier.removePrefix("minecraft:")

// this is how it should be in 1.21.5
// "block/$blockId"
this.background = "minecraft:textures/block/$blockId.png"
}

fun withBackground(background: Item) {
val id = background.identifier.removePrefix("minecraft:")
this.background = "minecraft:textures/item/$id.png"
}

fun withPosition(x: Float, y: Float) {
this.x = x
this.y = y
}

fun withPosition(vec: Vector2f) {
this.x = vec.x
this.y = vec.y
}

fun withRequirement(req: String) {
this.requirements += listOf(req)
}

fun withRequirementsAnyOf(requirements: List<String>) {
this.requirements += requirements
}

fun build(): Advancement {
return Advancement(
id, parent, title, description, icon, frame, showToast, isHidden, background, x, y, requirements
)
}
}

fun advancement(id: String, builder: AdvancementBuilder.() -> Unit): Advancement {
val adv = AdvancementBuilder(id)
builder.invoke(adv)
return adv.build()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.github.dockyardmc.advancement

enum class AdvancementFrame {
TASK,
CHALLENGE,
GOAL;
}
Loading
Loading