Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Optifine Cape #11

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ where
pub has_hat_layer: bool,
pub has_layers: bool,
pub has_cape: bool,
pub is_optifine_cape: bool,
pub arm_rotation: f32,
pub shadow_y_pos: Option<f32>,
pub shadow_is_square: bool,
Expand Down
1 change: 1 addition & 0 deletions nmsr-aas/src/model/request/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ impl CacheHandler<RenderRequestEntry, ResolvedRenderEntryTextures, ModelCacheCon
ResolvedRenderEntryTextureType::Ears(ResolvedRenderEntryEarsTextureType::Cape),
#[cfg(feature = "ears")]
ResolvedRenderEntryTextureType::Ears(ResolvedRenderEntryEarsTextureType::Emissive),
ResolvedRenderEntryTextureType::OptifineCape
];

for texture in textures_to_read {
Expand Down
2 changes: 2 additions & 0 deletions nmsr-aas/src/model/request/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub enum RenderRequestFeatures {
Custom,
#[cfg(feature = "ears")]
Ears,
OptifineCape
}

#[derive(Debug, Clone, PartialEq, Default, IsEmpty)]
Expand Down Expand Up @@ -290,6 +291,7 @@ impl RenderRequest {

request.features.remove(RenderRequestFeatures::BodyLayers);
request.features.remove(RenderRequestFeatures::Cape);
request.features.remove(RenderRequestFeatures::OptifineCape);
}

// If the request is custom, we add the custom feature, otherwise we remove it
Expand Down
50 changes: 49 additions & 1 deletion nmsr-aas/src/model/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use ears_rs::{alfalfa::AlfalfaDataKey, features::EarsFeatures, parser::EarsParse
use nmsr_rendering::high_level::parts::provider::ears::PlayerPartEarsTextureType;
use nmsr_rendering::high_level::types::PlayerPartTextureType;
use std::{collections::HashMap, sync::Arc};
use image::{ImageBuffer, ImageEncoder, ImageFormat, Rgba};
use strum::EnumCount;
use tracing::{instrument, trace_span, Instrument, Span};

Expand All @@ -32,6 +33,7 @@ pub enum ResolvedRenderEntryTextureType {
Skin,
#[cfg(feature = "ears")]
Ears(ResolvedRenderEntryEarsTextureType),
OptifineCape
}

impl From<ResolvedRenderEntryTextureType> for &'static str {
Expand All @@ -41,6 +43,7 @@ impl From<ResolvedRenderEntryTextureType> for &'static str {
ResolvedRenderEntryTextureType::Skin => "Skin",
#[cfg(feature = "ears")]
ResolvedRenderEntryTextureType::Ears(ears) => ears.key(),
ResolvedRenderEntryTextureType::OptifineCape => "OptifineCape"
}
}
}
Expand Down Expand Up @@ -92,6 +95,9 @@ impl From<ResolvedRenderEntryTextureType> for PlayerPartTextureType {
ResolvedRenderEntryTextureType::Ears(ears) => {
PlayerPartEarsTextureType::from(ears).into()
}
ResolvedRenderEntryTextureType::OptifineCape => {
Self::Cape
}
}
}
}
Expand Down Expand Up @@ -201,11 +207,27 @@ impl RenderRequestResolver {
return Ok(result);
}

let bytes = self
let mut bytes = self
.mojang_requests_client
.fetch_texture_from_mojang(texture_id, req_type)
.await?;

if req_type == MojangTextureRequestType::OptifineCape {
let img = image::load_from_memory(&bytes).unwrap();

let thumbnail = img.thumbnail(46, 22);
let mut canvas = ImageBuffer::<Rgba<u8>, Vec<u8>>::new(64, 32);

image::imageops::overlay(&mut canvas, &thumbnail, 0, 0);
let mut cursor = std::io::Cursor::new(Vec::new());

let encoder = image::codecs::png::PngEncoder::new(&mut cursor);

encoder.write_image(&canvas, canvas.width(), canvas.height(), image::ExtendedColorType::Rgba8).unwrap();

bytes = cursor.into_inner();
}

let texture = MojangTexture::new_named(texture_id.to_owned(), bytes);

