Skip to content

Commit 212d31c

Browse files
committed
fix: mineskin
1 parent abe07f8 commit 212d31c

19 files changed

Lines changed: 255 additions & 95 deletions

build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ dependencies {
3232

3333
api("cc.modlabs:KlassicX:2025.4.22.1239")
3434

35+
api("com.squareup.okhttp3:okhttp:4.12.0")
36+
3537
testImplementation("io.kotest:kotest-runner-junit5:$koTestVersion")
3638
testImplementation("io.mockk:mockk:${mockkVersion}")
3739
testImplementation("com.google.code.gson:gson:2.11.0")

src/main/kotlin/cc/modlabs/kpaper/extensions/InventoryExtensions.kt

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ import org.bukkit.entity.Player
1515
import org.bukkit.inventory.Inventory
1616
import org.bukkit.inventory.InventoryHolder
1717
import org.bukkit.inventory.ItemStack
18+
import org.bukkit.inventory.PlayerInventory
1819
import org.bukkit.inventory.meta.ItemMeta
1920
import org.bukkit.persistence.PersistentDataType
21+
import org.jetbrains.annotations.ApiStatus.Experimental
2022
import java.util.UUID
2123

22-
2324
/**
2425
* Each gridIndex is a 3x3 grid inside the inventory
2526
* There are a total of 6 grids
@@ -333,4 +334,90 @@ fun Item.ownedBy(uuid: UUID, time: Long = 20 * 10L): Item {
333334
owner = null
334335
}
335336
return this
337+
}
338+
339+
fun canItemFitInInventory(inventory: Inventory, itemToAdd: ItemStack): Boolean {
340+
// Clone the item to avoid modifying the original
341+
val itemToCheck = itemToAdd.clone()
342+
343+
if (itemToCheck.amount <= 0) {
344+
return true // Empty stack always fits
345+
}
346+
347+
// First pass: try to stack with existing similar items
348+
for (i in 0 until inventory.size) {
349+
val slot = inventory.getItem(i)
350+
351+
// Check if the slot has an item that can be stacked with our item
352+
if (slot != null && slot.isSimilar(itemToCheck)) {
353+
val maxStackSize = slot.type.maxStackSize
354+
val availableSpace = maxStackSize - slot.amount
355+
356+
if (availableSpace > 0) {
357+
itemToCheck.amount -= availableSpace
358+
359+
if (itemToCheck.amount <= 0) {
360+
return true // Item fits completely
361+
}
362+
}
363+
}
364+
}
365+
366+
// Second pass: check for empty slots
367+
var remainingAmount = itemToCheck.amount
368+
val maxStackSize = itemToCheck.type.maxStackSize
369+
370+
for (i in 0 until inventory.size) {
371+
val slot = inventory.getItem(i)
372+
373+
if (slot == null) {
374+
// This is an empty slot
375+
remainingAmount -= maxStackSize
376+
377+
if (remainingAmount <= 0) {
378+
return true // Item fits completely using empty slots
379+
}
380+
}
381+
}
382+
383+
return false // Cannot fit the item
384+
}
385+
386+
/**
387+
* Alternative version that simulates adding the item to the inventory by creating a clone,
388+
* useful when you need to know exactly how the item will be distributed.
389+
*
390+
* @param inventory The Bukkit/PaperMC inventory to check
391+
* @param itemToAdd The ItemStack that needs to be added to the inventory
392+
* @return Boolean indicating whether the item can fit into the inventory
393+
*/
394+
@Experimental
395+
fun canItemFitInInventorySimulated(inventory: PlayerInventory, itemToAdd: ItemStack): Boolean {
396+
// Clone the inventory to avoid modifying the original
397+
val tempInventory = inventory.holder?.server?.createInventory(null, inventory.size)
398+
?: return false
399+
400+
// Copy all items to the temporary inventory
401+
for (i in 0 until inventory.size) {
402+
val item = inventory.getItem(i)
403+
if (item != null) {
404+
tempInventory.setItem(i, item.clone())
405+
}
406+
}
407+
408+
// Try to add the item to the temporary inventory
409+
val leftover = tempInventory.addItem(itemToAdd.clone())
410+
411+
// If there are no leftover items, it means everything fit
412+
return leftover.isEmpty()
413+
}
414+
415+
/**
416+
* Extension function for PlayerInventory that checks if an ItemStack can fit
417+
*
418+
* @param itemToAdd The ItemStack to check
419+
* @return Boolean indicating whether the item fits
420+
*/
421+
fun PlayerInventory.canFit(itemToAdd: ItemStack): Boolean {
422+
return canItemFitInInventory(this, itemToAdd)
336423
}

src/main/kotlin/cc/modlabs/kpaper/extensions/PlayerExtensions.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,8 @@ val Player.connection: ServerGamePacketListenerImpl
121121

122122
fun Player.actionBar(text: String) {
123123
sendActionBar(text(text))
124+
}
125+
126+
fun broadcastActionbar(component: Component) {
127+
Bukkit.getOnlinePlayers().forEach { it.sendActionBar(component) }
124128
}

src/main/kotlin/cc/modlabs/kpaper/inventory/ItemBuilder.kt

Lines changed: 11 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ package cc.modlabs.kpaper.inventory
44

55
import cc.modlabs.kpaper.coroutines.taskRunLater
66
import cc.modlabs.kpaper.inventory._internal.ItemClickListener
7-
import cc.modlabs.kpaper.inventory.mineskin.MineSkinResponse
7+
import cc.modlabs.kpaper.inventory.mineskin.MineSkinFetcher
88
import cc.modlabs.kpaper.inventory.mineskin.MinecraftSkin
99
import cc.modlabs.kpaper.inventory.mineskin.SKIN
10-
import cc.modlabs.kpaper.inventory.mineskin.SkinTexture
1110
import cc.modlabs.kpaper.inventory.mineskin.Textures
11+
import cc.modlabs.kpaper.inventory.mineskin.models.texture.MineSkinResponse
1212
import com.destroystokyo.paper.profile.ProfileProperty
1313
import com.google.gson.Gson
1414
import dev.fruxz.ascend.extension.forceCastOrNull
@@ -31,10 +31,8 @@ import org.bukkit.inventory.meta.Damageable
3131
import org.bukkit.inventory.meta.ItemMeta
3232
import org.bukkit.inventory.meta.SkullMeta
3333
import org.bukkit.persistence.PersistentDataType
34-
import java.net.URL
3534
import java.util.*
3635
import java.util.concurrent.TimeUnit
37-
import kotlin.text.take
3836

3937

4038
/**
@@ -331,27 +329,13 @@ class ItemBuilder(material: Material, count: Int = 1, dsl: ItemBuilder.() -> Uni
331329
* @return An ItemBuilder object representing the player's skin texture.
332330
*/
333331
fun textureFromMineSkin(mineSkinUUID: String): ItemBuilder {
334-
if (textureCache.containsKey(mineSkinUUID)) {
335-
return textureFromSkinTexture(textureCache[mineSkinUUID]!!)
336-
}
332+
val fetcher = MineSkinFetcher.fetchSkinSignature(mineSkinUUID)
337333

338-
val target = URL("https://api.mineskin.org/get/uuid/$mineSkinUUID")
339-
val connection = target.openConnection()
340-
connection.setRequestProperty("User-Agent", "KPaper/1.0")
341-
val inputStream = connection.getInputStream()
342-
val scanner = Scanner(inputStream)
343-
val response = StringBuilder()
344-
while (scanner.hasNextLine()) {
345-
response.append(scanner.nextLine())
334+
if (fetcher == null) {
335+
throw IllegalArgumentException("Invalid MineSkin UUID: $mineSkinUUID")
346336
}
347-
scanner.close()
348-
val json = response.toString()
349-
val mineSkinResponse = Gson().fromJson(json, MineSkinResponse::class.java)
350-
val skinTexture = SkinTexture.fromMineSkinResponse(mineSkinResponse)
351-
352-
textureCache[mineSkinUUID] = skinTexture
353337

354-
return textureFromSkinTexture(skinTexture)
338+
return textureFromSkinTexture(fetcher)
355339
}
356340

357341
/**
@@ -360,13 +344,13 @@ class ItemBuilder(material: Material, count: Int = 1, dsl: ItemBuilder.() -> Uni
360344
* @param skinTexture The SkinTexture object containing the UUID, name, and texture information.
361345
* @return An ItemBuilder instance with the custom texture applied.
362346
*/
363-
private fun textureFromSkinTexture(skinTexture: SkinTexture): ItemBuilder {
364-
val skinProfile = Bukkit.createProfile(skinTexture.uuid, skinTexture.name)
347+
private fun textureFromSkinTexture(skinTexture: MineSkinResponse): ItemBuilder {
348+
val skinProfile = Bukkit.createProfile(UUID.randomUUID(), skinTexture.name)
365349
skinProfile.setProperty(
366350
ProfileProperty(
367351
"textures",
368-
skinTexture.texture.value,
369-
skinTexture.texture.signature
352+
skinTexture.texture.data.value,
353+
skinTexture.texture.data.signature
370354
)
371355
)
372356
val skullMeta = itemStack.itemMeta as SkullMeta
@@ -716,9 +700,7 @@ class ItemBuilder(material: Material, count: Int = 1, dsl: ItemBuilder.() -> Uni
716700
* It provides utility methods for creating `ItemBuilder` instances from `ItemStack` objects.
717701
*/
718702
companion object {
719-
val textureCache = mutableMapOf<String, SkinTexture>()
720-
721-
val invalidMaterials = arrayListOf(
703+
val invalidMaterials = arrayListOf(
722704
Material.AIR,
723705
Material.CAVE_AIR,
724706
Material.VOID_AIR,
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package cc.modlabs.kpaper.inventory.mineskin
2+
3+
import cc.modlabs.klassicx.tools.Environment
4+
import cc.modlabs.kpaper.inventory.mineskin.models.texture.MineSkinResponse
5+
import cc.modlabs.kpaper.inventory.mineskin.models.texture.MineSkinSingleSkinResponse
6+
import com.google.gson.Gson
7+
import okhttp3.OkHttpClient
8+
import okhttp3.Request
9+
10+
object MineSkinFetcher {
11+
12+
private val client = OkHttpClient()
13+
14+
private val apiKey = Environment.getString("MINESKIN_API_KEY")
15+
16+
private val gson = Gson()
17+
18+
private val skinCache = mutableMapOf<String, MineSkinResponse>()
19+
20+
fun fetchSkinSignature(skin: String): MineSkinResponse? {
21+
if (skinCache.containsKey(skin)) {
22+
return skinCache[skin]
23+
}
24+
25+
val request = Request.Builder()
26+
.url("https://api.mineskin.org/v2/skins/$skin")
27+
.addHeader("Accept", "application/json")
28+
.addHeader("User-Agent", "MineSkin-User-Agent")
29+
.addHeader("Authorization", "Bearer $apiKey")
30+
.build()
31+
val response = client.newCall(request).execute()
32+
val sR = gson.fromJson(response.body!!.string(), MineSkinSingleSkinResponse::class.java)
33+
if (sR.skin == null) {
34+
return null
35+
}
36+
37+
skinCache[skin] = sR.skin
38+
39+
return sR.skin
40+
}
41+
42+
43+
}

src/main/kotlin/cc/modlabs/kpaper/inventory/mineskin/MineSkinResponse.kt

Lines changed: 0 additions & 22 deletions
This file was deleted.

src/main/kotlin/cc/modlabs/kpaper/inventory/mineskin/MineSkinTextureData.kt

Lines changed: 0 additions & 10 deletions
This file was deleted.

src/main/kotlin/cc/modlabs/kpaper/inventory/mineskin/SkinTexture.kt

Lines changed: 0 additions & 21 deletions
This file was deleted.

src/main/kotlin/cc/modlabs/kpaper/inventory/mineskin/Urls.kt

Lines changed: 0 additions & 8 deletions
This file was deleted.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package cc.modlabs.kpaper.inventory.mineskin.models
2+
3+
data class MineSkinBaseResponse(
4+
val success: Boolean,
5+
val skins: List<MineSkinRawSkinResponse>,
6+
val pagination: MineSkinPagination,
7+
val warnings: List<MineSkinWarning>,
8+
val messages: List<Any>,
9+
val links: MineSkinLinks,
10+
)

0 commit comments

Comments
 (0)