Skip to content

Commit

Permalink
Dev Capes
Browse files Browse the repository at this point in the history
  • Loading branch information
Cezqr committed Dec 31, 2024
1 parent 2dc2dfa commit 7e33127
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package me.odin.mixin.mixins;

import com.mojang.authlib.GameProfile;
import me.odinmain.features.impl.render.DevPlayers;
import net.minecraft.client.network.NetworkPlayerInfo;
import net.minecraft.util.ResourceLocation;
import org.spongepowered.asm.mixin.Debug;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

@Debug(export = true)
@Mixin(NetworkPlayerInfo.class)
public abstract class MixinNetworkPlayerInfo {

@Shadow private ResourceLocation locationCape;

@Shadow @Final private GameProfile gameProfile;

@Inject(method = "getLocationCape", at = @At("HEAD"), cancellable = true)
private void getDevCape(CallbackInfoReturnable<ResourceLocation> cir) {
this.locationCape = DevPlayers.INSTANCE.hookGetLocationCape(this.gameProfile);
if (this.locationCape != null) cir.setReturnValue(this.locationCape);
}

}
1 change: 1 addition & 0 deletions odin/src/main/resources/mixins.odin.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"mixins.MixinModList",
"mixins.MixinMouseHelper",
"mixins.MixinNetworkManager",
"mixins.MixinNetworkPlayerInfo",
"mixins.MixinPlayerControllerMP",
"mixins.MixinRenderDragon",
"mixins.MixinRenderEntityItem",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package me.odinclient.mixin.mixins;

import com.mojang.authlib.GameProfile;
import me.odinmain.features.impl.render.DevPlayers;
import net.minecraft.client.network.NetworkPlayerInfo;
import net.minecraft.util.ResourceLocation;
import org.spongepowered.asm.mixin.Debug;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

@Debug(export = true)
@Mixin(NetworkPlayerInfo.class)
public abstract class MixinNetworkPlayerInfo {

@Shadow private ResourceLocation locationCape;

@Shadow @Final private GameProfile gameProfile;

@Inject(method = "getLocationCape", at = @At("HEAD"), cancellable = true)
private void getDevCape(CallbackInfoReturnable<ResourceLocation> cir) {
this.locationCape = DevPlayers.INSTANCE.hookGetLocationCape(this.gameProfile);
if (this.locationCape != null) cir.setReturnValue(this.locationCape);
}

}
1 change: 1 addition & 0 deletions odinclient/src/main/resources/mixins.odinclient.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"mixins.MixinMouseHelper",
"mixins.MixinNetHandlerPlayClient",
"mixins.MixinNetworkManager",
"mixins.MixinNetworkPlayerInfo",
"mixins.MixinPlayerControllerMP",
"mixins.MixinRenderDragon",
"mixins.MixinRenderEntityItem",
Expand Down
1 change: 1 addition & 0 deletions src/main/kotlin/me/odinmain/OdinMain.kt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ object OdinMain {
petCommand, visualWordsCommand, PosMsgCommand
)
OdinFont.init()
scope.launch(Dispatchers.IO) { DevPlayers.preloadCapes() }
}