self.model_cache.cache_texture(&texture).await?;
Expand All @@ -225,6 +247,7 @@ impl RenderRequestResolver {
let model: Option<RenderRequestEntryModel>;
let skin_texture: Option<MojangTexture>;
let cape_texture: Option<MojangTexture>;
let optifine_cape_texture: Option<MojangTexture>;

match &entry {
RenderRequestEntry::MojangPlayerUuid(id) | RenderRequestEntry::MojangOfflinePlayerUuid(id) => {
Expand All @@ -243,6 +266,18 @@ impl RenderRequestResolver {

let textures = result.textures()?;

let name = textures.name().unwrap();

//println!("Optifine Request User: {}", name);

let has_optifine_cape = self
.mojang_requests_client
.check_optifine_cape_status(name)
.await
.unwrap();

//println!("Optifine Cape Available From Web: {}", has_optifine_cape);

let skin = textures
.skin()
.ok_or_else(|| MojangRequestError::MissingSkinPropertyError(*id))?;
Expand All @@ -256,6 +291,12 @@ impl RenderRequestResolver {

skin_texture = self.fetch_game_profile_texture(textures.skin(), MojangTextureRequestType::Skin).await?;
cape_texture = self.fetch_game_profile_texture(cape, MojangTextureRequestType::Cape).await?;
if has_optifine_cape {
let texture_id = format!("OptifineCapeTexture_{}", name);
optifine_cape_texture = Some(self.fetch_texture_from_mojang(&texture_id, MojangTextureRequestType::OptifineCape).await?);
} else {
optifine_cape_texture = None;
}
}
RenderRequestEntry::GeyserPlayerUuid(id) => {
let (texture_id, player_model) =
Expand All @@ -264,18 +305,21 @@ impl RenderRequestResolver {

skin_texture = Some(self.fetch_texture_from_mojang(&texture_id, MojangTextureRequestType::Skin).await?);
cape_texture = None;
optifine_cape_texture = None;

model = Some(player_model);
}
RenderRequestEntry::TextureHash(skin_hash) => {
// If the skin is not cached, we'll have to fetch it from Mojang.
skin_texture = Some(self.fetch_texture_from_mojang(skin_hash, MojangTextureRequestType::Skin).await?);
cape_texture = None;
optifine_cape_texture = None;
model = None;
}
RenderRequestEntry::PlayerSkin(bytes) => {
skin_texture = Some(MojangTexture::new_unnamed(bytes.clone()));
cape_texture = None;
optifine_cape_texture = None;
model = None;
}
}
Expand All @@ -286,6 +330,10 @@ impl RenderRequestResolver {
textures.insert(ResolvedRenderEntryTextureType::Cape, cape_texture);
}

if let Some(optifine_cape_texture) = optifine_cape_texture {
textures.insert(ResolvedRenderEntryTextureType::OptifineCape, optifine_cape_texture);
}

if let Some(skin_texture) = skin_texture {
#[cfg(feature = "ears")]
Self::resolve_ears_textures(&skin_texture, &mut textures);
Expand Down
17 changes: 17 additions & 0 deletions nmsr-aas/src/model/resolver/mojang/client.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::error::Error;
use super::model::GameProfile;
use crate::{
config::MojankConfiguration,
Expand All @@ -18,6 +19,7 @@ pub struct MojangClient {
pub enum MojangTextureRequestType {
Skin,
Cape,
OptifineCape
}

impl MojangClient {
Expand All @@ -40,6 +42,16 @@ impl MojangClient {
.await
}

#[instrument(skip(self))]
pub(crate) async fn check_optifine_cape_status(
&self,
name: &str
) -> Result<bool, Box<dyn Error>> {
let url = self.build_request_url(MojangTextureRequestType::OptifineCape, name);
self.client.check_status(&url)
.await
}

pub async fn resolve_uuid_to_game_profile(
&self,
id: &Uuid,
Expand Down Expand Up @@ -98,6 +110,11 @@ impl MojangClient {
.textures_server_cape_url_template
.replace("{textures_server}", &mojank.textures_server)
.replace("{texture_id}", texture_id),
MojangTextureRequestType::OptifineCape => {
let name = texture_id.replacen("OptifineCapeTexture_", "", 1);
"http://s.optifine.net/capes/{name}.png"
.replace("{name}", &name)
}
}
}
}
7 changes: 7 additions & 0 deletions nmsr-aas/src/model/resolver/mojang/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ impl GameProfileTexture {
#[derive(Deserialize, Debug)]
pub struct GameProfileTextures {
textures: HashMap<String, GameProfileTexture>,
#[serde(rename = "profileName")]
profile_name: String
}

impl GameProfileTextures {
Expand All @@ -61,6 +63,11 @@ impl GameProfileTextures {
pub fn cape(&self) -> Option<&GameProfileTexture> {
self.textures.get(Self::CAPE_KEY)
}

#[must_use]
pub fn name(&self) -> Option<&String> {
Some(&self.profile_name)
}
}

#[derive(Deserialize)]
Expand Down
5 changes: 5 additions & 0 deletions nmsr-aas/src/routes/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ pub struct RenderRequestQueryParams {

pub noshading: Option<String>,
pub nolayers: Option<String>,
pub nooptifine: Option<String>,

#[serde(alias = "y")]
pub yaw: Option<f32>,
Expand Down Expand Up @@ -119,6 +120,10 @@ impl RenderRequestQueryParams {
excluded |= RenderRequestFeatures::UnProcessedSkin;
}

if self.nooptifine.is_some() {
excluded |= RenderRequestFeatures::OptifineCape;
}

excluded
}

Expand Down
23 changes: 21 additions & 2 deletions nmsr-aas/src/routes/render_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ pub(crate) async fn internal_render_model<'a>(
&parts,
);

//println!("Optifine Enabled: {} {}", part_context.is_optifine_cape, request.features.contains(RenderRequestFeatures::OptifineCape));

load_textures(resolved, state, request, &mut part_context, &mut scene).await?;

scene.render(&state.graphics_context)?;
Expand Down Expand Up @@ -97,7 +99,14 @@ async fn load_textures<'a>(
image_buffer = NMSRState::process_skin(image_buffer, request.features)?;
}

scene.set_texture(&state.graphics_context, texture_type.into(), &image_buffer);
if texture_type == ResolvedRenderEntryTextureType::OptifineCape && request.features.contains(RenderRequestFeatures::OptifineCape) {
scene.set_texture(&state.graphics_context, ResolvedRenderEntryTextureType::Cape.into(), &image_buffer);
} else if !(texture_type == ResolvedRenderEntryTextureType::Cape &&
part_provider.is_optifine_cape &&
request.features.contains(RenderRequestFeatures::OptifineCape)
) {
scene.set_texture(&state.graphics_context, texture_type.into(), &image_buffer);
}
}

if let Some(armor_slots) = part_provider.armor_slots.as_ref() {
Expand Down Expand Up @@ -141,13 +150,22 @@ pub(crate) fn create_part_context(
let has_layers = request.features.contains(RenderRequestFeatures::BodyLayers);
let has_hat_layer = request.features.contains(RenderRequestFeatures::HatLayer);

#[allow(unused_variables)]
let has_optifine_cape = resolved
.textures
.contains_key(&ResolvedRenderEntryTextureType::OptifineCape);

//println!("Optifine Cape Available: {}", has_optifine_cape);

#[allow(unused_variables)]
let has_cape = {
let has_cape_feature = request.features.contains(RenderRequestFeatures::Cape);
let has_cape = resolved
.textures
.contains_key(&ResolvedRenderEntryTextureType::Cape);

let has_optifine_cape_feature = request.features.contains(RenderRequestFeatures::OptifineCape);

let has_ears_feature = false;
let has_ears_cape = false;

Expand All @@ -161,7 +179,7 @@ pub(crate) fn create_part_context(
crate::model::resolver::ResolvedRenderEntryEarsTextureType::Cape,
));

has_cape_feature && (has_cape || (has_ears_feature && has_ears_cape))
has_cape_feature && (has_cape || (has_ears_feature && has_ears_cape) || (has_optifine_cape_feature && has_optifine_cape))
};

let shadow_y_pos = request.get_shadow_y_pos();
Expand Down Expand Up @@ -191,6 +209,7 @@ pub(crate) fn create_part_context(
has_layers,
has_hat_layer,
has_cape,
is_optifine_cape: has_optifine_cape,
arm_rotation,
shadow_y_pos,
shadow_is_square: request.mode.is_head() || request.mode.is_head_iso(),
Expand Down
32 changes: 32 additions & 0 deletions nmsr-aas/src/utils/http_client.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::error::Error;
use axum::http::{HeaderName, HeaderValue};
use http_body_util::{BodyExt, Empty};
use hyper::{body::Bytes, Method, Request};
Expand Down Expand Up @@ -92,6 +93,37 @@ impl NmsrHttpClient {
.map(|b| b.to_bytes())
.map_err(|e| MojangRequestError::BoxedRequestError(Box::new(e)))
}

#[instrument(skip(self))]
pub(crate) async fn check_status(&self, url: &str) -> Result<bool, Box<dyn Error>> {
let request = Request::builder()
.method(Method::GET)
.uri(url)
.body(SyncBody::new(Empty::new().map_err(|e| {
unreachable!("Empty body should not error: {}", e)
})))?;

let response = {
let mut svc = self.inner.clone();

let service = svc
.ready()
.await
.unwrap();

service
.call(request)
.await
.unwrap()
};

if response.status().is_success() {
Ok(true)
} else {
Ok(false)
}
}

}

fn create_http_client(rate_limit_per_second: u64) -> NmsrHttpClient {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub fn new_model_generator_without_part_context<I: ModelProjectImageIO>(
has_hat_layer: layers,
has_layers: layers,
has_cape: false,
is_optifine_cape: false,
arm_rotation: 10.0,
shadow_y_pos: None,
shadow_is_square: false,
Expand Down
1 change: 1 addition & 0 deletions utils/nmsr-rendering-parts-generator-experiment/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@ async fn process_group_logic(
has_hat_layer: parts.iter().any(|p| p.is_hat_layer()),
has_layers: parts.iter().any(|p| p.is_layer()),
has_cape: false,
is_optifine_cape: false,
arm_rotation,
shadow_y_pos,
shadow_is_square: false,
Expand Down
1 change: 1 addition & 0 deletions utils/nmsr-skin-template-generator-experiment/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ fn main() -> anyhow::Result<()> {
has_hat_layer: true,
has_layers: true,
has_cape: false,
is_optifine_cape: false,
arm_rotation: 0f32,
shadow_y_pos: None,
shadow_is_square: false,
Expand Down
1 change: 1 addition & 0 deletions utils/nmsr-software-rasterizer/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ fn main() {
has_hat_layer: true,
has_layers: true,
has_cape: false,
is_optifine_cape: false,
arm_rotation: 10.0,
shadow_y_pos: None,
shadow_is_square: false,
Expand Down