fun postInit() {
Expand Down
99 changes: 95 additions & 4 deletions src/main/kotlin/me/odinmain/features/impl/render/DevPlayers.kt
Original file line number Diff line number Diff line change
@@ -1,27 +1,38 @@
package me.odinmain.features.impl.render


import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import kotlinx.coroutines.DelicateCoroutinesApi
import com.google.gson.annotations.SerializedName
import com.mojang.authlib.GameProfile
import kotlinx.coroutines.runBlocking
import me.odinmain.OdinMain.mc
import me.odinmain.OdinMain.scope
import me.odinmain.features.impl.render.ClickGUIModule.devSize
import me.odinmain.utils.downloadFile
import me.odinmain.utils.getDataFromServer
import me.odinmain.utils.render.Color
import me.odinmain.utils.render.translate
import net.minecraft.client.entity.AbstractClientPlayer
import net.minecraft.client.model.ModelBase
import net.minecraft.client.model.ModelRenderer
import net.minecraft.client.renderer.GlStateManager
import net.minecraft.client.renderer.texture.DynamicTexture
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.event.RenderPlayerEvent
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import java.awt.image.BufferedImage
import java.io.File
import java.io.IOException
import java.lang.reflect.Type
import java.net.URL
import javax.imageio.ImageIO
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.math.cos
import kotlin.math.sin

Expand All @@ -30,7 +41,7 @@ object DevPlayers {
val isDev get() = devs.containsKey(mc.session?.username)

data class DevPlayer(val xScale: Float = 1f, val yScale: Float = 1f, val zScale: Float = 1f,
val wings: Boolean = false, val wingsColor: Color = Color(255, 255, 255))
val wings: Boolean = false, val wingsColor: Color = Color(255, 255, 255), var capeLocation: ResourceLocation? = null)
data class DevData(val devName: String, val wingsColor: Triple<Int, Int, Int>, val size: Triple<Float, Float, Float>, val wings: Boolean)

@Suppress("UNCHECKED_CAST")
Expand Down Expand Up @@ -98,10 +109,9 @@ object DevPlayers {
DragonWings.renderWings(event.entityPlayer, event.partialRenderTick, dev)
}

private val dragonWingTextureLocation = ResourceLocation("textures/entity/enderdragon/dragon.png")

object DragonWings : ModelBase() {

private val dragonWingTextureLocation = ResourceLocation("textures/entity/enderdragon/dragon.png")
private val wing: ModelRenderer
private val wingTip: ModelRenderer

Expand Down Expand Up @@ -174,4 +184,85 @@ object DevPlayers {
return f
}
}

val capeFolder = File(mc.mcDataDir, "config/odin/capes")
private val capeUpdateCache = mutableMapOf<String, Boolean>()
data class Capes(
@SerializedName("capes")
val capes: List<String>
)

@OptIn(ExperimentalEncodingApi::class)
fun preloadCapes() {
if (!capeFolder.exists()) capeFolder.mkdirs()

val capeList = fetchCapeList("https://odtheking.github.io/Odin/capes/capes.json")

capeList.forEach { fileName ->
val capeFile = File(capeFolder, fileName)
val capeUrl = "https://odtheking.github.io/Odin/capes/$fileName"

synchronized(capeUpdateCache) {
if (capeUpdateCache[fileName] != true) {
if (!capeFile.exists() || !isFileUpToDate(capeUrl, capeFile)) {
println("Downloading cape: $fileName")
downloadFile(capeUrl, capeFile.path)
}
capeUpdateCache[fileName] = true
}
}
}
}

private fun fetchCapeList(manifestUrl: String): List<String> {
return try {
val json = URL(manifestUrl).readText()
val manifest = Gson().fromJson(json, Capes::class.java)
manifest.capes
} catch (e: IOException) {
e.printStackTrace()
emptyList()
}
}

@OptIn(ExperimentalEncodingApi::class)
fun hookGetLocationCape(gameProfile: GameProfile): ResourceLocation? {
val name = gameProfile.name
if (!devs.containsKey(name)) return null
val dev = devs[name]

val nameEncoded = Base64.encode(name.toByteArray())
val capeFile = File(capeFolder, "$nameEncoded.png")

return getCapeLocation(dev, capeFile)
}

private fun isFileUpToDate(url: String, file: File): Boolean {
try {
val connection = URL(url).openConnection()
connection.connect()
val remoteLastModified = connection.getHeaderFieldDate("Last-Modified", 0L)
val localLastModified = file.lastModified()
return localLastModified >= remoteLastModified
} catch (e: IOException) {
e.printStackTrace()
}
return false
}

private fun getCapeLocation(dev: DevPlayer?, file: File): ResourceLocation? {
if (dev?.capeLocation == null && file.exists()) {
var image: BufferedImage? = null
try {
image = ImageIO.read(file)
} catch (e: IOException) {
e.printStackTrace()
}

val capeLocation = mc.textureManager.getDynamicTextureLocation("odincapes", DynamicTexture(image))
dev?.capeLocation = capeLocation
}
return dev?.capeLocation
}

}
23 changes: 12 additions & 11 deletions src/main/kotlin/me/odinmain/utils/WebUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import kotlinx.coroutines.withTimeoutOrNull
import me.odinmain.OdinMain.logger
import me.odinmain.features.impl.render.DevPlayers
import java.io.BufferedReader
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import java.io.InputStreamReader
import java.io.OutputStreamWriter
import java.net.HttpURLConnection
Expand Down Expand Up @@ -116,22 +118,21 @@ fun fetchURLData(url: String): String {
}

fun downloadFile(url: String, outputPath: String) {
val wrappedURL = URL(url)
val connection = wrappedURL.openConnection()
val url = URL(url)
val connection = url.openConnection()
connection.connect()

val inputStream = connection.getInputStream()
val outputStream = FileOutputStream(outputPath)
val inputStream: InputStream = connection.getInputStream()
val outputFile = File(outputPath)

val buffer = ByteArray(1024)
var bytesRead: Int
outputFile.parentFile?.mkdirs()

while (inputStream.read(buffer).also { bytesRead = it } != -1) {
outputStream.write(buffer, 0, bytesRead)
val outputStream = FileOutputStream(outputFile)
inputStream.use { input ->
outputStream.use { output ->
input.copyTo(output)
}
}

outputStream.close()
inputStream.close()
}

suspend fun hasBonusPaulScore(): Boolean = withTimeoutOrNull(5000) {
Expand Down

0 comments on commit 7e33127

Please sign in to comment.