From f560373b51a10e6c14f17f4addfa080698589720 Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 13 Jun 2023 21:55:30 -0600 Subject: [PATCH 001/238] Add enchant glint to armor trims --- .../java/com/wildfire/render/GenderLayer.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index b0a8b05e..bb961d2d 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -321,13 +321,17 @@ private void renderArmorTrim(ArmorMaterial material, MatrixStack matrixStack, Ve ArmorTrim trim, boolean hasGlint, boolean left) { BreastModelBox trimModelBox = left ? lTrim : rTrim; Sprite sprite = this.armorTrimsAtlas.getSprite(trim.getGenericModelId(material)); - // FIXME this doesn't render the enchantment glint as-is; changing this to use ItemRenderer.getArmorGlintConsumer - // instead does result in the glint rendering, but not in sync with the rest of the armor, which I personally - // consider to be a worse solution than simply leaving this as-is for the time being, as I don't understand - // Minecraft's renderer enough to try to actually fix this - celeste - VertexConsumer vertexConsumer = sprite.getTextureSpecificVertexConsumer( - ItemRenderer.getDirectItemGlintConsumer(vertexConsumerProvider, TexturedRenderLayers.getArmorTrims(), true, hasGlint)); + VertexConsumer vertexConsumer = sprite.getTextureSpecificVertexConsumer(vertexConsumerProvider.getBuffer(TexturedRenderLayers.getArmorTrims())); + // Render the armor trim itself renderBox(trimModelBox, matrixStack, vertexConsumer, packedLightIn, OverlayTexture.DEFAULT_UV, 1f, 1f, 1f, 1f); + // The enchantment glint however requires special handling; due to how Minecraft's enchant glint rendering works, rendering + // it at the same time as the trim itself results in the glint not rendering in sync with the rest of the armor. + // We *also* can't simply render the glint for both the trim and armor at the same time, due to the slight delta we apply + // to fix z-fighting between the trim and armor - and as such - a glint has to be rendered for each respective layer. + if(hasGlint) { + renderBox(trimModelBox, matrixStack, vertexConsumerProvider.getBuffer(RenderLayer.getArmorEntityGlint()), + packedLightIn, OverlayTexture.DEFAULT_UV, 1f, 1f, 1f, 1f); + } } private static void renderBox(WildfireModelRenderer.ModelBox model, MatrixStack matrixStack, VertexConsumer bufferIn, int packedLightIn, int packedOverlayIn, From a130db2370ca89b2a50d636ab51a7284d45091ea Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sun, 23 Jul 2023 12:33:37 -0400 Subject: [PATCH 002/238] Added text scrolling feature from 1.20 to WildfireButton.java Added ability to override breast physics when wearing armor (mainly for transparent armor resource packs) Changed default configuration values (not final, may change before release): Cleavage - 0.05 to 0 Floppy Multiplier - 0.95 to 0.75 Physics - False to True Max Breast Size Reduced to look more realistic. --- .../java/com/wildfire/gui/WildfireButton.java | 7 +- .../java/com/wildfire/gui/WildfireSlider.java | 3 +- .../WildfireCharacterSettingsScreen.java | 17 ++++- .../java/com/wildfire/main/GenderPlayer.java | 37 ++++------- .../com/wildfire/main/WildfireHelper.java | 30 +++++++-- .../wildfire/main/config/Configuration.java | 8 +-- .../com/wildfire/physics/BreastPhysics.java | 4 ++ .../java/com/wildfire/render/GenderLayer.java | 4 +- .../assets/wildfire_gender/lang/de_de.json | 39 ++++-------- .../assets/wildfire_gender/lang/en_us.json | 5 ++ .../assets/wildfire_gender/lang/nds_de.json | 60 ------------------ .../textures/gui/settings_bg.png | Bin 663 -> 662 bytes 12 files changed, 88 insertions(+), 126 deletions(-) delete mode 100644 src/main/resources/assets/wildfire_gender/lang/nds_de.json diff --git a/src/main/java/com/wildfire/gui/WildfireButton.java b/src/main/java/com/wildfire/gui/WildfireButton.java index ef10eb11..1418ffad 100644 --- a/src/main/java/com/wildfire/gui/WildfireButton.java +++ b/src/main/java/com/wildfire/gui/WildfireButton.java @@ -19,6 +19,7 @@ package com.wildfire.gui; import com.mojang.blaze3d.systems.RenderSystem; +import com.wildfire.main.WildfireHelper; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; @@ -50,10 +51,10 @@ public void renderButton(DrawContext ctx, int mouseX, int mouseY, float partialT if(!this.active) clr = 0x222222 + (84 << 24); if(!transparent) ctx.fill(getX(), getY(), getX() + getWidth(), getY() + getHeight(), clr); - int x = (int) (getX() + (getWidth() / 2f) - (font.getWidth(this.getMessage()) / 2f) + 1); - int y = (int) (getY() + (int) Math.ceil((float) getHeight() / 2f) - font.fontHeight / 2f); int textColor = active ? 0xFFFFFF : 0x666666; - ctx.drawTextWithShadow(font, this.getMessage(), x, y, textColor); + int i = this.getX() + 2; + int j = this.getX() + this.getWidth() - 2; + WildfireHelper.drawScrollableText(ctx, font, this.getMessage(), i, this.getY(), j, this.getY() + this.getHeight(), textColor); RenderSystem.setShaderColor(1f, 1f, 1f, 1f); } diff --git a/src/main/java/com/wildfire/gui/WildfireSlider.java b/src/main/java/com/wildfire/gui/WildfireSlider.java index e461b8da..90c69f9f 100644 --- a/src/main/java/com/wildfire/gui/WildfireSlider.java +++ b/src/main/java/com/wildfire/gui/WildfireSlider.java @@ -19,6 +19,7 @@ package com.wildfire.gui; import com.mojang.blaze3d.systems.RenderSystem; +import com.wildfire.main.WildfireHelper; import com.wildfire.main.config.FloatConfigKey; import it.unimi.dsi.fastutil.floats.Float2ObjectFunction; import it.unimi.dsi.fastutil.floats.FloatConsumer; @@ -112,7 +113,7 @@ public void renderButton(DrawContext ctx, int mouseX, int mouseY, float delta) { ctx.fill(xPos2 - 2, getY() + 1, xPos2, getY() + this.height - 1, 0xFFFFFF + (120 << 24)); RenderSystem.enableDepthTest(); TextRenderer font = MinecraftClient.getInstance().textRenderer; - ctx.drawCenteredTextWithShadow(font, getMessage(), this.getX() + this.width / 2, this.getY() + (this.height - 8) / 2, this.hovered || changed ? 0xFFFF55 : 0xFFFFFF); + WildfireHelper.drawCenteredText(ctx, font, getMessage(), this.getX() + this.width / 2, this.getY() + (this.height - 8) / 2, this.hovered || changed ? 0xFFFF55 : 0xFFFFFF); } } diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java index 9eb81522..a0124d7c 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java @@ -82,7 +82,18 @@ public void init() { } }, Tooltip.of(Text.translatable("wildfire_gender.tooltip.hide_in_armor")))); - this.addDrawableChild(this.bounceSlider = new WildfireSlider(xPos, yPos + 40, 158, 22, Configuration.BOUNCE_MULTIPLIER, aPlr.getBounceMultiplierRaw(), value -> { + this.addDrawableChild(new WildfireButton(xPos, yPos + 40, 157, 20, + Text.translatable("wildfire_gender.char_settings.override_armor_physics", aPlr.getArmorPhysicsOverride() ? ENABLED : DISABLED), button -> { + boolean enableArmorPhysicsOverride = !aPlr.getArmorPhysicsOverride(); + + System.out.println("Override: " + enableArmorPhysicsOverride); + if (aPlr.updateArmorPhysicsOverride(enableArmorPhysicsOverride )) { + button.setMessage(Text.translatable("wildfire_gender.char_settings.override_armor_physics", aPlr.getArmorPhysicsOverride() ? ENABLED : DISABLED)); + GenderPlayer.saveGenderInfo(aPlr); + } + }, Tooltip.of(Text.translatable("wildfire_gender.tooltip.override_armor_physics")))); + + this.addDrawableChild(this.bounceSlider = new WildfireSlider(xPos - 1, yPos + 60, 158, 20, Configuration.BOUNCE_MULTIPLIER, aPlr.getBounceMultiplierRaw(), value -> { }, value -> { float bounceText = 3 * value; float v = Math.round(bounceText * 10) / 10f; @@ -99,14 +110,14 @@ public void init() { } })); - this.addDrawableChild(this.floppySlider = new WildfireSlider(xPos, yPos + 60, 158, 22, Configuration.FLOPPY_MULTIPLIER, aPlr.getFloppiness(), value -> { + this.addDrawableChild(this.floppySlider = new WildfireSlider(xPos-1, yPos + 80, 158, 20, Configuration.FLOPPY_MULTIPLIER, aPlr.getFloppiness(), value -> { }, value -> Text.translatable("wildfire_gender.slider.floppy", Math.round(value * 100)), value -> { if (aPlr.updateFloppiness(value)) { GenderPlayer.saveGenderInfo(aPlr); } })); - this.addDrawableChild(new WildfireButton(xPos, yPos + 80, 157, 20, + this.addDrawableChild(new WildfireButton(xPos, yPos + 100, 157, 20, Text.translatable("wildfire_gender.char_settings.hurt_sounds", aPlr.hasHurtSounds() ? ENABLED : DISABLED), button -> { boolean enableHurtSounds = !aPlr.hasHurtSounds(); if (aPlr.updateHurtSounds(enableHurtSounds)) { diff --git a/src/main/java/com/wildfire/main/GenderPlayer.java b/src/main/java/com/wildfire/main/GenderPlayer.java index 12a0bacb..bccb68e5 100644 --- a/src/main/java/com/wildfire/main/GenderPlayer.java +++ b/src/main/java/com/wildfire/main/GenderPlayer.java @@ -46,6 +46,7 @@ public class GenderPlayer { public SyncStatus syncStatus = SyncStatus.UNKNOWN; private boolean showBreastsInArmor = Configuration.SHOW_IN_ARMOR.getDefault(); + private boolean armorPhysOverride = Configuration.ARMOR_PHYSICS_OVERRIDE.getDefault(); private final Configuration cfg; private final BreastPhysics lBreastPhysics, rBreastPhysics; @@ -74,7 +75,7 @@ public GenderPlayer(UUID uuid, Gender gender) { this.cfg.setDefault(Configuration.BREASTS_CLEAVAGE); this.cfg.setDefault(Configuration.BREAST_PHYSICS); - this.cfg.setDefault(Configuration.BREAST_PHYSICS_ARMOR); + this.cfg.setDefault(Configuration.ARMOR_PHYSICS_OVERRIDE); this.cfg.setDefault(Configuration.SHOW_IN_ARMOR); this.cfg.setDefault(Configuration.BOUNCE_MULTIPLIER); this.cfg.setDefault(Configuration.FLOPPY_MULTIPLIER); @@ -125,6 +126,12 @@ public boolean updateBreastPhysics(boolean value) { return updateValue(Configuration.BREAST_PHYSICS, value, v -> this.breastPhysics = v); } + public boolean getArmorPhysicsOverride() { + return armorPhysOverride; + } + public boolean updateArmorPhysicsOverride(boolean value) { + return updateValue(Configuration.ARMOR_PHYSICS_OVERRIDE, value, v -> this.armorPhysOverride = v); + } public boolean showBreastsInArmor() { return showBreastsInArmor; } @@ -166,6 +173,7 @@ public static JsonObject toJsonObject(GenderPlayer plr) { Configuration.BREAST_PHYSICS.save(obj, plr.hasBreastPhysics()); Configuration.SHOW_IN_ARMOR.save(obj, plr.showBreastsInArmor()); + Configuration.ARMOR_PHYSICS_OVERRIDE.save(obj, plr.getArmorPhysicsOverride()); Configuration.BOUNCE_MULTIPLIER.save(obj, plr.getBounceMultiplierRaw()); Configuration.FLOPPY_MULTIPLIER.save(obj, plr.getFloppiness()); @@ -178,29 +186,6 @@ public static JsonObject toJsonObject(GenderPlayer plr) { return obj; } - public static GenderPlayer fromJsonObject(JsonObject obj) { - GenderPlayer plr = new GenderPlayer(Configuration.USERNAME.read(obj)); - plr.updateGender(Configuration.GENDER.read(obj)); - plr.updateBustSize(Configuration.BUST_SIZE.read(obj)); - plr.updateHurtSounds(Configuration.HURT_SOUNDS.read(obj)); - - //physics - plr.updateBreastPhysics(Configuration.BREAST_PHYSICS.read(obj)); - plr.updateShowBreastsInArmor(Configuration.SHOW_IN_ARMOR.read(obj)); - plr.updateBounceMultiplier(Configuration.BOUNCE_MULTIPLIER.read(obj)); - plr.updateFloppiness(Configuration.FLOPPY_MULTIPLIER.read(obj)); - - Breasts breasts = plr.getBreasts(); - breasts.updateXOffset(Configuration.BREASTS_OFFSET_X.read(obj)); - breasts.updateYOffset(Configuration.BREASTS_OFFSET_Y.read(obj)); - breasts.updateZOffset(Configuration.BREASTS_OFFSET_Z.read(obj)); - breasts.updateUniboob(Configuration.BREASTS_UNIBOOB.read(obj)); - breasts.updateCleavage(Configuration.BREASTS_CLEAVAGE.read(obj)); - - return plr; - } - - public static GenderPlayer loadCachedPlayer(UUID uuid, boolean markForSync) { GenderPlayer plr = WildfireGender.getPlayerById(uuid); if (plr != null) { @@ -214,6 +199,7 @@ public static GenderPlayer loadCachedPlayer(UUID uuid, boolean markForSync) { //physics plr.updateBreastPhysics(config.get(Configuration.BREAST_PHYSICS)); plr.updateShowBreastsInArmor(config.get(Configuration.SHOW_IN_ARMOR)); + plr.updateArmorPhysicsOverride(config.get(Configuration.ARMOR_PHYSICS_OVERRIDE)); plr.updateBounceMultiplier(config.get(Configuration.BOUNCE_MULTIPLIER)); plr.updateFloppiness(config.get(Configuration.FLOPPY_MULTIPLIER)); @@ -241,6 +227,7 @@ public static void saveGenderInfo(GenderPlayer plr) { //physics config.set(Configuration.BREAST_PHYSICS, plr.hasBreastPhysics()); config.set(Configuration.SHOW_IN_ARMOR, plr.showBreastsInArmor()); + config.set(Configuration.ARMOR_PHYSICS_OVERRIDE, plr.getArmorPhysicsOverride()); config.set(Configuration.BOUNCE_MULTIPLIER, plr.getBounceMultiplierRaw()); config.set(Configuration.FLOPPY_MULTIPLIER, plr.getFloppiness()); @@ -285,7 +272,7 @@ public Text getDisplayName() { } public boolean hasFemaleHurtSounds() { - return this == FEMALE; + return this == FEMALE || this == OTHER; } public boolean canHaveBreasts() { diff --git a/src/main/java/com/wildfire/main/WildfireHelper.java b/src/main/java/com/wildfire/main/WildfireHelper.java index 14c6a522..067f8d27 100644 --- a/src/main/java/com/wildfire/main/WildfireHelper.java +++ b/src/main/java/com/wildfire/main/WildfireHelper.java @@ -29,7 +29,10 @@ import net.minecraft.entity.EquipmentSlot; import net.minecraft.item.*; import net.minecraft.text.Text; +import net.minecraft.util.Util; +import net.minecraft.util.math.MathHelper; +import java.util.Objects; import java.util.concurrent.ThreadLocalRandom; public class WildfireHelper { @@ -80,9 +83,28 @@ public static IGenderArmor getArmorConfig(ItemStack stack) { @Environment(EnvType.CLIENT) public static void drawCenteredText(DrawContext ctx, TextRenderer textRenderer, Text text, int x, int y, int color) { - float centeredX = (float) x - textRenderer.getWidth(text); - textRenderer.draw(text, centeredX, y, color, false, - ctx.getMatrices().peek().getPositionMatrix(), ctx.getVertexConsumers(), - TextRenderer.TextLayerType.NORMAL, Integer.MIN_VALUE, 1); + int centeredX = (int) x - textRenderer.getWidth(text) / 2; + ctx.drawText(textRenderer, text, centeredX, y, color, false); + } + + public static void drawScrollableText(DrawContext context, TextRenderer textRenderer, Text text, int left, int top, int right, int bottom, int color) { + int i = textRenderer.getWidth(text); + int var10000 = top + bottom; + Objects.requireNonNull(textRenderer); + int j = (var10000 - 9) / 2 + 1; + int k = right - left; + if (i > k) { + int l = i - k; + double d = (double) Util.getMeasuringTimeMs() / 1000.0; + double e = Math.max((double)l * 0.5, 3.0); + double f = Math.sin(1.5707963267948966 * Math.cos(6.283185307179586 * d / e)) / 2.0 + 0.5; + double g = MathHelper.lerp(f, 0.0, (double)l); + context.enableScissor(left, top, right, bottom); + context.drawText(textRenderer, text, left - (int)g, j, color, false); + context.disableScissor(); + } else { + drawCenteredText(context, textRenderer, text, (left + right) / 2, j, color); + } + } } \ No newline at end of file diff --git a/src/main/java/com/wildfire/main/config/Configuration.java b/src/main/java/com/wildfire/main/config/Configuration.java index 3bef5dca..afe13c0f 100644 --- a/src/main/java/com/wildfire/main/config/Configuration.java +++ b/src/main/java/com/wildfire/main/config/Configuration.java @@ -39,17 +39,17 @@ public class Configuration { public static final UUIDConfigKey USERNAME = new UUIDConfigKey("username", UUID.nameUUIDFromBytes("UNKNOWN".getBytes(StandardCharsets.UTF_8))); public static final GenderConfigKey GENDER = new GenderConfigKey("gender"); - public static final FloatConfigKey BUST_SIZE = new FloatConfigKey("bust_size", 0.6F, 0, 1); + public static final FloatConfigKey BUST_SIZE = new FloatConfigKey("bust_size", 0.6F, 0, 0.8f); public static final BooleanConfigKey HURT_SOUNDS = new BooleanConfigKey("hurt_sounds", true); public static final FloatConfigKey BREASTS_OFFSET_X = new FloatConfigKey("breasts_xOffset", 0.0F, -1, 1); public static final FloatConfigKey BREASTS_OFFSET_Y = new FloatConfigKey("breasts_yOffset", 0.0F, -1, 1); public static final FloatConfigKey BREASTS_OFFSET_Z = new FloatConfigKey("breasts_zOffset", 0.0F, -1, 0); public static final BooleanConfigKey BREASTS_UNIBOOB = new BooleanConfigKey("breasts_uniboob", true); - public static final FloatConfigKey BREASTS_CLEAVAGE = new FloatConfigKey("breasts_cleavage", 0.05F, 0, 0.1F); + public static final FloatConfigKey BREASTS_CLEAVAGE = new FloatConfigKey("breasts_cleavage", 0, 0, 0.1F); - public static final BooleanConfigKey BREAST_PHYSICS = new BooleanConfigKey("breast_physics", false); - public static final BooleanConfigKey BREAST_PHYSICS_ARMOR = new BooleanConfigKey("breast_physics_armor", false); + public static final BooleanConfigKey BREAST_PHYSICS = new BooleanConfigKey("breast_physics", true); + public static final BooleanConfigKey ARMOR_PHYSICS_OVERRIDE = new BooleanConfigKey("armor_physics_override", false); public static final BooleanConfigKey SHOW_IN_ARMOR = new BooleanConfigKey("show_in_armor", true); public static final FloatConfigKey BOUNCE_MULTIPLIER = new FloatConfigKey("bounce_multiplier", 0.34F, 0, 1); public static final FloatConfigKey FLOPPY_MULTIPLIER = new FloatConfigKey("floppy_multiplier", 0.95F, 0, 1); diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index 41af809b..81261358 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -68,6 +68,8 @@ public void update(PlayerEntity plr, IGenderArmor armor) { targetBreastSize = 0; } else { float tightness = MathHelper.clamp(armor.tightness(), 0, 1); + if(genderPlayer.getArmorPhysicsOverride()) tightness = 0; //override resistance + //Scale breast size by how tight the armor is, clamping at a max adjustment of shrinking by 0.15 targetBreastSize *= 1 - 0.15F * tightness; } @@ -85,6 +87,8 @@ public void update(PlayerEntity plr, IGenderArmor armor) { float bounceIntensity = (targetBreastSize * 3f) * genderPlayer.getBounceMultiplier(); float resistance = MathHelper.clamp(armor.physicsResistance(), 0, 1); + if(genderPlayer.getArmorPhysicsOverride()) resistance = 0; //override resistance + //Adjust bounce intensity by physics resistance of the worn armor bounceIntensity *= 1 - resistance; diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index b0a8b05e..f820dc36 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -111,7 +111,7 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume ItemStack armorStack = ent.getEquippedStack(EquipmentSlot.CHEST); //Note: When the stack is empty the helper will fall back to an implementation that returns the proper data IGenderArmor genderArmor = WildfireHelper.getArmorConfig(armorStack); - boolean isChestplateOccupied = genderArmor.coversBreasts(); + boolean isChestplateOccupied = genderArmor.coversBreasts() && !plr.getArmorPhysicsOverride(); if (genderArmor.alwaysHidesBreasts() || !plr.showBreastsInArmor() && isChestplateOccupied) { //If the armor always hides breasts or there is armor and the player configured breasts // to be hidden when wearing armor, we can just exit early rather than doing any calculations @@ -140,6 +140,8 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume rBreast = new BreastModelBox(64, 64, 20, 17, 0, 0.0F, 0F, 4, 5, (int) (4 - breastOffsetZ - reducer), 0.0F, false); preBreastSize = bSize; } + lBreast = new BreastModelBox(64, 64, 16, 17, -4F, 0.0F, 0F, 4, 5, (int) (4 - breastOffsetZ - reducer), 0.0F, false); + rBreast = new BreastModelBox(64, 64, 20, 17, 0, 0.0F, 0F, 4, 5, (int) (4 - breastOffsetZ - reducer), 0.0F, false); //Note: We only render if the entity is not visible to the player, so we can assume it is visible to the player float overlayAlpha = ent.isInvisible() ? 0.15F : 1; diff --git a/src/main/resources/assets/wildfire_gender/lang/de_de.json b/src/main/resources/assets/wildfire_gender/lang/de_de.json index ea6ad6a1..d1466570 100644 --- a/src/main/resources/assets/wildfire_gender/lang/de_de.json +++ b/src/main/resources/assets/wildfire_gender/lang/de_de.json @@ -1,44 +1,33 @@ { - "category.wildfire_gender.generic": "Wildfire's Weiblicher Geschlects Mod", - "key.wildfire_gender.gender_menu": "Geschlechtsmenu", + "category.wildfire_gender.generic": "Wildfire's Weibliche Geschlechtsmod", + "key.wildfire_gender.gender_menu": "Geschlechtsmenü", "wildfire_gender.hurt.female": "Spielerin nimmt schaden", - "wildfire_gender.player_list.title": "Weiblicher Geschlechts Mod", + "wildfire_gender.player_list.title": "Weibliche Geschlechts Mod", "wildfire_gender.player_list.settings_button": "Einstellungen", "wildfire_gender.player_list.sync_status": "Synchronisierungsstatus", "wildfire_gender.player_list.state.loading": "Lade Spieler..", - "wildfire_gender.player_list.state.synced": "Spieler geladen!", + "wildfire_gender.player_list.state.synced": "Geladener Spieler", - "wildfire_gender.wardrobe.title": "Anpassungsmenu", - "wildfire_gender.wardrobe.slider.breast_size": "Brustgrösse: %s%%", + "wildfire_gender.wardrobe.title": "Anpassungsmenü", + "wildfire_gender.wardrobe.slider.breast_size": "Brustgröße: %s%%", "wildfire_gender.wardrobe.slider.separation": "Separierung: %s", "wildfire_gender.wardrobe.slider.height": "Höhe: %s", "wildfire_gender.wardrobe.slider.depth": "Tiefe: %s", "wildfire_gender.wardrobe.slider.rotation": "Drehung: %s degrees", "wildfire_gender.appearance_settings.title": "Aussehen", - "wildfire_gender.char_settings.title": "Character Einstellungen", + "wildfire_gender.char_settings.title": "Spieler Einstellungen", "wildfire_gender.char_settings.physics": "Brustphysik: %s", "wildfire_gender.tooltip.breast_physics": "Brustphysik", "wildfire_gender.char_settings.hide_in_armor": "Versteckt mit Rüstung: %s", - "wildfire_gender.tooltip.hide_in_armor": "Versteckt das Brustmodel wenn eine Rüstung angezogen ist.", - "wildfire_gender.char_settings.hurt_sounds": "Weibliche Soundeffekte: %s", - "wildfire_gender.tooltip.hurt_sounds": "Weibliche Soundeffekte", - - "wildfire_gender.breast_customization.dual_physics": "Dualphysik: %s", - - "wildfire_gender.player_list.bounce_multiplier": "Wackel Multiplikator: %sx", - "wildfire_gender.player_list.breast_momentum": "Brust Schwung: %s%%", - "wildfire_gender.player_list.female_sounds": "Weibliche Soundeffekte: %s", - - "wildfire_gender.settings.title": "Wildfire's Einstellungsmenü", "wildfire_gender.acknowledge.confirm": "Okay", - "wildfire_gender.label.gender": "Geschlect", + "wildfire_gender.label.gender": "Geschlecht", "wildfire_gender.label.female": "Weiblich", - "wildfire_gender.label.male": "Mänlich", + "wildfire_gender.label.male": "Männlich", "wildfire_gender.label.other": "Andere", "wildfire_gender.label.enabled": "Aktiviert", @@ -46,15 +35,15 @@ "wildfire_gender.label.yes": "Ja", "wildfire_gender.label.no": "Nein", "wildfire_gender.label.exit": "X", - "wildfire_gender.label.too_far": "Zu weit weg.", + "wildfire_gender.label.too_far": "Zu weit weg", "wildfire_gender.label.with_creator": "Du spielst auf einem Server mit dem Programmierer dieser Mod!", "wildfire_gender.slider.bounce": "Wackel Intensität: %sx", "wildfire_gender.slider.floppy": "Brust Schwung: %s%%", - "wildfire_gender.slider.min_bounce": "Warum sind Physik aktiviert?", + "wildfire_gender.slider.min_bounce": "Warum ist die Physik überhaupt an?", "wildfire_gender.slider.max_bounce": "Anime Brust Physik!", - "wildfire_gender.tooltip.bounce_warning": "Wenn Sie 'Wackel Intensität' zu stark einstellen sieht es nicht mehr natürlich aus!", + "wildfire_gender.tooltip.bounce_warning": "Wenn die 'Wackel Intensität' zu stark einstellen ist sieht es nicht sehr natürlich aus!", - "wildfire_gender.cancer_awareness.title": "Hey, es ist Brustkrebsmonat!", + "wildfire_gender.cancer_awareness.title": "Hey, es ist Monat des Brustkrebses!", "wildfire_gender.cancer_awareness.description": "Klicke hier um an die §dDeutsche Krebshilfe§f zu spenden!" -} +} \ No newline at end of file diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index 76bfc1c6..371d643b 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -1,6 +1,7 @@ { "category.wildfire_gender.generic": "Wildfire's Female Gender Mod", "key.wildfire_gender.gender_menu": "Female Gender Menu", + "toast.wildfire_gender.get_started": "Press '%s' to get started!", "wildfire_gender.hurt.female": "Female Player Hurt", @@ -21,6 +22,10 @@ "wildfire_gender.char_settings.title": "Character Settings", "wildfire_gender.char_settings.physics": "Breast Physics: %s", "wildfire_gender.tooltip.breast_physics": "Enables Breast Physics", + + "wildfire_gender.char_settings.override_armor_physics": "Armor Physics: %s", + "wildfire_gender.tooltip.override_armor_physics": "Override Armor Physics Attributes When Wearing Armor", + "wildfire_gender.char_settings.hide_in_armor": "Hide In Armor: %s", "wildfire_gender.tooltip.hide_in_armor": "Hide Breast Model When Wearing Armors", "wildfire_gender.char_settings.hurt_sounds": "Female Hurt Sounds: %s", diff --git a/src/main/resources/assets/wildfire_gender/lang/nds_de.json b/src/main/resources/assets/wildfire_gender/lang/nds_de.json deleted file mode 100644 index 94b73425..00000000 --- a/src/main/resources/assets/wildfire_gender/lang/nds_de.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "category.wildfire_gender.generic": "Wildfire's Geslecht Mod", - "key.wildfire_gender.gender_menu": "Weibliches Geslecht Mod", - - "wildfire_gender.hurt.female": "Spelerin verletzt", - - "wildfire_gender.player_list.title": "Geslecht Mod", - "wildfire_gender.player_list.settings_button": "Instellen", - "wildfire_gender.player_list.sync_status": "Sync-Status", - "wildfire_gender.player_list.state.loading": "Daten warrt laden...", - "wildfire_gender.player_list.state.synced": "Synchrooniseert Spelers", - - "wildfire_gender.wardrobe.title": "Anpassen", - "wildfire_gender.wardrobe.slider.breast_size": "Bostgrött: %s%%", - "wildfire_gender.wardrobe.slider.separation": "Trenn: %s", - "wildfire_gender.wardrobe.slider.height": "Hööchd: %s", - "wildfire_gender.wardrobe.slider.depth": "Deep: %s", - "wildfire_gender.wardrobe.slider.rotation": "Rotatschoon: %s Grad", - - "wildfire_gender.appearance_settings.title": "Utsehns-Anstellen", - "wildfire_gender.char_settings.title": "Charakter-Anstellen", - "wildfire_gender.char_settings.physics": "Bostphysik: %s", - "wildfire_gender.tooltip.breast_physics": "Schaltet Bostphysik an", - "wildfire_gender.char_settings.hide_in_armor": "Mit Rüstung versteeken: %s", - "wildfire_gender.tooltip.hide_in_armor": "Versteekt de Bost bi dat Dregen vun Rüstung", - "wildfire_gender.char_settings.hurt_sounds": "Weibliche Hurt Sounds: %s", - "wildfire_gender.tooltip.hurt_sounds": "Schalt de Weiblüüd an", - - "wildfire_gender.breast_customization.dual_physics": "Dubbelphysik: %s", - - "wildfire_gender.player_list.bounce_multiplier": "Bounce Intensivtät: %sx", - "wildfire_gender.player_list.breast_momentum": "Bost-Momentum: %s%%", - "wildfire_gender.player_list.female_sounds": "Weiblüüd: %s", - - "wildfire_gender.settings.title": "Wildfire's Instellen", - - "wildfire_gender.acknowledge.confirm": "Okay", - - "wildfire_gender.label.gender": "Geslecht", - "wildfire_gender.label.female": "Weiblich", - "wildfire_gender.label.male": "Männich", - "wildfire_gender.label.other": "Divers", - - "wildfire_gender.label.enabled": "An", - "wildfire_gender.label.disabled": "Ut", - "wildfire_gender.label.yes": "Ja", - "wildfire_gender.label.no": "Nee", - "wildfire_gender.label.exit": "X", - "wildfire_gender.label.too_far": "To wiet weg", - "wildfire_gender.label.with_creator": "Du speelst op de sülvige Server as de Modemakerin!", - - "wildfire_gender.slider.bounce": "Bounce Intensivtät: %sx", - "wildfire_gender.slider.floppy": "Bost-Momentum: %s%%", - "wildfire_gender.slider.min_bounce": "Worüm sünd de Physik överhoopt an?", - "wildfire_gender.slider.max_bounce": "Anime Breast Physics!!!", - "wildfire_gender.tooltip.bounce_warning": "Wenn U de Bounce-Intensivtät to hooch instellen, köönt dat bannig unnatürlich ut!", - - "wildfire_gender.cancer_awareness.title": "Hey, dat is Maand vun dat Bostkrebsbewusstsein!", - "wildfire_gender.cancer_awareness.description": "Klken hier üm de §Susan G. Komet Foundation§f to sennen!" -} diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/settings_bg.png b/src/main/resources/assets/wildfire_gender/textures/gui/settings_bg.png index b15e2e6de169f9c91c0f9c7e33994c1466df673b..1ef3afa669c77d5f1a378da589c3f05423fc4885 100644 GIT binary patch literal 662 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911MRQ8&P5D>38$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&eBzI1?3h%1o(|NsB7W5+^6L(R?2!@|P2xVW;jvw`B%3a{P)Qk*40e!)OB zV89UUUm*k%EbxddW?V|b_jr9wxrg8517S)k(`^5#ha ztzZE0Hi$gPe!b70;lt))e~=guz@)+KdAE$gf8U#%9=AnFVK8W}Sz# zi#}h0vp-##2MRqfc=Bu|g1yof6r5mi;%qR29bC-@6Q3}9IfA|X8_xlDhI!H338$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&eBzI1?3h%1o(|NsB7W5+^6L(R?2!@|P2xVW;jvw`B%3a{P)Qk*40e!)OB zV89UUUm*k%EbxddW?0l1?(g;+b*=jy zxKy-?g025uXZ-ToBJTW)9m}qNtg3bs=iD>z)9?HD)(ie%|M0KH&Q4G82iuF*+3XAq z4S9B^Kx-I4yaz!I=cB*BW@o6ozf&J12m}u1^Q9mxAlv`0F^mn<2jVx(+m#LzXSiFF z1!voqo` Date: Sun, 23 Jul 2023 12:45:48 -0400 Subject: [PATCH 003/238] Floppy Multiplier actually changed now Sliders now use scrolling text labels as well. --- gradle.properties | 2 +- src/main/java/com/wildfire/gui/WildfireSlider.java | 4 +++- src/main/java/com/wildfire/main/config/Configuration.java | 2 +- src/main/resources/fabric.mod.json | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index e8dab01e..6b0a0eab 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ org.gradle.jvmargs=-Xmx1G loader_version=0.14.21 # Mod Properties - mod_version = fabric-1.20-3.0.1 + mod_version = fabric-1.20-3.1 maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod diff --git a/src/main/java/com/wildfire/gui/WildfireSlider.java b/src/main/java/com/wildfire/gui/WildfireSlider.java index 8eb2c6bc..c2469550 100644 --- a/src/main/java/com/wildfire/gui/WildfireSlider.java +++ b/src/main/java/com/wildfire/gui/WildfireSlider.java @@ -113,7 +113,9 @@ public void renderButton(DrawContext ctx, int mouseX, int mouseY, float delta) { ctx.fill(xPos2 - 2, getY() + 1, xPos2, getY() + this.height - 1, 0xFFFFFF + (120 << 24)); RenderSystem.enableDepthTest(); TextRenderer font = MinecraftClient.getInstance().textRenderer; - WildfireHelper.drawCenteredText(ctx, font, getMessage(), this.getX() + this.width / 2, this.getY() + (this.height - 8) / 2, this.hovered || changed ? 0xFFFF55 : 0xFFFFFF); + int i = this.getX() + 2; + int j = this.getX() + this.getWidth() - 2; + WildfireHelper.drawScrollableText(ctx, font, this.getMessage(), i, this.getY(), j, this.getY() + this.getHeight(), this.hovered || changed ? 0xFFFF55 : 0xFFFFFF); } } diff --git a/src/main/java/com/wildfire/main/config/Configuration.java b/src/main/java/com/wildfire/main/config/Configuration.java index afe13c0f..c57a2d29 100644 --- a/src/main/java/com/wildfire/main/config/Configuration.java +++ b/src/main/java/com/wildfire/main/config/Configuration.java @@ -52,7 +52,7 @@ public class Configuration { public static final BooleanConfigKey ARMOR_PHYSICS_OVERRIDE = new BooleanConfigKey("armor_physics_override", false); public static final BooleanConfigKey SHOW_IN_ARMOR = new BooleanConfigKey("show_in_armor", true); public static final FloatConfigKey BOUNCE_MULTIPLIER = new FloatConfigKey("bounce_multiplier", 0.34F, 0, 1); - public static final FloatConfigKey FLOPPY_MULTIPLIER = new FloatConfigKey("floppy_multiplier", 0.95F, 0, 1); + public static final FloatConfigKey FLOPPY_MULTIPLIER = new FloatConfigKey("floppy_multiplier", 0.75F, 0, 1); private static final TypeAdapter ADAPTER = new Gson().getAdapter(JsonObject.class); diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 72734f76..6bb1c49f 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -1,7 +1,7 @@ { "schemaVersion": 1, "id": "wildfire_gender", - "version": "1.20-3.0.1", + "version": "1.20-3.1", "name": "Wildfire's Female Gender Mod", "description": "Allows players to choose what gender they want to be.", "authors": [ From 48b632a892eabb7f3774f405fbc5ee0421ba7dd1 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sun, 23 Jul 2023 13:32:44 -0400 Subject: [PATCH 004/238] Changed github workflows Renamed targetBounce to targetBounceY in BreastPhysics.java --- .github/workflows/fabric.yml | 1 - .../com/wildfire/physics/BreastPhysics.java | 42 +++++++++---------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/.github/workflows/fabric.yml b/.github/workflows/fabric.yml index 4e27fd1c..0f0c5249 100644 --- a/.github/workflows/fabric.yml +++ b/.github/workflows/fabric.yml @@ -1,7 +1,6 @@ name: Build Fabric mod with Gradle on: push: - branches: [ fabric-1.19.4 ] jobs: build: runs-on: ubuntu-22.04 diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index 81261358..59251082 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -33,7 +33,7 @@ public class BreastPhysics { - private float bounceVel = 0, targetBounce = 0, velocity = 0, wfg_femaleBreast, wfg_preBounce; + private float bounceVel = 0, targetBounceY = 0, velocity = 0, wfg_femaleBreast, wfg_preBounce; private float bounceRotVel = 0, targetRotVel = 0, rotVelocity = 0, wfg_bounceRotation, wfg_preBounceRotation; private float bounceVelX = 0, targetBounceX = 0, velocityX = 0, wfg_femaleBreastX, wfg_preBounceX; @@ -102,8 +102,8 @@ public void update(PlayerEntity plr, IGenderArmor armor) { if(plr.fallDistance == 0) alreadyFalling = false; - this.targetBounce = (float) motion.y * bounceIntensity; - this.targetBounce += breastWeight; + this.targetBounceY = (float) motion.y * bounceIntensity; + this.targetBounceY += breastWeight; float horizVel = (float) Math.sqrt(Math.pow(motion.x, 2) + Math.pow(motion.z, 2)) * (bounceIntensity); //float horizLocal = -horizVel * ((plr.getRotationYawHead()-plr.renderYawOffset)<0?-1:1); this.targetRotVel = -((plr.bodyYaw - plr.prevBodyYaw) / 15f) * bounceIntensity; @@ -112,7 +112,7 @@ public void update(PlayerEntity plr, IGenderArmor armor) { f = f * f * f; if(f < 1.0F) f = 1.0F; - this.targetBounce += MathHelper.cos(plr.limbAnimator.getPos() * 0.6662F + (float)Math.PI) * 0.5F * plr.limbAnimator.getSpeed() * 0.5F / f; + this.targetBounceY += MathHelper.cos(plr.limbAnimator.getPos() * 0.6662F + (float)Math.PI) * 0.5F * plr.limbAnimator.getSpeed() * 0.5F / f; //System.out.println(plr.rotationYaw); this.targetRotVel += (float) motion.y * bounceIntensity * randomB; @@ -120,11 +120,11 @@ public void update(PlayerEntity plr, IGenderArmor armor) { if(plr.getPose() == EntityPose.CROUCHING && !this.justSneaking) { this.justSneaking = true; - this.targetBounce += bounceIntensity; + this.targetBounceY += bounceIntensity; } if(plr.getPose() != EntityPose.CROUCHING && this.justSneaking) { this.justSneaking = false; - this.targetBounce += bounceIntensity; + this.targetBounceY += bounceIntensity; } @@ -138,7 +138,7 @@ public void update(PlayerEntity plr, IGenderArmor armor) { float rotationR = (float) MathHelper.clampedLerp(-(float)Math.PI / 4F, (float)Math.PI / 4F, (double) ((MathHelper.sin(-rowTime + 1.0F) + 1.0F) / 2.0F)); //System.out.println(rotationL + ", " + rotationR); if(rotationL < -1 || rotationR < -0.6f) { - this.targetBounce = bounceIntensity / 3.25f; + this.targetBounceY = bounceIntensity / 3.25f; } } @@ -146,9 +146,9 @@ public void update(PlayerEntity plr, IGenderArmor armor) { float speed = (float) cart.getVelocity().lengthSquared(); if(Math.random() * speed < 0.5f && speed > 0.2f) { if(Math.random() > 0.5) { - this.targetBounce = -bounceIntensity / 6f; + this.targetBounceY = -bounceIntensity / 6f; } else { - this.targetBounce = bounceIntensity / 6f; + this.targetBounceY = bounceIntensity / 6f; } } /*if(rotationL < -1 || rotationR < -1) { @@ -158,7 +158,7 @@ public void update(PlayerEntity plr, IGenderArmor armor) { if(plr.getVehicle() instanceof HorseEntity horse) { float movement = (float) horse.getVelocity().lengthSquared(); if(horse.age % clampMovement(movement) == 5 && movement > 0.1f) { - this.targetBounce = bounceIntensity / 4f; + this.targetBounceY = bounceIntensity / 4f; } //horse } @@ -166,29 +166,29 @@ public void update(PlayerEntity plr, IGenderArmor armor) { float movement = (float) pig.getVelocity().lengthSquared(); //System.out.println(movement); if(pig.age % clampMovement(movement) == 5 && movement > 0.08f) { - this.targetBounce = bounceIntensity / 4f; + this.targetBounceY = bounceIntensity / 4f; } //horse } if(plr.getVehicle() instanceof StriderEntity strider) { - this.targetBounce += ((float) (strider.getMountedHeightOffset()*3f) - 4.5f) * bounceIntensity; + this.targetBounceY += ((float) (strider.getMountedHeightOffset()*3f) - 4.5f) * bounceIntensity; //horse } //System.out.println("VEHICLE"); } if(plr.handSwinging && plr.age % 5 == 0 && plr.getPose() != EntityPose.SLEEPING) { if(Math.random() > 0.5) { - this.targetBounce += -0.25f * bounceIntensity; + this.targetBounceY += -0.25f * bounceIntensity; } else { - this.targetBounce += 0.25f * bounceIntensity; + this.targetBounceY += 0.25f * bounceIntensity; } } if(plr.getPose() == EntityPose.SLEEPING && !this.alreadySleeping) { - this.targetBounce = bounceIntensity; + this.targetBounceY = bounceIntensity; this.alreadySleeping = true; } if(plr.getPose() != EntityPose.SLEEPING && this.alreadySleeping) { - this.targetBounce = bounceIntensity; + this.targetBounceY = bounceIntensity; this.alreadySleeping = false; } /*if(plr.getPose() == EntityPose.SWIMMING) { @@ -208,17 +208,17 @@ public void update(PlayerEntity plr, IGenderArmor armor) { float distanceFromMax = Math.abs(bounceVel - 2.65f) * 0.5f; if(bounceVel < -0.5f) { - targetBounce += distanceFromMin; + targetBounceY += distanceFromMin; } if(bounceVel > 2.5f) { - targetBounce -= distanceFromMax; + targetBounceY -= distanceFromMax; } - if(targetBounce < -1.5f) targetBounce = -1.5f; - if(targetBounce > 2.5f) targetBounce = 2.5f; + if(targetBounceY < -1.5f) targetBounceY = -1.5f; + if(targetBounceY > 2.5f) targetBounceY = 2.5f; if(targetRotVel < -25f) targetRotVel = -25f; if(targetRotVel > 25f) targetRotVel = 25f; - this.velocity = MathHelper.lerp(bounceAmount, this.velocity, (this.targetBounce - this.bounceVel) * delta); + this.velocity = MathHelper.lerp(bounceAmount, this.velocity, (this.targetBounceY - this.bounceVel) * delta); //this.preY = MathHelper.lerp(0.5f, this.preY, (this.targetBounce - this.bounceVel) * 1.25f); this.bounceVel += this.velocity * percent * 1.1625f; From ce11e6b44a4e16d3dce4d82da268007c40126623 Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 23 Jul 2023 12:02:39 -0600 Subject: [PATCH 005/238] Fix hurt sounds requiring the mod server-side as well --- .../java/com/wildfire/main/GenderPlayer.java | 20 ++-- .../wildfire/main/WildfireEventHandler.java | 70 ------------- .../com/wildfire/main/WildfireGender.java | 1 - .../wildfire/main/WildfireGenderServer.java | 3 + .../com/wildfire/main/WildfireSounds.java | 11 +-- .../wildfire/mixins/LivingEntityMixin.java | 98 +++++++++++++++++++ .../mixins/PlayerEntityServerMixin.java | 77 --------------- .../assets/wildfire_gender/lang/cs_cz.json | 2 - .../assets/wildfire_gender/lang/de_ch.json | 2 - .../assets/wildfire_gender/lang/de_de.json | 2 - .../assets/wildfire_gender/lang/en_us.json | 2 - .../assets/wildfire_gender/lang/fr_fr.json | 2 - .../assets/wildfire_gender/lang/lol_us.json | 2 - .../assets/wildfire_gender/sounds.json | 11 +-- .../resources/wildfire_gender.mixins.json | 2 +- 15 files changed, 120 insertions(+), 185 deletions(-) create mode 100644 src/main/java/com/wildfire/mixins/LivingEntityMixin.java delete mode 100644 src/main/java/com/wildfire/mixins/PlayerEntityServerMixin.java diff --git a/src/main/java/com/wildfire/main/GenderPlayer.java b/src/main/java/com/wildfire/main/GenderPlayer.java index bccb68e5..d344b694 100644 --- a/src/main/java/com/wildfire/main/GenderPlayer.java +++ b/src/main/java/com/wildfire/main/GenderPlayer.java @@ -22,9 +22,11 @@ import com.wildfire.main.config.ConfigKey; import com.wildfire.main.config.Configuration; import com.wildfire.physics.BreastPhysics; +import net.minecraft.sound.SoundEvent; import net.minecraft.text.Text; import net.minecraft.util.Formatting; +import javax.annotation.Nullable; import java.util.UUID; import java.util.function.Consumer; @@ -257,26 +259,30 @@ public enum SyncStatus { } public enum Gender { - FEMALE(Text.translatable("wildfire_gender.label.female").formatted(Formatting.LIGHT_PURPLE)), - MALE(Text.translatable("wildfire_gender.label.male").formatted(Formatting.BLUE)), - OTHER(Text.translatable("wildfire_gender.label.other").formatted(Formatting.GREEN)); + FEMALE(Text.translatable("wildfire_gender.label.female").formatted(Formatting.LIGHT_PURPLE), true, WildfireSounds.FEMALE_HURT), + MALE(Text.translatable("wildfire_gender.label.male").formatted(Formatting.BLUE), false, null), + OTHER(Text.translatable("wildfire_gender.label.other").formatted(Formatting.GREEN), true, WildfireSounds.FEMALE_HURT); private final Text name; + private final boolean canHaveBreasts; + private final @Nullable SoundEvent hurtSound; - Gender(Text name) { + Gender(Text name, boolean canHaveBreasts, @Nullable SoundEvent hurtSound) { this.name = name; + this.canHaveBreasts = canHaveBreasts; + this.hurtSound = hurtSound; } public Text getDisplayName() { return name; } - public boolean hasFemaleHurtSounds() { - return this == FEMALE || this == OTHER; + public @Nullable SoundEvent getHurtSound() { + return hurtSound; } public boolean canHaveBreasts() { - return this != MALE; + return canHaveBreasts; } } } diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index 7032caac..e7a03a6e 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -22,8 +22,6 @@ import com.wildfire.main.networking.PacketSendGenderInfo; import com.wildfire.main.networking.PacketSync; -import java.util.Random; -import java.util.Set; import java.util.UUID; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents; @@ -34,12 +32,7 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.client.option.KeyBinding; -import net.minecraft.client.sound.EntityTrackingSoundInstance; import net.minecraft.client.util.InputUtil; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.sound.SoundCategory; -import net.minecraft.sound.SoundEvent; -import net.minecraft.sound.SoundEvents; import net.minecraft.util.Identifier; import org.lwjgl.glfw.GLFW; @@ -112,72 +105,9 @@ public static void registerClientEvents() { } }); - //Receive hurt - - ClientPlayNetworking.registerGlobalReceiver(new Identifier(WildfireGender.MODID, "hurt"), - (client, handler, buf, responseSender) -> { - UUID uuid = buf.readUuid(); - GenderPlayer.Gender gender = buf.readEnumConstant(GenderPlayer.Gender.class); - boolean hurtSounds = buf.readBoolean(); - - //Vector3d pos = new Vector3d(buf.readDouble(), buf.readDouble(), buf.readDouble()); - - SoundEvent hurtSound = null; - if(gender == GenderPlayer.Gender.FEMALE) { - hurtSound = Math.random() > 0.5f ? WildfireSounds.FEMALE_HURT1 : WildfireSounds.FEMALE_HURT2; - } - if(hurtSound == null) return; - - if(hurtSounds) { - PlayerEntity ent = MinecraftClient.getInstance().world.getPlayerByUuid(uuid); - if (ent != null) { - long randomLong = new Random().nextLong(0L,1L); - final SoundEvent hurtSound2 = hurtSound; - // ensures it's executed in the main thread - client.execute(() -> { - client.getSoundManager().play(new EntityTrackingSoundInstance(hurtSound2, SoundCategory.PLAYERS, 1f, 1f, ent, randomLong)); - }); - } - } - }); - ClientPlayNetworking.registerGlobalReceiver(new Identifier(WildfireGender.MODID, "sync"), (client, handler, buf, responseSender) -> { PacketSync.handle(client, handler, buf, responseSender); }); } - - //TODO: Eventually we may want to replace this with a map or something and replace things like drowning sounds with other drowning sounds - private final Set playerHurtSounds = Set.of(SoundEvents.ENTITY_PLAYER_HURT, - SoundEvents.ENTITY_PLAYER_HURT_DROWN, - SoundEvents.ENTITY_PLAYER_HURT_FREEZE, - SoundEvents.ENTITY_PLAYER_HURT_ON_FIRE, - SoundEvents.ENTITY_PLAYER_HURT_SWEET_BERRY_BUSH - ); -/* - @SubscribeEvent(priority = EventPriority.LOWEST) - public void onPlaySound(PlaySoundAtEntityEvent event) { - if (playerHurtSounds.contains(event.getSound()) && event.getEntity() instanceof Player p && p.level.isClientSide) { - //Cancel as we handle all hurt sounds manually so that we can - event.setCanceled(true); - SoundEvent soundEvent = event.getSound(); - if (p.hurtTime == p.hurtDuration && p.hurtTime > 0) { - //Note: We check hurtTime == hurtDuration and hurtTime > 0 or otherwise when the server sends a hurt sound to the client - // and the client will check itself instead of the player who was damaged. - GenderPlayer plr = WildfireGender.getPlayerById(p.getUUID()); - if (plr != null && plr.hasHurtSounds() && plr.getGender().hasFemaleHurtSounds()) { - //If the player who produced the hurt sound is a female sound replace it - soundEvent = Math.random() > 0.5f ? WildfireSounds.FEMALE_HURT1 : WildfireSounds.FEMALE_HURT2; - } - } else if (p.getUUID().equals(Minecraft.getInstance().player.getUUID())) { - //Skip playing remote hurt sounds. Note: sounds played via /playsound will not be intercepted - // as they are played directly - //Note: This might behave slightly strangely if a mod is manually firing a player damage sound - // only on the server and not also on the client - //TODO: Ideally we would fix that edge case but I find it highly unlikely it will ever actually occur - return; - } - p.level.playLocalSound(p.getX(), p.getY(), p.getZ(), soundEvent, event.getCategory(), event.getVolume(), event.getPitch(), false); - } - }*/ } diff --git a/src/main/java/com/wildfire/main/WildfireGender.java b/src/main/java/com/wildfire/main/WildfireGender.java index 792a803e..17a07f73 100644 --- a/src/main/java/com/wildfire/main/WildfireGender.java +++ b/src/main/java/com/wildfire/main/WildfireGender.java @@ -39,7 +39,6 @@ public class WildfireGender implements ClientModInitializer { @Override public void onInitializeClient() { WildfireEventHandler.registerClientEvents(); - WildfireSounds.register(); } @Nullable diff --git a/src/main/java/com/wildfire/main/WildfireGenderServer.java b/src/main/java/com/wildfire/main/WildfireGenderServer.java index 0db83d77..5c0ab908 100644 --- a/src/main/java/com/wildfire/main/WildfireGenderServer.java +++ b/src/main/java/com/wildfire/main/WildfireGenderServer.java @@ -27,6 +27,9 @@ public class WildfireGenderServer implements ModInitializer { @Override public void onInitialize() { + // while this class is named 'Server', this is actually a common code path, + // so we can safely register here for both sides. + WildfireSounds.register(); ServerPlayNetworking.registerGlobalReceiver(new Identifier(WildfireGender.MODID, "send_gender_info"), (server, playerEntity, handler, buf, responseSender) -> { PacketSendGenderInfo.handle(server, playerEntity, handler, buf, responseSender); diff --git a/src/main/java/com/wildfire/main/WildfireSounds.java b/src/main/java/com/wildfire/main/WildfireSounds.java index e50176e9..6583e978 100644 --- a/src/main/java/com/wildfire/main/WildfireSounds.java +++ b/src/main/java/com/wildfire/main/WildfireSounds.java @@ -24,14 +24,9 @@ import net.minecraft.util.Identifier; public class WildfireSounds { - private static Identifier SND1 = new Identifier(WildfireGender.MODID, "female_hurt1"); - public static SoundEvent FEMALE_HURT1 = SoundEvent.of(SND1); + public static final SoundEvent FEMALE_HURT = SoundEvent.of(new Identifier(WildfireGender.MODID, "female_hurt")); - private static Identifier SND2 = new Identifier(WildfireGender.MODID, "female_hurt2"); - public static SoundEvent FEMALE_HURT2 = SoundEvent.of(SND2); - - public static void register() { - Registry.register(Registries.SOUND_EVENT, SND1, FEMALE_HURT1); - Registry.register(Registries.SOUND_EVENT, SND2, FEMALE_HURT2); + protected static void register() { + Registry.register(Registries.SOUND_EVENT, FEMALE_HURT.getId(), FEMALE_HURT); } } diff --git a/src/main/java/com/wildfire/mixins/LivingEntityMixin.java b/src/main/java/com/wildfire/mixins/LivingEntityMixin.java new file mode 100644 index 00000000..96c8e79b --- /dev/null +++ b/src/main/java/com/wildfire/mixins/LivingEntityMixin.java @@ -0,0 +1,98 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.mixins; + +import com.wildfire.main.GenderPlayer; +import com.wildfire.main.WildfireGender; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.sound.SoundEvent; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +/* + * A note on why this implementation in particular was chosen: + * + * While this could be reduced down to one mixin (the `#onDamaged(DamageSource)` one in particular), and forego any sort + * of server-side handling, this is being done as (at least as of when this is being written,) the mod fails to ever + * perform an initial sync upon a player joining a dedicated server; as such, we're using client- *and* server-side mixins + * to provide some level of consistency, even if syncing isn't consistent. + * + * We're additionally playing *alongside* the vanilla hurt sound, largely for the sake of accessibility, as the vanilla + * hurt sound may provide important context as for why a player is taking damage, which could prove especially helpful + * for players with poor/no eyesight. + * + * Additionally, completely replacing the hurt sound server-side would essentially require the mod client-side as well + * to hear *any* hurt sounds from players with this setting enabled, which rules out mixins to methods such as + * `PlayerEntity#getHurtSound(DamageSource)`. + */ +@Mixin(LivingEntity.class) +public class LivingEntityMixin { + @Environment(EnvType.CLIENT) + @Inject( + method = "onDamaged", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/entity/LivingEntity;playSound(Lnet/minecraft/sound/SoundEvent;FF)V" + ) + ) + public void clientGenderHurtSound(DamageSource damageSource, CallbackInfo ci) { + MinecraftClient client = MinecraftClient.getInstance(); + if(client.player == null || client.world == null) return; + + if((LivingEntity)(Object)this instanceof PlayerEntity player) { + if(player.getWorld().isClient() && player.getUuid().equals(client.player.getUuid())) { + this.playGenderHurtSound(player); + } + } + } + + @Inject( + method = "damage", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/entity/LivingEntity;playHurtSound(Lnet/minecraft/entity/damage/DamageSource;)V" + ) + ) + public void serverGenderHurtSound(DamageSource source, float amount, CallbackInfoReturnable cir) { + if((LivingEntity)(Object)this instanceof PlayerEntity player) { + if(!player.getWorld().isClient()) this.playGenderHurtSound(player); + } + } + + @Unique + private void playGenderHurtSound(PlayerEntity player) { + GenderPlayer genderPlayer = WildfireGender.getPlayerById(player.getUuid()); + if(genderPlayer == null || !genderPlayer.hasHurtSounds()) return; + + SoundEvent hurtSound = genderPlayer.getGender().getHurtSound(); + if(hurtSound != null) { + float pitch = (player.getRandom().nextFloat() - player.getRandom().nextFloat()) * 0.2F + 1.0F; + player.playSound(hurtSound, 1f, pitch); + } + } +} diff --git a/src/main/java/com/wildfire/mixins/PlayerEntityServerMixin.java b/src/main/java/com/wildfire/mixins/PlayerEntityServerMixin.java deleted file mode 100644 index c9e40803..00000000 --- a/src/main/java/com/wildfire/mixins/PlayerEntityServerMixin.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -package com.wildfire.mixins; -import com.mojang.authlib.GameProfile; -import com.wildfire.main.GenderPlayer; -import com.wildfire.main.WildfireGender; -import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; -import net.fabricmc.fabric.api.networking.v1.PlayerLookup; -import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; -import net.minecraft.entity.EntityType; -import net.minecraft.entity.LivingEntity; -import net.minecraft.entity.damage.DamageSource; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.network.PacketByteBuf; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.server.world.ServerWorld; -import net.minecraft.util.Identifier; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(value = PlayerEntity.class, priority = 900) -public abstract class PlayerEntityServerMixin extends LivingEntity { - - - public PlayerEntityServerMixin(World world, BlockPos pos, float yaw, GameProfile profile) { - super(EntityType.PLAYER, world); - } - - @Inject(method = "applyDamage", at = @At("HEAD"), cancellable = true) - private void onDamagePlayer(DamageSource source, float amount, CallbackInfo ci) { - if (!this.isInvulnerableTo(source)) { - PlayerEntity self = (PlayerEntity) (Object) this; - - amount = this.applyArmorToDamage(source, amount); - amount = this.modifyAppliedDamage(source, amount); - float f = amount; - amount = Math.max(amount - this.getAbsorptionAmount(), 0.0F); - - if(amount != 0.0f) { - //send to client hurt sound? - GenderPlayer plr = WildfireGender.getPlayerById(self.getUuid()); - if (plr != null) { - PacketByteBuf buf = PacketByteBufs.create(); - buf.writeUuid(plr.uuid); - buf.writeEnumConstant(plr.getGender()); - buf.writeBoolean(plr.hasHurtSounds()); - for (ServerPlayerEntity player : PlayerLookup.tracking((ServerWorld) getWorld(), getWorld().getPlayerByUuid(plr.uuid).getBlockPos())) { - if (ServerPlayNetworking.canSend(player, new Identifier("wildfire_gender", "hurt"))) { - ServerPlayNetworking.send(player, new Identifier("wildfire_gender", "hurt"), buf); - } - } - } - } - } - } - -} \ No newline at end of file diff --git a/src/main/resources/assets/wildfire_gender/lang/cs_cz.json b/src/main/resources/assets/wildfire_gender/lang/cs_cz.json index b8a36ef6..360c76d2 100644 --- a/src/main/resources/assets/wildfire_gender/lang/cs_cz.json +++ b/src/main/resources/assets/wildfire_gender/lang/cs_cz.json @@ -2,8 +2,6 @@ "category.wildfire_gender.generic": "Wildfire's Female Gender Mod", "key.wildfire_gender.gender_menu": "Menu ženského pohlaví", - "wildfire_gender.hurt.female": "Zranění ženského hráče", - "wildfire_gender.player_list.title": "Female Gender Mod", "wildfire_gender.player_list.settings_button": "Nastavení", "wildfire_gender.player_list.sync_status": "Status synchronizace", diff --git a/src/main/resources/assets/wildfire_gender/lang/de_ch.json b/src/main/resources/assets/wildfire_gender/lang/de_ch.json index 0dc3a51a..b24d8971 100644 --- a/src/main/resources/assets/wildfire_gender/lang/de_ch.json +++ b/src/main/resources/assets/wildfire_gender/lang/de_ch.json @@ -2,8 +2,6 @@ "category.wildfire_gender.generic": "Wildfire's Wiiblicher Geschlects Mod", "key.wildfire_gender.gender_menu": "Geschlechtsmenu", - "wildfire_gender.hurt.female": "Spielerin nimmt schade", - "wildfire_gender.player_list.title": "Wiiblicher Geschlechts Mod", "wildfire_gender.player_list.settings_button": "Iistellige", "wildfire_gender.player_list.sync_status": "Synchronisierigsstatus", diff --git a/src/main/resources/assets/wildfire_gender/lang/de_de.json b/src/main/resources/assets/wildfire_gender/lang/de_de.json index d1466570..40bcfcf0 100644 --- a/src/main/resources/assets/wildfire_gender/lang/de_de.json +++ b/src/main/resources/assets/wildfire_gender/lang/de_de.json @@ -2,8 +2,6 @@ "category.wildfire_gender.generic": "Wildfire's Weibliche Geschlechtsmod", "key.wildfire_gender.gender_menu": "Geschlechtsmenü", - "wildfire_gender.hurt.female": "Spielerin nimmt schaden", - "wildfire_gender.player_list.title": "Weibliche Geschlechts Mod", "wildfire_gender.player_list.settings_button": "Einstellungen", "wildfire_gender.player_list.sync_status": "Synchronisierungsstatus", diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index 371d643b..2cffad15 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -3,8 +3,6 @@ "key.wildfire_gender.gender_menu": "Female Gender Menu", "toast.wildfire_gender.get_started": "Press '%s' to get started!", - "wildfire_gender.hurt.female": "Female Player Hurt", - "wildfire_gender.player_list.title": "Female Gender Mod", "wildfire_gender.player_list.settings_button": "Settings", "wildfire_gender.player_list.sync_status": "Sync Status", diff --git a/src/main/resources/assets/wildfire_gender/lang/fr_fr.json b/src/main/resources/assets/wildfire_gender/lang/fr_fr.json index 2e463d78..c0466b48 100644 --- a/src/main/resources/assets/wildfire_gender/lang/fr_fr.json +++ b/src/main/resources/assets/wildfire_gender/lang/fr_fr.json @@ -2,8 +2,6 @@ "category.wildfire_gender.generic": "Wildfire's Female Gender Mod", "key.wildfire_gender.gender_menu": "Menu Genre Féminin", - "wildfire_gender.hurt.female": "Joueur Féminin Blessé", - "wildfire_gender.player_list.title": "Female Gender Mod", "wildfire_gender.player_list.settings_button": "Paramètres", "wildfire_gender.player_list.sync_status": "Sync Statut", diff --git a/src/main/resources/assets/wildfire_gender/lang/lol_us.json b/src/main/resources/assets/wildfire_gender/lang/lol_us.json index 5ffe53f4..7e5d07e2 100644 --- a/src/main/resources/assets/wildfire_gender/lang/lol_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/lol_us.json @@ -2,8 +2,6 @@ "category.wildfire_gender.generic": "Women Gunda Mood", "key.wildfire_gender.gender_menu": "Women Gunda Mano", - "wildfire_gender.hurt.female": "Women kat hurtz", - "wildfire_gender.player_list.title": "Women Gunda Mood", "wildfire_gender.player_list.settings_button": "Setinz", "wildfire_gender.player_list.sync_status": "Sinc stauz", diff --git a/src/main/resources/assets/wildfire_gender/sounds.json b/src/main/resources/assets/wildfire_gender/sounds.json index d156c254..fda4c354 100644 --- a/src/main/resources/assets/wildfire_gender/sounds.json +++ b/src/main/resources/assets/wildfire_gender/sounds.json @@ -1,12 +1,5 @@ { - "female_hurt1": { - "category": "block", - "subtitle": "wildfire_gender.hurt.female", - "sounds": [ "wildfire_gender:female_damage" ] - }, - "female_hurt2": { - "category": "block", - "subtitle": "wildfire_gender.hurt.female", - "sounds": [ "wildfire_gender:female_damage2" ] + "female_hurt": { + "sounds": [ "wildfire_gender:female_damage", "wildfire_gender:female_damage2" ] } } \ No newline at end of file diff --git a/src/main/resources/wildfire_gender.mixins.json b/src/main/resources/wildfire_gender.mixins.json index 514d97fc..e6dd8f4e 100644 --- a/src/main/resources/wildfire_gender.mixins.json +++ b/src/main/resources/wildfire_gender.mixins.json @@ -4,7 +4,7 @@ "package": "com.wildfire.mixins", "compatibilityLevel": "JAVA_17", "mixins": [ - "PlayerEntityServerMixin" + "LivingEntityMixin" ], "client": [ "PlayerEntityMixin", From 1b355b41f4e9afaf2ab2cd8f68abfadc1d25026c Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sun, 23 Jul 2023 16:09:09 -0400 Subject: [PATCH 006/238] Fixed "Playing with Creator" prompt not working properly. Minor adjustments to stuff --- .../gui/screen/WildfirePlayerListScreen.java | 2 +- .../com/wildfire/physics/BreastPhysics.java | 50 +++++++++++++++++-- .../java/com/wildfire/render/GenderLayer.java | 2 - 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WildfirePlayerListScreen.java b/src/main/java/com/wildfire/gui/screen/WildfirePlayerListScreen.java index 15fe7445..27e3cbb0 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfirePlayerListScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfirePlayerListScreen.java @@ -137,7 +137,7 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { ClientPlayNetworkHandler clientPlayNetworkHandler = client.player.networkHandler; boolean withCreator = clientPlayNetworkHandler.getPlayerList().stream() - .anyMatch((player) -> player.getProfile().getId() == CREATOR_UUID); + .anyMatch((player) -> player.getProfile().getId().equals(CREATOR_UUID)); if(withCreator) { WildfireHelper.drawCenteredText(ctx, this.textRenderer, Text.translatable("wildfire_gender.label.with_creator"), this.width / 2, y + 89, 0xFF00FF); } diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index 59251082..5a756015 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -21,6 +21,8 @@ import com.wildfire.api.IGenderArmor; import com.wildfire.main.GenderPlayer; import com.wildfire.main.WildfireHelper; +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.client.render.entity.PlayerEntityRenderer; import net.minecraft.entity.EntityPose; import net.minecraft.entity.passive.HorseEntity; import net.minecraft.entity.passive.PigEntity; @@ -29,6 +31,7 @@ import net.minecraft.entity.vehicle.BoatEntity; import net.minecraft.entity.vehicle.MinecartEntity; import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.RotationAxis; import net.minecraft.util.math.Vec3d; public class BreastPhysics { @@ -61,6 +64,43 @@ public void update(PlayerEntity plr, IGenderArmor armor) { return; } + float h = 0; //tickDelta + + float i = plr.getLeaningPitch(0); + float j; + float k; + + AbstractClientPlayerEntity aPlr = (AbstractClientPlayerEntity) plr; + float bodyXRotation = 0; + float bodyYRotation = 0; + + if (plr.isFallFlying()) { + j = (float) plr.getRoll() + h; + k = MathHelper.clamp(j * j / 100.0F, 0.0F, 1.0F); + if (!plr.isUsingRiptide()) { + bodyXRotation = k * (-90.0F - plr.getPitch()); + } + + Vec3d vec3d = plr.getRotationVec(h); + Vec3d vec3d2 = aPlr.lerpVelocity(h); + double d = vec3d2.horizontalLengthSquared(); + double e = vec3d.horizontalLengthSquared(); + if (d > 0.0 && e > 0.0) { + double l = (vec3d2.x * vec3d.x + vec3d2.z * vec3d.z) / Math.sqrt(d * e); + double m = vec3d2.x * vec3d.z - vec3d2.z * vec3d.x; + bodyYRotation = (float) (Math.signum(m) * Math.acos(l)); + } + } else if (i > 0.0F) { + j = aPlr.isTouchingWater() ? -90.0F - aPlr.getPitch() : -90.0F; + k = MathHelper.lerp(i, 0.0F, j); + bodyXRotation = k; + } else if(plr.isSleeping()) { + bodyXRotation = 90f; + } else if(plr.getPose() == EntityPose.CROUCHING) { + bodyXRotation = -15f; + } + + float breastWeight = genderPlayer.getBustSize() * 1.25f; float targetBreastSize = genderPlayer.getBustSize(); @@ -108,11 +148,13 @@ public void update(PlayerEntity plr, IGenderArmor armor) { //float horizLocal = -horizVel * ((plr.getRotationYawHead()-plr.renderYawOffset)<0?-1:1); this.targetRotVel = -((plr.bodyYaw - plr.prevBodyYaw) / 15f) * bounceIntensity; - float f = (float) plr.getVelocity().lengthSquared() / 0.2F; - f = f * f * f; - if(f < 1.0F) f = 1.0F; + //System.out.println("Body Rotation: " + (bodyXRotation) / 90); + + float f2 = (float) plr.getVelocity().lengthSquared() / 0.2F; + f2 = f2 * f2 * f2; + if(f2 < 1.0F) f2 = 1.0F; - this.targetBounceY += MathHelper.cos(plr.limbAnimator.getPos() * 0.6662F + (float)Math.PI) * 0.5F * plr.limbAnimator.getSpeed() * 0.5F / f; + this.targetBounceY += MathHelper.cos(plr.limbAnimator.getPos() * 0.6662F + (float)Math.PI) * 0.5F * plr.limbAnimator.getSpeed() * 0.5F / f2; //System.out.println(plr.rotationYaw); this.targetRotVel += (float) motion.y * bounceIntensity * randomB; diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index f820dc36..26933fef 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -140,8 +140,6 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume rBreast = new BreastModelBox(64, 64, 20, 17, 0, 0.0F, 0F, 4, 5, (int) (4 - breastOffsetZ - reducer), 0.0F, false); preBreastSize = bSize; } - lBreast = new BreastModelBox(64, 64, 16, 17, -4F, 0.0F, 0F, 4, 5, (int) (4 - breastOffsetZ - reducer), 0.0F, false); - rBreast = new BreastModelBox(64, 64, 20, 17, 0, 0.0F, 0F, 4, 5, (int) (4 - breastOffsetZ - reducer), 0.0F, false); //Note: We only render if the entity is not visible to the player, so we can assume it is visible to the player float overlayAlpha = ent.isInvisible() ? 0.15F : 1; From d78770ea7ac8ec22a7f5f38159e8470a34496bcb Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sun, 23 Jul 2023 16:25:34 -0400 Subject: [PATCH 007/238] Slight GUI changes --- .../gui/screen/WardrobeBrowserScreen.java | 6 +++--- .../textures/gui/wardrobe_bg2.png | Bin 0 -> 714 bytes .../wildfire_gender/textures/presets/preset1.png | Bin 0 -> 5839 bytes .../wildfire_gender/textures/presets/preset2.png | Bin 0 -> 6388 bytes .../wildfire_gender/textures/presets/preset3.png | Bin 0 -> 5268 bytes .../wildfire_gender/textures/presets/preset4.png | Bin 0 -> 5901 bytes 6 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/wardrobe_bg2.png create mode 100644 src/main/resources/assets/wildfire_gender/textures/presets/preset1.png create mode 100644 src/main/resources/assets/wildfire_gender/textures/presets/preset2.png create mode 100644 src/main/resources/assets/wildfire_gender/textures/presets/preset3.png create mode 100644 src/main/resources/assets/wildfire_gender/textures/presets/preset4.png diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index 3db4fb73..fdbbbe7d 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -40,7 +40,7 @@ import org.joml.Quaternionf; public class WardrobeBrowserScreen extends BaseWildfireScreen { - private static final Identifier BACKGROUND = new Identifier(WildfireGender.MODID, "textures/gui/wardrobe_bg.png"); + private static final Identifier BACKGROUND = new Identifier(WildfireGender.MODID, "textures/gui/wardrobe_bg2.png"); public static float modelRotation = 0.5F; public WardrobeBrowserScreen(Screen parent, UUID uuid) { @@ -100,11 +100,11 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { int y = this.height / 2; modelRotation = 0.6f; - ctx.drawText(textRenderer, title, x - 42, y - 62, 4473924, false); + ctx.drawText(textRenderer, title, x - 118, y - 62, 4473924, false); try { RenderSystem.setShaderColor(1f, 1.0F, 1.0F, 1.0F); int xP = this.width / 2 - 82; - int yP = this.height / 2 + 32; + int yP = this.height / 2 + 40; PlayerEntity ent = minecraft.world.getPlayerByUuid(this.playerUUID); if(ent != null) { drawEntityOnScreen(xP, yP, 45, (xP - mouseX), (yP - 76 - mouseY), minecraft.world.getPlayerByUuid(this.playerUUID)); diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/wardrobe_bg2.png b/src/main/resources/assets/wildfire_gender/textures/gui/wardrobe_bg2.png new file mode 100644 index 0000000000000000000000000000000000000000..c2e69cfc9bd0184b88218dbeb9d2171bb72326e3 GIT binary patch literal 714 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911MRQ8&P5D>38$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&eBzI1?3h%1o(|NsB7W5+^6L(R?2!@|O{v$MIlK&EnZZq)`-oFzei!9X=& zz!2iE)Q`-6>Kfty_ZFdp-xMyfr074 zKL#cag$9NnD_87ikpEp0KkwXi_1xN{^$b6(3zPdrAACNucD;lH17krw10#z7kO3lM zHf)4wV|uXoTqInA;X6A_1tZG=R%vda!9dd)>|V`bg-akAbYLAre{i|Y0S1ulo%bv| zWVt`o{P}4u(BN-TzWz5qgmYP!xSyIW-$TAh9B6C8a zlVmI}(KdJ}kxmq2={P1)y!SJzbKdhgfBeqp_s4wZnft!(`?{|0^}W9L{YP@aABYfJ7h54Ac9z7KKwk-yLAt7}74trbA*aH&-5_fbQ_ToRP#|KDDtDSNT zc9aXxagQI`gejsYE=?Wmsfg|>CzKuweqBGl#pp_dsBm$~ScBJbU)3`|D;#cs{^+u5 z=>20sB5As2uIW;Yg436ib%zmqP8V!=H=1=V`uG~{14Wa{w&?2;C11W|S7mmcdbE@4 zhh<@AzqTx1mHpBvdA{eFB{DYkUWn+8&;$+k4)4||F*)?{g8T6wJ81199BVtb=z0hd zFS(cl#&HT5O3@#uVe1NcMu5`&wWpZ0t~XkI+#q%8S4UpOD=c4ZbBY76j8C2VO3dvi z*J{&(r%MoNJ=t+J>1x&3usSmzqg27lYtaV_)(15#Eo#*iv&yOuJv?o5ep{vF>x&Of z1v5z-x^>HnSYgtsdLE*e8W}S8N-`hcxd0Ku)0ET1A80Ew{DV|X5%N9*rWX# ziB4_~-Qo{CTYDr0_Fb^OHb0{Jvxl0Gi9WomMJWj31r#+IAO62~WHoAGe|%YHxsu$@ zAqS;=0)C|LY_5G`rQP2Qc@Cd(7>##_XRdFqIpntPltS>)-2=W#1YWuStJ*(z5c9=i zj-i%rU#53X5l$F)PyWZ{l__uuAq03EVVKRhe~$z%^S8(J|x#7BYt$ zwfJ$9wGwr za})Jb&f!xoLpodc{BTo-)let8&Y3>o6tZ8-9T*}+r@LtGTYbcN=T}0=It;@@LcUL^JAkkHkJ-A{5$Et{!uFZ zzTWYTj`5SZN_e46to!O6<1EQ6KXwkI>lE`_#l~9rI_G=bm0Z*O&c&X3DqF(cX!8?M z?7mZFP3E;|Pe*@VR#*01O7BDQ>D{~-+tB$xTP7Umcu^GvkNcbM!KwC>os>_;oIO5GRfq6 z=3!%sD>ZX@Z5N==uClk6ty=flKHqE*GzGac)<*kAjVk)!-ED(;>eL&Zmo`^e4rLil z>qVShA1#OWY>6YU#6A0j$jEZ?t0Lw~eL0{u`dZ#+Y@}!;$oPp7a^Xrad28LH!dq`& zy(I2b>!o+JCZ^!JH`ZiE9RWM|5u@v(f zkir0cAOl_&BhBtJu=rF1<}AO>?H)M5_Nx7$ItO?zEC|A)G@=30tNt-0M?j2nClsms zK^<`2>}o*_w5TzD`mZdP(TBNXkauhC-Ddel%jo)+#J>+Mv~2Aui5dbeSN=Vjz2x}< zOnWR?T3QD^2@(t~OsUa-`=@GLPH3Z&X0%Q-cfh zug(&POMU5GfrfmgtwT;$fDH@CAES__^RTxAu_#_<|7 zBZ|g#H>aotX^<{dWin~@uJ-L_Y4U}8o}Mr=@TKcpsZdJLP}T6k#fdcCQ<15s5)sEpJwJkg7~mPD``6G1MaLZT8HP zr@C;Vv~{s_)vOw&mEl*}0?e=vVi) zb(n2;rDT2WbL7QYh?3x$Cd~3oJDA~T4QzL_t5BsW%clz5c+{Xy7!z;)zn;gsJ92?% zcz;JS8lM5VAWYZEL~;-DO27Cs|28yPh&^494jE)Bvl*hKecHn;RDEhuvIFMN0bicO z#2)fk#qrO4V!owdKF zzU%-qOCPX@K`6uXbm6?65@Z-lP)aUtN1x*dB4_J-hg9K{_v4||V)q^H*s_K3RsVyDy127*Pf&8k&sL9-g)=8ENV1Ra- zxO|?IHL-B+rIZ|copJVjd8$jUDNVUR4*u~Obot0|sTS+a4j8#8pr@`x0;NgBuex*( zd6Vj8K&uO&Xdr%5%XHQ0gJ9S>WB} z$B{mOMCVU%fP;a&GBa5z;MH@wC@?!OD5kq)A`fL%+4i`6%7lUsU*p0AKr~`xVG0QV z0;mrQ1`3bx5=gQj+Q4i&_eXHW{}`M)J7l?F090mLLm|xHGkSNYW1c6XEd8nyJA!&i zGa-5~!Jf^p0dgot3jMf`yJaX#oTf~>98V?hu0_c+Di{T-j9(3_Ho$o3 z8-P*+5Er7%#61*&?IuBSLAXrTH#{9WZ7R!*Jp3&wUK3$x6{+=Fv%;Gb}pip*hCPW1}^cK*NjyxKZCL(3qX_9H(eMm6>W``|2 z4`Ns=N~!}0>Q464t*Ik`^)DVF0Y1enMYhCpKwhcVz3S{EGK?E&0?6Wpftj+ms9*}n zMmC5Q0|-s$Ca`;}KZoQd&d$%~`X^cv{V`@|6iRx|LceF>(7jm$?hFtD%=pQ(F0ZPi zd1)wW1JrP3AFq#oC0)(tPGJ-;vtQ670x*|+q<1uGPe>=|cQ(3Nk17u&W#FjX=dAK42GLg>k#ERk-K3uV$c%dqSQ*g8xGui)+7{k>CHZl&)bU!!4R3?#Q z#LBUEkGuli_8&9?4Mfs$7eLqAnwb>w;e9s9&-Er0s&4?y1gI?#lkPcx3E&jK0Cz#x ztUpaZ;pSsLK9V9%5f}o&^VjF6Cgp?h6gEFEP+{x+Rr_KT!KKU|9D;(A4JZpNg^yM1 zZ<>WY@iPDcfFnc4-Lq1lzhUkFkuZdz^U+}73Gl;A#=xzC`Pq9|_;C1+-;$gSPHL@D zmO$jF0Rlqatj6;mVr0v2qZd#%Ri%4&ZJL0*ZgzqZJWmbmK8~V_fVjT#&5^erOWC-r zZdgh{cAn2*2%sdEe+gAj=VJM5VBa~QR|@vBpSgKH9sa#PiQoy&txw>x;5~t&QjYwk zOlQxIFH#L_pQ2#iW3OvpBQ^^qwsD z>F_XUS9yh)50F24X$$d*ku25u%TNv6n80B*HrgK?wOh2lseyy=TB9ig_EPZgw@qM< zYJr35bwz@5{V)=wj4y$SWm{L5FV-GLM>eFmG%%j&2 zp!+cy%;5-uidk^au|$^A^t4!gYt7a(BIFr^4VpA@?@!$&lkMd}OH_%CM(IV#Ts8J! z(o(`$g|896*Z+sRZ^GfvB)6(uX?wrFllF={KL*HcE>VUb}x zRMdMhYjk990kZxk#dSPHiIJaH6B41sD5A;dwy4{US!HxO*}2=M?+nVC8$em3!=G$1 z;H#YNOqtGK{Jl-xF3fvoLGQu*7(;Zv(|qR0-7Pwl_{ukjY#bJYGHi?>tSD&>d*{UG zyD9}pM*f(0L#3EO?9&ZSNY9HGQ=pk6HLs$`-im4B>T*03!c^y-4)FrPZyc9}0BQ*q z8lsV&@REz4cQaV$Vq0>6lq03QK|O%*D&vdi4Acx$vDx!m~T|o z!36u0J!(4HpHBt=G1W|l#ivod(Ve)vHi>uh>!SzHT~U0+Oqr!TT1QsVvX^5NY=;lOT&Q1;LoL|c=1VL%+c-cMrUzmM)q?I?8v|uBLD&j! zgb$U1;Z-JlvVqbiP)y5O2MbjY>>zc4EU zWbN1kT=kARGrh#h7;x65iTAHZPH%+ofHMhYF0Zyh>$6f5vI{Y<5@^1b_dCuRmU0#k z$lAnye2M2wJ{D`a6$y*gl<~UuJfiHK;Ca`8e*2;8=AOJI!&&(?KxNe^#wf|Z1NsFA_b(h`DE<>DtHa-7av(M+vCTpIPZ_gEO=Ml~X*lxM z@{_9FJS+RYAgllLp&*2}G`S7D4m2K`Ne{v!j=keBR2_sFozm`%{K*sSyd|X+aDN7w zVHzm}@VCxyx8@>iqR3{--_fDnc`Ub(QVz)-P-LS+^5|bzow_SyBY3vtuh@S#14_Fi znPb}%j$0f>z}KmlEXiLYe44isWE8LzHFze0-8e3+BK5=3{^WMe@fws*m-g2h-!X^}1w0Hra74e+wI4{u?`K z{=+r1+X~>`3wYQBSbzvo!e~5=CYbPl`Nwf9KJy>P@!*}{{(;J8--##l&9^nUU&s9# zaP{t%9j}B7woAtyspew`NJ|_GO4H zNaPCm149lixgq~>+pPBe;im*}@bwASoCv~)r-`pt^j54ZchjFP0=E}H6T9D5)kTbf zn_LMi;AC;L%w3z6OWVQio=NLWZo{7*c&<^;1yc`Ej9iON;N?S43goY-tbw9*V2Mk| zWf-pIAfwPV@HJ9itjl=QRt)b_D`zpaR|1f#bNL;jaes0P*vl9$g}^{ShL0$iKcEyD zS)%n0K`-X$|8h+*T)xGuHj&fTpWbFIN$-_a)^{4Z^Ah}7E40I5w|&L-0NQ^6t5?zj literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/wildfire_gender/textures/presets/preset2.png b/src/main/resources/assets/wildfire_gender/textures/presets/preset2.png new file mode 100644 index 0000000000000000000000000000000000000000..a14db5ddfed4c32f90699091992ded389ce9209b GIT binary patch literal 6388 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 JZ#V`e{SV9%@uC0# literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/wildfire_gender/textures/presets/preset3.png b/src/main/resources/assets/wildfire_gender/textures/presets/preset3.png new file mode 100644 index 0000000000000000000000000000000000000000..bc32b9afe728662d2c6f6bd3265ef082b0936819 GIT binary patch literal 5268 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%0AQa7WpVSCN3Z3`y^?% zI%Iv1ITO|-JC7{}Up989&<>=>q-WLowR}#m^YaP$x?PCO8|(G-d4}N!Kk6aN!VZnB zO07lBQM;39vvZu_d`b~;`LmJaiCVYO)^S|z>pe{z_AjM8=u8TD=p{X4bE*a4VFIkj zip}$#7ptAGG@;m!fk#~5Z<<&J3p^sx@#PQU)gC)Gn7JhoMKS&0iT$Jf|2If z31j;SB#6GIvJE#Iwuw=fEL*(swSx;r92a-J))Wr^UZt3YXHh=wLPh04u2U)ty!=Ex zZ>=ex8kF3S{fNlL4#HpFCp;Vb5elRAdum;LamWX3=Fue&3WGpO5m_^kpa015NP5n3 z$hEMyC|1mUJ7I-om`-`F67?MWC#+%2h!uw(5$8evs@MHR2E>O3SOx;pW+!gHf2W?E z>lw30JVyghu^Yy*IfK~g+U1z`knn4rnCtcB!V1-gE~PaMq4Vb_!nj$6vde4S1CTcK zb0uTTx$bMpG(7G*7LQ#y{t#y0P((ZkH)iH~-lNR59GR$xw|~p0+WBpAokfBQ?DXiY zj;_*Cu<2itkS+r{_l+rrJ%*H`m&j}M{YKD)rvWKWxmzO!kYA5zno?|=9g%o6|Lx21 zR{vrj5a6cW|2ehN97FFo+SstZ_tKl3Q;$+gpt;)zIs|b(Z8{}c+S~w&ERk|ed>`HB`}(cC7zL-A=f5Ix`6MgSM@n_DsIYNj)MKs SJHQWcs6gUc--?x6nEwU8>zCXB literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/wildfire_gender/textures/presets/preset4.png b/src/main/resources/assets/wildfire_gender/textures/presets/preset4.png new file mode 100644 index 0000000000000000000000000000000000000000..716f314a4218ef31df116e4125c59c815f61169e GIT binary patch literal 5901 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 Date: Sun, 23 Jul 2023 17:47:58 -0400 Subject: [PATCH 008/238] WildfireBreastCustomizationScreen.java update --- .../wildfire/gui/screen/WildfireBreastCustomizationScreen.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java index 4a3ca51e..25036458 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java @@ -56,7 +56,7 @@ public void init() { button -> MinecraftClient.getInstance().setScreen(parent))); this.addDrawableChild(this.breastSlider = new WildfireSlider(this.width / 2 + 30, j - 48, 158, 20, Configuration.BUST_SIZE, plr.getBustSize(), - plr::updateBustSize, value -> Text.translatable("wildfire_gender.wardrobe.slider.breast_size", Math.round(value * 100)), onSave)); + plr::updateBustSize, value -> Text.translatable("wildfire_gender.wardrobe.slider.breast_size", Math.round(value * 1.25f * 100)), onSave)); //Customization this.addDrawableChild(this.xOffsetBoobSlider = new WildfireSlider(this.width / 2 + 30, j - 27, 158, 20, Configuration.BREASTS_OFFSET_X, breasts.getXOffset(), From 937c5f6231b9f45b8f94ea58c5afedffe4443ab7 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sun, 23 Jul 2023 18:33:58 -0400 Subject: [PATCH 009/238] Physics override respects breathing animation --- src/main/java/com/wildfire/render/GenderLayer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index 26933fef..3ddd325e 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -175,9 +175,9 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume float resistance = MathHelper.clamp(genderArmor.physicsResistance(), 0, 1); //Note: We only check if the breathing animation should be enabled if the chestplate's physics resistance // is less than or equal to 0.5 so that if we won't be rendering it we can avoid doing extra calculations - boolean breathingAnimation = resistance <= 0.5F && + boolean breathingAnimation = ((plr.getArmorPhysicsOverride() || resistance <= 0.5F) && (!ent.isSubmergedInWater() || StatusEffectUtil.hasWaterBreathing(ent) || - ent.getWorld().getBlockState(new BlockPos(ent.getBlockX(), ent.getBlockY(), ent.getBlockZ())).isOf(Blocks.BUBBLE_COLUMN)); + ent.getWorld().getBlockState(new BlockPos(ent.getBlockX(), ent.getBlockY(), ent.getBlockZ())).isOf(Blocks.BUBBLE_COLUMN))); boolean bounceEnabled = plr.hasBreastPhysics() && (!isChestplateOccupied || resistance < 1); //oh, you found this? int combineTex = LivingEntityRenderer.getOverlay(ent, 0); From 15fbffa6012473da4a2dea782d61eb1b0847ebbd Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sun, 23 Jul 2023 23:35:24 -0400 Subject: [PATCH 010/238] Changed base configuration min and max inclusive values. --- src/main/java/com/wildfire/main/config/Configuration.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/wildfire/main/config/Configuration.java b/src/main/java/com/wildfire/main/config/Configuration.java index c57a2d29..6190b66a 100644 --- a/src/main/java/com/wildfire/main/config/Configuration.java +++ b/src/main/java/com/wildfire/main/config/Configuration.java @@ -51,8 +51,8 @@ public class Configuration { public static final BooleanConfigKey BREAST_PHYSICS = new BooleanConfigKey("breast_physics", true); public static final BooleanConfigKey ARMOR_PHYSICS_OVERRIDE = new BooleanConfigKey("armor_physics_override", false); public static final BooleanConfigKey SHOW_IN_ARMOR = new BooleanConfigKey("show_in_armor", true); - public static final FloatConfigKey BOUNCE_MULTIPLIER = new FloatConfigKey("bounce_multiplier", 0.34F, 0, 1); - public static final FloatConfigKey FLOPPY_MULTIPLIER = new FloatConfigKey("floppy_multiplier", 0.75F, 0, 1); + public static final FloatConfigKey BOUNCE_MULTIPLIER = new FloatConfigKey("bounce_multiplier", 0.34F, 0, 0.5f); + public static final FloatConfigKey FLOPPY_MULTIPLIER = new FloatConfigKey("floppy_multiplier", 0.75F, 0.25f, 1); private static final TypeAdapter ADAPTER = new Gson().getAdapter(JsonObject.class); From b6bfe8f277764bbd8b3bca79035ee8b7e7448783 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sun, 23 Jul 2023 23:38:48 -0400 Subject: [PATCH 011/238] Removed armor texture cache. --- src/main/java/com/wildfire/render/GenderLayer.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index 3ddd325e..40d59932 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -64,7 +64,7 @@ import java.util.Map; public class GenderLayer extends FeatureRenderer> { - private static final Map ARMOR_TEXTURE_CACHE = new HashMap<>(); + private final SpriteAtlasTexture armorTrimsAtlas; private BreastModelBox lBreast, rBreast; @@ -92,8 +92,7 @@ public GenderLayer(FeatureRendererContext Date: Sun, 23 Jul 2023 23:44:47 -0400 Subject: [PATCH 012/238] Added simplified chinese translation (Mc_candycube6623) --- .../assets/wildfire_gender/lang/zh_cn.json | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/main/resources/assets/wildfire_gender/lang/zh_cn.json diff --git a/src/main/resources/assets/wildfire_gender/lang/zh_cn.json b/src/main/resources/assets/wildfire_gender/lang/zh_cn.json new file mode 100644 index 00000000..00f51f3f --- /dev/null +++ b/src/main/resources/assets/wildfire_gender/lang/zh_cn.json @@ -0,0 +1,62 @@ +{ + "category.wildfire_gender.generic": "Wildfire's Female Gender Mod", + "key.wildfire_gender.gender_menu": "性别菜单", + + "wildfire_gender.hurt.female": "女玩家受伤", + + "wildfire_gender.player_list.title": "Female Gender Mod", + "wildfire_gender.player_list.settings_button": "设置", + "wildfire_gender.player_list.sync_status": "同步状态", + "wildfire_gender.player_list.state.loading": "正在加载数据...", + "wildfire_gender.player_list.state.synced": "同步玩家", + + "wildfire_gender.wardrobe.title": "自定义菜单", + "wildfire_gender.wardrobe.slider.breast_size": "乳房大小:%s%%", + "wildfire_gender.wardrobe.slider.separation": "分离:%s", + "wildfire_gender.wardrobe.slider.height": "高度:%s", + "wildfire_gender.wardrobe.slider.depth": "深度:%s", + "wildfire_gender.wardrobe.slider.rotation": "旋转:%s度", + + "wildfire_gender.appearance_settings.title": "外观设置", + "wildfire_gender.char_settings.title": "角色设置", + "wildfire_gender.char_settings.physics": "乳房物理:%s", + "wildfire_gender.tooltip.breast_physics": "启用乳房物理", + "wildfire_gender.char_settings.armor_physics": "盔甲物理:%s", + "wildfire_gender.tooltip.armor_physics": "在装备盔甲的情况下实现乳房物理", + "wildfire_gender.char_settings.hide_in_armor": "隐藏在盔甲中:%s", + "wildfire_gender.tooltip.hide_in_armor": "穿着盔甲时隐藏乳房模型", + "wildfire_gender.char_settings.hurt_sounds": "女性受伤的声音:%s", + "wildfire_gender.tooltip.hurt_sounds": "使女性受伤的声音", + + "wildfire_gender.breast_customization.dual_physics": "对偶物理:%s", + + "wildfire_gender.player_list.bounce_multiplier": "反弹系数:%sx", + "wildfire_gender.player_list.breast_momentum": "乳房动量:%s%%", + "wildfire_gender.player_list.female_sounds": "女性声音:%s", + + "wildfire_gender.settings.title": "Wildfire的设置菜单", + + "wildfire_gender.acknowledge.confirm": "好", + + "wildfire_gender.label.gender": "性别", + "wildfire_gender.label.female": "女性", + "wildfire_gender.label.male": "男性", + "wildfire_gender.label.other": "其他", + + "wildfire_gender.label.enabled": "启用", + "wildfire_gender.label.disabled": "禁用", + "wildfire_gender.label.yes": "是", + "wildfire_gender.label.no": "否", + "wildfire_gender.label.exit": "❌", + "wildfire_gender.label.too_far": "太远了", + "wildfire_gender.label.with_creator": "您正在与该模组的作者一起在服务器上玩!", + + "wildfire_gender.slider.bounce": "弹跳强度:%sx", + "wildfire_gender.slider.floppy": "乳房动量:%s%%", + "wildfire_gender.slider.min_bounce": "为什么开启物理?", + "wildfire_gender.slider.max_bounce": "动漫乳房物理!!!", + "wildfire_gender.tooltip.bounce_warning": "将“反弹强度”设置为高值看起来非常不自然!", + + "wildfire_gender.cancer_awareness.title": "嘿,这是乳腺癌宣传月!", + "wildfire_gender.cancer_awareness.description": "点击这里捐赠给§dSusan G. Komen Foundation§f!§r" +} From 31bafb480a7f7618de6f6b627a041aa36cc4ea38 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sun, 23 Jul 2023 23:46:48 -0400 Subject: [PATCH 013/238] Added Ukrainian Localization (Nezila) --- .../assets/wildfire_gender/lang/uk_ua.json | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 src/main/resources/assets/wildfire_gender/lang/uk_ua.json diff --git a/src/main/resources/assets/wildfire_gender/lang/uk_ua.json b/src/main/resources/assets/wildfire_gender/lang/uk_ua.json new file mode 100644 index 00000000..9f26a0fc --- /dev/null +++ b/src/main/resources/assets/wildfire_gender/lang/uk_ua.json @@ -0,0 +1,60 @@ +{ + "category.wildfire_gender.generic": "Wildfire's Female Gender Mod", + "key.wildfire_gender.gender_menu": "Меню жіночої статі", + + "wildfire_gender.hurt.female": "Жіночого гравця поранено", + + "wildfire_gender.player_list.title": "Female Gender Mod", + "wildfire_gender.player_list.settings_button": "Налаштування", + "wildfire_gender.player_list.sync_status": "Синхронізація стану", + "wildfire_gender.player_list.state.loading": "Завантаження даних...", + "wildfire_gender.player_list.state.synced": "Синхронізований гравець", + + "wildfire_gender.wardrobe.title": "Меню кастомізації", + "wildfire_gender.wardrobe.slider.breast_size": "Розмір грудей: %s%%", + "wildfire_gender.wardrobe.slider.separation": "Відстань: %s", + "wildfire_gender.wardrobe.slider.height": "Висота: %s", + "wildfire_gender.wardrobe.slider.depth": "Глибина: %s", + "wildfire_gender.wardrobe.slider.rotation": "Обертання: %s градусів", + + "wildfire_gender.appearance_settings.title": "Параметри зовнішнього вигляду", + "wildfire_gender.char_settings.title": "Параметри персонажа", + "wildfire_gender.char_settings.physics": "Фізика грудей: %s", + "wildfire_gender.tooltip.breast_physics": "Вмикає фізику грудей", + "wildfire_gender.char_settings.hide_in_armor": "Приховувати під бронею: %s", + "wildfire_gender.tooltip.hide_in_armor": "Приховує модель грудей під час носіння броні", + "wildfire_gender.char_settings.hurt_sounds": "Жіночі звуки поранення: %s", + "wildfire_gender.tooltip.hurt_sounds": "Вмикає жіночі звуки поранення", + + "wildfire_gender.breast_customization.dual_physics": "Подвійна фізика %s", + + "wildfire_gender.player_list.bounce_multiplier": "Множник пружності: %sx", + "wildfire_gender.player_list.breast_momentum": "Імпульс грудей: %s%%", + "wildfire_gender.player_list.female_sounds": "Жіночі звуки: %s", + + "wildfire_gender.settings.title": "Меню налаштувань Wildfire", + + "wildfire_gender.acknowledge.confirm": "Згоден", + + "wildfire_gender.label.gender": "Стать", + "wildfire_gender.label.female": "Жінка", + "wildfire_gender.label.male": "Чоловік", + "wildfire_gender.label.other": "Інше", + + "wildfire_gender.label.enabled": "Увімкнено", + "wildfire_gender.label.disabled": "Вимкнено", + "wildfire_gender.label.yes": "Так", + "wildfire_gender.label.no": "Ні", + "wildfire_gender.label.exit": "X", + "wildfire_gender.label.too_far": "Занадто далеко", + "wildfire_gender.label.with_creator": "Ви граєте на одному ж сервері з творцем цього моду!", + + "wildfire_gender.slider.bounce": "Інтенсивність пружності: %sx", + "wildfire_gender.slider.floppy": "Імпульс грудей: %s%%", + "wildfire_gender.slider.min_bounce": "Чому фізика взагалі включена?", + "wildfire_gender.slider.max_bounce": "Аніме фізика грудей!!!", + "wildfire_gender.tooltip.bounce_warning": "Встановлення «Інтенсивності пружності» на високе значення виглядатиме дуже неприродно!", + + "wildfire_gender.cancer_awareness.title": "Привіт, сьогодні Місяць боротьби з раком молочної залози!", + "wildfire_gender.cancer_awareness.description": "Натисніть тут, щоб зробити пожертву §dSusan G. Komen Foundation§f!" +} From cda183f558a77c0610cbd02d732420c5c7e0bb4d Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Mon, 24 Jul 2023 00:58:30 -0400 Subject: [PATCH 014/238] Beginning of breast preset functionality (subject to change) --- .../gui/WildfireBreastPresetList.java | 109 ++++++++++++++++++ .../java/com/wildfire/gui/WildfireButton.java | 4 + .../WildfireBreastCustomizationScreen.java | 63 ++++++++-- .../assets/wildfire_gender/lang/en_us.json | 2 + 4 files changed, 167 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/wildfire/gui/WildfireBreastPresetList.java diff --git a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java new file mode 100644 index 00000000..9ccfb79e --- /dev/null +++ b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java @@ -0,0 +1,109 @@ +package com.wildfire.gui; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.wildfire.gui.screen.WildfireBreastCustomizationScreen; +import com.wildfire.main.WildfireGender; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.gui.widget.EntryListWidget; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; + +public class WildfireBreastPresetList extends EntryListWidget { + + private class BreastPresetListEntry { + + public Identifier ident; + + public BreastPresetListEntry(String location) { + ident = new Identifier(WildfireGender.MODID, "textures/presets/" + location); + } + } + private BreastPresetListEntry[] BREAST_PRESETS = new BreastPresetListEntry[] { + new BreastPresetListEntry("preset1.png"), + new BreastPresetListEntry("preset2.png"), + new BreastPresetListEntry("preset3.png"), + new BreastPresetListEntry("preset4.png"), + }; + private static final Identifier TXTR_SYNC = new Identifier(WildfireGender.MODID, "textures/sync.png"); + private static final Identifier TXTR_UNKNOWN = new Identifier(WildfireGender.MODID, "textures/unknown.png"); + private static final Identifier TXTR_CACHED = new Identifier(WildfireGender.MODID, "textures/cached.png"); + private final int listWidth; + private final WildfireBreastCustomizationScreen parent; + + public WildfireBreastPresetList(WildfireBreastCustomizationScreen parent, int listWidth, int top, int bottom) { + super(MinecraftClient.getInstance(), parent.width-4, parent.height, top-6, bottom, 54); + this.parent = parent; + this.listWidth = listWidth; + this.refreshList(); + } + + @Override + protected int getScrollbarPositionX() { + return parent.width / 2 + 181; + } + + @Override + public int getRowWidth() { + return this.listWidth; + } + + public void refreshList() { + this.clearEntries(); + if(this.client.world == null || this.client.player == null) return; + ClientPlayNetworkHandler clientPlayNetworkHandler = this.client.player.networkHandler; + + for(int i = 0; i < BREAST_PRESETS.length; i++) { + addEntry(new Entry(BREAST_PRESETS[i])); + } + } + + @Override + public void appendNarrations(NarrationMessageBuilder builder) {} + + @Environment(EnvType.CLIENT) + public class Entry extends EntryListWidget.Entry { + private final Identifier thumbnail; + public final BreastPresetListEntry nInfo; + private final WildfireButton btnOpenGUI; + + private Entry(final BreastPresetListEntry nInfo) { + this.nInfo = nInfo; + this.thumbnail = nInfo.ident; + btnOpenGUI = new WildfireButton(0, 0, 54, 54, Text.empty(), button -> { + /*GenderPlayer aPlr = WildfireGender.getPlayerById(nInfo.getProfile().getId()); + if(aPlr == null) return; + + try { + MinecraftClient.getInstance().setScreen(new WardrobeBrowserScreen(parent, nInfo.getProfile().getId())); + } catch(Exception ignored) {}*/ + }); + } + + @Override + public void render(DrawContext ctx, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float partialTicks) { + TextRenderer font = MinecraftClient.getInstance().textRenderer; + + RenderSystem.setShaderTexture(0, thumbnail); + ctx.drawTexture(thumbnail, x + 154, y + 2, 0, 0, 50, 50, 50,50); + + this.btnOpenGUI.setX(x + 152); + this.btnOpenGUI.setY(y); + this.btnOpenGUI.render(ctx, mouseX, mouseY, partialTicks); + + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if(this.btnOpenGUI.mouseClicked(mouseX, mouseY, button)) { + return true; + } + return super.mouseClicked(mouseX, mouseY, button); + } + } +} diff --git a/src/main/java/com/wildfire/gui/WildfireButton.java b/src/main/java/com/wildfire/gui/WildfireButton.java index 1418ffad..1b1ebaa9 100644 --- a/src/main/java/com/wildfire/gui/WildfireButton.java +++ b/src/main/java/com/wildfire/gui/WildfireButton.java @@ -62,4 +62,8 @@ public WildfireButton setTransparent(boolean b) { this.transparent = b; return this; } + public WildfireButton setActive(boolean b) { + this.active = b; + return this; + } } \ No newline at end of file diff --git a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java index 25036458..e4310aa8 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java @@ -19,6 +19,7 @@ package com.wildfire.gui.screen; import com.mojang.blaze3d.systems.RenderSystem; +import com.wildfire.gui.WildfireBreastPresetList; import com.wildfire.gui.WildfireButton; import com.wildfire.gui.WildfireSlider; import com.wildfire.main.Breasts; @@ -36,14 +37,18 @@ public class WildfireBreastCustomizationScreen extends BaseWildfireScreen { private WildfireSlider breastSlider, xOffsetBoobSlider, yOffsetBoobSlider, zOffsetBoobSlider, cleavageSlider; + private WildfireButton btnDualPhysics, btnPresets, btnCustomization; + + private WildfireBreastPresetList PRESET_LIST; public WildfireBreastCustomizationScreen(Screen parent, UUID uuid) { super(Text.translatable("wildfire_gender.appearance_settings.title"), parent, uuid); } + private int currentTab = 0; // 0 = customization, 1 = presets @Override public void init() { - int j = this.height / 2; + int j = this.height / 2 - 11; GenderPlayer plr = getPlayer(); Breasts breasts = plr.getBreasts(); @@ -52,9 +57,37 @@ public void init() { GenderPlayer.saveGenderInfo(plr); }; - this.addDrawableChild(new WildfireButton(this.width / 2 + 178, j - 61, 9, 9, Text.translatable("wildfire_gender.label.exit"), + this.addDrawableChild(new WildfireButton(this.width / 2 + 178, j - 72, 9, 9, Text.translatable("wildfire_gender.label.exit"), button -> MinecraftClient.getInstance().setScreen(parent))); + //Customization Tab + this.addDrawableChild(btnCustomization = new WildfireButton(this.width / 2 + 30, j - 60, 158 / 2 - 1, 10, + Text.translatable("wildfire_gender.breast_customization.tab_customization"), button -> { + currentTab = 0; + btnCustomization.active = false; + btnPresets.active = true; + + }).setActive(false)); + + //Presets Tab + this.addDrawableChild(btnPresets = new WildfireButton(this.width / 2 + 31 + 158/2, j - 60, 158 / 2 - 1, 10, + Text.translatable("wildfire_gender.breast_customization.tab_presets"), button -> { + currentTab = 1; + btnCustomization.active = true; + btnPresets.active = false; + })); + + //Preset Tab Below + PRESET_LIST = new WildfireBreastPresetList(this, 54, (j - 40), (j + 89)); + PRESET_LIST.setRenderBackground(false); + PRESET_LIST.setRenderHorizontalShadows(false); + //PLAYER_LIST.refreshList(); + this.addSelectableChild(this.PRESET_LIST); + + + + + //Customization Tab Below this.addDrawableChild(this.breastSlider = new WildfireSlider(this.width / 2 + 30, j - 48, 158, 20, Configuration.BUST_SIZE, plr.getBustSize(), plr::updateBustSize, value -> Text.translatable("wildfire_gender.wardrobe.slider.breast_size", Math.round(value * 1.25f * 100)), onSave)); @@ -69,7 +102,7 @@ public void init() { this.addDrawableChild(this.cleavageSlider = new WildfireSlider(this.width / 2 + 30, j + 36, 158, 20, Configuration.BREASTS_CLEAVAGE, breasts.getCleavage(), breasts::updateCleavage, value -> Text.translatable("wildfire_gender.wardrobe.slider.rotation", Math.round((Math.round(value * 100f) / 100f) * 100)), onSave)); - this.addDrawableChild(new WildfireButton(this.width / 2 + 30, j + 57, 158, 20, + this.addDrawableChild(this.btnDualPhysics =new WildfireButton(this.width / 2 + 30, j + 57, 158, 20, Text.translatable("wildfire_gender.breast_customization.dual_physics", Text.translatable(breasts.isUniboob() ? "wildfire_gender.label.no" : "wildfire_gender.label.yes")), button -> { boolean isUniboob = !breasts.isUniboob(); if (breasts.updateUniboob(isUniboob)) { @@ -108,18 +141,26 @@ public void render(DrawContext ctx, int f1, int f2, float f3) { } boolean canHaveBreasts = plr.getGender().canHaveBreasts(); - breastSlider.visible = canHaveBreasts; - xOffsetBoobSlider.visible = canHaveBreasts; - yOffsetBoobSlider.visible = canHaveBreasts; - zOffsetBoobSlider.visible = canHaveBreasts; - cleavageSlider.visible = canHaveBreasts; + breastSlider.visible = canHaveBreasts && currentTab == 0; + xOffsetBoobSlider.visible = canHaveBreasts && currentTab == 0; + yOffsetBoobSlider.visible = canHaveBreasts && currentTab == 0; + zOffsetBoobSlider.visible = canHaveBreasts && currentTab == 0; + cleavageSlider.visible = canHaveBreasts && currentTab == 0; + btnDualPhysics.visible = canHaveBreasts && currentTab == 0; int x = this.width / 2; int y = this.height / 2; - ctx.fill(x + 28, y - 64, x + 190, y + 79, 0x55000000); - ctx.fill(x + 29, y - 63, x + 189, y - 50, 0x55000000); - ctx.drawText(textRenderer, getTitle(), x + 32, y - 60, 0xFFFFFF, false); + ctx.fill(x + 28, y - 64 - 21, x + 190, y + 79, 0x55000000); + ctx.fill(x + 29, y - 63 - 21, x + 189, y - 50, 0x55000000); + ctx.drawText(textRenderer, getTitle(), x + 32, y - 60 - 21, 0xFFFFFF, false); super.render(ctx, f1, f2, f3); + + if(currentTab == 0) { + + } + if(currentTab == 1) { + PRESET_LIST.render(ctx, f1, f2, f3); + } } @Override diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index 2cffad15..1522d137 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -10,6 +10,8 @@ "wildfire_gender.player_list.state.synced": "Synced Player", "wildfire_gender.wardrobe.title": "Customization Menu", + "wildfire_gender.breast_customization.tab_customization": "Customization", + "wildfire_gender.breast_customization.tab_presets": "Presets", "wildfire_gender.wardrobe.slider.breast_size": "Breast Size: %s%%", "wildfire_gender.wardrobe.slider.separation": "Separation: %s", "wildfire_gender.wardrobe.slider.height": "Height: %s", From eff6590ed58ac12f67f3e7bb3444908771f67435 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Tue, 25 Jul 2023 01:02:51 -0400 Subject: [PATCH 015/238] Configuration file saves "pretty printed" now, easier readability. --- src/main/java/com/wildfire/main/config/Configuration.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/wildfire/main/config/Configuration.java b/src/main/java/com/wildfire/main/config/Configuration.java index 6190b66a..c5df000e 100644 --- a/src/main/java/com/wildfire/main/config/Configuration.java +++ b/src/main/java/com/wildfire/main/config/Configuration.java @@ -128,6 +128,7 @@ public void updateConfig() { public void save() { try (FileWriter writer = new FileWriter(CFG_FILE); JsonWriter jsonWriter = new JsonWriter(writer)) { + jsonWriter.setIndent(" "); ADAPTER.write(jsonWriter, SAVE_VALUES); //System.out.println("[Configuration] Saved New File!"); } catch (IOException e1) { From 7ee5f19b6d291333347510431455141eb8f003bb Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Tue, 25 Jul 2023 01:03:21 -0400 Subject: [PATCH 016/238] WildfireSlider.java works by clicking on it without having to drag now. --- src/main/java/com/wildfire/gui/WildfireSlider.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/com/wildfire/gui/WildfireSlider.java b/src/main/java/com/wildfire/gui/WildfireSlider.java index c2469550..7fdf9959 100644 --- a/src/main/java/com/wildfire/gui/WildfireSlider.java +++ b/src/main/java/com/wildfire/gui/WildfireSlider.java @@ -85,6 +85,11 @@ public void onRelease(double mouseX, double mouseY) { save(); } + @Override + public void onClick(double mouseX, double mouseY) { + this.setValueFromMouse(mouseX); + } + @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { boolean result = super.keyPressed(keyCode, scanCode, modifiers); From b3784ee5cf0eb660288267982a99eed646430cc7 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Tue, 25 Jul 2023 01:24:17 -0400 Subject: [PATCH 017/238] Preset Updates --- .../gui/WildfireBreastPresetList.java | 82 +++++++++++++++---- .../java/com/wildfire/gui/WildfireButton.java | 1 + .../WildfireBreastCustomizationScreen.java | 31 ++++--- 3 files changed, 84 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java index 9ccfb79e..5e46b9ff 100644 --- a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java +++ b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java @@ -11,24 +11,33 @@ import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; import net.minecraft.client.gui.widget.EntryListWidget; import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.text.Style; import net.minecraft.text.Text; +import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; +import java.time.format.TextStyle; + public class WildfireBreastPresetList extends EntryListWidget { + public boolean active = true; + public boolean visible = true; + private class BreastPresetListEntry { public Identifier ident; + public String name; - public BreastPresetListEntry(String location) { + public BreastPresetListEntry(String name, String location) { + this.name = name; ident = new Identifier(WildfireGender.MODID, "textures/presets/" + location); } } private BreastPresetListEntry[] BREAST_PRESETS = new BreastPresetListEntry[] { - new BreastPresetListEntry("preset1.png"), - new BreastPresetListEntry("preset2.png"), - new BreastPresetListEntry("preset3.png"), - new BreastPresetListEntry("preset4.png"), + new BreastPresetListEntry("Normal", "preset1.png"), + new BreastPresetListEntry("Curved", "preset2.png"), + new BreastPresetListEntry("Small", "preset3.png"), + new BreastPresetListEntry("Large", "preset4.png"), }; private static final Identifier TXTR_SYNC = new Identifier(WildfireGender.MODID, "textures/sync.png"); private static final Identifier TXTR_UNKNOWN = new Identifier(WildfireGender.MODID, "textures/unknown.png"); @@ -37,12 +46,37 @@ public BreastPresetListEntry(String location) { private final WildfireBreastCustomizationScreen parent; public WildfireBreastPresetList(WildfireBreastCustomizationScreen parent, int listWidth, int top, int bottom) { - super(MinecraftClient.getInstance(), parent.width-4, parent.height, top-6, bottom, 54); + super(MinecraftClient.getInstance(), 156, parent.height, top, bottom, 32); + this.setRenderHeader(false, 0); + this.setRenderSelection(false); + this.setRenderBackground(false); + this.setRenderHorizontalShadows(false); this.parent = parent; this.listWidth = listWidth; this.refreshList(); } + @Override + protected void renderList(DrawContext context, int mouseX, int mouseY, float delta) { + int i = this.getRowLeft(); + int j = this.getRowWidth(); + int k = this.itemHeight; + int l = this.getEntryCount(); + + for(int m = 0; m < l; ++m) { + int n = this.getRowTop(m); + int o = this.getRowBottom(m); + if (o >= this.top && n <= this.bottom) { + this.renderEntry(context, mouseX, mouseY, delta, m, i, n, j, k); + } + } + + } + + @Override + protected int getRowTop(int index) { + return this.top - (int)this.getScrollAmount() + index * this.itemHeight + this.headerHeight; + } @Override protected int getScrollbarPositionX() { return parent.width / 2 + 181; @@ -56,7 +90,6 @@ public int getRowWidth() { public void refreshList() { this.clearEntries(); if(this.client.world == null || this.client.player == null) return; - ClientPlayNetworkHandler clientPlayNetworkHandler = this.client.player.networkHandler; for(int i = 0; i < BREAST_PRESETS.length; i++) { addEntry(new Entry(BREAST_PRESETS[i])); @@ -75,7 +108,7 @@ public class Entry extends EntryListWidget.Entry private Entry(final BreastPresetListEntry nInfo) { this.nInfo = nInfo; this.thumbnail = nInfo.ident; - btnOpenGUI = new WildfireButton(0, 0, 54, 54, Text.empty(), button -> { + btnOpenGUI = new WildfireButton(0, 0, getRowWidth() - 6, itemHeight, Text.empty(), button -> { /*GenderPlayer aPlr = WildfireGender.getPlayerById(nInfo.getProfile().getId()); if(aPlr == null) return; @@ -87,12 +120,16 @@ private Entry(final BreastPresetListEntry nInfo) { @Override public void render(DrawContext ctx, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float partialTicks) { + if(!visible) return; + TextRenderer font = MinecraftClient.getInstance().textRenderer; + //ctx.fill(x, y, x + entryWidth, y + entryHeight, 0x55005555); - RenderSystem.setShaderTexture(0, thumbnail); - ctx.drawTexture(thumbnail, x + 154, y + 2, 0, 0, 50, 50, 50,50); + ctx.drawTexture(thumbnail, x + 2, y + 2, 0, 0, 28, 28, 28,28); - this.btnOpenGUI.setX(x + 152); + ctx.drawText(font, Text.of(nInfo.name), x + 34, y + 4, 0xFFFFFFFF, false); + //ctx.drawText(font, Text.translatable("07/25/2023 1:19 AM").formatted(Formatting.ITALIC), x + 34, y + 20, 0xFF888888, false); + this.btnOpenGUI.setX(x); this.btnOpenGUI.setY(y); this.btnOpenGUI.render(ctx, mouseX, mouseY, partialTicks); @@ -100,10 +137,27 @@ public void render(DrawContext ctx, int index, int y, int x, int entryWidth, int @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - if(this.btnOpenGUI.mouseClicked(mouseX, mouseY, button)) { - return true; + if(active && visible) { + if (this.btnOpenGUI.mouseClicked(mouseX, mouseY, button)) { + return true; + } + return super.mouseClicked(mouseX, mouseY, button); } - return super.mouseClicked(mouseX, mouseY, button); + return false; } } + + + public int getLeft() { + return left; + } + public int getRight() { + return right; + } + public int getTop() { + return top; + } + public int getBottom() { + return bottom; + } } diff --git a/src/main/java/com/wildfire/gui/WildfireButton.java b/src/main/java/com/wildfire/gui/WildfireButton.java index 1b1ebaa9..d207c06c 100644 --- a/src/main/java/com/wildfire/gui/WildfireButton.java +++ b/src/main/java/com/wildfire/gui/WildfireButton.java @@ -36,6 +36,7 @@ public WildfireButton(int x, int y, int w, int h, Text text, ButtonWidget.PressA } public WildfireButton(int x, int y, int w, int h, Text text, ButtonWidget.PressAction onPress) { this(x, y, w, h, text, onPress, DEFAULT_NARRATION_SUPPLIER); + } public WildfireButton(int x, int y, int w, int h, Text text, ButtonWidget.PressAction onPress, Tooltip tooltip) { this(x, y, w, h, text, onPress, DEFAULT_NARRATION_SUPPLIER); diff --git a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java index e4310aa8..607efe44 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java @@ -77,16 +77,6 @@ public void init() { btnPresets.active = false; })); - //Preset Tab Below - PRESET_LIST = new WildfireBreastPresetList(this, 54, (j - 40), (j + 89)); - PRESET_LIST.setRenderBackground(false); - PRESET_LIST.setRenderHorizontalShadows(false); - //PLAYER_LIST.refreshList(); - this.addSelectableChild(this.PRESET_LIST); - - - - //Customization Tab Below this.addDrawableChild(this.breastSlider = new WildfireSlider(this.width / 2 + 30, j - 48, 158, 20, Configuration.BUST_SIZE, plr.getBustSize(), plr::updateBustSize, value -> Text.translatable("wildfire_gender.wardrobe.slider.breast_size", Math.round(value * 1.25f * 100)), onSave)); @@ -111,6 +101,15 @@ public void init() { } })); + + //Preset Tab Below + PRESET_LIST = new WildfireBreastPresetList(this, 156, (j - 48), (j + 77)); + PRESET_LIST.setLeftPos(this.width / 2 + 30); + + this.addSelectableChild(this.PRESET_LIST); + + this.currentTab = 0; + super.init(); } @@ -150,17 +149,17 @@ public void render(DrawContext ctx, int f1, int f2, float f3) { int x = this.width / 2; int y = this.height / 2; - ctx.fill(x + 28, y - 64 - 21, x + 190, y + 79, 0x55000000); - ctx.fill(x + 29, y - 63 - 21, x + 189, y - 50, 0x55000000); + ctx.fill(x + 28, y - 64 - 21, x + 190, y + 68, 0x55000000); + ctx.fill(x + 29, y - 63 - 21, x + 189, y - 60, 0x55000000); ctx.drawText(textRenderer, getTitle(), x + 32, y - 60 - 21, 0xFFFFFF, false); - super.render(ctx, f1, f2, f3); - if(currentTab == 0) { - - } + PRESET_LIST.visible = currentTab == 1; if(currentTab == 1) { + ctx.fill(PRESET_LIST.getLeft(), PRESET_LIST.getTop(), PRESET_LIST.getRight(), PRESET_LIST.getBottom(), 0x55000000); PRESET_LIST.render(ctx, f1, f2, f3); } + + super.render(ctx, f1, f2, f3); } @Override From 175038a4f64d3391fcb194c21076c1c67de4b447 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sat, 29 Jul 2023 01:18:53 -0400 Subject: [PATCH 018/238] Renamed WardrobeBrowserScreen's Title from "Customization Menu" to "Wildfire's Female Gender Mod" --- src/main/resources/assets/wildfire_gender/lang/en_us.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index 1522d137..2d8a493b 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -9,7 +9,7 @@ "wildfire_gender.player_list.state.loading": "Loading Data...", "wildfire_gender.player_list.state.synced": "Synced Player", - "wildfire_gender.wardrobe.title": "Customization Menu", + "wildfire_gender.wardrobe.title": "Wildfire's Female Gender Mod", "wildfire_gender.breast_customization.tab_customization": "Customization", "wildfire_gender.breast_customization.tab_presets": "Presets", "wildfire_gender.wardrobe.slider.breast_size": "Breast Size: %s%%", From 07908a0b5adce4b383593ff879e40d451b062669 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sat, 29 Jul 2023 01:19:22 -0400 Subject: [PATCH 019/238] Removed player list, Pressing 'G' now opens the menu directly to modify your own settings. --- .../com/wildfire/gui/WildfirePlayerList.java | 166 ------------------ .../gui/screen/WardrobeBrowserScreen.java | 9 +- .../gui/screen/WildfirePlayerListScreen.java | 161 ----------------- .../wildfire/main/WildfireEventHandler.java | 7 +- 4 files changed, 7 insertions(+), 336 deletions(-) delete mode 100644 src/main/java/com/wildfire/gui/WildfirePlayerList.java delete mode 100644 src/main/java/com/wildfire/gui/screen/WildfirePlayerListScreen.java diff --git a/src/main/java/com/wildfire/gui/WildfirePlayerList.java b/src/main/java/com/wildfire/gui/WildfirePlayerList.java deleted file mode 100644 index 10f2e50f..00000000 --- a/src/main/java/com/wildfire/gui/WildfirePlayerList.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -package com.wildfire.gui; - -import com.google.common.collect.ComparisonChain; -import com.google.common.collect.Ordering; -import com.mojang.blaze3d.systems.RenderSystem; -import com.wildfire.gui.screen.WildfirePlayerListScreen; -import com.wildfire.gui.screen.WardrobeBrowserScreen; -import com.wildfire.main.WildfireGender; -import com.wildfire.main.GenderPlayer; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.PlayerSkinDrawer; -import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; -import net.minecraft.client.gui.widget.EntryListWidget; -import net.minecraft.client.network.ClientPlayNetworkHandler; -import net.minecraft.client.network.PlayerListEntry; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.scoreboard.Team; -import net.minecraft.text.Text; -import net.minecraft.util.Formatting; -import net.minecraft.util.Identifier; -import net.minecraft.world.GameMode; - -import java.util.Comparator; - -public class WildfirePlayerList extends EntryListWidget { - private static final Ordering ENTRY_ORDERING = Ordering.from(new WildfirePlayerList.EntryOrderComparator()); - - private static final Identifier TXTR_SYNC = new Identifier(WildfireGender.MODID, "textures/sync.png"); - private static final Identifier TXTR_UNKNOWN = new Identifier(WildfireGender.MODID, "textures/unknown.png"); - private static final Identifier TXTR_CACHED = new Identifier(WildfireGender.MODID, "textures/cached.png"); - private final int listWidth; - private final WildfirePlayerListScreen parent; - - public WildfirePlayerList(WildfirePlayerListScreen parent, int listWidth, int top, int bottom) { - super(MinecraftClient.getInstance(), parent.width-4, parent.height, top-6, bottom, 20); - this.parent = parent; - this.listWidth = listWidth; - this.refreshList(); - } - - @Override - protected int getScrollbarPositionX() { - return parent.width / 2 + 53; - } - - @Override - public int getRowWidth() { - return this.listWidth; - } - - public void refreshList() { - this.clearEntries(); - if(this.client.world == null || this.client.player == null) return; - ClientPlayNetworkHandler clientPlayNetworkHandler = this.client.player.networkHandler; - - for(PlayerListEntry playerList : ENTRY_ORDERING.sortedCopy(clientPlayNetworkHandler.getPlayerList())) { - PlayerEntity player = this.client.world.getPlayerByUuid(playerList.getProfile().getId()); - if(player != null) addEntry(new Entry(playerList)); - } - } - - @Override - public void appendNarrations(NarrationMessageBuilder builder) {} - - @Environment(EnvType.CLIENT) - public class Entry extends EntryListWidget.Entry { - private final String name; - public final PlayerListEntry nInfo; - private final WildfireButton btnOpenGUI; - - private Entry(final PlayerListEntry nInfo) { - this.nInfo = nInfo; - this.name = nInfo.getProfile().getName(); - btnOpenGUI = new WildfireButton(0, 0, 112, 20, Text.empty(), button -> { - GenderPlayer aPlr = WildfireGender.getPlayerById(nInfo.getProfile().getId()); - if(aPlr == null) return; - - try { - MinecraftClient.getInstance().setScreen(new WardrobeBrowserScreen(parent, nInfo.getProfile().getId())); - } catch(Exception ignored) {} - }); - GenderPlayer aPlr = WildfireGender.getPlayerById(nInfo.getProfile().getId()); - if(aPlr != null) { - btnOpenGUI.active = !aPlr.lockSettings; - } - } - - @Override - public void render(DrawContext ctx, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float partialTicks) { - TextRenderer font = MinecraftClient.getInstance().textRenderer; - GenderPlayer aPlr = WildfireGender.getPlayerById(nInfo.getProfile().getId()); - - RenderSystem.setShaderTexture(0, nInfo.getSkinTexture()); - PlayerSkinDrawer.draw(ctx, nInfo.getSkinTexture(), x+2, y+2, 16); - ctx.drawText(font, name, x + 23, y + 2, 0xFFFFFF, false); - - if(aPlr != null) { - btnOpenGUI.active = !aPlr.lockSettings; - - ctx.drawText(font, aPlr.getGender().getDisplayName(), x + 23, y + 11, 0xFFFFFF, false); - if (aPlr.getSyncStatus() == GenderPlayer.SyncStatus.SYNCED) { - ctx.drawTexture(TXTR_SYNC, x + 98, y + 11, 12, 8, 0, 0, 12, 8, 12, 8); - if (mouseX > x + 98 - 2 && mouseY > y + 11 - 2 && mouseX < y + 98 + 12 + 2 && mouseY < y + 20) { - parent.setTooltip(Text.translatable("wildfire_gender.player_list.state.synced")); - } - - } else if (aPlr.getSyncStatus() == GenderPlayer.SyncStatus.UNKNOWN) { - ctx.drawTexture(TXTR_UNKNOWN, x + 98, y + 11, 12, 8, 0, 0, 12, 8, 12, 8); - } - } else { - btnOpenGUI.active = false; - ctx.drawText(font, Text.translatable("wildfire_gender.label.too_far").formatted(Formatting.RED), x + 23, y + 11, 0xFFFFFF, false); - } - this.btnOpenGUI.setX(x); - this.btnOpenGUI.setY(y); - this.btnOpenGUI.render(ctx, mouseX, mouseY, partialTicks); - - if(this.btnOpenGUI.isHovered()) { - WildfirePlayerListScreen.HOVER_PLAYER = aPlr; - } - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if(this.btnOpenGUI.mouseClicked(mouseX, mouseY, button)) { - return true; - } - return super.mouseClicked(mouseX, mouseY, button); - } - } - - @Environment(EnvType.CLIENT) - static class EntryOrderComparator implements Comparator { - private EntryOrderComparator() {} - - public int compare(PlayerListEntry playerListEntry, PlayerListEntry playerListEntry2) { - Team team = playerListEntry.getScoreboardTeam(); - Team team2 = playerListEntry2.getScoreboardTeam(); - return ComparisonChain.start().compareTrueFirst(playerListEntry.getGameMode() != GameMode.SPECTATOR, playerListEntry2.getGameMode() != GameMode.SPECTATOR).compare(team != null ? team.getName() : "", team2 != null ? team2.getName() : "").compare(playerListEntry.getProfile().getName(), playerListEntry2.getProfile().getName(), String::compareToIgnoreCase).result(); - } - } - -} diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index fdbbbe7d..68cead02 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -108,14 +108,9 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { PlayerEntity ent = minecraft.world.getPlayerByUuid(this.playerUUID); if(ent != null) { drawEntityOnScreen(xP, yP, 45, (xP - mouseX), (yP - 76 - mouseY), minecraft.world.getPlayerByUuid(this.playerUUID)); - } else { - //player left, fallback - minecraft.setScreen(new WildfirePlayerListScreen(minecraft)); } - } catch(Exception e) { - //error, fallback - minecraft.setScreen(new WildfirePlayerListScreen(minecraft)); - } + } catch(Exception e) {} + super.render(ctx, mouseX, mouseY, delta); } diff --git a/src/main/java/com/wildfire/gui/screen/WildfirePlayerListScreen.java b/src/main/java/com/wildfire/gui/screen/WildfirePlayerListScreen.java deleted file mode 100644 index 27e3cbb0..00000000 --- a/src/main/java/com/wildfire/gui/screen/WildfirePlayerListScreen.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -package com.wildfire.gui.screen; - -import com.mojang.blaze3d.systems.RenderSystem; -import com.wildfire.gui.WildfireButton; -import com.wildfire.gui.WildfirePlayerList; -import com.wildfire.main.GenderPlayer; -import com.wildfire.main.GenderPlayer.Gender; -import com.wildfire.main.WildfireGender; - -import com.wildfire.main.WildfireHelper; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.network.ClientPlayNetworkHandler; -import net.minecraft.client.render.GameRenderer; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.text.Text; -import net.minecraft.util.Formatting; -import net.minecraft.util.Identifier; - -import javax.annotation.Nullable; -import java.util.Calendar; -import java.util.UUID; - -public class WildfirePlayerListScreen extends Screen { - private static final UUID CREATOR_UUID = UUID.fromString("33c937ae-6bfc-423e-a38e-3a613e7c1256"); - private static final Identifier TXTR_BACKGROUND = new Identifier(WildfireGender.MODID, "textures/gui/player_list.png"); - private static final Identifier TXTR_RIBBON = new Identifier(WildfireGender.MODID, "textures/bc_ribbon.png"); - - private @Nullable Text tooltip; - public static GenderPlayer HOVER_PLAYER; - private WildfirePlayerList PLAYER_LIST; - private final MinecraftClient client; - - public WildfirePlayerListScreen(MinecraftClient mc) { - super(Text.translatable("wildfire_gender.player_list.title")); - this.client = mc; - } - - @Override - public void removed() { - super.removed(); - } - - @Override - public boolean shouldPause() { - return false; - } - - @Override - public void init() { - int y = this.height / 2 - 20; - - this.addDrawableChild(new WildfireButton(this.width / 2 + 53, y - 74, 9, 9, Text.translatable("wildfire_gender.label.exit"), button -> MinecraftClient.getInstance().setScreen(null))); - - PLAYER_LIST = new WildfirePlayerList(this, 118, (y - 61), (y + 71)); - PLAYER_LIST.setRenderBackground(false); - PLAYER_LIST.setRenderHorizontalShadows(false); - this.addSelectableChild(this.PLAYER_LIST); - - super.init(); - } - - @SuppressWarnings("DataFlowIssue") - @Override - public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { - TextRenderer font = client.textRenderer; - HOVER_PLAYER = null; - PLAYER_LIST.refreshList(); - - super.renderBackground(ctx); - RenderSystem.setShader(GameRenderer::getPositionTexProgram); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - int i = (this.width - 132) / 2; - int j = (this.height - 156) / 2 - 20; - ctx.drawTexture(TXTR_BACKGROUND, i, j, 0, 0, 192, 174); - - int x = (this.width / 2); - int y = (this.height / 2) - 20; - - super.render(ctx, mouseX, mouseY, delta); - - double scale = client.getWindow().getScaleFactor(); - int left = x - 59; - int bottom = y - 32; - int width = 118; - int height = 134; - RenderSystem.enableScissor((int)(left * scale), (int) (bottom * scale), - (int)(width * scale), (int) (height * scale)); - - PLAYER_LIST.render(ctx, mouseX, mouseY, delta); - RenderSystem.disableScissor(); - - if(HOVER_PLAYER != null) { - int dialogX = x + 75; - int dialogY = y - 73; - PlayerEntity pEntity = client.world.getPlayerByUuid(HOVER_PLAYER.uuid); - if(pEntity != null) { - ctx.drawTextWithShadow(font, pEntity.getDisplayName().copy().formatted(Formatting.UNDERLINE), dialogX, dialogY - 2, 0xFFFFFF); - } - - Gender gender = HOVER_PLAYER.getGender(); - ctx.drawTextWithShadow(font, Text.translatable("wildfire_gender.label.gender").append(" ").append(gender.getDisplayName()), dialogX, dialogY + 10, 0xBBBBBB); - if (gender.canHaveBreasts()) { - ctx.drawTextWithShadow(font, Text.translatable("wildfire_gender.wardrobe.slider.breast_size", Math.round(HOVER_PLAYER.getBustSize() * 100)), dialogX, dialogY + 20, 0xBBBBBB); - ctx.drawTextWithShadow(font, Text.translatable("wildfire_gender.char_settings.physics", Text.translatable(HOVER_PLAYER.hasBreastPhysics() ? "wildfire_gender.label.enabled" : "wildfire_gender.label.disabled")), dialogX, dialogY + 40, 0xBBBBBB); - ctx.drawTextWithShadow(font, Text.translatable("wildfire_gender.player_list.bounce_multiplier", HOVER_PLAYER.getBounceMultiplier()), dialogX + 6, dialogY + 50, 0xBBBBBB); - ctx.drawTextWithShadow(font, Text.translatable("wildfire_gender.player_list.breast_momentum", Math.round(HOVER_PLAYER.getFloppiness() * 100)), dialogX + 6, dialogY + 60, 0xBBBBBB); - - ctx.drawTextWithShadow(font, Text.translatable("wildfire_gender.player_list.female_sounds", Text.translatable(HOVER_PLAYER.hasHurtSounds() ? "wildfire_gender.label.enabled" : "wildfire_gender.label.disabled")), dialogX, dialogY + 80, 0xBBBBBB); - } - if(pEntity != null) { - WardrobeBrowserScreen.drawEntityOnScreen(x - 110, y + 45, 45, (x - mouseX - 110), (y - 26 - mouseY), pEntity); - } - } - - ctx.drawText(font, Text.translatable("wildfire_gender.player_list.title"), x - 60, y - 73, 4473924, false); - - ClientPlayNetworkHandler clientPlayNetworkHandler = client.player.networkHandler; - boolean withCreator = clientPlayNetworkHandler.getPlayerList().stream() - .anyMatch((player) -> player.getProfile().getId().equals(CREATOR_UUID)); - if(withCreator) { - WildfireHelper.drawCenteredText(ctx, this.textRenderer, Text.translatable("wildfire_gender.label.with_creator"), this.width / 2, y + 89, 0xFF00FF); - } - - //Breast Cancer Awareness Month Notification - if(Calendar.getInstance().get(Calendar.MONTH) == Calendar.OCTOBER) { - ctx.fill(x - 159, y + 106, x + 159, y + 136, 0x55000000); - ctx.drawTextWithShadow(font, Text.translatable("wildfire_gender.cancer_awareness.title").formatted(Formatting.BOLD, Formatting.ITALIC), this.width / 2 - 148, y + 117, 0xFFFFFF); - RenderSystem.setShader(GameRenderer::getPositionTexProgram); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - ctx.drawTexture(TXTR_RIBBON, x + 130, y + 109, 26, 26, 0, 0, 20, 20, 20, 20); - } - if(tooltip != null) { - ctx.drawTooltip(textRenderer, tooltip, mouseX, mouseY); - } - } - - public void setTooltip(@Nullable Text tooltip) { - this.tooltip = tooltip; - } -} diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index e7a03a6e..bcf55b40 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -18,7 +18,7 @@ package com.wildfire.main; -import com.wildfire.gui.screen.WildfirePlayerListScreen; +import com.wildfire.gui.screen.WardrobeBrowserScreen; import com.wildfire.main.networking.PacketSendGenderInfo; import com.wildfire.main.networking.PacketSync; @@ -101,7 +101,10 @@ public static void registerClientEvents() { } while (toggleEditGUI.wasPressed()) { - client.setScreen(new WildfirePlayerListScreen(client)); + //client.setScreen(new WildfirePlayerListScreen(client)); //old screen + try { + MinecraftClient.getInstance().setScreen(new WardrobeBrowserScreen(null, MinecraftClient.getInstance().player.getUuid())); + } catch(Exception ignored) {} } }); From 340dd3d9fc21a586516d2be703e5ae5cff5c9cbb Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sat, 29 Jul 2023 01:20:09 -0400 Subject: [PATCH 020/238] Removed player list, Pressing 'G' now opens the menu directly to modify your own settings. --- .../gui/screen/WildfireBreastCustomizationScreen.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java index 607efe44..4248647e 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java @@ -130,13 +130,7 @@ public void render(DrawContext ctx, int f1, int f2, float f3) { PlayerEntity ent = minecraft.world.getPlayerByUuid(this.playerUUID); if(ent != null) { WardrobeBrowserScreen.drawEntityOnScreen(xP, yP, 200, -20, -20, ent); - } else { - //player left, fallback - minecraft.setScreen(new WildfirePlayerListScreen(minecraft)); } - } catch(Exception e) { - //error, fallback - minecraft.setScreen(new WildfirePlayerListScreen(minecraft)); } boolean canHaveBreasts = plr.getGender().canHaveBreasts(); From 9dd947c07cc00aab9d9c9ffce9d9b695917ea1f9 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sat, 29 Jul 2023 01:20:47 -0400 Subject: [PATCH 021/238] I'm too quick to push changes, oops forgot something. --- .../wildfire/gui/screen/WildfireBreastCustomizationScreen.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java index 4248647e..91bc5c6a 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java @@ -131,7 +131,7 @@ public void render(DrawContext ctx, int f1, int f2, float f3) { if(ent != null) { WardrobeBrowserScreen.drawEntityOnScreen(xP, yP, 200, -20, -20, ent); } - } + } catch(Exception e) {} boolean canHaveBreasts = plr.getGender().canHaveBreasts(); breastSlider.visible = canHaveBreasts && currentTab == 0; From 66705c101f8088392820bf97127abd6139494387 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sun, 30 Jul 2023 18:24:24 -0400 Subject: [PATCH 022/238] More Breast Preset work --- .../gui/WildfireBreastPresetList.java | 31 +++++++++++++++++- .../gui/screen/WardrobeBrowserScreen.java | 28 ++++++++++++---- .../textures/gui/wardrobe_bg3.png | Bin 0 -> 717 bytes 3 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/wardrobe_bg3.png diff --git a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java index 5e46b9ff..248163a0 100644 --- a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java +++ b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java @@ -1,10 +1,14 @@ package com.wildfire.gui; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.mojang.blaze3d.systems.RenderSystem; import com.wildfire.gui.screen.WildfireBreastCustomizationScreen; import com.wildfire.main.WildfireGender; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; @@ -16,7 +20,11 @@ import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; +import java.io.File; +import java.io.FileReader; +import java.nio.file.Path; import java.time.format.TextStyle; +import java.util.Map; public class WildfireBreastPresetList extends EntryListWidget { @@ -27,12 +35,33 @@ private class BreastPresetListEntry { public Identifier ident; public String name; + private JsonObject data; public BreastPresetListEntry(String name, String location) { this.name = name; - ident = new Identifier(WildfireGender.MODID, "textures/presets/" + location); + this.ident = new Identifier(WildfireGender.MODID, "textures/presets/" + location); + load(); + } + + private void load() { + Path saveDir = FabricLoader.getInstance().getConfigDir(); + System.out.println("SAVE DIR: " + saveDir.toString()); + + data = new JsonObject(); + File CFG_FILE = saveDir.resolve("WildfireGender/presets").resolve(this.name + ".json").toFile(); + + try (FileReader configurationFile = new FileReader(CFG_FILE)) { + JsonObject obj = new Gson().fromJson(configurationFile, JsonObject.class); + for (Map.Entry entry : obj.entrySet()) { + String key = entry.getKey(); + data.add(key, entry.getValue()); + } + } catch(Exception e) { + e.printStackTrace(); + } } } + private BreastPresetListEntry[] BREAST_PRESETS = new BreastPresetListEntry[] { new BreastPresetListEntry("Normal", "preset1.png"), new BreastPresetListEntry("Curved", "preset2.png"), diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index 68cead02..4ebed34d 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -40,9 +40,12 @@ import org.joml.Quaternionf; public class WardrobeBrowserScreen extends BaseWildfireScreen { - private static final Identifier BACKGROUND = new Identifier(WildfireGender.MODID, "textures/gui/wardrobe_bg2.png"); + private static final Identifier BACKGROUND_FEMALE = new Identifier(WildfireGender.MODID, "textures/gui/wardrobe_bg2.png"); + private static final Identifier BACKGROUND = new Identifier(WildfireGender.MODID, "textures/gui/wardrobe_bg3.png"); public static float modelRotation = 0.5F; + private WildfireButton btnAppearanceSettings; + public WardrobeBrowserScreen(Screen parent, UUID uuid) { super(Text.translatable("wildfire_gender.wardrobe.title"), parent, uuid); } @@ -61,14 +64,21 @@ public void init() { if (plr.updateGender(gender)) { button.setMessage(getGenderLabel(gender)); GenderPlayer.saveGenderInfo(plr); + + //re-render menu (re-open it) + MinecraftClient.getInstance().setScreen(new WardrobeBrowserScreen(null, playerUUID)); } })); - this.addDrawableChild(new WildfireButton(this.width / 2 - 42, j - 32, 158, 20, Text.translatable("wildfire_gender.appearance_settings.title").append("..."), - button -> MinecraftClient.getInstance().setScreen(new WildfireBreastCustomizationScreen(WardrobeBrowserScreen.this, this.playerUUID)))); - - this.addDrawableChild(new WildfireButton(this.width / 2 - 42, j - 12, 158, 20, Text.translatable("wildfire_gender.char_settings.title").append("..."), - button -> MinecraftClient.getInstance().setScreen(new WildfireCharacterSettingsScreen(WardrobeBrowserScreen.this, this.playerUUID)))); + if(plr.getGender() != Gender.MALE) { + this.addDrawableChild(btnAppearanceSettings = new WildfireButton(this.width / 2 - 42, j - 32, 158, 20, Text.translatable("wildfire_gender.appearance_settings.title").append("..."), + button -> MinecraftClient.getInstance().setScreen(new WildfireBreastCustomizationScreen(WardrobeBrowserScreen.this, this.playerUUID)))); + this.addDrawableChild(new WildfireButton(this.width / 2 - 42, j - 12, 158, 20, Text.translatable("wildfire_gender.char_settings.title").append("..."), + button -> MinecraftClient.getInstance().setScreen(new WildfireCharacterSettingsScreen(WardrobeBrowserScreen.this, this.playerUUID)))); + } else { + this.addDrawableChild(new WildfireButton(this.width / 2 - 42, j - 32, 158, 20, Text.translatable("wildfire_gender.char_settings.title").append("..."), + button -> MinecraftClient.getInstance().setScreen(new WildfireCharacterSettingsScreen(WardrobeBrowserScreen.this, this.playerUUID)))); + } this.addDrawableChild(new WildfireButton(this.width / 2 + 111, j - 63, 9, 9, Text.translatable("wildfire_gender.label.exit"), button -> MinecraftClient.getInstance().setScreen(parent))); @@ -92,7 +102,11 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { RenderSystem.setShader(GameRenderer::getPositionTexProgram); RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - ctx.drawTexture(BACKGROUND, (this.width - 248) / 2, (this.height - 134) / 2, 0, 0, 248, 156); + if(plr.getGender() != Gender.MALE) { + ctx.drawTexture(BACKGROUND_FEMALE, (this.width - 248) / 2, (this.height - 134) / 2, 0, 0, 248, 156); + } else { + ctx.drawTexture(BACKGROUND, (this.width - 248) / 2, (this.height - 134) / 2, 0, 0, 248, 156); + } if(plr == null) return; diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/wardrobe_bg3.png b/src/main/resources/assets/wildfire_gender/textures/gui/wardrobe_bg3.png new file mode 100644 index 0000000000000000000000000000000000000000..69975b7337bffaf97ea11a6942c81ff1fc990371 GIT binary patch literal 717 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911MRQ8&P5D>38$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&eBzI1?3h%1o(|NsB7W5+^6L(R?2!@|O{v$MIlK&EnZZq)`-oFzei!9X=& zz!2Nm_P_eJqnsIE zu9>||OVhJ2ir5wZsFC@yjsL!9<(F+@YyJw%==r`s=01~u!|JuS->Pn4`mk;V0~1HX zZ*~C(21b?xyBGaqs(Ev{)^_f7^|wDe{xf{AElloLKk$5JwOtZO!Cz($g$AGw5b=Ob znj6AluzNL!6>I{CDEJRm08+K%;4zTur)!R{w3bU~0J0h0y=U1W%e_IS{vRL91L1Rb zU;k@o_;7sY<8R*W3g>&ZEx-hc9thkeq4C64Q^9KSQ29aSfzYziB9JuMs za$xTK(7fD;23c+gz7Ma}BbiOkUjB7vx0%ENn~!&@O|Se7Z+Ln?;rc#)xupl?%KtMZ zY-K%=#ocgKjA6AdLs&G!nr#dbxr`fb4OIy>_8HIhZC|CWy)QiD05BObc)I$ztaD0e F0stCl_>lkr literal 0 HcmV?d00001 From f5de384858cfc2cd3504223cbddd13244bafda11 Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 18 Aug 2023 21:43:02 -0600 Subject: [PATCH 023/238] Move initial join sync to entity tracking This fixes the initial join sync on dedicated servers, as well as various issues around the sync being sent too early (which is most prominent on Quilt), which results in the receiving client crashing This also splits event lambdas into their own functions for readability, and merges PacketSync and PacketGenderInfo to a single WildfireSync class, using PacketGenderInfo (renamed to PacketSync) as the only packet class sent over the network. --- .../java/com/wildfire/main/GenderPlayer.java | 5 +- .../wildfire/main/WildfireEventHandler.java | 120 +++++++----------- .../wildfire/main/WildfireGenderServer.java | 32 +++-- .../main/networking/PacketSendGenderInfo.java | 67 ---------- .../wildfire/main/networking/PacketSync.java | 88 ------------- ...{PacketGenderInfo.java => SyncPacket.java} | 18 ++- .../main/networking/WildfireSync.java | 108 ++++++++++++++++ 7 files changed, 188 insertions(+), 250 deletions(-) delete mode 100644 src/main/java/com/wildfire/main/networking/PacketSendGenderInfo.java delete mode 100644 src/main/java/com/wildfire/main/networking/PacketSync.java rename src/main/java/com/wildfire/main/networking/{PacketGenderInfo.java => SyncPacket.java} (89%) create mode 100644 src/main/java/com/wildfire/main/networking/WildfireSync.java diff --git a/src/main/java/com/wildfire/main/GenderPlayer.java b/src/main/java/com/wildfire/main/GenderPlayer.java index d344b694..4d3cae6c 100644 --- a/src/main/java/com/wildfire/main/GenderPlayer.java +++ b/src/main/java/com/wildfire/main/GenderPlayer.java @@ -44,8 +44,6 @@ public class GenderPlayer { private float bounceMultiplier = Configuration.BOUNCE_MULTIPLIER.getDefault(); private float floppyMultiplier = Configuration.FLOPPY_MULTIPLIER.getDefault(); - public boolean lockSettings = false; - public SyncStatus syncStatus = SyncStatus.UNKNOWN; private boolean showBreastsInArmor = Configuration.SHOW_IN_ARMOR.getDefault(); private boolean armorPhysOverride = Configuration.ARMOR_PHYSICS_OVERRIDE.getDefault(); @@ -191,7 +189,6 @@ public static JsonObject toJsonObject(GenderPlayer plr) { public static GenderPlayer loadCachedPlayer(UUID uuid, boolean markForSync) { GenderPlayer plr = WildfireGender.getPlayerById(uuid); if (plr != null) { - plr.lockSettings = false; plr.syncStatus = SyncStatus.CACHED; Configuration config = plr.getConfig(); plr.updateGender(config.get(Configuration.GENDER)); @@ -218,7 +215,7 @@ public static GenderPlayer loadCachedPlayer(UUID uuid, boolean markForSync) { } return null; } - + public static void saveGenderInfo(GenderPlayer plr) { Configuration config = plr.getConfig(); config.set(Configuration.USERNAME, plr.uuid); diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index bcf55b40..138bc47d 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -19,98 +19,64 @@ package com.wildfire.main; import com.wildfire.gui.screen.WardrobeBrowserScreen; -import com.wildfire.main.networking.PacketSendGenderInfo; -import com.wildfire.main.networking.PacketSync; - -import java.util.UUID; - +import com.wildfire.main.networking.WildfireSync; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; -import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.client.option.KeyBinding; import net.minecraft.client.util.InputUtil; -import net.minecraft.util.Identifier; +import net.minecraft.entity.Entity; +import net.minecraft.world.World; import org.lwjgl.glfw.GLFW; -public class WildfireEventHandler { - - public static final KeyBinding toggleEditGUI = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.wildfire_gender.gender_menu", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_G, "category.wildfire_gender.generic")); +import java.util.UUID; - private static int timer = 0; +@Environment(EnvType.CLIENT) +public class WildfireEventHandler { + public static final KeyBinding toggleEditGUI = KeyBindingHelper.registerKeyBinding( + new KeyBinding("key.wildfire_gender.gender_menu", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_G, "category.wildfire_gender.generic")); + private static long timer = 0; public static void registerClientEvents() { + ClientEntityEvents.ENTITY_LOAD.register(WildfireEventHandler::onEntityLoad); + ClientTickEvents.END_CLIENT_TICK.register(WildfireEventHandler::onClientTick); + ClientPlayNetworking.registerGlobalReceiver(WildfireSync.SYNC_IDENTIFIER, WildfireSync::handle); + } - ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { - if (!handler.getPlayer().getWorld().isClient()) { - //Send all other players to the player who joined. Note: We don't send the player to - // other players as that will happen once the player finishes sending themselves to the server - PacketSync.sendTo(handler.getPlayer()); - } - }); - - ClientEntityEvents.ENTITY_LOAD.register((entity, world) -> { - if(!world.isClient) return; - if(entity instanceof AbstractClientPlayerEntity plr) { - UUID uuid = plr.getUuid(); - GenderPlayer aPlr = WildfireGender.getPlayerById(plr.getUuid()); - if (aPlr == null) { - aPlr = new GenderPlayer(uuid); - WildfireGender.CLOTHING_PLAYERS.put(uuid, aPlr); - WildfireGender.loadGenderInfoAsync(uuid, uuid.equals(MinecraftClient.getInstance().player.getUuid())); - return; - } - } - /*if(!world.isClient) return; - - if(entity instanceof AbstractClientPlayerEntity plr) { - UUID uuid = plr.getUuid(); - GenderPlayer aPlr = WildfireGender.getPlayerById(plr.getUuid()); - if(aPlr == null) { - aPlr = new GenderPlayer(uuid); - WildfireGender.CLOTHING_PLAYERS.put(uuid, aPlr); - WildfireGender.loadGenderInfoAsync(uuid, uuid.equals(MinecraftClient.getInstance().player.getUuid())); - return; - } - }*/ - }); - - ClientTickEvents.END_CLIENT_TICK.register(client -> { - if(client.world == null) WildfireGender.CLOTHING_PLAYERS.clear(); - - boolean isVanillaServer = !ClientPlayNetworking.canSend(new Identifier(WildfireGender.MODID, "send_gender_info")); - - - if(!isVanillaServer) { - //20 ticks per second / 5 = 4 times per second - - timer++; - if (timer >= 5) { - try { - GenderPlayer aPlr = WildfireGender.getPlayerById(MinecraftClient.getInstance().player.getUuid()); - if(aPlr == null /*|| !aPlr.needsSync*/) return; - PacketSendGenderInfo.send(aPlr); - } catch (Exception e) { - //e.printStackTrace(); - } - timer = 0; - } - } - - while (toggleEditGUI.wasPressed()) { - //client.setScreen(new WildfirePlayerListScreen(client)); //old screen - try { - MinecraftClient.getInstance().setScreen(new WardrobeBrowserScreen(null, MinecraftClient.getInstance().player.getUuid())); - } catch(Exception ignored) {} + private static void onEntityLoad(Entity entity, World world) { + if(!world.isClient() || MinecraftClient.getInstance().player == null) return; + if(entity instanceof AbstractClientPlayerEntity plr) { + UUID uuid = plr.getUuid(); + GenderPlayer aPlr = WildfireGender.getPlayerById(plr.getUuid()); + if(aPlr == null) { + aPlr = new GenderPlayer(uuid); + WildfireGender.CLOTHING_PLAYERS.put(uuid, aPlr); + WildfireGender.loadGenderInfoAsync(uuid, uuid.equals(MinecraftClient.getInstance().player.getUuid())); } - }); + } + } - ClientPlayNetworking.registerGlobalReceiver(new Identifier(WildfireGender.MODID, "sync"), - (client, handler, buf, responseSender) -> { - PacketSync.handle(client, handler, buf, responseSender); - }); + private static void onClientTick(MinecraftClient client) { + if(client.world == null || client.player == null) { + WildfireGender.CLOTHING_PLAYERS.clear(); + return; + } + + // Only attempt to sync if the server will accept the packet, and only once every 5 ticks, or around 4 times a second + if(ClientPlayNetworking.canSend(WildfireSync.SEND_GENDER_IDENTIFIER) && timer++ % 5 == 0) { + GenderPlayer aPlr = WildfireGender.getPlayerById(client.player.getUuid()); + // sendToServer will only actually send a packet if any changes have been made that need to be synced, + // or if we haven't synced before. + if(aPlr != null) WildfireSync.sendToServer(aPlr); + } + + if(toggleEditGUI.wasPressed() && client.currentScreen == null) { + client.setScreen(new WardrobeBrowserScreen(null, client.player.getUuid())); + } } } diff --git a/src/main/java/com/wildfire/main/WildfireGenderServer.java b/src/main/java/com/wildfire/main/WildfireGenderServer.java index 5c0ab908..2244634e 100644 --- a/src/main/java/com/wildfire/main/WildfireGenderServer.java +++ b/src/main/java/com/wildfire/main/WildfireGenderServer.java @@ -17,22 +17,34 @@ */ package com.wildfire.main; -import com.wildfire.main.networking.PacketSendGenderInfo; + +import com.wildfire.main.networking.WildfireSync; import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.networking.v1.EntityTrackingEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; -import net.minecraft.util.Identifier; - +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.network.ServerPlayerEntity; public class WildfireGenderServer implements ModInitializer { - - @Override public void onInitialize() { // while this class is named 'Server', this is actually a common code path, // so we can safely register here for both sides. WildfireSounds.register(); - ServerPlayNetworking.registerGlobalReceiver(new Identifier(WildfireGender.MODID, "send_gender_info"), - (server, playerEntity, handler, buf, responseSender) -> { - PacketSendGenderInfo.handle(server, playerEntity, handler, buf, responseSender); - }); + ServerPlayNetworking.registerGlobalReceiver(WildfireSync.SEND_GENDER_IDENTIFIER, WildfireSync::handle); + EntityTrackingEvents.START_TRACKING.register(this::onBeginTracking); + } + + private void onBeginTracking(Entity tracked, ServerPlayerEntity syncTo) { + if(tracked instanceof PlayerEntity toSync) { + GenderPlayer genderToSync = WildfireGender.getPlayerById(toSync.getUuid()); + if(genderToSync == null) return; + // Note that we intentionally don't check if we've previously synced a player with this code path; + // because we use entity tracking to sync, it's entirely possible that one player would leave the + // tracking distance of another, change their settings, and then re-enter their tracking distance; + // we wouldn't sync while they're out of tracking distance, and as such, their settings would be out + // of sync until they relog. + WildfireSync.sendToClient(syncTo, genderToSync); + } } -} \ No newline at end of file +} diff --git a/src/main/java/com/wildfire/main/networking/PacketSendGenderInfo.java b/src/main/java/com/wildfire/main/networking/PacketSendGenderInfo.java deleted file mode 100644 index 1e66a8ac..00000000 --- a/src/main/java/com/wildfire/main/networking/PacketSendGenderInfo.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -package com.wildfire.main.networking; - -import com.wildfire.main.GenderPlayer; -import com.wildfire.main.WildfireGender; -import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; -import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; -import net.fabricmc.fabric.api.networking.v1.PacketSender; -import net.minecraft.network.PacketByteBuf; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.network.ServerPlayNetworkHandler; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.util.Identifier; - -public class PacketSendGenderInfo extends PacketGenderInfo { - - public PacketSendGenderInfo(GenderPlayer plr) { - super(plr); - } - - public PacketSendGenderInfo(PacketByteBuf buffer) { - super(buffer); - } - - public static void handle(MinecraftServer server, ServerPlayerEntity player, ServerPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender) { - PacketSendGenderInfo packet = new PacketSendGenderInfo(buf); - - if (player == null || !player.getUuid().equals(packet.uuid)) { - //Validate the uuid matches the player who sent it - return; - } - GenderPlayer plr = WildfireGender.getOrAddPlayerById(packet.uuid); - packet.updatePlayerFromPacket(plr); - //System.out.println("Received data from player " + plr.uuid); - //Sync changes to other online players - PacketSync.sendToOthers(player, plr); - } - - // Send Packet - - public static void send(GenderPlayer plr) { - if(plr == null || !plr.needsSync) return; - PacketSendGenderInfo packet = new PacketSendGenderInfo(plr); - PacketByteBuf buffer = PacketByteBufs.create(); - packet.encode(buffer); - ClientPlayNetworking.send(new Identifier(WildfireGender.MODID, "send_gender_info"), buffer); - //WildfireGender.NETWORK.sendToServer(new PacketSendGenderInfo(plr)); - plr.needsSync = false; - } -} diff --git a/src/main/java/com/wildfire/main/networking/PacketSync.java b/src/main/java/com/wildfire/main/networking/PacketSync.java deleted file mode 100644 index 2c13962a..00000000 --- a/src/main/java/com/wildfire/main/networking/PacketSync.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -package com.wildfire.main.networking; - -import com.wildfire.main.GenderPlayer; -import com.wildfire.main.WildfireGender; -import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; -import net.fabricmc.fabric.api.networking.v1.PacketSender; -import net.fabricmc.fabric.api.networking.v1.PlayerLookup; -import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.ClientPlayNetworkHandler; -import net.minecraft.network.PacketByteBuf; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.util.Identifier; - -import java.util.Map; -import java.util.UUID; -import java.util.function.Supplier; - -public class PacketSync extends PacketGenderInfo { - - public PacketSync(GenderPlayer plr) { - super(plr); - } - - public PacketSync(PacketByteBuf buffer) { - super(buffer); - } - - public static void handle(MinecraftClient client, ClientPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender) { - PacketSync packet = new PacketSync(buf); - - if (!packet.uuid.equals(MinecraftClient.getInstance().player.getUuid())) { - GenderPlayer plr = WildfireGender.getOrAddPlayerById(packet.uuid); - packet.updatePlayerFromPacket(plr); - plr.syncStatus = GenderPlayer.SyncStatus.SYNCED; - plr.lockSettings = true; - //System.out.println("Received player data " + plr.uuid); - } else { - //System.out.println("Ignoring packet, this is yourself."); - } - } - - // Send Packet - - public static void sendToOthers(ServerPlayerEntity player, GenderPlayer genderPlayer) { - if (genderPlayer != null && player.getServer() != null) { - PacketSync packet = new PacketSync(genderPlayer); - PacketByteBuf buffer = PacketByteBufs.create(); - packet.encode(buffer); - - for (ServerPlayerEntity serverPlayer : PlayerLookup.all(player.getServer())) { - if (!player.getUuid().equals(serverPlayer.getUuid())) { - ServerPlayNetworking.send(serverPlayer, new Identifier(WildfireGender.MODID, "sync"), buffer); - } - } - } - } - - public static void sendTo(ServerPlayerEntity player) { - for (Map.Entry entry : WildfireGender.CLOTHING_PLAYERS.entrySet()) { - UUID uuid = entry.getKey(); - if (!player.getUuid().equals(uuid)) { - PacketSync packet = new PacketSync(entry.getValue()); - PacketByteBuf buffer = PacketByteBufs.create(); - packet.encode(buffer); - ServerPlayNetworking.send(player, new Identifier(WildfireGender.MODID, "sync"), buffer); - } - } - } -} diff --git a/src/main/java/com/wildfire/main/networking/PacketGenderInfo.java b/src/main/java/com/wildfire/main/networking/SyncPacket.java similarity index 89% rename from src/main/java/com/wildfire/main/networking/PacketGenderInfo.java rename to src/main/java/com/wildfire/main/networking/SyncPacket.java index eec1cac0..588eff36 100644 --- a/src/main/java/com/wildfire/main/networking/PacketGenderInfo.java +++ b/src/main/java/com/wildfire/main/networking/SyncPacket.java @@ -21,11 +21,12 @@ import com.wildfire.main.Breasts; import com.wildfire.main.GenderPlayer; import com.wildfire.main.GenderPlayer.Gender; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.minecraft.network.PacketByteBuf; import java.util.UUID; -public abstract class PacketGenderInfo { +class SyncPacket { protected final UUID uuid; private final Gender gender; private final float bust_size; @@ -42,7 +43,7 @@ public abstract class PacketGenderInfo { private final boolean hurtSounds; - protected PacketGenderInfo(GenderPlayer plr) { + protected SyncPacket(GenderPlayer plr) { this.uuid = plr.uuid; this.gender = plr.getGender(); this.bust_size = plr.getBustSize(); @@ -63,7 +64,7 @@ protected PacketGenderInfo(GenderPlayer plr) { this.cleavage = breasts.getCleavage(); } - protected PacketGenderInfo(PacketByteBuf buffer) { + protected SyncPacket(PacketByteBuf buffer) { this.uuid = buffer.readUuid(); this.gender = buffer.readEnumConstant(Gender.class); this.bust_size = buffer.readFloat(); @@ -82,7 +83,7 @@ protected PacketGenderInfo(PacketByteBuf buffer) { this.cleavage = buffer.readFloat(); } - public void encode(PacketByteBuf buffer) { + protected void encode(PacketByteBuf buffer) { buffer.writeUuid(this.uuid); buffer.writeEnumConstant(this.gender); buffer.writeFloat(this.bust_size); @@ -118,4 +119,13 @@ protected void updatePlayerFromPacket(GenderPlayer plr) { breasts.updateUniboob(uniboob); breasts.updateCleavage(cleavage); } + + /** + * Convenience method for creating a sync packet to send over the network + */ + protected PacketByteBuf getPacket() { + PacketByteBuf packet = PacketByteBufs.create(); + this.encode(packet); + return packet; + } } diff --git a/src/main/java/com/wildfire/main/networking/WildfireSync.java b/src/main/java/com/wildfire/main/networking/WildfireSync.java new file mode 100644 index 00000000..50d86254 --- /dev/null +++ b/src/main/java/com/wildfire/main/networking/WildfireSync.java @@ -0,0 +1,108 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.main.networking; + +import com.wildfire.main.GenderPlayer; +import com.wildfire.main.WildfireGender; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.fabricmc.fabric.api.networking.v1.PlayerLookup; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; + +public class WildfireSync { + // While these two identifiers could be combined into one `sync` identifier, this is kept as-is for the sake of compatibility + // with older versions, and servers that may implement syncing on other platforms (such as Spigot or any of its forks). + public static final Identifier SEND_GENDER_IDENTIFIER = new Identifier(WildfireGender.MODID, "send_gender_info"); + public static final Identifier SYNC_IDENTIFIER = new Identifier(WildfireGender.MODID, "sync"); + + @SuppressWarnings("unused") + @Environment(EnvType.CLIENT) + public static void handle(MinecraftClient client, ClientPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender) { + SyncPacket packet = new SyncPacket(buf); + if(client.player == null || packet.uuid.equals(client.player.getUuid())) return; + + GenderPlayer plr = WildfireGender.getOrAddPlayerById(packet.uuid); + packet.updatePlayerFromPacket(plr); + plr.syncStatus = GenderPlayer.SyncStatus.SYNCED; + } + + @SuppressWarnings("unused") + public static void handle(MinecraftServer server, ServerPlayerEntity player, ServerPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender) { + SyncPacket packet = new SyncPacket(buf); + if(player == null || !player.getUuid().equals(packet.uuid)) return; + + GenderPlayer plr = WildfireGender.getOrAddPlayerById(packet.uuid); + packet.updatePlayerFromPacket(plr); + sendToAllClients(player, plr); + } + + /** + * Sync a player's configuration to all nearby connected players + * + * @param toSync The {@link ServerPlayerEntity player} to sync + * @param genderPlayer The {@link GenderPlayer configuration} for the target player + */ + public static void sendToAllClients(ServerPlayerEntity toSync, GenderPlayer genderPlayer) { + if(genderPlayer == null || toSync.getServer() == null) return; + + PacketByteBuf packet = new SyncPacket(genderPlayer).getPacket(); + PlayerLookup.tracking(toSync).forEach((sendTo) -> { + if(sendTo.getUuid().equals(toSync.getUuid())) return; + sendPacketToClient(sendTo, packet); + }); + } + + /** + * Sync a player's configuration to another connected player + * + * @param sendTo The {@link ServerPlayerEntity player} to send the sync to + * @param toSync The {@link GenderPlayer configuration} for the player being synced + */ + public static void sendToClient(ServerPlayerEntity sendTo, GenderPlayer toSync) { + sendPacketToClient(sendTo, new SyncPacket(toSync).getPacket()); + } + + /** + * Send the client player's configuration to the server for syncing to other players + * + * @param plr The {@link GenderPlayer configuration} for the client player + */ + @Environment(EnvType.CLIENT) + public static void sendToServer(GenderPlayer plr) { + if(plr == null || !plr.needsSync) return; + PacketByteBuf buffer = new SyncPacket(plr).getPacket(); + ClientPlayNetworking.send(SEND_GENDER_IDENTIFIER, buffer); + plr.needsSync = false; + } + + private static void sendPacketToClient(ServerPlayerEntity sendTo, PacketByteBuf packet) { + if(ServerPlayNetworking.canSend(sendTo, SYNC_IDENTIFIER)) { + ServerPlayNetworking.send(sendTo, SYNC_IDENTIFIER, packet); + } + } +} From 1e30d097eb23190a989d81ce93f946f063d01026 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Mon, 18 Sep 2023 19:06:18 -0400 Subject: [PATCH 024/238] More Breast Preset work P.S. It's a jumbled mess atm --- .../gui/WildfireBreastPresetList.java | 60 ++++--- .../gui/screen/BaseWildfireScreen.java | 2 +- .../WildfireBreastCustomizationScreen.java | 42 ++++- .../config/BreastPresetConfiguration.java | 155 ++++++++++++++++++ .../assets/wildfire_gender/lang/en_us.json | 4 + 5 files changed, 228 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/wildfire/main/config/BreastPresetConfiguration.java diff --git a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java index 248163a0..be442777 100644 --- a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java +++ b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java @@ -5,7 +5,9 @@ import com.google.gson.JsonObject; import com.mojang.blaze3d.systems.RenderSystem; import com.wildfire.gui.screen.WildfireBreastCustomizationScreen; +import com.wildfire.main.GenderPlayer; import com.wildfire.main.WildfireGender; +import com.wildfire.main.config.BreastPresetConfiguration; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.loader.api.FabricLoader; @@ -24,6 +26,7 @@ import java.io.FileReader; import java.nio.file.Path; import java.time.format.TextStyle; +import java.util.ArrayList; import java.util.Map; public class WildfireBreastPresetList extends EntryListWidget { @@ -31,42 +34,22 @@ public class WildfireBreastPresetList extends EntryListWidget entry : obj.entrySet()) { - String key = entry.getKey(); - data.add(key, entry.getValue()); - } - } catch(Exception e) { - e.printStackTrace(); - } - } } private BreastPresetListEntry[] BREAST_PRESETS = new BreastPresetListEntry[] { - new BreastPresetListEntry("Normal", "preset1.png"), - new BreastPresetListEntry("Curved", "preset2.png"), - new BreastPresetListEntry("Small", "preset3.png"), - new BreastPresetListEntry("Large", "preset4.png"), + }; private static final Identifier TXTR_SYNC = new Identifier(WildfireGender.MODID, "textures/sync.png"); private static final Identifier TXTR_UNKNOWN = new Identifier(WildfireGender.MODID, "textures/unknown.png"); @@ -85,6 +68,9 @@ public WildfireBreastPresetList(WildfireBreastCustomizationScreen parent, int li this.refreshList(); } + public BreastPresetListEntry[] getPresetList() { + return BREAST_PRESETS; + } @Override protected void renderList(DrawContext context, int mouseX, int mouseY, float delta) { int i = this.getRowLeft(); @@ -118,6 +104,16 @@ public int getRowWidth() { public void refreshList() { this.clearEntries(); + + //BREAST_PRESETS + BreastPresetConfiguration[] CONFIGS = BreastPresetConfiguration.getBreastPresetConfigurationFiles(); + ArrayList tmpPresets = new ArrayList<>(); + for(BreastPresetConfiguration presetCfg : CONFIGS) { + System.out.println("Preset Name: " + presetCfg.get(BreastPresetConfiguration.PRESET_NAME)); + tmpPresets.add(new BreastPresetListEntry(presetCfg.get(BreastPresetConfiguration.PRESET_NAME), presetCfg)); + } + BREAST_PRESETS = tmpPresets.toArray(new BreastPresetListEntry[tmpPresets.size()]); + if(this.client.world == null || this.client.player == null) return; for(int i = 0; i < BREAST_PRESETS.length; i++) { @@ -138,12 +134,12 @@ private Entry(final BreastPresetListEntry nInfo) { this.nInfo = nInfo; this.thumbnail = nInfo.ident; btnOpenGUI = new WildfireButton(0, 0, getRowWidth() - 6, itemHeight, Text.empty(), button -> { - /*GenderPlayer aPlr = WildfireGender.getPlayerById(nInfo.getProfile().getId()); - if(aPlr == null) return; - - try { - MinecraftClient.getInstance().setScreen(new WardrobeBrowserScreen(parent, nInfo.getProfile().getId())); - } catch(Exception ignored) {}*/ + parent.getPlayer().updateBustSize(nInfo.data.get(BreastPresetConfiguration.BUST_SIZE)); + parent.getPlayer().getBreasts().updateXOffset(nInfo.data.get(BreastPresetConfiguration.BREASTS_OFFSET_X)); + parent.getPlayer().getBreasts().updateYOffset(nInfo.data.get(BreastPresetConfiguration.BREASTS_OFFSET_Y)); + parent.getPlayer().getBreasts().updateZOffset(nInfo.data.get(BreastPresetConfiguration.BREASTS_OFFSET_Z)); + parent.getPlayer().getBreasts().updateCleavage(nInfo.data.get(BreastPresetConfiguration.BREASTS_CLEAVAGE)); + parent.getPlayer().getBreasts().updateUniboob(nInfo.data.get(BreastPresetConfiguration.BREASTS_UNIBOOB)); }); } diff --git a/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java b/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java index 25ba026d..bf30dfa1 100644 --- a/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java +++ b/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java @@ -36,7 +36,7 @@ protected BaseWildfireScreen(Text title, Screen parent, UUID uuid) { this.playerUUID = uuid; } - protected GenderPlayer getPlayer() { + public GenderPlayer getPlayer() { return WildfireGender.getPlayerById(this.playerUUID); } diff --git a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java index 91bc5c6a..86a65bca 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java @@ -25,6 +25,7 @@ import com.wildfire.main.Breasts; import com.wildfire.main.GenderPlayer; import com.wildfire.main.config.Configuration; +import com.wildfire.main.config.BreastPresetConfiguration; import it.unimi.dsi.fastutil.floats.FloatConsumer; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; @@ -38,6 +39,7 @@ public class WildfireBreastCustomizationScreen extends BaseWildfireScreen { private WildfireSlider breastSlider, xOffsetBoobSlider, yOffsetBoobSlider, zOffsetBoobSlider, cleavageSlider; private WildfireButton btnDualPhysics, btnPresets, btnCustomization; + private WildfireButton btnAddPreset, btnDeletePreset; private WildfireBreastPresetList PRESET_LIST; @@ -66,18 +68,35 @@ public void init() { currentTab = 0; btnCustomization.active = false; btnPresets.active = true; + btnAddPreset.visible = false; + btnDeletePreset.visible = false; - }).setActive(false)); - + })).setActive(false); //Presets Tab this.addDrawableChild(btnPresets = new WildfireButton(this.width / 2 + 31 + 158/2, j - 60, 158 / 2 - 1, 10, Text.translatable("wildfire_gender.breast_customization.tab_presets"), button -> { currentTab = 1; btnCustomization.active = true; btnPresets.active = false; + btnAddPreset.visible = true; + btnDeletePreset.visible = true; + PRESET_LIST.refreshList(); + })); + this.addDrawableChild(btnAddPreset = new WildfireButton(this.width / 2 + 31 + 158/2, j + 80, 158 / 2 - 1, 12, + Text.translatable("wildfire_gender.breast_customization.presets.add_new"), button -> { + createNewPreset("Test Preset"); })); + btnAddPreset.visible = false; + + this.addDrawableChild(btnDeletePreset = new WildfireButton(this.width / 2 + 30, j + 80, 158 / 2 - 1, 12, + Text.translatable("wildfire_gender.breast_customization.presets.delete"), button -> { + + })).setActive(false); + btnDeletePreset.visible = false; //Customization Tab Below + + this.addDrawableChild(this.breastSlider = new WildfireSlider(this.width / 2 + 30, j - 48, 158, 20, Configuration.BUST_SIZE, plr.getBustSize(), plr::updateBustSize, value -> Text.translatable("wildfire_gender.wardrobe.slider.breast_size", Math.round(value * 1.25f * 100)), onSave)); @@ -113,6 +132,20 @@ public void init() { super.init(); } + private void createNewPreset(String presetName) { + BreastPresetConfiguration cfg = new BreastPresetConfiguration(presetName); + cfg.set(BreastPresetConfiguration.PRESET_NAME, presetName); + cfg.set(BreastPresetConfiguration.BUST_SIZE, this.getPlayer().getBustSize()); + cfg.set(BreastPresetConfiguration.BREASTS_UNIBOOB, this.getPlayer().getBreasts().isUniboob()); + cfg.set(BreastPresetConfiguration.BREASTS_CLEAVAGE, this.getPlayer().getBreasts().getCleavage()); + cfg.set(BreastPresetConfiguration.BREASTS_OFFSET_X, this.getPlayer().getBreasts().getXOffset()); + cfg.set(BreastPresetConfiguration.BREASTS_OFFSET_Y, this.getPlayer().getBreasts().getYOffset()); + cfg.set(BreastPresetConfiguration.BREASTS_OFFSET_Z, this.getPlayer().getBreasts().getZOffset()); + cfg.save(); + + PRESET_LIST.refreshList(); + } + @Override public void render(DrawContext ctx, int f1, int f2, float f3) { MinecraftClient minecraft = MinecraftClient.getInstance(); @@ -147,12 +180,17 @@ public void render(DrawContext ctx, int f1, int f2, float f3) { ctx.fill(x + 29, y - 63 - 21, x + 189, y - 60, 0x55000000); ctx.drawText(textRenderer, getTitle(), x + 32, y - 60 - 21, 0xFFFFFF, false); + PRESET_LIST.visible = currentTab == 1; if(currentTab == 1) { ctx.fill(PRESET_LIST.getLeft(), PRESET_LIST.getTop(), PRESET_LIST.getRight(), PRESET_LIST.getBottom(), 0x55000000); PRESET_LIST.render(ctx, f1, f2, f3); } + if(PRESET_LIST.getPresetList().length == 0 && currentTab == 1) { + ctx.drawText(textRenderer, "No Presets Found", x + ((190 + 28) / 2) - textRenderer.getWidth("No Presets Found") / 2, y - 4, 0xFFFFFF, false); + } + super.render(ctx, f1, f2, f3); } diff --git a/src/main/java/com/wildfire/main/config/BreastPresetConfiguration.java b/src/main/java/com/wildfire/main/config/BreastPresetConfiguration.java new file mode 100644 index 00000000..e36e3815 --- /dev/null +++ b/src/main/java/com/wildfire/main/config/BreastPresetConfiguration.java @@ -0,0 +1,155 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.main.config; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonWriter; +import net.fabricmc.loader.api.FabricLoader; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Map; + +public class BreastPresetConfiguration { + + public static final StringConfigKey PRESET_NAME = new StringConfigKey("preset_name", ""); + public static final FloatConfigKey BUST_SIZE = new FloatConfigKey("bust_size", 0.6F, 0, 0.8f); + + public static final FloatConfigKey BREASTS_OFFSET_X = new FloatConfigKey("breasts_xOffset", 0.0F, -1, 1); + public static final FloatConfigKey BREASTS_OFFSET_Y = new FloatConfigKey("breasts_yOffset", 0.0F, -1, 1); + public static final FloatConfigKey BREASTS_OFFSET_Z = new FloatConfigKey("breasts_zOffset", 0.0F, -1, 0); + public static final BooleanConfigKey BREASTS_UNIBOOB = new BooleanConfigKey("breasts_uniboob", true); + public static final FloatConfigKey BREASTS_CLEAVAGE = new FloatConfigKey("breasts_cleavage", 0, 0, 0.1F); + + private static final TypeAdapter ADAPTER = new Gson().getAdapter(JsonObject.class); + + private final File CFG_FILE; + public JsonObject SAVE_VALUES = new JsonObject(); + + public BreastPresetConfiguration(String cfgName) { + + Path saveDir = FabricLoader.getInstance().getConfigDir(); + System.out.println("Breast Presets Save Dir: " + saveDir.toString()); + if(!Files.isDirectory(saveDir.resolve("WildfireGender/presets"))) { + try { + Files.createDirectory(saveDir.resolve("WildfireGender/presets")); + } catch (IOException e) { + e.printStackTrace(); + } + } + //.getOrCreateGameRelativePath(FMLPaths.CONFIGDIR.get().resolve(saveLoc), saveLoc); + CFG_FILE = saveDir.resolve("WildfireGender/presets").resolve(cfgName + ".json").toFile(); + } + + public static BreastPresetConfiguration[] getBreastPresetConfigurationFiles() { + ArrayList tmp = new ArrayList<>(); + + Path saveDir = FabricLoader.getInstance().getConfigDir(); + File[] presetFiles = saveDir.resolve("WildfireGender/presets").toFile().listFiles(); + for(File f : presetFiles) { + BreastPresetConfiguration cfg = new BreastPresetConfiguration(f.getName().replace(".json", "")); + cfg.load(); // Load the preset values + tmp.add(cfg); + } + + if(tmp.size() == 0) { + return new BreastPresetConfiguration[] {}; + } + return tmp.toArray(new BreastPresetConfiguration[tmp.size()]); + } + + public void finish() { + if(CFG_FILE.exists()) { + load(); //load file + updateConfig(); + } else { + //save(); //save all values to default in new file. + } + } + + public void set(ConfigKey key, TYPE value) { + key.save(SAVE_VALUES, value); + } + + public TYPE get(ConfigKey key) { + return key.read(SAVE_VALUES); + } + + public void removeParameter(ConfigKey key) { + removeParameter(key.key); + } + + public void removeParameter(String key) { + SAVE_VALUES.remove(key); + } + + public void updateConfig() { + JsonObject obj; + try (FileReader configurationFile = new FileReader(CFG_FILE)) { + obj = new Gson().fromJson(configurationFile, JsonObject.class); //GsonHelper.parse(configurationFile); + //Merge with existing values + for (Map.Entry entry : SAVE_VALUES.entrySet()) { + obj.add(entry.getKey(), entry.getValue()); + } + } catch(Exception ignored) { + return; + } + try (FileWriter writer = new FileWriter(CFG_FILE); + JsonWriter jsonWriter = new JsonWriter(writer)) { + ADAPTER.write(jsonWriter, obj); + //System.out.println("[Configuration] Saved Existing File!"); + } catch(Exception ignored) {} + } + + public void save() { + try (FileWriter writer = new FileWriter(CFG_FILE); + JsonWriter jsonWriter = new JsonWriter(writer)) { + jsonWriter.setIndent(" "); + ADAPTER.write(jsonWriter, SAVE_VALUES); + //System.out.println("[Configuration] Saved New File!"); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + + //load file values to this class for use in the program + public void load() { + //System.out.println("[Configuration] Loading..."); + + try (FileReader configurationFile = new FileReader(CFG_FILE)) { + JsonObject obj = new Gson().fromJson(configurationFile, JsonObject.class); + for (Map.Entry entry : obj.entrySet()) { + String key = entry.getKey(); + SAVE_VALUES.add(key, entry.getValue()); + } + //System.out.println("[Configuration] Loaded!\n\n"); + } catch(Exception e) { + //System.out.println("[Configuration] Failed!\n\n"); + e.printStackTrace(); + } + } +} diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index 2d8a493b..a1a4894b 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -12,6 +12,10 @@ "wildfire_gender.wardrobe.title": "Wildfire's Female Gender Mod", "wildfire_gender.breast_customization.tab_customization": "Customization", "wildfire_gender.breast_customization.tab_presets": "Presets", + + "wildfire_gender.breast_customization.presets.add_new": "Add New...", + "wildfire_gender.breast_customization.presets.delete": "Delete", + "wildfire_gender.wardrobe.slider.breast_size": "Breast Size: %s%%", "wildfire_gender.wardrobe.slider.separation": "Separation: %s", "wildfire_gender.wardrobe.slider.height": "Height: %s", From e803648b5901906859492d6a6d4ed52a4f162fb5 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Mon, 18 Sep 2023 19:44:21 -0400 Subject: [PATCH 025/238] Fixed crash when opening Breast Customization screen because the presets folder didn't exist (it creates it now) --- .../main/config/BreastPresetConfiguration.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/wildfire/main/config/BreastPresetConfiguration.java b/src/main/java/com/wildfire/main/config/BreastPresetConfiguration.java index e36e3815..0b26f768 100644 --- a/src/main/java/com/wildfire/main/config/BreastPresetConfiguration.java +++ b/src/main/java/com/wildfire/main/config/BreastPresetConfiguration.java @@ -69,11 +69,17 @@ public static BreastPresetConfiguration[] getBreastPresetConfigurationFiles() { ArrayList tmp = new ArrayList<>(); Path saveDir = FabricLoader.getInstance().getConfigDir(); - File[] presetFiles = saveDir.resolve("WildfireGender/presets").toFile().listFiles(); - for(File f : presetFiles) { - BreastPresetConfiguration cfg = new BreastPresetConfiguration(f.getName().replace(".json", "")); - cfg.load(); // Load the preset values - tmp.add(cfg); + File presetFileLocation = saveDir.resolve("WildfireGender/presets").toFile(); + if(!presetFileLocation.exists()) { + presetFileLocation.mkdirs(); + } + File[] presetFiles = presetFileLocation.listFiles(); + if(presetFiles.length > 0) { + for (File f : presetFiles) { + BreastPresetConfiguration cfg = new BreastPresetConfiguration(f.getName().replace(".json", "")); + cfg.load(); // Load the preset values + tmp.add(cfg); + } } if(tmp.size() == 0) { From f59a6492c73cfd1d853e8688c0d9a49de8ffa687 Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 21 Sep 2023 12:04:43 -0600 Subject: [PATCH 026/238] Port to 1.20.2 --- gradle.properties | 16 ++--- .../gui/WildfireBreastPresetList.java | 6 +- .../gui/screen/WardrobeBrowserScreen.java | 26 ++++---- .../WildfireBreastCustomizationScreen.java | 59 +++++++++---------- .../WildfireCharacterSettingsScreen.java | 15 +++-- .../com/wildfire/physics/BreastPhysics.java | 33 ++++------- .../java/com/wildfire/render/GenderLayer.java | 9 +-- src/main/resources/fabric.mod.json | 12 ++-- 8 files changed, 77 insertions(+), 99 deletions(-) diff --git a/gradle.properties b/gradle.properties index 6b0a0eab..ae88a371 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,15 +2,15 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties - # check these on https://fabricmc.net/develop - minecraft_version=1.20 - yarn_mappings=1.20+build.1 - loader_version=0.14.21 +# check these on https://fabricmc.net/develop +minecraft_version=1.20.2 +yarn_mappings=1.20.2+build.1 +loader_version=0.14.22 # Mod Properties - mod_version = fabric-1.20-3.1 - maven_group = com.wildfiregender.main - archives_base_name = Female-Gender-Mod +mod_version = fabric-1.20.2-3.1 +maven_group = com.wildfiregender.main +archives_base_name = Female-Gender-Mod # Dependencies - fabric_version=0.83.0+1.20 +fabric_version=0.89.0+1.20.2 diff --git a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java index be442777..75696637 100644 --- a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java +++ b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java @@ -60,9 +60,7 @@ public BreastPresetListEntry(String name, BreastPresetConfiguration data) { public WildfireBreastPresetList(WildfireBreastCustomizationScreen parent, int listWidth, int top, int bottom) { super(MinecraftClient.getInstance(), 156, parent.height, top, bottom, 32); this.setRenderHeader(false, 0); - this.setRenderSelection(false); this.setRenderBackground(false); - this.setRenderHorizontalShadows(false); this.parent = parent; this.listWidth = listWidth; this.refreshList(); @@ -71,6 +69,10 @@ public WildfireBreastPresetList(WildfireBreastCustomizationScreen parent, int li public BreastPresetListEntry[] getPresetList() { return BREAST_PRESETS; } + + @Override + protected void drawSelectionHighlight(DrawContext context, int y, int entryWidth, int entryHeight, int borderColor, int fillColor) {} + @Override protected void renderList(DrawContext context, int mouseX, int mouseY, float delta) { int i = this.getRowLeft(); diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index 4ebed34d..bd4b425e 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -44,8 +44,6 @@ public class WardrobeBrowserScreen extends BaseWildfireScreen { private static final Identifier BACKGROUND = new Identifier(WildfireGender.MODID, "textures/gui/wardrobe_bg3.png"); public static float modelRotation = 0.5F; - private WildfireButton btnAppearanceSettings; - public WardrobeBrowserScreen(Screen parent, UUID uuid) { super(Text.translatable("wildfire_gender.wardrobe.title"), parent, uuid); } @@ -70,8 +68,8 @@ public void init() { } })); - if(plr.getGender() != Gender.MALE) { - this.addDrawableChild(btnAppearanceSettings = new WildfireButton(this.width / 2 - 42, j - 32, 158, 20, Text.translatable("wildfire_gender.appearance_settings.title").append("..."), + if(plr.getGender().canHaveBreasts()) { + this.addDrawableChild(new WildfireButton(this.width / 2 - 42, j - 32, 158, 20, Text.translatable("wildfire_gender.appearance_settings.title").append("..."), button -> MinecraftClient.getInstance().setScreen(new WildfireBreastCustomizationScreen(WardrobeBrowserScreen.this, this.playerUUID)))); this.addDrawableChild(new WildfireButton(this.width / 2 - 42, j - 12, 158, 20, Text.translatable("wildfire_gender.char_settings.title").append("..."), button -> MinecraftClient.getInstance().setScreen(new WildfireCharacterSettingsScreen(WardrobeBrowserScreen.this, this.playerUUID)))); @@ -92,21 +90,19 @@ private Text getGenderLabel(Gender gender) { return Text.translatable("wildfire_gender.label.gender").append(" - ").append(gender.getDisplayName()); } + @Override + public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delta) { + super.renderBackground(ctx, mouseX, mouseY, delta); + Identifier backgroundTexture = getPlayer().getGender().canHaveBreasts() ? BACKGROUND_FEMALE : BACKGROUND; + ctx.drawTexture(backgroundTexture, (this.width - 248) / 2, (this.height - 134) / 2, 0, 0, 248, 156); + } + @SuppressWarnings("DataFlowIssue") @Override public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { + super.render(ctx, mouseX, mouseY, delta); MinecraftClient minecraft = MinecraftClient.getInstance(); GenderPlayer plr = getPlayer(); - super.renderBackground(ctx); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - - RenderSystem.setShader(GameRenderer::getPositionTexProgram); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - if(plr.getGender() != Gender.MALE) { - ctx.drawTexture(BACKGROUND_FEMALE, (this.width - 248) / 2, (this.height - 134) / 2, 0, 0, 248, 156); - } else { - ctx.drawTexture(BACKGROUND, (this.width - 248) / 2, (this.height - 134) / 2, 0, 0, 248, 156); - } if(plr == null) return; @@ -124,8 +120,6 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { drawEntityOnScreen(xP, yP, 45, (xP - mouseX), (yP - 76 - mouseY), minecraft.world.getPlayerByUuid(this.playerUUID)); } } catch(Exception e) {} - - super.render(ctx, mouseX, mouseY, delta); } public static void drawEntityOnScreen(int x, int y, int size, float mouseX, float mouseY, LivingEntity entity) { diff --git a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java index 86a65bca..8e1ab60a 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java @@ -42,12 +42,12 @@ public class WildfireBreastCustomizationScreen extends BaseWildfireScreen { private WildfireButton btnAddPreset, btnDeletePreset; private WildfireBreastPresetList PRESET_LIST; + private int currentTab = 0; // 0 = customization, 1 = presets public WildfireBreastCustomizationScreen(Screen parent, UUID uuid) { super(Text.translatable("wildfire_gender.appearance_settings.title"), parent, uuid); } - private int currentTab = 0; // 0 = customization, 1 = presets @Override public void init() { int j = this.height / 2 - 11; @@ -146,52 +146,51 @@ private void createNewPreset(String presetName) { PRESET_LIST.refreshList(); } - @Override - public void render(DrawContext ctx, int f1, int f2, float f3) { - MinecraftClient minecraft = MinecraftClient.getInstance(); - GenderPlayer plr = getPlayer(); - super.renderBackground(ctx); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - - if(plr == null) return; - - try { - RenderSystem.setShaderColor(1f,1.0F, 1.0F, 1.0F); - int xP = this.width / 2 - 102; - int yP = this.height / 2 + 275; - //noinspection DataFlowIssue - PlayerEntity ent = minecraft.world.getPlayerByUuid(this.playerUUID); - if(ent != null) { - WardrobeBrowserScreen.drawEntityOnScreen(xP, yP, 200, -20, -20, ent); - } - } catch(Exception e) {} - - boolean canHaveBreasts = plr.getGender().canHaveBreasts(); + private void updatePresetTab() { + boolean canHaveBreasts = getPlayer().getGender().canHaveBreasts(); breastSlider.visible = canHaveBreasts && currentTab == 0; xOffsetBoobSlider.visible = canHaveBreasts && currentTab == 0; yOffsetBoobSlider.visible = canHaveBreasts && currentTab == 0; zOffsetBoobSlider.visible = canHaveBreasts && currentTab == 0; cleavageSlider.visible = canHaveBreasts && currentTab == 0; btnDualPhysics.visible = canHaveBreasts && currentTab == 0; + PRESET_LIST.visible = currentTab == 1; + } + @Override + public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delta) { + super.renderBackground(ctx, mouseX, mouseY, delta); int x = this.width / 2; int y = this.height / 2; ctx.fill(x + 28, y - 64 - 21, x + 190, y + 68, 0x55000000); ctx.fill(x + 29, y - 63 - 21, x + 189, y - 60, 0x55000000); ctx.drawText(textRenderer, getTitle(), x + 32, y - 60 - 21, 0xFFFFFF, false); - - - PRESET_LIST.visible = currentTab == 1; if(currentTab == 1) { ctx.fill(PRESET_LIST.getLeft(), PRESET_LIST.getTop(), PRESET_LIST.getRight(), PRESET_LIST.getBottom(), 0x55000000); - PRESET_LIST.render(ctx, f1, f2, f3); } + } - if(PRESET_LIST.getPresetList().length == 0 && currentTab == 1) { - ctx.drawText(textRenderer, "No Presets Found", x + ((190 + 28) / 2) - textRenderer.getWidth("No Presets Found") / 2, y - 4, 0xFFFFFF, false); - } + @Override + public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { + if(client == null || client.player == null || client.world == null) return; + + updatePresetTab(); + super.render(ctx, mouseX, mouseY, delta); + RenderSystem.setShaderColor(1f, 1.0F, 1.0F, 1.0F); + + int xP = this.width / 2 - 102; + int yP = this.height / 2 + 275; + PlayerEntity ent = client.world.getPlayerByUuid(this.playerUUID); + if(ent != null) WardrobeBrowserScreen.drawEntityOnScreen(xP, yP, 200, -20, -20, ent); - super.render(ctx, f1, f2, f3); + int x = this.width / 2; + int y = this.height / 2; + if(currentTab == 1) { + PRESET_LIST.render(ctx, mouseX, mouseY, delta); + if(PRESET_LIST.getPresetList().length == 0) { + ctx.drawText(textRenderer, "No Presets Found", x + ((190 + 28) / 2) - textRenderer.getWidth("No Presets Found") / 2, y - 4, 0xFFFFFF, false); + } + } } @Override diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java index a0124d7c..701047ab 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java @@ -129,21 +129,20 @@ public void init() { super.init(); } + @Override + public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delta) { + super.renderBackground(ctx, mouseX, mouseY, delta); + ctx.drawTexture(BACKGROUND, (this.width - 172) / 2, (this.height - 124) / 2, 0, 0, 172, 144); + } + @Override public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { - super.renderBackground(ctx); + super.render(ctx, mouseX, mouseY, delta); //noinspection DataFlowIssue PlayerEntity plrEntity = MinecraftClient.getInstance().world.getPlayerByUuid(this.playerUUID); - - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - RenderSystem.setShader(GameRenderer::getPositionTexProgram); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - ctx.drawTexture(BACKGROUND, (this.width - 172) / 2, (this.height - 124) / 2, 0, 0, 172, 144); - int x = this.width / 2; int y = this.height / 2; ctx.drawText(textRenderer, getTitle(), x - 79, yPos - 10, 4473924, false); - super.render(ctx, mouseX, mouseY, delta); if(plrEntity != null) { WildfireHelper.drawCenteredText(ctx, this.textRenderer, plrEntity.getDisplayName(), x, yPos - 30, 0xFFFFFF); } diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index 5a756015..f0211d0d 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -44,15 +44,16 @@ public class BreastPhysics { private float breastSize = 0, preBreastSize = 0; - private Vec3d motion; private Vec3d prePos; - private GenderPlayer genderPlayer; + private final GenderPlayer genderPlayer; + public BreastPhysics(GenderPlayer genderPlayer) { this.genderPlayer = genderPlayer; } private int randomB = 1; private boolean alreadyFalling = false; + public void update(PlayerEntity plr, IGenderArmor armor) { this.wfg_preBounce = this.wfg_femaleBreast; this.wfg_preBounceX = this.wfg_femaleBreastX; @@ -121,7 +122,7 @@ public void update(PlayerEntity plr, IGenderArmor armor) { } - this.motion = plr.getPos().subtract(this.prePos); + Vec3d motion = plr.getPos().subtract(this.prePos); this.prePos = plr.getPos(); //System.out.println(motion); @@ -178,7 +179,6 @@ public void update(PlayerEntity plr, IGenderArmor armor) { float rotationL = (float) MathHelper.clampedLerp(-(float)Math.PI / 3F, -0.2617994F, (double) ((MathHelper.sin(-rowTime2) + 1.0F) / 2.0F)); float rotationR = (float) MathHelper.clampedLerp(-(float)Math.PI / 4F, (float)Math.PI / 4F, (double) ((MathHelper.sin(-rowTime + 1.0F) + 1.0F) / 2.0F)); - //System.out.println(rotationL + ", " + rotationR); if(rotationL < -1 || rotationR < -0.6f) { this.targetBounceY = bounceIntensity / 3.25f; } @@ -187,43 +187,30 @@ public void update(PlayerEntity plr, IGenderArmor armor) { if(plr.getVehicle() instanceof MinecartEntity cart) { float speed = (float) cart.getVelocity().lengthSquared(); if(Math.random() * speed < 0.5f && speed > 0.2f) { - if(Math.random() > 0.5) { - this.targetBounceY = -bounceIntensity / 6f; - } else { - this.targetBounceY = bounceIntensity / 6f; - } + this.targetBounceY = (Math.random() > 0.5 ? -bounceIntensity : bounceIntensity) / 6f; } - /*if(rotationL < -1 || rotationR < -1) { - aPlr.targetBounce = bounceIntensity / 3.25f; - }*/ } if(plr.getVehicle() instanceof HorseEntity horse) { float movement = (float) horse.getVelocity().lengthSquared(); if(horse.age % clampMovement(movement) == 5 && movement > 0.1f) { this.targetBounceY = bounceIntensity / 4f; } - //horse } if(plr.getVehicle() instanceof PigEntity pig) { float movement = (float) pig.getVelocity().lengthSquared(); - //System.out.println(movement); if(pig.age % clampMovement(movement) == 5 && movement > 0.08f) { this.targetBounceY = bounceIntensity / 4f; } - //horse } if(plr.getVehicle() instanceof StriderEntity strider) { - this.targetBounceY += ((float) (strider.getMountedHeightOffset()*3f) - 4.5f) * bounceIntensity; - //horse + double heightOffset = (double)strider.getHeight() - 0.19 + + (double)(0.12F * MathHelper.cos(strider.limbAnimator.getPos() * 1.5f) + * 2F * Math.min(0.25F, strider.limbAnimator.getSpeed())); + this.targetBounceY += ((float) (heightOffset * 3f) - 4.5f) * bounceIntensity; } - //System.out.println("VEHICLE"); } if(plr.handSwinging && plr.age % 5 == 0 && plr.getPose() != EntityPose.SLEEPING) { - if(Math.random() > 0.5) { - this.targetBounceY += -0.25f * bounceIntensity; - } else { - this.targetBounceY += 0.25f * bounceIntensity; - } + this.targetBounceY += (Math.random() > 0.5 ? -0.25f : 0.25f) * bounceIntensity; } if(plr.getPose() == EntityPose.SLEEPING && !this.alreadySleeping) { this.targetBounceY = bounceIntensity; diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index 0c94b051..921ae6cc 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -308,7 +308,7 @@ private void renderVanillaLikeBreastArmor(PlayerEntity entity, MatrixStack matri renderBox(armor, matrixStack, overlayVertexConsumer, packedLightIn, OverlayTexture.DEFAULT_UV, 1, 1, 1, 1); } - ArmorTrim.getTrim(entity.getWorld().getRegistryManager(), armorStack).ifPresent((trim) -> { + ArmorTrim.getTrim(entity.getWorld().getRegistryManager(), armorStack, true).ifPresent((trim) -> { renderArmorTrim(armorItem.getMaterial(), matrixStack, vertexConsumerProvider, packedLightIn, trim, hasGlint, left); }); } finally { @@ -320,7 +320,8 @@ private void renderArmorTrim(ArmorMaterial material, MatrixStack matrixStack, Ve ArmorTrim trim, boolean hasGlint, boolean left) { BreastModelBox trimModelBox = left ? lTrim : rTrim; Sprite sprite = this.armorTrimsAtlas.getSprite(trim.getGenericModelId(material)); - VertexConsumer vertexConsumer = sprite.getTextureSpecificVertexConsumer(vertexConsumerProvider.getBuffer(TexturedRenderLayers.getArmorTrims())); + VertexConsumer vertexConsumer = sprite.getTextureSpecificVertexConsumer( + vertexConsumerProvider.getBuffer(TexturedRenderLayers.getArmorTrims(trim.getPattern().value().decal()))); // Render the armor trim itself renderBox(trimModelBox, matrixStack, vertexConsumer, packedLightIn, OverlayTexture.DEFAULT_UV, 1f, 1f, 1f, 1f); // The enchantment glint however requires special handling; due to how Minecraft's enchant glint rendering works, rendering @@ -334,9 +335,9 @@ private void renderArmorTrim(ArmorMaterial material, MatrixStack matrixStack, Ve } private static void renderBox(WildfireModelRenderer.ModelBox model, MatrixStack matrixStack, VertexConsumer bufferIn, int packedLightIn, int packedOverlayIn, - float red, float green, float blue, float alpha) { + float red, float green, float blue, float alpha) { Matrix4f matrix4f = matrixStack.peek().getPositionMatrix(); - Matrix3f matrix3f = matrixStack.peek().getNormalMatrix(); + Matrix3f matrix3f = matrixStack.peek().getNormalMatrix(); for (WildfireModelRenderer.TexturedQuad quad : model.quads) { Vector3f vector3f = new Vector3f(quad.normal.x, quad.normal.y, quad.normal.z); vector3f.mul(matrix3f); diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 6bb1c49f..6c104a58 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -37,17 +37,13 @@ "wildfire_gender.mixins.json" ], "depends": { - "fabricloader": ">=0.14.21", - "fabric-api": ">=0.83.0", - "minecraft": "~1.20", + "fabricloader": "*", + "fabric-api": "*", + "minecraft": "~1.20.2", "java": ">=17" }, "conflicts": { "skinlayers": "*", - "gender": "*" - }, - "suggests": { - "mermod": ">=2.3", - "trinkets": ">=3.6.0" + "allthetrims": "*" } } \ No newline at end of file From 3316c68895898b6191514d0c436b05a672ed4d6b Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 21 Sep 2023 12:12:15 -0600 Subject: [PATCH 027/238] Cleanup some dead code --- .../com/wildfire/main/WildfireGender.java | 52 +------------------ .../com/wildfire/main/WildfireHelper.java | 2 +- .../wildfire/mixins/PlayerEntityMixin.java | 21 +------- 3 files changed, 4 insertions(+), 71 deletions(-) diff --git a/src/main/java/com/wildfire/main/WildfireGender.java b/src/main/java/com/wildfire/main/WildfireGender.java index 17a07f73..c3bc33a8 100644 --- a/src/main/java/com/wildfire/main/WildfireGender.java +++ b/src/main/java/com/wildfire/main/WildfireGender.java @@ -18,22 +18,15 @@ package com.wildfire.main; -import java.io.File; import java.util.HashMap; import java.util.Map; import java.util.UUID; import javax.annotation.Nullable; import net.fabricmc.api.ClientModInitializer; -import net.minecraft.client.MinecraftClient; public class WildfireGender implements ClientModInitializer { - public static final String VERSION = "3.0.1"; + public static final String VERSION = "3.1"; public static final String MODID = "wildfire_gender"; - - public static boolean modEnabled = true; - public static final boolean SYNCING_ENABLED = false; - - private static final String PROTOCOL_VERSION = "2"; public static Map CLOTHING_PLAYERS = new HashMap<>(); @Override @@ -50,56 +43,13 @@ public static GenderPlayer getOrAddPlayerById(UUID id) { return CLOTHING_PLAYERS.computeIfAbsent(id, GenderPlayer::new); } - public static void loadGenderInfoAsync(UUID uuid, boolean markForSync) { Thread thread = new Thread(() -> WildfireGender.loadGenderInfo(uuid, markForSync)); thread.setName("WFGM_GetPlayer-" + uuid); thread.start(); } - public static void refreshAllGenders() { - if(MinecraftClient.getInstance().world == null) return; - /* - Thread thread = new Thread(new Runnable() { - public void run() { - NetworkPlayerInfo[] playersC = Minecraft.getInstance().getConnection().getPlayerInfoMap().toArray(new NetworkPlayerInfo[Minecraft.getInstance().getConnection().getPlayerInfoMap().size()]); - for(int h = 0; h < playersC.length; h++) { - NetworkPlayerInfo loadedPlayer = playersC[h]; - GenderPlayer plr = WildfireGender.getPlayerByName(loadedPlayer.getGameProfile().getId().toString()); - if(plr != null) { - plr.refreshCape(); - } - } - } - - }); - thread.setName("WFGM_GetAllPlayers"); - thread.start();*/ - } - public static GenderPlayer loadGenderInfo(UUID uuid, boolean markForSync) { return GenderPlayer.loadCachedPlayer(uuid, markForSync); } - /* - public static void drawTextLabel(PoseStack m, String txt, int x, int y) { - GlStateManager._disableBlend(); - Screen.fill(m, x, y, x + (Minecraft.getInstance()).font.width(txt) + 3, y + 11, 1610612736); - Minecraft.getInstance().font.draw(m, txt, x + 2, y + 2, 16777215); - } - public static void drawRightTextLabel(PoseStack m, String txt, int x, int y) { - GlStateManager._disableBlend(); - int w = (Minecraft.getInstance()).font.width(txt) + 3; - Screen.fill(m, x - w, y, x, y + 11, 1610612736); - Minecraft.getInstance().font.draw(m, txt, x - w + 2, y + 2, 16777215); - } - public static void drawCenterTextLabel(PoseStack m, String txt, int x, int y) { - GlStateManager._disableBlend(); - int w = (Minecraft.getInstance()).font.width(txt) + 3; - Screen.fill(m, x - w / 2, y, x + w / 2 + 1, y + 11, 1610612736); - Minecraft.getInstance().font.draw(m, txt, x - w / 2 + 2, y + 2, 16777215); - }*/ - - public interface WildfireCB { - void onExecute(boolean success, Object data); - } } \ No newline at end of file diff --git a/src/main/java/com/wildfire/main/WildfireHelper.java b/src/main/java/com/wildfire/main/WildfireHelper.java index 067f8d27..320b878e 100644 --- a/src/main/java/com/wildfire/main/WildfireHelper.java +++ b/src/main/java/com/wildfire/main/WildfireHelper.java @@ -83,7 +83,7 @@ public static IGenderArmor getArmorConfig(ItemStack stack) { @Environment(EnvType.CLIENT) public static void drawCenteredText(DrawContext ctx, TextRenderer textRenderer, Text text, int x, int y, int color) { - int centeredX = (int) x - textRenderer.getWidth(text) / 2; + int centeredX = x - textRenderer.getWidth(text) / 2; ctx.drawText(textRenderer, text, centeredX, y, color, false); } diff --git a/src/main/java/com/wildfire/mixins/PlayerEntityMixin.java b/src/main/java/com/wildfire/mixins/PlayerEntityMixin.java index a595f3a7..f7af9ea8 100644 --- a/src/main/java/com/wildfire/mixins/PlayerEntityMixin.java +++ b/src/main/java/com/wildfire/mixins/PlayerEntityMixin.java @@ -17,6 +17,7 @@ */ package com.wildfire.mixins; + import com.mojang.authlib.GameProfile; import com.wildfire.api.IGenderArmor; import com.wildfire.main.GenderPlayer; @@ -38,27 +39,12 @@ @Environment(EnvType.CLIENT) @Mixin(value = PlayerEntity.class, priority = 900) public abstract class PlayerEntityMixin extends LivingEntity { - - public float wfg_femaleBreast; - public float wfg_preBounce; - - float bounceVel = 0; - float targetBounce = 0; - float preY = 0; - - boolean justSneaking = false; - boolean alreadySleeping = false; - public PlayerEntityMixin(World world, BlockPos pos, float yaw, GameProfile profile) { super(EntityType.PLAYER, world); } @Inject(at = @At("TAIL"), method = "tick") public void onTick(CallbackInfo info) { - tickWildfire(); - } - - public void tickWildfire() { if(!this.getWorld().isClient()) return; GenderPlayer aPlr = WildfireGender.getPlayerById(this.getUuid()); if(aPlr == null) return; @@ -67,8 +53,5 @@ public void tickWildfire() { aPlr.getLeftBreastPhysics().update(plr, armor); aPlr.getRightBreastPhysics().update(plr, armor); - - } - -} \ No newline at end of file +} From e2349b2f2257edcf4e90388f7375404d452e8565 Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 21 Sep 2023 12:18:51 -0600 Subject: [PATCH 028/238] Lock presets tab behind a development environment This is meant for release readiness, as this feature isn't fully ready for release yet. --- .../gui/screen/WildfireBreastCustomizationScreen.java | 8 ++++++++ src/main/resources/assets/wildfire_gender/lang/en_us.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java index 8e1ab60a..1942218c 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java @@ -27,9 +27,11 @@ import com.wildfire.main.config.Configuration; import com.wildfire.main.config.BreastPresetConfiguration; import it.unimi.dsi.fastutil.floats.FloatConsumer; +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.tooltip.Tooltip; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.text.Text; @@ -75,6 +77,9 @@ public void init() { //Presets Tab this.addDrawableChild(btnPresets = new WildfireButton(this.width / 2 + 31 + 158/2, j - 60, 158 / 2 - 1, 10, Text.translatable("wildfire_gender.breast_customization.tab_presets"), button -> { + // TODO temporary release readiness fix: lock presets tab behind a development environment (-Dfabric.development=true) + if(!FabricLoader.getInstance().isDevelopmentEnvironment()) return; + currentTab = 1; btnCustomization.active = true; btnPresets.active = false; @@ -82,6 +87,9 @@ public void init() { btnDeletePreset.visible = true; PRESET_LIST.refreshList(); })); + if(!FabricLoader.getInstance().isDevelopmentEnvironment()) { + btnPresets.setTooltip(Tooltip.of(Text.translatable("wildfire_gender.coming_soon"))); + } this.addDrawableChild(btnAddPreset = new WildfireButton(this.width / 2 + 31 + 158/2, j + 80, 158 / 2 - 1, 12, Text.translatable("wildfire_gender.breast_customization.presets.add_new"), button -> { createNewPreset("Test Preset"); diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index a1a4894b..ccfa3159 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -65,5 +65,5 @@ "wildfire_gender.tooltip.bounce_warning": "Setting 'Bounce Intensity' to a high value will look very unnatural!", "wildfire_gender.cancer_awareness.title": "Hey, it's Breast Cancer Awareness Month!", - "wildfire_gender.cancer_awareness.description": "Click here to donate to §dSusan G. Komen Foundation§f!" + "wildfire_gender.coming_soon": "Coming soon" } From f61c4f1432dc87a9b8747482f3fc775740abed78 Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 21 Sep 2023 12:28:40 -0600 Subject: [PATCH 029/238] Fix invisibility also hiding armor --- .../java/com/wildfire/render/GenderLayer.java | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index 921ae6cc..ac96940c 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -98,10 +98,6 @@ public Identifier getArmorResource(ArmorItem item, boolean legs, @Nullable Strin @Override public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, @Nonnull AbstractClientPlayerEntity ent, float limbAngle, float limbDistance, float partialTicks, float animationProgress, float headYaw, float headPitch) { - if (ent.isInvisibleTo(MinecraftClient.getInstance().player)) { - //Exit early if the entity shouldn't actually be seen - return; - } //Surround with a try/catch to fix for essential mod. try { GenderPlayer plr = WildfireGender.getPlayerById(ent.getUuid()); @@ -116,6 +112,10 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume // to be hidden when wearing armor, we can just exit early rather than doing any calculations return; } + if(!isChestplateOccupied && ent.isInvisibleTo(MinecraftClient.getInstance().player)) { + // nothing to render here, just exit early + return; + } PlayerEntityRenderer rend = (PlayerEntityRenderer) MinecraftClient.getInstance().getEntityRenderDispatcher().getRenderer(ent); PlayerEntityModel model = rend.getModel(); @@ -267,13 +267,17 @@ private void renderBreastWithTransforms(AbstractClientPlayerEntity entity, Model private void renderBreast(AbstractClientPlayerEntity entity, ItemStack armorStack, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, RenderLayer breastRenderType, int packedLightIn, int packedOverlayIn, float alpha, boolean left) { VertexConsumer vertexConsumer = vertexConsumerProvider.getBuffer(breastRenderType); - renderBox(left ? lBreast : rBreast, matrixStack, vertexConsumer, packedLightIn, packedOverlayIn, 1f, 1f, 1f, alpha); - if (entity.isPartVisible(PlayerModelPart.JACKET)) { - matrixStack.translate(0, 0, -0.015f); - matrixStack.scale(1.05f, 1.05f, 1.05f); - renderBox(left ? lBreastWear : rBreastWear, matrixStack, vertexConsumer, packedLightIn, packedOverlayIn, 1f, 1f, 1f, alpha); + // We don't want to render the player if they have invisibility active + if (!entity.isInvisibleTo(MinecraftClient.getInstance().player)) { + renderBox(left ? lBreast : rBreast, matrixStack, vertexConsumer, packedLightIn, packedOverlayIn, 1f, 1f, 1f, alpha); + if (entity.isPartVisible(PlayerModelPart.JACKET)) { + matrixStack.translate(0, 0, -0.015f); + matrixStack.scale(1.05f, 1.05f, 1.05f); + renderBox(left ? lBreastWear : rBreastWear, matrixStack, vertexConsumer, packedLightIn, packedOverlayIn, 1f, 1f, 1f, alpha); + } } + // But we do still want to render their chestplate to match vanilla behavior if (!armorStack.isEmpty() && armorStack.getItem() instanceof ArmorItem armorItem) { renderVanillaLikeBreastArmor(entity, matrixStack, vertexConsumerProvider, armorItem, armorStack, packedLightIn, left); } From 2b8034c33fc9ccddbb92503710350cbebc008aed Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Tue, 10 Oct 2023 23:40:10 -0400 Subject: [PATCH 030/238] Re-Added Breast Cancer Awareness Prompt (onto the customization screen as opposed to the now-removed player list) Re-Added "playing with creator" prompt --- .../gui/screen/WardrobeBrowserScreen.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index bd4b425e..ea8f8136 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -20,14 +20,18 @@ import com.wildfire.main.GenderPlayer.Gender; import com.wildfire.main.WildfireGender; + +import java.util.Calendar; import java.util.UUID; import com.wildfire.gui.WildfireButton; import com.wildfire.main.GenderPlayer; import com.mojang.blaze3d.systems.RenderSystem; +import com.wildfire.main.WildfireHelper; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.render.DiffuseLighting; import net.minecraft.client.render.GameRenderer; import net.minecraft.client.render.VertexConsumerProvider; @@ -35,13 +39,19 @@ import net.minecraft.client.util.math.MatrixStack; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.listener.ClientPacketListener; import net.minecraft.text.Text; +import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; import org.joml.Quaternionf; public class WardrobeBrowserScreen extends BaseWildfireScreen { private static final Identifier BACKGROUND_FEMALE = new Identifier(WildfireGender.MODID, "textures/gui/wardrobe_bg2.png"); private static final Identifier BACKGROUND = new Identifier(WildfireGender.MODID, "textures/gui/wardrobe_bg3.png"); + private static final Identifier TXTR_RIBBON = new Identifier(WildfireGender.MODID, "textures/bc_ribbon.png"); + + private static final UUID CREATOR_UUID = UUID.fromString("33c937ae-6bfc-423e-a38e-3a613e7c1256"); + public static float modelRotation = 0.5F; public WardrobeBrowserScreen(Screen parent, UUID uuid) { @@ -120,6 +130,22 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { drawEntityOnScreen(xP, yP, 45, (xP - mouseX), (yP - 76 - mouseY), minecraft.world.getPlayerByUuid(this.playerUUID)); } } catch(Exception e) {} + + ClientPlayNetworkHandler clientPlayNetworkHandler = client.player.networkHandler; + boolean withCreator = clientPlayNetworkHandler.getPlayerList().stream() + .anyMatch((player) -> player.getProfile().getId().equals(CREATOR_UUID)); + if(withCreator) { + WildfireHelper.drawCenteredText(ctx, this.textRenderer, Text.translatable("wildfire_gender.label.with_creator"), this.width / 2, y + 89, 0xFF00FF); + } + + //Breast Cancer Awareness Month Notification + if(Calendar.getInstance().get(Calendar.MONTH) == Calendar.OCTOBER) { + ctx.fill(x - 159, y + 106, x + 159, y + 136, 0x55000000); + ctx.drawTextWithShadow(textRenderer, Text.translatable("wildfire_gender.cancer_awareness.title").formatted(Formatting.BOLD, Formatting.ITALIC), this.width / 2 - 148, y + 117, 0xFFFFFF); + RenderSystem.setShader(GameRenderer::getPositionTexProgram); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + ctx.drawTexture(TXTR_RIBBON, x + 130, y + 109, 26, 26, 0, 0, 20, 20, 20, 20); + } } public static void drawEntityOnScreen(int x, int y, int size, float mouseX, float mouseY, LivingEntity entity) { From 26dab294e7daad9a41b2be374bb4a63d8aa202ad Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Tue, 10 Oct 2023 23:43:00 -0400 Subject: [PATCH 031/238] Fixed it's positioning --- .../java/com/wildfire/gui/screen/WardrobeBrowserScreen.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index ea8f8136..f1c40fbc 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -131,6 +131,8 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { } } catch(Exception e) {} + y = y - 45; + ClientPlayNetworkHandler clientPlayNetworkHandler = client.player.networkHandler; boolean withCreator = clientPlayNetworkHandler.getPlayerList().stream() .anyMatch((player) -> player.getProfile().getId().equals(CREATOR_UUID)); From d217127232d3e642acfb919bf5ceed397a478488 Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 12 Oct 2023 17:32:26 -0600 Subject: [PATCH 032/238] Various small UI user experience fixes - Remove/rewrite a bunch of redundant tooltips in character settings Tooltips such as "Enables Breast Phyiscs" are bad, as they just repeat the setting name, but worded slightly differently. This changes them to actually describe the setting in better detail, or removes the tooltip entirely if the setting name is already fairly self-explanatory. - Don't re-open the main player settings menu when the gender is updated This is aimed at avoiding the narrator repeating the screen title whenever this setting is changed, as it logically wouldn't make sense to do so in this case. - Move the playing with creator prompt down to not overlap with the GUI Also moves it under the breast cancer awareness month banner when relevant. --- .../gui/screen/WardrobeBrowserScreen.java | 90 ++++++++----------- .../WildfireBreastCustomizationScreen.java | 2 +- .../WildfireCharacterSettingsScreen.java | 39 ++++---- .../assets/wildfire_gender/lang/cs_cz.json | 18 +--- .../assets/wildfire_gender/lang/de_ch.json | 18 +--- .../assets/wildfire_gender/lang/de_de.json | 8 +- .../assets/wildfire_gender/lang/en_us.json | 20 ++--- .../assets/wildfire_gender/lang/es_mx.json | 20 +---- .../assets/wildfire_gender/lang/fr_fr.json | 18 +--- .../assets/wildfire_gender/lang/lol_us.json | 18 +--- .../assets/wildfire_gender/lang/uk_ua.json | 20 +---- .../assets/wildfire_gender/lang/zh_cn.json | 22 +---- 12 files changed, 90 insertions(+), 203 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index f1c40fbc..220e57b9 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -31,15 +31,12 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.render.DiffuseLighting; -import net.minecraft.client.render.GameRenderer; import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.entity.EntityRenderDispatcher; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.network.listener.ClientPacketListener; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; @@ -49,10 +46,9 @@ public class WardrobeBrowserScreen extends BaseWildfireScreen { private static final Identifier BACKGROUND_FEMALE = new Identifier(WildfireGender.MODID, "textures/gui/wardrobe_bg2.png"); private static final Identifier BACKGROUND = new Identifier(WildfireGender.MODID, "textures/gui/wardrobe_bg3.png"); private static final Identifier TXTR_RIBBON = new Identifier(WildfireGender.MODID, "textures/bc_ribbon.png"); - private static final UUID CREATOR_UUID = UUID.fromString("33c937ae-6bfc-423e-a38e-3a613e7c1256"); - - public static float modelRotation = 0.5F; + private static final boolean isBreastCancerAwarenessMonth = Calendar.getInstance().get(Calendar.MONTH) == Calendar.OCTOBER; + private WildfireButton appearanceSettings, characterSettings; public WardrobeBrowserScreen(Screen parent, UUID uuid) { super(Text.translatable("wildfire_gender.wardrobe.title"), parent, uuid); @@ -60,10 +56,10 @@ public WardrobeBrowserScreen(Screen parent, UUID uuid) { @Override public void init() { - int j = this.height / 2; + int y = this.height / 2; GenderPlayer plr = getPlayer(); - this.addDrawableChild(new WildfireButton(this.width / 2 - 42, j - 52, 158, 20, getGenderLabel(plr.getGender()), button -> { + this.addDrawableChild(new WildfireButton(this.width / 2 - 42, y - 52, 158, 20, getGenderLabel(plr.getGender()), button -> { Gender gender = switch (plr.getGender()) { case MALE -> Gender.FEMALE; case FEMALE -> Gender.OTHER; @@ -72,26 +68,18 @@ public void init() { if (plr.updateGender(gender)) { button.setMessage(getGenderLabel(gender)); GenderPlayer.saveGenderInfo(plr); - - //re-render menu (re-open it) - MinecraftClient.getInstance().setScreen(new WardrobeBrowserScreen(null, playerUUID)); + updateCharacterOptionButtons(); } })); - if(plr.getGender().canHaveBreasts()) { - this.addDrawableChild(new WildfireButton(this.width / 2 - 42, j - 32, 158, 20, Text.translatable("wildfire_gender.appearance_settings.title").append("..."), - button -> MinecraftClient.getInstance().setScreen(new WildfireBreastCustomizationScreen(WardrobeBrowserScreen.this, this.playerUUID)))); - this.addDrawableChild(new WildfireButton(this.width / 2 - 42, j - 12, 158, 20, Text.translatable("wildfire_gender.char_settings.title").append("..."), - button -> MinecraftClient.getInstance().setScreen(new WildfireCharacterSettingsScreen(WardrobeBrowserScreen.this, this.playerUUID)))); - } else { - this.addDrawableChild(new WildfireButton(this.width / 2 - 42, j - 32, 158, 20, Text.translatable("wildfire_gender.char_settings.title").append("..."), - button -> MinecraftClient.getInstance().setScreen(new WildfireCharacterSettingsScreen(WardrobeBrowserScreen.this, this.playerUUID)))); - } + this.addDrawableChild(appearanceSettings = new WildfireButton(this.width / 2 - 42, y - 32, 158, 20, Text.translatable("wildfire_gender.appearance_settings.title").append("..."), + button -> MinecraftClient.getInstance().setScreen(new WildfireBreastCustomizationScreen(WardrobeBrowserScreen.this, this.playerUUID)))); + this.addDrawableChild(characterSettings = new WildfireButton(this.width / 2 - 42, y - 12, 158, 20, Text.translatable("wildfire_gender.char_settings.title").append("..."), + button -> MinecraftClient.getInstance().setScreen(new WildfireCharacterSettingsScreen(WardrobeBrowserScreen.this, this.playerUUID)))); + updateCharacterOptionButtons(); - this.addDrawableChild(new WildfireButton(this.width / 2 + 111, j - 63, 9, 9, Text.translatable("wildfire_gender.label.exit"), + this.addDrawableChild(new WildfireButton(this.width / 2 + 111, y - 63, 9, 9, Text.literal("X"), button -> MinecraftClient.getInstance().setScreen(parent))); - - modelRotation = 0.6F; super.init(); } @@ -100,6 +88,12 @@ private Text getGenderLabel(Gender gender) { return Text.translatable("wildfire_gender.label.gender").append(" - ").append(gender.getDisplayName()); } + private void updateCharacterOptionButtons() { + int y = this.height / 2; + boolean canHaveBreasts = appearanceSettings.visible = getPlayer().getGender().canHaveBreasts(); + characterSettings.setY(y - (canHaveBreasts ? 12 : 32)); + } + @Override public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delta) { super.renderBackground(ctx, mouseX, mouseY, delta); @@ -107,46 +101,36 @@ public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delt ctx.drawTexture(backgroundTexture, (this.width - 248) / 2, (this.height - 134) / 2, 0, 0, 248, 156); } - @SuppressWarnings("DataFlowIssue") @Override public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { super.render(ctx, mouseX, mouseY, delta); - MinecraftClient minecraft = MinecraftClient.getInstance(); - GenderPlayer plr = getPlayer(); - - if(plr == null) return; - int x = this.width / 2; int y = this.height / 2; - modelRotation = 0.6f; - ctx.drawText(textRenderer, title, x - 118, y - 62, 4473924, false); - try { - RenderSystem.setShaderColor(1f, 1.0F, 1.0F, 1.0F); + + if(client != null && client.world != null) { int xP = this.width / 2 - 82; int yP = this.height / 2 + 40; - PlayerEntity ent = minecraft.world.getPlayerByUuid(this.playerUUID); - if(ent != null) { - drawEntityOnScreen(xP, yP, 45, (xP - mouseX), (yP - 76 - mouseY), minecraft.world.getPlayerByUuid(this.playerUUID)); - } - } catch(Exception e) {} - - y = y - 45; - - ClientPlayNetworkHandler clientPlayNetworkHandler = client.player.networkHandler; - boolean withCreator = clientPlayNetworkHandler.getPlayerList().stream() - .anyMatch((player) -> player.getProfile().getId().equals(CREATOR_UUID)); - if(withCreator) { - WildfireHelper.drawCenteredText(ctx, this.textRenderer, Text.translatable("wildfire_gender.label.with_creator"), this.width / 2, y + 89, 0xFF00FF); + PlayerEntity ent = client.world.getPlayerByUuid(this.playerUUID); + if(ent != null) drawEntityOnScreen(xP, yP, 45, (xP - mouseX), (yP - 76 - mouseY), ent); + } + + if(client != null && client.player != null) { + boolean withCreator = client.player.networkHandler.getPlayerList().stream() + .anyMatch((player) -> player.getProfile().getId().equals(CREATOR_UUID)); + if(withCreator) { + int creatorY = y + 65; + // move down so we don't overlap with the breast cancer awareness month banner + if(isBreastCancerAwarenessMonth) creatorY += 30; + WildfireHelper.drawCenteredText(ctx, this.textRenderer, Text.translatable("wildfire_gender.label.with_creator"), this.width / 2, creatorY, 0xFF00FF); + } } - //Breast Cancer Awareness Month Notification - if(Calendar.getInstance().get(Calendar.MONTH) == Calendar.OCTOBER) { - ctx.fill(x - 159, y + 106, x + 159, y + 136, 0x55000000); - ctx.drawTextWithShadow(textRenderer, Text.translatable("wildfire_gender.cancer_awareness.title").formatted(Formatting.BOLD, Formatting.ITALIC), this.width / 2 - 148, y + 117, 0xFFFFFF); - RenderSystem.setShader(GameRenderer::getPositionTexProgram); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - ctx.drawTexture(TXTR_RIBBON, x + 130, y + 109, 26, 26, 0, 0, 20, 20, 20, 20); + if(isBreastCancerAwarenessMonth) { + int bcaY = y - 45; + ctx.fill(x - 159, bcaY + 106, x + 159, bcaY + 136, 0x55000000); + ctx.drawTextWithShadow(textRenderer, Text.translatable("wildfire_gender.cancer_awareness.title").formatted(Formatting.BOLD, Formatting.ITALIC), this.width / 2 - 148, bcaY + 117, 0xFFFFFF); + ctx.drawTexture(TXTR_RIBBON, x + 130, bcaY + 109, 26, 26, 0, 0, 20, 20, 20, 20); } } diff --git a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java index 1942218c..e98ef296 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java @@ -61,7 +61,7 @@ public void init() { GenderPlayer.saveGenderInfo(plr); }; - this.addDrawableChild(new WildfireButton(this.width / 2 + 178, j - 72, 9, 9, Text.translatable("wildfire_gender.label.exit"), + this.addDrawableChild(new WildfireButton(this.width / 2 + 178, j - 72, 9, 9, Text.literal("X"), button -> MinecraftClient.getInstance().setScreen(parent))); //Customization Tab diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java index 701047ab..72b69b35 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java @@ -24,14 +24,12 @@ import com.wildfire.main.config.Configuration; import java.util.UUID; -import com.mojang.blaze3d.systems.RenderSystem; import com.wildfire.gui.WildfireButton; import com.wildfire.main.GenderPlayer; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.tooltip.Tooltip; -import net.minecraft.client.render.GameRenderer; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.text.Text; import net.minecraft.util.Formatting; @@ -44,7 +42,6 @@ public class WildfireCharacterSettingsScreen extends BaseWildfireScreen { private static final Identifier BACKGROUND = new Identifier(WildfireGender.MODID, "textures/gui/settings_bg.png"); private WildfireSlider bounceSlider, floppySlider; - private int yPos = 0; private boolean bounceWarning; protected WildfireCharacterSettingsScreen(Screen parent, UUID uuid) { @@ -54,14 +51,12 @@ protected WildfireCharacterSettingsScreen(Screen parent, UUID uuid) { @Override public void init() { GenderPlayer aPlr = getPlayer(); - int x = this.width / 2; int y = this.height / 2; - - yPos = y - 47; + int yPos = y - 47; int xPos = x - 156 / 2 - 1; - this.addDrawableChild(new WildfireButton(this.width / 2 + 73, yPos - 11, 9, 9, Text.translatable("wildfire_gender.label.exit"), + this.addDrawableChild(new WildfireButton(this.width / 2 + 73, yPos - 11, 9, 9, Text.literal("X"), button -> MinecraftClient.getInstance().setScreen(parent))); this.addDrawableChild(new WildfireButton(xPos, yPos, 157, 20, @@ -71,7 +66,7 @@ public void init() { button.setMessage(Text.translatable("wildfire_gender.char_settings.physics", enablePhysics ? ENABLED : DISABLED)); GenderPlayer.saveGenderInfo(aPlr); } - }, Tooltip.of(Text.translatable("wildfire_gender.tooltip.breast_physics")))); + })); this.addDrawableChild(new WildfireButton(xPos, yPos + 20, 157, 20, Text.translatable("wildfire_gender.char_settings.hide_in_armor", aPlr.showBreastsInArmor() ? DISABLED : ENABLED), button -> { @@ -80,37 +75,36 @@ public void init() { button.setMessage(Text.translatable("wildfire_gender.char_settings.hide_in_armor", enableShowInArmor ? DISABLED : ENABLED)); GenderPlayer.saveGenderInfo(aPlr); } - }, Tooltip.of(Text.translatable("wildfire_gender.tooltip.hide_in_armor")))); + })); this.addDrawableChild(new WildfireButton(xPos, yPos + 40, 157, 20, Text.translatable("wildfire_gender.char_settings.override_armor_physics", aPlr.getArmorPhysicsOverride() ? ENABLED : DISABLED), button -> { boolean enableArmorPhysicsOverride = !aPlr.getArmorPhysicsOverride(); - - System.out.println("Override: " + enableArmorPhysicsOverride); if (aPlr.updateArmorPhysicsOverride(enableArmorPhysicsOverride )) { button.setMessage(Text.translatable("wildfire_gender.char_settings.override_armor_physics", aPlr.getArmorPhysicsOverride() ? ENABLED : DISABLED)); GenderPlayer.saveGenderInfo(aPlr); } - }, Tooltip.of(Text.translatable("wildfire_gender.tooltip.override_armor_physics")))); + }, Tooltip.of(Text.translatable("wildfire_gender.tooltip.override_armor_physics.line1") + .append("\n\n") + .append(Text.translatable("wildfire_gender.tooltip.override_armor_physics.line2"))) + )); - this.addDrawableChild(this.bounceSlider = new WildfireSlider(xPos - 1, yPos + 60, 158, 20, Configuration.BOUNCE_MULTIPLIER, aPlr.getBounceMultiplierRaw(), value -> { + this.addDrawableChild(this.bounceSlider = new WildfireSlider(xPos, yPos + 60, 158, 20, Configuration.BOUNCE_MULTIPLIER, aPlr.getBounceMultiplierRaw(), value -> { }, value -> { float bounceText = 3 * value; float v = Math.round(bounceText * 10) / 10f; bounceWarning = v > 1; - if (v == 3) { - return Text.translatable("wildfire_gender.slider.max_bounce"); - } else if (Math.round(bounceText * 100) / 100f == 0) { - return Text.translatable("wildfire_gender.slider.min_bounce"); - } - return Text.translatable("wildfire_gender.slider.bounce", v); + + if(v == 1.5f) return Text.translatable("wildfire_gender.slider.max_bounce"); + else if(v == 0f) return Text.translatable("wildfire_gender.slider.min_bounce"); + else return Text.translatable("wildfire_gender.slider.bounce", v); }, value -> { if (aPlr.updateBounceMultiplier(value)) { GenderPlayer.saveGenderInfo(aPlr); } })); - this.addDrawableChild(this.floppySlider = new WildfireSlider(xPos-1, yPos + 80, 158, 20, Configuration.FLOPPY_MULTIPLIER, aPlr.getFloppiness(), value -> { + this.addDrawableChild(this.floppySlider = new WildfireSlider(xPos, yPos + 80, 158, 20, Configuration.FLOPPY_MULTIPLIER, aPlr.getFloppiness(), value -> { }, value -> Text.translatable("wildfire_gender.slider.floppy", Math.round(value * 100)), value -> { if (aPlr.updateFloppiness(value)) { GenderPlayer.saveGenderInfo(aPlr); @@ -137,11 +131,12 @@ public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delt @Override public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { + if(client == null || client.world == null) return; super.render(ctx, mouseX, mouseY, delta); - //noinspection DataFlowIssue - PlayerEntity plrEntity = MinecraftClient.getInstance().world.getPlayerByUuid(this.playerUUID); + PlayerEntity plrEntity = client.world.getPlayerByUuid(this.playerUUID); int x = this.width / 2; int y = this.height / 2; + int yPos = y - 47; ctx.drawText(textRenderer, getTitle(), x - 79, yPos - 10, 4473924, false); if(plrEntity != null) { WildfireHelper.drawCenteredText(ctx, this.textRenderer, plrEntity.getDisplayName(), x, yPos - 30, 0xFFFFFF); diff --git a/src/main/resources/assets/wildfire_gender/lang/cs_cz.json b/src/main/resources/assets/wildfire_gender/lang/cs_cz.json index 360c76d2..5d2a0ef4 100644 --- a/src/main/resources/assets/wildfire_gender/lang/cs_cz.json +++ b/src/main/resources/assets/wildfire_gender/lang/cs_cz.json @@ -7,6 +7,9 @@ "wildfire_gender.player_list.sync_status": "Status synchronizace", "wildfire_gender.player_list.state.loading": "Načítaní dat...", "wildfire_gender.player_list.state.synced": "Hráč synchronizován", + "wildfire_gender.player_list.bounce_multiplier": "Houpání: %sx", + "wildfire_gender.player_list.breast_momentum": "Hybnost: %s%%", + "wildfire_gender.player_list.female_sounds": "Ženské zvuky: %s", "wildfire_gender.wardrobe.title": "Menu přizpůsobení", "wildfire_gender.wardrobe.slider.breast_size": "Velikost: %s%%", @@ -18,22 +21,12 @@ "wildfire_gender.appearance_settings.title": "Nastavení vzhledu", "wildfire_gender.char_settings.title": "Nastavení charakteru", "wildfire_gender.char_settings.physics": "Fyzika prsou: %s", - "wildfire_gender.tooltip.breast_physics": "Zapne/vypne fyziku prsou", "wildfire_gender.char_settings.hide_in_armor": "Schovat v brnění: %s", - "wildfire_gender.tooltip.hide_in_armor": "Schová model prsou, když je oblečeno brnění", "wildfire_gender.char_settings.hurt_sounds": "Ženské zvuky zranění: %s", "wildfire_gender.tooltip.hurt_sounds": "Nahradí normální zvuky zranění za ženské", "wildfire_gender.breast_customization.dual_physics": "Dvojí fyzika: %s", - "wildfire_gender.player_list.bounce_multiplier": "Houpání: %sx", - "wildfire_gender.player_list.breast_momentum": "Hybnost: %s%%", - "wildfire_gender.player_list.female_sounds": "Ženské zvuky: %s", - - "wildfire_gender.settings.title": "Wildfireova nastavení", - - "wildfire_gender.acknowledge.confirm": "Chápu", - "wildfire_gender.label.gender": "Pohlaví", "wildfire_gender.label.female": "Žena", "wildfire_gender.label.male": "Muž", @@ -43,8 +36,6 @@ "wildfire_gender.label.disabled": "Vypnuto", "wildfire_gender.label.yes": "Ano", "wildfire_gender.label.no": "Ne", - "wildfire_gender.label.exit": "X", - "wildfire_gender.label.too_far": "Příliš daleko", "wildfire_gender.label.with_creator": "Hrajete na serveru s tvůrcem tohohle modu!", "wildfire_gender.slider.bounce": "Intenzita houpání: %sx", @@ -53,6 +44,5 @@ "wildfire_gender.slider.max_bounce": "Anime fyzika!!!", "wildfire_gender.tooltip.bounce_warning": "Vysoké hodnoty 'Intenzity houpání' budou vypadat velmi nepřirozeně!", - "wildfire_gender.cancer_awareness.title": "Hej, právě probíhá měsíc povědomí o rakovině prsu!", - "wildfire_gender.cancer_awareness.description": "Klikni sem pro podpoření §dNadace Susan G. Komen§f!" + "wildfire_gender.cancer_awareness.title": "Hej, právě probíhá měsíc povědomí o rakovině prsu!" } diff --git a/src/main/resources/assets/wildfire_gender/lang/de_ch.json b/src/main/resources/assets/wildfire_gender/lang/de_ch.json index b24d8971..899759a2 100644 --- a/src/main/resources/assets/wildfire_gender/lang/de_ch.json +++ b/src/main/resources/assets/wildfire_gender/lang/de_ch.json @@ -7,6 +7,9 @@ "wildfire_gender.player_list.sync_status": "Synchronisierigsstatus", "wildfire_gender.player_list.state.loading": "Lade Spieler..", "wildfire_gender.player_list.state.synced": "Spieler glade!", + "wildfire_gender.player_list.bounce_multiplier": "Wackel Multiplikator: %sx", + "wildfire_gender.player_list.breast_momentum": "Brust Schwung: %s%%", + "wildfire_gender.player_list.female_sounds": "Wiibliche Soundeffekte: %s", "wildfire_gender.wardrobe.title": "Ahpassigsmenu", "wildfire_gender.wardrobe.slider.breast_size": "Brustgrössi: %s%%", @@ -18,22 +21,12 @@ "wildfire_gender.appearance_settings.title": "Ussehe", "wildfire_gender.char_settings.title": "Character Iinstellige", "wildfire_gender.char_settings.physics": "Brustphysik: %s", - "wildfire_gender.tooltip.breast_physics": "Brustphysik", "wildfire_gender.char_settings.hide_in_armor": "Versteckt mit Rüstig: %s", - "wildfire_gender.tooltip.hide_in_armor": "Versteckt das Brustmodel wenn e Rüstung a'zoge isch.", "wildfire_gender.char_settings.hurt_sounds": "Wiibliche Soundeffekte: %s", "wildfire_gender.tooltip.hurt_sounds": "Wiibliche Soundeffekt", "wildfire_gender.breast_customization.dual_physics": "Dualphysik: %s", - "wildfire_gender.player_list.bounce_multiplier": "Wackel Multiplikator: %sx", - "wildfire_gender.player_list.breast_momentum": "Brust Schwung: %s%%", - "wildfire_gender.player_list.female_sounds": "Wiibliche Soundeffekte: %s", - - "wildfire_gender.settings.title": "Wildfire's Iinstelligsmenü", - - "wildfire_gender.acknowledge.confirm": "Okay", - "wildfire_gender.label.gender": "Geschlecht", "wildfire_gender.label.female": "Wiiblich", "wildfire_gender.label.male": "Mänlich", @@ -43,8 +36,6 @@ "wildfire_gender.label.disabled": "Deaktiviert", "wildfire_gender.label.yes": "Ja", "wildfire_gender.label.no": "Nei", - "wildfire_gender.label.exit": "X", - "wildfire_gender.label.too_far": "Z'Wiit weg.", "wildfire_gender.label.with_creator": "Du spielsch uf nem Server mit dem Programmierer vu dem Mod!", "wildfire_gender.slider.bounce": "Wackel Intensität: %sx", @@ -53,6 +44,5 @@ "wildfire_gender.slider.max_bounce": "Anime Brust Physik!", "wildfire_gender.tooltip.bounce_warning": "Wenn 'Wackel Intensität' z'höch iigstellt isch, gsehts unnatürlich uus!", - "wildfire_gender.cancer_awareness.title": "Hey, s'isch Brustkrebsmonet", - "wildfire_gender.cancer_awareness.description": "Klicl da um an z'§dPink Ribbon§f z'spende!" + "wildfire_gender.cancer_awareness.title": "Hey, s'isch Brustkrebsmonet" } diff --git a/src/main/resources/assets/wildfire_gender/lang/de_de.json b/src/main/resources/assets/wildfire_gender/lang/de_de.json index 40bcfcf0..da32e5f3 100644 --- a/src/main/resources/assets/wildfire_gender/lang/de_de.json +++ b/src/main/resources/assets/wildfire_gender/lang/de_de.json @@ -18,11 +18,8 @@ "wildfire_gender.appearance_settings.title": "Aussehen", "wildfire_gender.char_settings.title": "Spieler Einstellungen", "wildfire_gender.char_settings.physics": "Brustphysik: %s", - "wildfire_gender.tooltip.breast_physics": "Brustphysik", "wildfire_gender.char_settings.hide_in_armor": "Versteckt mit Rüstung: %s", - "wildfire_gender.acknowledge.confirm": "Okay", - "wildfire_gender.label.gender": "Geschlecht", "wildfire_gender.label.female": "Weiblich", "wildfire_gender.label.male": "Männlich", @@ -32,8 +29,6 @@ "wildfire_gender.label.disabled": "Deaktiviert", "wildfire_gender.label.yes": "Ja", "wildfire_gender.label.no": "Nein", - "wildfire_gender.label.exit": "X", - "wildfire_gender.label.too_far": "Zu weit weg", "wildfire_gender.label.with_creator": "Du spielst auf einem Server mit dem Programmierer dieser Mod!", "wildfire_gender.slider.bounce": "Wackel Intensität: %sx", @@ -42,6 +37,5 @@ "wildfire_gender.slider.max_bounce": "Anime Brust Physik!", "wildfire_gender.tooltip.bounce_warning": "Wenn die 'Wackel Intensität' zu stark einstellen ist sieht es nicht sehr natürlich aus!", - "wildfire_gender.cancer_awareness.title": "Hey, es ist Monat des Brustkrebses!", - "wildfire_gender.cancer_awareness.description": "Klicke hier um an die §dDeutsche Krebshilfe§f zu spenden!" + "wildfire_gender.cancer_awareness.title": "Hey, es ist Monat des Brustkrebses!" } \ No newline at end of file diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index ccfa3159..c27ccbcd 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -8,6 +8,9 @@ "wildfire_gender.player_list.sync_status": "Sync Status", "wildfire_gender.player_list.state.loading": "Loading Data...", "wildfire_gender.player_list.state.synced": "Synced Player", + "wildfire_gender.player_list.bounce_multiplier": "Bounce Multiplier: %sx", + "wildfire_gender.player_list.breast_momentum": "Breast Momentum: %s%%", + "wildfire_gender.player_list.female_sounds": "Female Sounds: %s", "wildfire_gender.wardrobe.title": "Wildfire's Female Gender Mod", "wildfire_gender.breast_customization.tab_customization": "Customization", @@ -25,26 +28,17 @@ "wildfire_gender.appearance_settings.title": "Appearance Settings", "wildfire_gender.char_settings.title": "Character Settings", "wildfire_gender.char_settings.physics": "Breast Physics: %s", - "wildfire_gender.tooltip.breast_physics": "Enables Breast Physics", "wildfire_gender.char_settings.override_armor_physics": "Armor Physics: %s", - "wildfire_gender.tooltip.override_armor_physics": "Override Armor Physics Attributes When Wearing Armor", + "wildfire_gender.tooltip.override_armor_physics.line1": "Breast physics will no longer be reduced/suppressed by your equipped armor while enabled", + "wildfire_gender.tooltip.override_armor_physics.line2": "This is intended for use with resource packs that hide armor, or any similar minimal armor packs", "wildfire_gender.char_settings.hide_in_armor": "Hide In Armor: %s", - "wildfire_gender.tooltip.hide_in_armor": "Hide Breast Model When Wearing Armors", "wildfire_gender.char_settings.hurt_sounds": "Female Hurt Sounds: %s", - "wildfire_gender.tooltip.hurt_sounds": "Enables the Female Hurt Sounds", + "wildfire_gender.tooltip.hurt_sounds": "Your character will play a female hurt sound when taking damage if your gender is set to either Female or Other", "wildfire_gender.breast_customization.dual_physics": "Dual-Physics: %s", - "wildfire_gender.player_list.bounce_multiplier": "Bounce Multiplier: %sx", - "wildfire_gender.player_list.breast_momentum": "Breast Momentum: %s%%", - "wildfire_gender.player_list.female_sounds": "Female Sounds: %s", - - "wildfire_gender.settings.title": "Wildfire's Settings Menu", - - "wildfire_gender.acknowledge.confirm": "Okay", - "wildfire_gender.label.gender": "Gender", "wildfire_gender.label.female": "Female", "wildfire_gender.label.male": "Male", @@ -54,8 +48,6 @@ "wildfire_gender.label.disabled": "Disabled", "wildfire_gender.label.yes": "Yes", "wildfire_gender.label.no": "No", - "wildfire_gender.label.exit": "X", - "wildfire_gender.label.too_far": "Too Far Away", "wildfire_gender.label.with_creator": "You are playing on a server with the creator of this mod!", "wildfire_gender.slider.bounce": "Bounce Intensity: %sx", diff --git a/src/main/resources/assets/wildfire_gender/lang/es_mx.json b/src/main/resources/assets/wildfire_gender/lang/es_mx.json index 449156f0..e5bebcc0 100644 --- a/src/main/resources/assets/wildfire_gender/lang/es_mx.json +++ b/src/main/resources/assets/wildfire_gender/lang/es_mx.json @@ -2,13 +2,14 @@ "category.wildfire_gender.generic": "Mod del genero femenino de Wildfire", "key.wildfire_gender.gender_menu": "Menu de genero", - "wildfire_gender.hurt.female": "Jugadora Herida", - "wildfire_gender.player_list.title": "Mod del genero femenino", "wildfire_gender.player_list.settings_button": "Ajustes", "wildfire_gender.player_list.sync_status": "Estatus de sincronización", "wildfire_gender.player_list.state.loading": "Cargando datos...", "wildfire_gender.player_list.state.synced": "Jugador Sincronizado", + "wildfire_gender.player_list.bounce_multiplier": "Multiplicador de rebote: %sx", + "wildfire_gender.player_list.breast_momentum": "Ímpetu del busto: %s%%", + "wildfire_gender.player_list.female_sounds": "Sonidos femeninos: %s", "wildfire_gender.wardrobe.title": "Menú de Personalización", "wildfire_gender.wardrobe.slider.breast_size": "Tamaño del busto: %s%%", @@ -20,22 +21,12 @@ "wildfire_gender.appearance_settings.title": "Ajustes de apariencia", "wildfire_gender.char_settings.title": "Ajustes de personaje", "wildfire_gender.char_settings.physics": "Físicas del busto: %s", - "wildfire_gender.tooltip.breast_physics": "Activa las físicas del busto", "wildfire_gender.char_settings.hide_in_armor": "Esconder en la armadura: %s", - "wildfire_gender.tooltip.hide_in_armor": "Esconde el modelo del busto al usar armadura.", "wildfire_gender.char_settings.hurt_sounds": "Sonidos de dolor: %s", "wildfire_gender.tooltip.hurt_sounds": "Habilita sonidos nuevos femeninos", "wildfire_gender.breast_customization.dual_physics": "Físicas duales: %s", - "wildfire_gender.player_list.bounce_multiplier": "Multiplicador de rebote: %sx", - "wildfire_gender.player_list.breast_momentum": "Ímpetu del busto: %s%%", - "wildfire_gender.player_list.female_sounds": "Sonidos femeninos: %s", - - "wildfire_gender.settings.title": "Menu de ajustes de Wildfire", - - "wildfire_gender.acknowledge.confirm": "Okey", - "wildfire_gender.label.gender": "Género", "wildfire_gender.label.female": "Mujer", "wildfire_gender.label.male": "Hombre", @@ -45,8 +36,6 @@ "wildfire_gender.label.disabled": "No", "wildfire_gender.label.yes": "Sí", "wildfire_gender.label.no": "No", - "wildfire_gender.label.exit": "X", - "wildfire_gender.label.too_far": "¡Muy lejos!", "wildfire_gender.label.with_creator": "¡Estás jugando en un servidor con la creadora del mod!", "wildfire_gender.slider.bounce": "Intensidad de rebote: %sx", @@ -55,6 +44,5 @@ "wildfire_gender.slider.max_bounce": "¡¡¡FÍSICAS DE ANIME!!!", "wildfire_gender.tooltip.bounce_warning": "Colocar la 'Intensidad de rebote' muy alta se vera muy anti-natural.", - "wildfire_gender.cancer_awareness.title": "Hey, es el mes de concientización sobre el cáncer de mama", - "wildfire_gender.cancer_awareness.description": "Haz click aquí para donar a §dSusan G. Komen Foundation§f!" + "wildfire_gender.cancer_awareness.title": "Hey, es el mes de concientización sobre el cáncer de mama" } diff --git a/src/main/resources/assets/wildfire_gender/lang/fr_fr.json b/src/main/resources/assets/wildfire_gender/lang/fr_fr.json index c0466b48..030ba978 100644 --- a/src/main/resources/assets/wildfire_gender/lang/fr_fr.json +++ b/src/main/resources/assets/wildfire_gender/lang/fr_fr.json @@ -7,6 +7,9 @@ "wildfire_gender.player_list.sync_status": "Sync Statut", "wildfire_gender.player_list.state.loading": "Chargement des Données...", "wildfire_gender.player_list.state.synced": "Joueur Synchronisé", + "wildfire_gender.player_list.bounce_multiplier": "Multiplicateur de Rebond: %sx", + "wildfire_gender.player_list.breast_momentum": "Élan de la Poitrine: %s%%", + "wildfire_gender.player_list.female_sounds": "Sons Féminins: %s", "wildfire_gender.wardrobe.title": "Menu de Personnalisation", "wildfire_gender.wardrobe.slider.breast_size": "Taille de la Poitrine: %s%%", @@ -18,22 +21,12 @@ "wildfire_gender.appearance_settings.title": "Paramètres d'Apparence", "wildfire_gender.char_settings.title": "Paramètres de Personnage", "wildfire_gender.char_settings.physics": "Physiques de la Poitrine: %s", - "wildfire_gender.tooltip.breast_physics": "Activer Physique de la Poitrine", "wildfire_gender.char_settings.hide_in_armor": "Cacher dans l'Armure: %s", - "wildfire_gender.tooltip.hide_in_armor": "Cacher la Poitrine avec une Armure Équippée", "wildfire_gender.char_settings.hurt_sounds": "Sons Blessés Féminins: %s", "wildfire_gender.tooltip.hurt_sounds": "Active les Sons Blessés Féminins", "wildfire_gender.breast_customization.dual_physics": "Double-Physiques: %s", - "wildfire_gender.player_list.bounce_multiplier": "Multiplicateur de Rebond: %sx", - "wildfire_gender.player_list.breast_momentum": "Élan de la Poitrine: %s%%", - "wildfire_gender.player_list.female_sounds": "Sons Féminins: %s", - - "wildfire_gender.settings.title": "Menu de Paramètres de Wildfire", - - "wildfire_gender.acknowledge.confirm": "Ok", - "wildfire_gender.label.gender": "Genre", "wildfire_gender.label.female": "Féminin", "wildfire_gender.label.male": "Masculin", @@ -43,8 +36,6 @@ "wildfire_gender.label.disabled": "Desactivé", "wildfire_gender.label.yes": "Oui", "wildfire_gender.label.no": "Non", - "wildfire_gender.label.exit": "X", - "wildfire_gender.label.too_far": "Trop Loin", "wildfire_gender.label.with_creator": "Vous êtes en train de jouer sur un serveur avec la créatrice de ce mod!", "wildfire_gender.slider.bounce": "Intensité de Rebond: %sx", @@ -53,6 +44,5 @@ "wildfire_gender.slider.max_bounce": "Anime Breast Physics!!!", "wildfire_gender.tooltip.bounce_warning": "Mettre 'Intensité de Rebond' sur une valeur élevée aura pas l'air naturel!", - "wildfire_gender.cancer_awareness.title": "Hey, c'est le Mois de la Sensibilisation au Cancer du Sein!", - "wildfire_gender.cancer_awareness.description": "Cliquez ici pour faire un don à la §dFondation Susan G. Komen§f!" + "wildfire_gender.cancer_awareness.title": "Hey, c'est le Mois de la Sensibilisation au Cancer du Sein!" } diff --git a/src/main/resources/assets/wildfire_gender/lang/lol_us.json b/src/main/resources/assets/wildfire_gender/lang/lol_us.json index 7e5d07e2..3ea21618 100644 --- a/src/main/resources/assets/wildfire_gender/lang/lol_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/lol_us.json @@ -7,6 +7,9 @@ "wildfire_gender.player_list.sync_status": "Sinc stauz", "wildfire_gender.player_list.state.loading": "Dawnludin fush...", "wildfire_gender.player_list.state.synced": "Dawnludedd kat", + "wildfire_gender.player_list.bounce_multiplier": "Crazy Bouncy: %sx", + "wildfire_gender.player_list.breast_momentum": "Haw huvy: %s%%", + "wildfire_gender.player_list.female_sounds": "Nyas: %s", "wildfire_gender.wardrobe.title": "kat two setinz", "wildfire_gender.wardrobe.slider.breast_size": "Baluon saiz: %s%%", @@ -18,22 +21,12 @@ "wildfire_gender.appearance_settings.title": "Wat u luk laik", "wildfire_gender.char_settings.title": "Wat iz dat sopsoss 2 luk laik", "wildfire_gender.char_settings.physics": "Bouncy-Bouncy: %s", - "wildfire_gender.tooltip.breast_physics": "Maiks u bouncy.", "wildfire_gender.char_settings.hide_in_armor": "Hait shirt plz: %s", - "wildfire_gender.tooltip.hide_in_armor": "Haits shirt from u cuz it bad.", "wildfire_gender.char_settings.hurt_sounds": "Women kat oofs: %s", "wildfire_gender.tooltip.hurt_sounds": "Maiks kats oof.", "wildfire_gender.breast_customization.dual_physics": "Double bouncy-bouncy: %s", - "wildfire_gender.player_list.bounce_multiplier": "Crazy Bouncy: %sx", - "wildfire_gender.player_list.breast_momentum": "Haw huvy: %s%%", - "wildfire_gender.player_list.female_sounds": "Nyas: %s", - - "wildfire_gender.settings.title": "Wildfire's Setinz Mood", - - "wildfire_gender.acknowledge.confirm": "K", - "wildfire_gender.label.gender": "Gunda", "wildfire_gender.label.female": "Women", "wildfire_gender.label.male": "MAN", @@ -43,8 +36,6 @@ "wildfire_gender.label.disabled": "dissaibedd", "wildfire_gender.label.yes": "pls", "wildfire_gender.label.no": "naw", - "wildfire_gender.label.exit": "X", - "wildfire_gender.label.too_far": "Whai u runnin!", "wildfire_gender.label.with_creator": "pett owna iz un serva gg", "wildfire_gender.slider.bounce": "Crazy Bouncy: %sx", @@ -53,6 +44,5 @@ "wildfire_gender.slider.max_bounce": "BALONS!", "wildfire_gender.tooltip.bounce_warning": "Usin 'Crazy Bouncy' 2 haiar valve iz luk badd!", - "wildfire_gender.cancer_awareness.title": "Hay, It cancor awarniss mouth!", - "wildfire_gender.cancer_awareness.description": "Tuch haer 2 spend munies 2 §dSusan G. Komen Fauntion§f!" + "wildfire_gender.cancer_awareness.title": "Hay, It cancor awarniss mouth!" } diff --git a/src/main/resources/assets/wildfire_gender/lang/uk_ua.json b/src/main/resources/assets/wildfire_gender/lang/uk_ua.json index 9f26a0fc..15e68b0d 100644 --- a/src/main/resources/assets/wildfire_gender/lang/uk_ua.json +++ b/src/main/resources/assets/wildfire_gender/lang/uk_ua.json @@ -2,13 +2,14 @@ "category.wildfire_gender.generic": "Wildfire's Female Gender Mod", "key.wildfire_gender.gender_menu": "Меню жіночої статі", - "wildfire_gender.hurt.female": "Жіночого гравця поранено", - "wildfire_gender.player_list.title": "Female Gender Mod", "wildfire_gender.player_list.settings_button": "Налаштування", "wildfire_gender.player_list.sync_status": "Синхронізація стану", "wildfire_gender.player_list.state.loading": "Завантаження даних...", "wildfire_gender.player_list.state.synced": "Синхронізований гравець", + "wildfire_gender.player_list.bounce_multiplier": "Множник пружності: %sx", + "wildfire_gender.player_list.breast_momentum": "Імпульс грудей: %s%%", + "wildfire_gender.player_list.female_sounds": "Жіночі звуки: %s", "wildfire_gender.wardrobe.title": "Меню кастомізації", "wildfire_gender.wardrobe.slider.breast_size": "Розмір грудей: %s%%", @@ -20,22 +21,12 @@ "wildfire_gender.appearance_settings.title": "Параметри зовнішнього вигляду", "wildfire_gender.char_settings.title": "Параметри персонажа", "wildfire_gender.char_settings.physics": "Фізика грудей: %s", - "wildfire_gender.tooltip.breast_physics": "Вмикає фізику грудей", "wildfire_gender.char_settings.hide_in_armor": "Приховувати під бронею: %s", - "wildfire_gender.tooltip.hide_in_armor": "Приховує модель грудей під час носіння броні", "wildfire_gender.char_settings.hurt_sounds": "Жіночі звуки поранення: %s", "wildfire_gender.tooltip.hurt_sounds": "Вмикає жіночі звуки поранення", "wildfire_gender.breast_customization.dual_physics": "Подвійна фізика %s", - "wildfire_gender.player_list.bounce_multiplier": "Множник пружності: %sx", - "wildfire_gender.player_list.breast_momentum": "Імпульс грудей: %s%%", - "wildfire_gender.player_list.female_sounds": "Жіночі звуки: %s", - - "wildfire_gender.settings.title": "Меню налаштувань Wildfire", - - "wildfire_gender.acknowledge.confirm": "Згоден", - "wildfire_gender.label.gender": "Стать", "wildfire_gender.label.female": "Жінка", "wildfire_gender.label.male": "Чоловік", @@ -45,8 +36,6 @@ "wildfire_gender.label.disabled": "Вимкнено", "wildfire_gender.label.yes": "Так", "wildfire_gender.label.no": "Ні", - "wildfire_gender.label.exit": "X", - "wildfire_gender.label.too_far": "Занадто далеко", "wildfire_gender.label.with_creator": "Ви граєте на одному ж сервері з творцем цього моду!", "wildfire_gender.slider.bounce": "Інтенсивність пружності: %sx", @@ -55,6 +44,5 @@ "wildfire_gender.slider.max_bounce": "Аніме фізика грудей!!!", "wildfire_gender.tooltip.bounce_warning": "Встановлення «Інтенсивності пружності» на високе значення виглядатиме дуже неприродно!", - "wildfire_gender.cancer_awareness.title": "Привіт, сьогодні Місяць боротьби з раком молочної залози!", - "wildfire_gender.cancer_awareness.description": "Натисніть тут, щоб зробити пожертву §dSusan G. Komen Foundation§f!" + "wildfire_gender.cancer_awareness.title": "Привіт, сьогодні Місяць боротьби з раком молочної залози!" } diff --git a/src/main/resources/assets/wildfire_gender/lang/zh_cn.json b/src/main/resources/assets/wildfire_gender/lang/zh_cn.json index 00f51f3f..3e83cf50 100644 --- a/src/main/resources/assets/wildfire_gender/lang/zh_cn.json +++ b/src/main/resources/assets/wildfire_gender/lang/zh_cn.json @@ -2,13 +2,14 @@ "category.wildfire_gender.generic": "Wildfire's Female Gender Mod", "key.wildfire_gender.gender_menu": "性别菜单", - "wildfire_gender.hurt.female": "女玩家受伤", - "wildfire_gender.player_list.title": "Female Gender Mod", "wildfire_gender.player_list.settings_button": "设置", "wildfire_gender.player_list.sync_status": "同步状态", "wildfire_gender.player_list.state.loading": "正在加载数据...", "wildfire_gender.player_list.state.synced": "同步玩家", + "wildfire_gender.player_list.bounce_multiplier": "反弹系数:%sx", + "wildfire_gender.player_list.breast_momentum": "乳房动量:%s%%", + "wildfire_gender.player_list.female_sounds": "女性声音:%s", "wildfire_gender.wardrobe.title": "自定义菜单", "wildfire_gender.wardrobe.slider.breast_size": "乳房大小:%s%%", @@ -20,24 +21,12 @@ "wildfire_gender.appearance_settings.title": "外观设置", "wildfire_gender.char_settings.title": "角色设置", "wildfire_gender.char_settings.physics": "乳房物理:%s", - "wildfire_gender.tooltip.breast_physics": "启用乳房物理", - "wildfire_gender.char_settings.armor_physics": "盔甲物理:%s", - "wildfire_gender.tooltip.armor_physics": "在装备盔甲的情况下实现乳房物理", "wildfire_gender.char_settings.hide_in_armor": "隐藏在盔甲中:%s", - "wildfire_gender.tooltip.hide_in_armor": "穿着盔甲时隐藏乳房模型", "wildfire_gender.char_settings.hurt_sounds": "女性受伤的声音:%s", "wildfire_gender.tooltip.hurt_sounds": "使女性受伤的声音", "wildfire_gender.breast_customization.dual_physics": "对偶物理:%s", - "wildfire_gender.player_list.bounce_multiplier": "反弹系数:%sx", - "wildfire_gender.player_list.breast_momentum": "乳房动量:%s%%", - "wildfire_gender.player_list.female_sounds": "女性声音:%s", - - "wildfire_gender.settings.title": "Wildfire的设置菜单", - - "wildfire_gender.acknowledge.confirm": "好", - "wildfire_gender.label.gender": "性别", "wildfire_gender.label.female": "女性", "wildfire_gender.label.male": "男性", @@ -47,8 +36,6 @@ "wildfire_gender.label.disabled": "禁用", "wildfire_gender.label.yes": "是", "wildfire_gender.label.no": "否", - "wildfire_gender.label.exit": "❌", - "wildfire_gender.label.too_far": "太远了", "wildfire_gender.label.with_creator": "您正在与该模组的作者一起在服务器上玩!", "wildfire_gender.slider.bounce": "弹跳强度:%sx", @@ -57,6 +44,5 @@ "wildfire_gender.slider.max_bounce": "动漫乳房物理!!!", "wildfire_gender.tooltip.bounce_warning": "将“反弹强度”设置为高值看起来非常不自然!", - "wildfire_gender.cancer_awareness.title": "嘿,这是乳腺癌宣传月!", - "wildfire_gender.cancer_awareness.description": "点击这里捐赠给§dSusan G. Komen Foundation§f!§r" + "wildfire_gender.cancer_awareness.title": "嘿,这是乳腺癌宣传月!" } From 502ffe9085502cc039262a4d4aaae143a0108e18 Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 24 Oct 2023 21:29:43 -0600 Subject: [PATCH 033/238] Use clearAndInit() instead --- .../gui/screen/WardrobeBrowserScreen.java | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index 220e57b9..b1d9eec5 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -48,7 +48,6 @@ public class WardrobeBrowserScreen extends BaseWildfireScreen { private static final Identifier TXTR_RIBBON = new Identifier(WildfireGender.MODID, "textures/bc_ribbon.png"); private static final UUID CREATOR_UUID = UUID.fromString("33c937ae-6bfc-423e-a38e-3a613e7c1256"); private static final boolean isBreastCancerAwarenessMonth = Calendar.getInstance().get(Calendar.MONTH) == Calendar.OCTOBER; - private WildfireButton appearanceSettings, characterSettings; public WardrobeBrowserScreen(Screen parent, UUID uuid) { super(Text.translatable("wildfire_gender.wardrobe.title"), parent, uuid); @@ -68,15 +67,16 @@ public void init() { if (plr.updateGender(gender)) { button.setMessage(getGenderLabel(gender)); GenderPlayer.saveGenderInfo(plr); - updateCharacterOptionButtons(); + clearAndInit(); } })); - this.addDrawableChild(appearanceSettings = new WildfireButton(this.width / 2 - 42, y - 32, 158, 20, Text.translatable("wildfire_gender.appearance_settings.title").append("..."), - button -> MinecraftClient.getInstance().setScreen(new WildfireBreastCustomizationScreen(WardrobeBrowserScreen.this, this.playerUUID)))); - this.addDrawableChild(characterSettings = new WildfireButton(this.width / 2 - 42, y - 12, 158, 20, Text.translatable("wildfire_gender.char_settings.title").append("..."), + if (plr.getGender().canHaveBreasts()) { + this.addDrawableChild(new WildfireButton(this.width / 2 - 42, y - 32, 158, 20, Text.translatable("wildfire_gender.appearance_settings.title").append("..."), + button -> MinecraftClient.getInstance().setScreen(new WildfireBreastCustomizationScreen(WardrobeBrowserScreen.this, this.playerUUID)))); + } + this.addDrawableChild(new WildfireButton(this.width / 2 - 42, y - (plr.getGender().canHaveBreasts() ? 12 : 32), 158, 20, Text.translatable("wildfire_gender.char_settings.title").append("..."), button -> MinecraftClient.getInstance().setScreen(new WildfireCharacterSettingsScreen(WardrobeBrowserScreen.this, this.playerUUID)))); - updateCharacterOptionButtons(); this.addDrawableChild(new WildfireButton(this.width / 2 + 111, y - 63, 9, 9, Text.literal("X"), button -> MinecraftClient.getInstance().setScreen(parent))); @@ -88,12 +88,6 @@ private Text getGenderLabel(Gender gender) { return Text.translatable("wildfire_gender.label.gender").append(" - ").append(gender.getDisplayName()); } - private void updateCharacterOptionButtons() { - int y = this.height / 2; - boolean canHaveBreasts = appearanceSettings.visible = getPlayer().getGender().canHaveBreasts(); - characterSettings.setY(y - (canHaveBreasts ? 12 : 32)); - } - @Override public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delta) { super.renderBackground(ctx, mouseX, mouseY, delta); From 18b78fe59fe9d355125606b7173e9b1edf997eb7 Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 26 Nov 2023 19:34:55 -0700 Subject: [PATCH 034/238] Copy player breast settings to armor stands This splits rendering into two different renderers, one for the base skin, and another for the armor layer, which brings rendering more in-line with how vanilla handles such rendering. This additionally splits GenderPlayer into two classes, a new EntityConfig, and the now renamed PlayerConfig, which subclasses the former. The way that this attaches the renderer to the armor stand is that when a player equips a chestplate onto an armor stand, the mod will copy their settings onto the chestplate item stack in the `WildfireGender` NBT tag, and read that on the client-side for rendering. This shouldn't cause any issues with modded armors, as it will only do so when the armor config specifies to do so, which by default will only apply on armors with complete physics resistance (with gold added as a manual exception). --- .../java/com/wildfire/api/IGenderArmor.java | 15 + .../java/com/wildfire/api/WildfireAPI.java | 49 +- .../gui/WildfireBreastPresetList.java | 14 - .../gui/screen/BaseWildfireScreen.java | 4 +- .../gui/screen/WardrobeBrowserScreen.java | 8 +- .../WildfireBreastCustomizationScreen.java | 10 +- .../WildfireCharacterSettingsScreen.java | 18 +- src/main/java/com/wildfire/main/Gender.java | 35 ++ .../wildfire/main/WildfireEventHandler.java | 31 +- .../com/wildfire/main/WildfireGender.java | 30 +- .../wildfire/main/WildfireGenderServer.java | 3 +- .../com/wildfire/main/WildfireHelper.java | 35 ++ .../wildfire/main/config/Configuration.java | 3 + .../wildfire/main/config/GenderConfigKey.java | 2 +- .../main/{ => entitydata}/Breasts.java | 50 +- .../main/entitydata/EntityConfig.java | 169 +++++++ .../PlayerConfig.java} | 124 ++--- .../wildfire/main/networking/SyncPacket.java | 12 +- .../main/networking/WildfireSync.java | 39 +- .../mixins/ArmorStandEntityMixin.java | 67 +++ .../mixins/ArmorStandEntityRendererMixin.java | 45 ++ ...Mixin.java => BreastPhysicsTickMixin.java} | 42 +- .../wildfire/mixins/LivingEntityMixin.java | 6 +- .../wildfire/mixins/PlayerRenderMixin.java | 10 +- .../com/wildfire/physics/BreastPhysics.java | 118 ++--- .../com/wildfire/render/GenderArmorLayer.java | 184 ++++++++ .../java/com/wildfire/render/GenderLayer.java | 440 ++++++++---------- .../render/armor/EmptyGenderArmor.java | 7 +- .../render/armor/SimpleGenderArmor.java | 22 +- .../resources/wildfire_gender.mixins.json | 6 +- 30 files changed, 1055 insertions(+), 543 deletions(-) create mode 100644 src/main/java/com/wildfire/main/Gender.java rename src/main/java/com/wildfire/main/{ => entitydata}/Breasts.java (60%) create mode 100644 src/main/java/com/wildfire/main/entitydata/EntityConfig.java rename src/main/java/com/wildfire/main/{GenderPlayer.java => entitydata/PlayerConfig.java} (72%) create mode 100644 src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java create mode 100644 src/main/java/com/wildfire/mixins/ArmorStandEntityRendererMixin.java rename src/main/java/com/wildfire/mixins/{PlayerEntityMixin.java => BreastPhysicsTickMixin.java} (53%) create mode 100644 src/main/java/com/wildfire/render/GenderArmorLayer.java diff --git a/src/main/java/com/wildfire/api/IGenderArmor.java b/src/main/java/com/wildfire/api/IGenderArmor.java index 5cc4bf4f..4a72d1fd 100644 --- a/src/main/java/com/wildfire/api/IGenderArmor.java +++ b/src/main/java/com/wildfire/api/IGenderArmor.java @@ -68,4 +68,19 @@ default float physicsResistance() { default float tightness() { return 0; } + + /** + * Determines whether armor stands should copy the breast settings of the player equipping this chestplate + * onto it. + * + * @implNote If this returns {@code true}, any time a player equips this chestplate onto an armor stand, their + * breast settings will be copied onto the item stack's NBT under the key {@code WildfireGender}. + * + * @return Defaults to returning {@code true} if this armor {@link #coversBreasts() covers the breasts} + * (and {@link #alwaysHidesBreasts() doesn't hide them}), and {@link #physicsResistance() has + * complete physics resistance}. + */ + default boolean armorStandsCopySettings() { + return !alwaysHidesBreasts() && coversBreasts() && physicsResistance() == 1f; + } } \ No newline at end of file diff --git a/src/main/java/com/wildfire/api/WildfireAPI.java b/src/main/java/com/wildfire/api/WildfireAPI.java index 8496e260..660fb91c 100644 --- a/src/main/java/com/wildfire/api/WildfireAPI.java +++ b/src/main/java/com/wildfire/api/WildfireAPI.java @@ -18,24 +18,31 @@ package com.wildfire.api; -import com.wildfire.main.GenderPlayer; +import com.wildfire.main.config.Configuration; +import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.WildfireGender; +import com.wildfire.main.Gender; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.UUID; +import java.util.concurrent.Future; +@SuppressWarnings("unused") public class WildfireAPI { - private static Map GENDER_ARMORS = new HashMap<>(); + private static final Map GENDER_ARMORS = new HashMap<>(); /** - * Add custom attributes to the armor you apply this to. + * Add custom physics resistance attributes to a chestplate * - * @param item the item that you are adding {@link IGenderArmor} to. - * @param genderArmor the class implementing {@link IGenderArmor} to apply to the item. + * @param item the item that you are linking this {@link IGenderArmor} to + * @param genderArmor the class implementing the {@link IGenderArmor} to apply to the item * @see IGenderArmor */ public static void addGenderArmor(Item item, IGenderArmor genderArmor) { @@ -43,37 +50,43 @@ public static void addGenderArmor(Item item, IGenderArmor genderArmor) { } /** - * Add custom attributes to the armor you apply this to. + * Get the config for a {@link PlayerEntity} * - * @param uuid the uuid of the {@link net.minecraft.entity.player.PlayerEntity }. + * @param uuid the uuid of the target {@link PlayerEntity} * @see IGenderArmor */ - public static GenderPlayer getPlayerById(UUID uuid) { + public static @Nullable PlayerConfig getPlayerById(UUID uuid) { return WildfireGender.getPlayerById(uuid); } /** - * Get the player's {@link com.wildfire.main.GenderPlayer.Gender }. + * Get the player's {@link Gender} * - * @param uuid the uuid of the {@link PlayerEntity }. - * @see GenderPlayer.Gender + * @param uuid the uuid of the target {@link PlayerEntity}. + * @see Gender */ - public static GenderPlayer.Gender getPlayerGender(UUID uuid) { - return WildfireGender.getPlayerById(uuid).getGender(); + public static @Nonnull Gender getPlayerGender(UUID uuid) { + PlayerConfig cfg = WildfireGender.getPlayerById(uuid); + if(cfg == null) return Configuration.GENDER.getDefault(); + return cfg.getGender(); } /** - * Load the cached Gender Settings file for the specified {@link UUID } + *

Load the cached Gender Settings file for the specified {@link UUID}

* - * @param uuid the uuid of the {@link PlayerEntity }. + *

You should avoid using this unless you need to, as the mod will do this for you when loading a player entity.

+ * + * @param uuid the uuid of the target {@link PlayerEntity} * @param markForSync true if you want to send the gender settings to the server upon loading. */ - public static void loadGenderInfo(UUID uuid, boolean markForSync) { - WildfireGender.loadGenderInfoAsync(uuid, markForSync); + public static Future> loadGenderInfo(UUID uuid, boolean markForSync) { + return WildfireGender.loadGenderInfo(uuid, markForSync); } /** - * Get the list of armors supported by Wildfire's Female Gender Mod. + * Get every registered {@link IGenderArmor custom armor configuration} + * + * @implNote This does not provide vanilla armor configurations; see {@link com.wildfire.render.armor.SimpleGenderArmor} for that. */ public static Map getGenderArmors() { return GENDER_ARMORS; diff --git a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java index 75696637..df0ea1a0 100644 --- a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java +++ b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java @@ -1,33 +1,19 @@ package com.wildfire.gui; -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.mojang.blaze3d.systems.RenderSystem; import com.wildfire.gui.screen.WildfireBreastCustomizationScreen; -import com.wildfire.main.GenderPlayer; import com.wildfire.main.WildfireGender; import com.wildfire.main.config.BreastPresetConfiguration; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; -import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; import net.minecraft.client.gui.widget.EntryListWidget; -import net.minecraft.client.network.ClientPlayNetworkHandler; -import net.minecraft.text.Style; import net.minecraft.text.Text; -import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; -import java.io.File; -import java.io.FileReader; -import java.nio.file.Path; -import java.time.format.TextStyle; import java.util.ArrayList; -import java.util.Map; public class WildfireBreastPresetList extends EntryListWidget { diff --git a/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java b/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java index bf30dfa1..ec0a7f56 100644 --- a/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java +++ b/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java @@ -18,7 +18,7 @@ package com.wildfire.gui.screen; -import com.wildfire.main.GenderPlayer; +import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.WildfireGender; import java.util.UUID; @@ -36,7 +36,7 @@ protected BaseWildfireScreen(Text title, Screen parent, UUID uuid) { this.playerUUID = uuid; } - public GenderPlayer getPlayer() { + public PlayerConfig getPlayer() { return WildfireGender.getPlayerById(this.playerUUID); } diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index b1d9eec5..62216b0d 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -18,14 +18,14 @@ package com.wildfire.gui.screen; -import com.wildfire.main.GenderPlayer.Gender; +import com.wildfire.main.Gender; import com.wildfire.main.WildfireGender; import java.util.Calendar; import java.util.UUID; import com.wildfire.gui.WildfireButton; -import com.wildfire.main.GenderPlayer; +import com.wildfire.main.entitydata.PlayerConfig; import com.mojang.blaze3d.systems.RenderSystem; import com.wildfire.main.WildfireHelper; import net.minecraft.client.MinecraftClient; @@ -56,7 +56,7 @@ public WardrobeBrowserScreen(Screen parent, UUID uuid) { @Override public void init() { int y = this.height / 2; - GenderPlayer plr = getPlayer(); + PlayerConfig plr = getPlayer(); this.addDrawableChild(new WildfireButton(this.width / 2 - 42, y - 52, 158, 20, getGenderLabel(plr.getGender()), button -> { Gender gender = switch (plr.getGender()) { @@ -66,7 +66,7 @@ public void init() { }; if (plr.updateGender(gender)) { button.setMessage(getGenderLabel(gender)); - GenderPlayer.saveGenderInfo(plr); + PlayerConfig.saveGenderInfo(plr); clearAndInit(); } })); diff --git a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java index e98ef296..95d28604 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java @@ -22,8 +22,8 @@ import com.wildfire.gui.WildfireBreastPresetList; import com.wildfire.gui.WildfireButton; import com.wildfire.gui.WildfireSlider; -import com.wildfire.main.Breasts; -import com.wildfire.main.GenderPlayer; +import com.wildfire.main.entitydata.Breasts; +import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.config.Configuration; import com.wildfire.main.config.BreastPresetConfiguration; import it.unimi.dsi.fastutil.floats.FloatConsumer; @@ -54,11 +54,11 @@ public WildfireBreastCustomizationScreen(Screen parent, UUID uuid) { public void init() { int j = this.height / 2 - 11; - GenderPlayer plr = getPlayer(); + PlayerConfig plr = getPlayer(); Breasts breasts = plr.getBreasts(); FloatConsumer onSave = value -> { //Just save as we updated the actual value in value change - GenderPlayer.saveGenderInfo(plr); + PlayerConfig.saveGenderInfo(plr); }; this.addDrawableChild(new WildfireButton(this.width / 2 + 178, j - 72, 9, 9, Text.literal("X"), @@ -124,7 +124,7 @@ public void init() { boolean isUniboob = !breasts.isUniboob(); if (breasts.updateUniboob(isUniboob)) { button.setMessage(Text.translatable("wildfire_gender.breast_customization.dual_physics", Text.translatable(isUniboob ? "wildfire_gender.label.no" : "wildfire_gender.label.yes"))); - GenderPlayer.saveGenderInfo(plr); + PlayerConfig.saveGenderInfo(plr); } })); diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java index 72b69b35..7b5f4b45 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java @@ -25,7 +25,7 @@ import java.util.UUID; import com.wildfire.gui.WildfireButton; -import com.wildfire.main.GenderPlayer; +import com.wildfire.main.entitydata.PlayerConfig; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; @@ -50,7 +50,7 @@ protected WildfireCharacterSettingsScreen(Screen parent, UUID uuid) { @Override public void init() { - GenderPlayer aPlr = getPlayer(); + PlayerConfig aPlr = getPlayer(); int x = this.width / 2; int y = this.height / 2; int yPos = y - 47; @@ -64,7 +64,7 @@ public void init() { boolean enablePhysics = !aPlr.hasBreastPhysics(); if (aPlr.updateBreastPhysics(enablePhysics)) { button.setMessage(Text.translatable("wildfire_gender.char_settings.physics", enablePhysics ? ENABLED : DISABLED)); - GenderPlayer.saveGenderInfo(aPlr); + PlayerConfig.saveGenderInfo(aPlr); } })); @@ -73,7 +73,7 @@ public void init() { boolean enableShowInArmor = !aPlr.showBreastsInArmor(); if (aPlr.updateShowBreastsInArmor(enableShowInArmor)) { button.setMessage(Text.translatable("wildfire_gender.char_settings.hide_in_armor", enableShowInArmor ? DISABLED : ENABLED)); - GenderPlayer.saveGenderInfo(aPlr); + PlayerConfig.saveGenderInfo(aPlr); } })); @@ -82,14 +82,14 @@ public void init() { boolean enableArmorPhysicsOverride = !aPlr.getArmorPhysicsOverride(); if (aPlr.updateArmorPhysicsOverride(enableArmorPhysicsOverride )) { button.setMessage(Text.translatable("wildfire_gender.char_settings.override_armor_physics", aPlr.getArmorPhysicsOverride() ? ENABLED : DISABLED)); - GenderPlayer.saveGenderInfo(aPlr); + PlayerConfig.saveGenderInfo(aPlr); } }, Tooltip.of(Text.translatable("wildfire_gender.tooltip.override_armor_physics.line1") .append("\n\n") .append(Text.translatable("wildfire_gender.tooltip.override_armor_physics.line2"))) )); - this.addDrawableChild(this.bounceSlider = new WildfireSlider(xPos, yPos + 60, 158, 20, Configuration.BOUNCE_MULTIPLIER, aPlr.getBounceMultiplierRaw(), value -> { + this.addDrawableChild(this.bounceSlider = new WildfireSlider(xPos, yPos + 60, 158, 20, Configuration.BOUNCE_MULTIPLIER, aPlr.getBounceMultiplier(), value -> { }, value -> { float bounceText = 3 * value; float v = Math.round(bounceText * 10) / 10f; @@ -100,14 +100,14 @@ public void init() { else return Text.translatable("wildfire_gender.slider.bounce", v); }, value -> { if (aPlr.updateBounceMultiplier(value)) { - GenderPlayer.saveGenderInfo(aPlr); + PlayerConfig.saveGenderInfo(aPlr); } })); this.addDrawableChild(this.floppySlider = new WildfireSlider(xPos, yPos + 80, 158, 20, Configuration.FLOPPY_MULTIPLIER, aPlr.getFloppiness(), value -> { }, value -> Text.translatable("wildfire_gender.slider.floppy", Math.round(value * 100)), value -> { if (aPlr.updateFloppiness(value)) { - GenderPlayer.saveGenderInfo(aPlr); + PlayerConfig.saveGenderInfo(aPlr); } })); @@ -116,7 +116,7 @@ public void init() { boolean enableHurtSounds = !aPlr.hasHurtSounds(); if (aPlr.updateHurtSounds(enableHurtSounds)) { button.setMessage(Text.translatable("wildfire_gender.char_settings.hurt_sounds", enableHurtSounds ? ENABLED : DISABLED)); - GenderPlayer.saveGenderInfo(aPlr); + PlayerConfig.saveGenderInfo(aPlr); } }, Tooltip.of(Text.translatable("wildfire_gender.tooltip.hurt_sounds")))); diff --git a/src/main/java/com/wildfire/main/Gender.java b/src/main/java/com/wildfire/main/Gender.java new file mode 100644 index 00000000..d508f911 --- /dev/null +++ b/src/main/java/com/wildfire/main/Gender.java @@ -0,0 +1,35 @@ +package com.wildfire.main; + +import net.minecraft.sound.SoundEvent; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import javax.annotation.Nullable; + +public enum Gender { + FEMALE(Text.translatable("wildfire_gender.label.female").formatted(Formatting.LIGHT_PURPLE), true, WildfireSounds.FEMALE_HURT), + MALE(Text.translatable("wildfire_gender.label.male").formatted(Formatting.BLUE), false, null), + OTHER(Text.translatable("wildfire_gender.label.other").formatted(Formatting.GREEN), true, WildfireSounds.FEMALE_HURT); + + private final Text name; + private final boolean canHaveBreasts; + private final @Nullable SoundEvent hurtSound; + + Gender(Text name, boolean canHaveBreasts, @Nullable SoundEvent hurtSound) { + this.name = name; + this.canHaveBreasts = canHaveBreasts; + this.hurtSound = hurtSound; + } + + public Text getDisplayName() { + return name; + } + + public @Nullable SoundEvent getHurtSound() { + return hurtSound; + } + + public boolean canHaveBreasts() { + return canHaveBreasts; + } +} diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index 138bc47d..56f584b7 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -19,15 +19,19 @@ package com.wildfire.main; import com.wildfire.gui.screen.WardrobeBrowserScreen; +import com.wildfire.main.entitydata.EntityConfig; +import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.networking.WildfireSync; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.option.KeyBinding; import net.minecraft.client.util.InputUtil; import net.minecraft.entity.Entity; @@ -44,7 +48,9 @@ public class WildfireEventHandler { public static void registerClientEvents() { ClientEntityEvents.ENTITY_LOAD.register(WildfireEventHandler::onEntityLoad); + ClientEntityEvents.ENTITY_UNLOAD.register(WildfireEventHandler::onEntityUnload); ClientTickEvents.END_CLIENT_TICK.register(WildfireEventHandler::onClientTick); + ClientPlayConnectionEvents.DISCONNECT.register(WildfireEventHandler::disconnect); ClientPlayNetworking.registerGlobalReceiver(WildfireSync.SYNC_IDENTIFIER, WildfireSync::handle); } @@ -52,24 +58,26 @@ private static void onEntityLoad(Entity entity, World world) { if(!world.isClient() || MinecraftClient.getInstance().player == null) return; if(entity instanceof AbstractClientPlayerEntity plr) { UUID uuid = plr.getUuid(); - GenderPlayer aPlr = WildfireGender.getPlayerById(plr.getUuid()); + PlayerConfig aPlr = WildfireGender.getPlayerById(plr.getUuid()); if(aPlr == null) { - aPlr = new GenderPlayer(uuid); - WildfireGender.CLOTHING_PLAYERS.put(uuid, aPlr); - WildfireGender.loadGenderInfoAsync(uuid, uuid.equals(MinecraftClient.getInstance().player.getUuid())); + aPlr = new PlayerConfig(uuid); + WildfireGender.PLAYER_CACHE.put(uuid, aPlr); + WildfireGender.loadGenderInfo(uuid, uuid.equals(MinecraftClient.getInstance().player.getUuid())); } } } + private static void onEntityUnload(Entity entity, World world) { + // note that we don't attempt to unload players; they're instead only ever unloaded once we leave a world + EntityConfig.ENTITY_CACHE.remove(entity.getUuid()); + } + private static void onClientTick(MinecraftClient client) { - if(client.world == null || client.player == null) { - WildfireGender.CLOTHING_PLAYERS.clear(); - return; - } + if(client.world == null || client.player == null) return; // Only attempt to sync if the server will accept the packet, and only once every 5 ticks, or around 4 times a second if(ClientPlayNetworking.canSend(WildfireSync.SEND_GENDER_IDENTIFIER) && timer++ % 5 == 0) { - GenderPlayer aPlr = WildfireGender.getPlayerById(client.player.getUuid()); + PlayerConfig aPlr = WildfireGender.getPlayerById(client.player.getUuid()); // sendToServer will only actually send a packet if any changes have been made that need to be synced, // or if we haven't synced before. if(aPlr != null) WildfireSync.sendToServer(aPlr); @@ -79,4 +87,9 @@ private static void onClientTick(MinecraftClient client) { client.setScreen(new WardrobeBrowserScreen(null, client.player.getUuid())); } } + + private static void disconnect(ClientPlayNetworkHandler networkHandler, MinecraftClient client) { + WildfireGender.PLAYER_CACHE.clear(); + EntityConfig.ENTITY_CACHE.clear(); + } } diff --git a/src/main/java/com/wildfire/main/WildfireGender.java b/src/main/java/com/wildfire/main/WildfireGender.java index c3bc33a8..bf6c8749 100644 --- a/src/main/java/com/wildfire/main/WildfireGender.java +++ b/src/main/java/com/wildfire/main/WildfireGender.java @@ -20,36 +20,38 @@ import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.UUID; +import java.util.concurrent.Future; +import javax.annotation.Nonnull; import javax.annotation.Nullable; + +import com.mojang.logging.LogUtils; +import com.wildfire.main.entitydata.PlayerConfig; import net.fabricmc.api.ClientModInitializer; +import net.minecraft.util.Util; +import org.slf4j.Logger; public class WildfireGender implements ClientModInitializer { public static final String VERSION = "3.1"; public static final String MODID = "wildfire_gender"; - public static Map CLOTHING_PLAYERS = new HashMap<>(); + public static final Logger LOGGER = LogUtils.getLogger(); + public static final Map PLAYER_CACHE = new HashMap<>(); @Override public void onInitializeClient() { WildfireEventHandler.registerClientEvents(); } - @Nullable - public static GenderPlayer getPlayerById(UUID id) { - return CLOTHING_PLAYERS.get(id); + public static @Nullable PlayerConfig getPlayerById(UUID id) { + return PLAYER_CACHE.get(id); } - public static GenderPlayer getOrAddPlayerById(UUID id) { - return CLOTHING_PLAYERS.computeIfAbsent(id, GenderPlayer::new); + public static @Nonnull PlayerConfig getOrAddPlayerById(UUID id) { + return PLAYER_CACHE.computeIfAbsent(id, PlayerConfig::new); } - public static void loadGenderInfoAsync(UUID uuid, boolean markForSync) { - Thread thread = new Thread(() -> WildfireGender.loadGenderInfo(uuid, markForSync)); - thread.setName("WFGM_GetPlayer-" + uuid); - thread.start(); + public static Future> loadGenderInfo(UUID uuid, boolean markForSync) { + return Util.getIoWorkerExecutor().submit(() -> Optional.ofNullable(PlayerConfig.loadCachedPlayer(uuid, markForSync))); } - - public static GenderPlayer loadGenderInfo(UUID uuid, boolean markForSync) { - return GenderPlayer.loadCachedPlayer(uuid, markForSync); - } } \ No newline at end of file diff --git a/src/main/java/com/wildfire/main/WildfireGenderServer.java b/src/main/java/com/wildfire/main/WildfireGenderServer.java index 2244634e..e14d9b39 100644 --- a/src/main/java/com/wildfire/main/WildfireGenderServer.java +++ b/src/main/java/com/wildfire/main/WildfireGenderServer.java @@ -18,6 +18,7 @@ package com.wildfire.main; +import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.networking.WildfireSync; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.networking.v1.EntityTrackingEvents; @@ -37,7 +38,7 @@ public void onInitialize() { private void onBeginTracking(Entity tracked, ServerPlayerEntity syncTo) { if(tracked instanceof PlayerEntity toSync) { - GenderPlayer genderToSync = WildfireGender.getPlayerById(toSync.getUuid()); + PlayerConfig genderToSync = WildfireGender.getPlayerById(toSync.getUuid()); if(genderToSync == null) return; // Note that we intentionally don't check if we've previously synced a player with this code path; // because we use entity tracking to sync, it's entirely possible that one player would leave the diff --git a/src/main/java/com/wildfire/main/WildfireHelper.java b/src/main/java/com/wildfire/main/WildfireHelper.java index 320b878e..4e1fcbce 100644 --- a/src/main/java/com/wildfire/main/WildfireHelper.java +++ b/src/main/java/com/wildfire/main/WildfireHelper.java @@ -20,18 +20,25 @@ import com.wildfire.api.IGenderArmor; import com.wildfire.api.WildfireAPI; +import com.wildfire.main.entitydata.Breasts; +import com.wildfire.main.entitydata.EntityConfig; +import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.render.armor.SimpleGenderArmor; import com.wildfire.render.armor.EmptyGenderArmor; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.render.entity.PlayerModelPart; import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.*; +import net.minecraft.nbt.NbtCompound; import net.minecraft.text.Text; import net.minecraft.util.Util; import net.minecraft.util.math.MathHelper; +import javax.annotation.Nonnull; import java.util.Objects; import java.util.concurrent.ThreadLocalRandom; @@ -87,6 +94,7 @@ public static void drawCenteredText(DrawContext ctx, TextRenderer textRenderer, ctx.drawText(textRenderer, text, centeredX, y, color, false); } + @Environment(EnvType.CLIENT) public static void drawScrollableText(DrawContext context, TextRenderer textRenderer, Text text, int left, int top, int right, int bottom, int color) { int i = textRenderer.getWidth(text); int var10000 = top + bottom; @@ -105,6 +113,33 @@ public static void drawScrollableText(DrawContext context, TextRenderer textRend } else { drawCenteredText(context, textRenderer, text, (left + right) / 2, j, color); } + } + /** + *

Write a player's gender config to NBT on the given item stack.

+ * + *

This only copies enough data to render breasts similarly to how they'd appear on the given player, which includes:

+ *
    + *
  • {@link EntityConfig#getBustSize() Breast size}
  • + *
  • {@link Breasts#getCleavage() Cleavage}
  • + *
  • {@link Breasts#isUniboob() Uniboob}
  • + *
  • {@link Breasts#getXOffset() X}, {@link Breasts#getYOffset() Y}, and {@link Breasts#getZOffset() Z} offsets
  • + *
  • Whether the {@link PlayerEntity#isPartVisible player's jacket layer is visible}
  • + *
+ * + * @see EntityConfig#readFromStack + */ + public static void writeToNbt(@Nonnull PlayerEntity player, @Nonnull PlayerConfig config, @Nonnull ItemStack armor) { + NbtCompound nbt = new NbtCompound(); + nbt.putFloat("BreastSize", config.getGender().canHaveBreasts() && config.showBreastsInArmor() ? config.getBustSize() : 0f); + nbt.putFloat("Cleavage", config.getBreasts().getCleavage()); + nbt.putBoolean("Uniboob", config.getBreasts().isUniboob()); + nbt.putFloat("XOffset", config.getBreasts().getXOffset()); + nbt.putFloat("YOffset", config.getBreasts().getYOffset()); + nbt.putFloat("ZOffset", config.getBreasts().getZOffset()); + // note that we also copy this to properly copy the exact size, as the player model will push the breast armor + // layer out a bit if they have a visible jacket layer + nbt.putBoolean("Jacket", player.isPartVisible(PlayerModelPart.JACKET)); + armor.setSubNbt("WildfireGender", nbt); } } \ No newline at end of file diff --git a/src/main/java/com/wildfire/main/config/Configuration.java b/src/main/java/com/wildfire/main/config/Configuration.java index c5df000e..0d4ee2ff 100644 --- a/src/main/java/com/wildfire/main/config/Configuration.java +++ b/src/main/java/com/wildfire/main/config/Configuration.java @@ -23,6 +23,9 @@ import com.google.gson.JsonObject; import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonWriter; +import com.wildfire.main.entitydata.Breasts; +import com.wildfire.main.entitydata.EntityConfig; +import com.wildfire.main.entitydata.PlayerConfig; import net.fabricmc.loader.api.FabricLoader; import java.io.File; diff --git a/src/main/java/com/wildfire/main/config/GenderConfigKey.java b/src/main/java/com/wildfire/main/config/GenderConfigKey.java index 7586d5d6..bbced0a0 100644 --- a/src/main/java/com/wildfire/main/config/GenderConfigKey.java +++ b/src/main/java/com/wildfire/main/config/GenderConfigKey.java @@ -21,7 +21,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; -import com.wildfire.main.GenderPlayer.Gender; +import com.wildfire.main.Gender; public class GenderConfigKey extends ConfigKey { diff --git a/src/main/java/com/wildfire/main/Breasts.java b/src/main/java/com/wildfire/main/entitydata/Breasts.java similarity index 60% rename from src/main/java/com/wildfire/main/Breasts.java rename to src/main/java/com/wildfire/main/entitydata/Breasts.java index 15d77ee2..39b64710 100644 --- a/src/main/java/com/wildfire/main/Breasts.java +++ b/src/main/java/com/wildfire/main/entitydata/Breasts.java @@ -16,12 +16,16 @@ along with this program. If not, see . */ -package com.wildfire.main; +package com.wildfire.main.entitydata; import com.wildfire.main.config.ConfigKey; import com.wildfire.main.config.Configuration; import java.util.function.Consumer; +/** + * Data class representing an entity's breast appearance settings + */ +@SuppressWarnings("UnusedReturnValue") public class Breasts { private float xOffset = Configuration.BREASTS_OFFSET_X.getDefault(), yOffset = Configuration.BREASTS_OFFSET_Y.getDefault(), zOffset = Configuration.BREASTS_OFFSET_Z.getDefault(); @@ -36,42 +40,86 @@ private boolean updateValue(ConfigKey key, VALUE value, Consumer< return false; } + /** + * How far apart the player's breasts should be rendered from each other, also referred to as Separation in the UI + * + * @implNote Negative float values renders the breasts further apart, while positive values renders them closer together + * + * @return A {@code float} between {@code -1f} and {@code 1f} + */ public float getXOffset() { return xOffset; } + /** + * @see #getXOffset() + */ public boolean updateXOffset(float value) { return updateValue(Configuration.BREASTS_OFFSET_X, value, v -> this.xOffset = v); } + /** + * How far up or down the player's breasts should be rendered, also referred to as Height in the UI + * + * @implNote Negative values renders the breasts lower down, while positive values renders them higher up + * + * @return A {@code float} between {@code -1f} and {@code 1f} + */ public float getYOffset() { return yOffset; } + /** + * @see #getYOffset() + */ public boolean updateYOffset(float value) { return updateValue(Configuration.BREASTS_OFFSET_Y, value, v -> this.yOffset = v); } + /** + * How far back the player's breasts should be rendered, also referred to as Depth in the UI + * + * @return A {@code float} between {@code 0f} and {@code 1f} + */ public float getZOffset() { return zOffset; } + /** + * @see #getZOffset() + */ public boolean updateZOffset(float value) { return updateValue(Configuration.BREASTS_OFFSET_Z, value, v -> this.zOffset = v); } + /** + * How much rotation outward there should be on each of the player's breasts + * + * @return A {@code float} between {@code 0f} and {@code 0.1f} + */ public float getCleavage() { return cleavage; } + /** + * @see #getCleavage() + */ public boolean updateCleavage(float value) { return updateValue(Configuration.BREASTS_CLEAVAGE, value, v -> this.cleavage = v); } + /** + * Determines if breast physics should be independent of each other; also referred to as Dual-Physics in the UI + * + * @return {@code false} if physics should be independent on each breast, {@code true} if both should use the same physics + */ public boolean isUniboob() { return uniboob; } + /** + * @see #isUniboob() + */ public boolean updateUniboob(boolean value) { return updateValue(Configuration.BREASTS_UNIBOOB, value, v -> this.uniboob = v); } diff --git a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java new file mode 100644 index 00000000..637319c1 --- /dev/null +++ b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java @@ -0,0 +1,169 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.main.entitydata; + +import com.wildfire.api.IGenderArmor; +import com.wildfire.main.WildfireGender; +import com.wildfire.main.WildfireHelper; +import com.wildfire.main.config.Configuration; +import com.wildfire.main.Gender; +import com.wildfire.physics.BreastPhysics; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.decoration.ArmorStandEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.UUID; + +/** + *

A stripped down version of a {@link PlayerConfig player's config}, intended for use with non-player entities.

+ * + *

Unlike players, this has very minimal configuration support.

+ * + *

Currently only used for {@link ArmorStandEntity armor stands}, and as a superclass for {@link PlayerConfig player configs}.

+ */ +public class EntityConfig { + + public static final HashMap ENTITY_CACHE = new HashMap<>(); + + public final UUID uuid; + protected Gender gender = Configuration.GENDER.getDefault(); + protected float pBustSize = Configuration.BUST_SIZE.getDefault(); + protected boolean breastPhysics = Configuration.BREAST_PHYSICS.getDefault(); + protected float bounceMultiplier = Configuration.BOUNCE_MULTIPLIER.getDefault(); + protected float floppyMultiplier = Configuration.FLOPPY_MULTIPLIER.getDefault(); + protected boolean showBreastsInArmor = Configuration.SHOW_IN_ARMOR.getDefault(); + // note: hurt sounds and armor physics override are not defined here, as they have no relevance + // to entities, and are instead entirely in PlayerConfig + protected final BreastPhysics lBreastPhysics, rBreastPhysics; + protected final Breasts breasts; + protected boolean jacketLayer = true; + + EntityConfig(UUID uuid) { + this.uuid = uuid; + this.breasts = new Breasts(); + lBreastPhysics = new BreastPhysics(this); + rBreastPhysics = new BreastPhysics(this); + } + + /** + * Copy gender settings included in the given {@link ItemStack item NBT} to the current entity + * + * @see WildfireHelper#writeToNbt + */ + public void readFromStack(@Nonnull ItemStack chestplate) { + NbtCompound nbt = !chestplate.isEmpty() ? chestplate.getSubNbt("WildfireGender") : null; + if(nbt == null) { + this.gender = Gender.MALE; + return; + } + this.pBustSize = nbt.contains("BreastSize") ? nbt.getFloat("BreastSize") : 0f; + this.gender = this.pBustSize > 0.02f ? Gender.FEMALE : Gender.MALE; + if(nbt.contains("Cleavage")) breasts.updateCleavage(nbt.getFloat("Cleavage")); + if(nbt.contains("Uniboob")) breasts.updateUniboob(nbt.getBoolean("Uniboob")); + if(nbt.contains("XOffset")) breasts.updateXOffset(nbt.getFloat("XOffset")); + if(nbt.contains("YOffset")) breasts.updateYOffset(nbt.getFloat("YOffset")); + if(nbt.contains("ZOffset")) breasts.updateZOffset(nbt.getFloat("ZOffset")); + if(nbt.contains("Jacket")) jacketLayer = nbt.getBoolean("Jacket"); + } + + /** + * Get the configuration for a given entity + * + * @return {@link EntityConfig}, {@link PlayerConfig} if given a {@link PlayerEntity player}, + * or {@code null} if given a baby entity + */ + public static @Nullable EntityConfig getEntity(@Nonnull LivingEntity entity) { + if(entity instanceof PlayerEntity) { + return WildfireGender.getPlayerById(entity.getUuid()); + } + if(entity.isBaby()) { + // rendering breaks quite spectacularly on baby mobs, so just immediately give up + return null; + } + return ENTITY_CACHE.computeIfAbsent(entity.getUuid(), EntityConfig::new); + } + + public @Nonnull Gender getGender() { + return gender; + } + + public @Nonnull Breasts getBreasts() { + return breasts; + } + + public float getBustSize() { + return pBustSize; + } + + public boolean hasBreastPhysics() { + return breastPhysics; + } + + public boolean getArmorPhysicsOverride() { + return false; + } + + public boolean showBreastsInArmor() { + return true; + } + + public float getBounceMultiplier() { + return bounceMultiplier; + } + + public float getFloppiness() { + return this.floppyMultiplier; + } + + public @Nonnull BreastPhysics getLeftBreastPhysics() { + return lBreastPhysics; + } + public @Nonnull BreastPhysics getRightBreastPhysics() { + return rBreastPhysics; + } + + /** + * Only used in the case of {@link ArmorStandEntity armor stands}; returns {@code true} if the player who equipped + * the armor stand's chestplate has their jacket layer visible. + */ + public boolean hasJacketLayer() { + return jacketLayer; + } + + @Environment(EnvType.CLIENT) + public void tickBreastPhysics(@Nonnull LivingEntity entity) { + IGenderArmor armor = WildfireHelper.getArmorConfig(entity.getEquippedStack(EquipmentSlot.CHEST)); + + getLeftBreastPhysics().update(entity, armor); + getRightBreastPhysics().update(entity, armor); + } + + @Override + public String toString() { + return "%s(uuid=%s, gender=%s)".formatted(getClass().getCanonicalName(), uuid, gender); + } +} diff --git a/src/main/java/com/wildfire/main/GenderPlayer.java b/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java similarity index 72% rename from src/main/java/com/wildfire/main/GenderPlayer.java rename to src/main/java/com/wildfire/main/entitydata/PlayerConfig.java index 4d3cae6c..58ce2983 100644 --- a/src/main/java/com/wildfire/main/GenderPlayer.java +++ b/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java @@ -16,51 +16,37 @@ along with this program. If not, see . */ -package com.wildfire.main; +package com.wildfire.main.entitydata; import com.google.gson.JsonObject; +import com.wildfire.main.WildfireGender; import com.wildfire.main.config.ConfigKey; import com.wildfire.main.config.Configuration; -import com.wildfire.physics.BreastPhysics; -import net.minecraft.sound.SoundEvent; -import net.minecraft.text.Text; -import net.minecraft.util.Formatting; +import com.wildfire.main.Gender; +import net.minecraft.item.ItemStack; -import javax.annotation.Nullable; +import javax.annotation.Nonnull; import java.util.UUID; import java.util.function.Consumer; -public class GenderPlayer { +/** + * A version of {@link EntityConfig} backed by a {@link Configuration} for use with players + */ +public class PlayerConfig extends EntityConfig { public boolean needsSync; - public final UUID uuid; - private Gender gender; - private float pBustSize = Configuration.BUST_SIZE.getDefault(); - - private boolean hurtSounds = Configuration.HURT_SOUNDS.getDefault(); - - //physics variables - private boolean breastPhysics = Configuration.BREAST_PHYSICS.getDefault(); - private float bounceMultiplier = Configuration.BOUNCE_MULTIPLIER.getDefault(); - private float floppyMultiplier = Configuration.FLOPPY_MULTIPLIER.getDefault(); - public SyncStatus syncStatus = SyncStatus.UNKNOWN; - private boolean showBreastsInArmor = Configuration.SHOW_IN_ARMOR.getDefault(); - private boolean armorPhysOverride = Configuration.ARMOR_PHYSICS_OVERRIDE.getDefault(); private final Configuration cfg; - private final BreastPhysics lBreastPhysics, rBreastPhysics; - private final Breasts breasts; + private boolean hurtSounds = Configuration.HURT_SOUNDS.getDefault(); + private boolean armorPhysOverride = Configuration.ARMOR_PHYSICS_OVERRIDE.getDefault(); - public GenderPlayer(UUID uuid) { + public PlayerConfig(UUID uuid) { this(uuid, Configuration.GENDER.getDefault()); } - public GenderPlayer(UUID uuid, Gender gender) { - lBreastPhysics = new BreastPhysics(this); - rBreastPhysics = new BreastPhysics(this); - breasts = new Breasts(); - this.uuid = uuid; + public PlayerConfig(UUID uuid, Gender gender) { + super(uuid); this.gender = gender; this.cfg = new Configuration("WildfireGender", this.uuid.toString()); this.cfg.set(Configuration.USERNAME, this.uuid); @@ -82,6 +68,10 @@ public GenderPlayer(UUID uuid, Gender gender) { this.cfg.finish(); } + // this shouldn't ever be called on players, but just to be safe, override with a noop. + @Override + public void readFromStack(@Nonnull ItemStack chestplate) {} + public Configuration getConfig() { return cfg; } @@ -94,18 +84,10 @@ private boolean updateValue(ConfigKey key, VALUE value, Consumer< return false; } - public Gender getGender() { - return gender; - } - public boolean updateGender(Gender value) { return updateValue(Configuration.GENDER, value, v -> this.gender = v); } - public float getBustSize() { - return pBustSize; - } - public boolean updateBustSize(float value) { return updateValue(Configuration.BUST_SIZE, value, v -> this.pBustSize = v); } @@ -118,10 +100,6 @@ public boolean updateHurtSounds(boolean value) { return updateValue(Configuration.HURT_SOUNDS, value, v -> this.hurtSounds = v); } - public boolean hasBreastPhysics() { - return breastPhysics; - } - public boolean updateBreastPhysics(boolean value) { return updateValue(Configuration.BREAST_PHYSICS, value, v -> this.breastPhysics = v); } @@ -129,9 +107,11 @@ public boolean updateBreastPhysics(boolean value) { public boolean getArmorPhysicsOverride() { return armorPhysOverride; } + public boolean updateArmorPhysicsOverride(boolean value) { return updateValue(Configuration.ARMOR_PHYSICS_OVERRIDE, value, v -> this.armorPhysOverride = v); } + public boolean showBreastsInArmor() { return showBreastsInArmor; } @@ -140,22 +120,10 @@ public boolean updateShowBreastsInArmor(boolean value) { return updateValue(Configuration.SHOW_IN_ARMOR, value, v -> this.showBreastsInArmor = v); } - public float getBounceMultiplier() { - return Math.round((this.getBounceMultiplierRaw() * 3) * 100) / 100f; - } - - public float getBounceMultiplierRaw() { - return bounceMultiplier; - } - public boolean updateBounceMultiplier(float value) { return updateValue(Configuration.BOUNCE_MULTIPLIER, value, v -> this.bounceMultiplier = v); } - public float getFloppiness() { - return this.floppyMultiplier; - } - public boolean updateFloppiness(float value) { return updateValue(Configuration.FLOPPY_MULTIPLIER, value, v -> this.floppyMultiplier = v); } @@ -164,7 +132,7 @@ public SyncStatus getSyncStatus() { return this.syncStatus; } - public static JsonObject toJsonObject(GenderPlayer plr) { + public static JsonObject toJsonObject(PlayerConfig plr) { JsonObject obj = new JsonObject(); Configuration.USERNAME.save(obj, plr.uuid); Configuration.GENDER.save(obj, plr.getGender()); @@ -174,7 +142,7 @@ public static JsonObject toJsonObject(GenderPlayer plr) { Configuration.BREAST_PHYSICS.save(obj, plr.hasBreastPhysics()); Configuration.SHOW_IN_ARMOR.save(obj, plr.showBreastsInArmor()); Configuration.ARMOR_PHYSICS_OVERRIDE.save(obj, plr.getArmorPhysicsOverride()); - Configuration.BOUNCE_MULTIPLIER.save(obj, plr.getBounceMultiplierRaw()); + Configuration.BOUNCE_MULTIPLIER.save(obj, plr.getBounceMultiplier()); Configuration.FLOPPY_MULTIPLIER.save(obj, plr.getFloppiness()); Breasts breasts = plr.getBreasts(); @@ -186,8 +154,8 @@ public static JsonObject toJsonObject(GenderPlayer plr) { return obj; } - public static GenderPlayer loadCachedPlayer(UUID uuid, boolean markForSync) { - GenderPlayer plr = WildfireGender.getPlayerById(uuid); + public static PlayerConfig loadCachedPlayer(UUID uuid, boolean markForSync) { + PlayerConfig plr = WildfireGender.getPlayerById(uuid); if (plr != null) { plr.syncStatus = SyncStatus.CACHED; Configuration config = plr.getConfig(); @@ -216,7 +184,7 @@ public static GenderPlayer loadCachedPlayer(UUID uuid, boolean markForSync) { return null; } - public static void saveGenderInfo(GenderPlayer plr) { + public static void saveGenderInfo(PlayerConfig plr) { Configuration config = plr.getConfig(); config.set(Configuration.USERNAME, plr.uuid); config.set(Configuration.GENDER, plr.getGender()); @@ -227,7 +195,7 @@ public static void saveGenderInfo(GenderPlayer plr) { config.set(Configuration.BREAST_PHYSICS, plr.hasBreastPhysics()); config.set(Configuration.SHOW_IN_ARMOR, plr.showBreastsInArmor()); config.set(Configuration.ARMOR_PHYSICS_OVERRIDE, plr.getArmorPhysicsOverride()); - config.set(Configuration.BOUNCE_MULTIPLIER, plr.getBounceMultiplierRaw()); + config.set(Configuration.BOUNCE_MULTIPLIER, plr.getBounceMultiplier()); config.set(Configuration.FLOPPY_MULTIPLIER, plr.getFloppiness()); config.set(Configuration.BREASTS_OFFSET_X, plr.getBreasts().getXOffset()); @@ -240,46 +208,12 @@ public static void saveGenderInfo(GenderPlayer plr) { plr.needsSync = true; } - public Breasts getBreasts() { - return breasts; - } - - public BreastPhysics getLeftBreastPhysics() { - return lBreastPhysics; - } - public BreastPhysics getRightBreastPhysics() { - return rBreastPhysics; + @Override + public boolean hasJacketLayer() { + throw new UnsupportedOperationException("PlayerConfig does not support #hasJacketLayer(); use PlayerEntity#isPartVisible instead"); } public enum SyncStatus { CACHED, SYNCED, UNKNOWN } - - public enum Gender { - FEMALE(Text.translatable("wildfire_gender.label.female").formatted(Formatting.LIGHT_PURPLE), true, WildfireSounds.FEMALE_HURT), - MALE(Text.translatable("wildfire_gender.label.male").formatted(Formatting.BLUE), false, null), - OTHER(Text.translatable("wildfire_gender.label.other").formatted(Formatting.GREEN), true, WildfireSounds.FEMALE_HURT); - - private final Text name; - private final boolean canHaveBreasts; - private final @Nullable SoundEvent hurtSound; - - Gender(Text name, boolean canHaveBreasts, @Nullable SoundEvent hurtSound) { - this.name = name; - this.canHaveBreasts = canHaveBreasts; - this.hurtSound = hurtSound; - } - - public Text getDisplayName() { - return name; - } - - public @Nullable SoundEvent getHurtSound() { - return hurtSound; - } - - public boolean canHaveBreasts() { - return canHaveBreasts; - } - } } diff --git a/src/main/java/com/wildfire/main/networking/SyncPacket.java b/src/main/java/com/wildfire/main/networking/SyncPacket.java index 588eff36..d13ce4fd 100644 --- a/src/main/java/com/wildfire/main/networking/SyncPacket.java +++ b/src/main/java/com/wildfire/main/networking/SyncPacket.java @@ -18,9 +18,9 @@ package com.wildfire.main.networking; -import com.wildfire.main.Breasts; -import com.wildfire.main.GenderPlayer; -import com.wildfire.main.GenderPlayer.Gender; +import com.wildfire.main.entitydata.Breasts; +import com.wildfire.main.entitydata.PlayerConfig; +import com.wildfire.main.Gender; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.minecraft.network.PacketByteBuf; @@ -43,7 +43,7 @@ class SyncPacket { private final boolean hurtSounds; - protected SyncPacket(GenderPlayer plr) { + protected SyncPacket(PlayerConfig plr) { this.uuid = plr.uuid; this.gender = plr.getGender(); this.bust_size = plr.getBustSize(); @@ -52,7 +52,7 @@ protected SyncPacket(GenderPlayer plr) { //physics variables this.breast_physics = plr.hasBreastPhysics(); this.show_in_armor = plr.showBreastsInArmor(); - this.bounceMultiplier = plr.getBounceMultiplierRaw(); + this.bounceMultiplier = plr.getBounceMultiplier(); this.floppyMultiplier = plr.getFloppiness(); Breasts breasts = plr.getBreasts(); @@ -100,7 +100,7 @@ protected void encode(PacketByteBuf buffer) { buffer.writeFloat(this.cleavage); } - protected void updatePlayerFromPacket(GenderPlayer plr) { + protected void updatePlayerFromPacket(PlayerConfig plr) { plr.updateGender(gender); plr.updateBustSize(bust_size); plr.updateHurtSounds(hurtSounds); diff --git a/src/main/java/com/wildfire/main/networking/WildfireSync.java b/src/main/java/com/wildfire/main/networking/WildfireSync.java index 50d86254..34d7aefa 100644 --- a/src/main/java/com/wildfire/main/networking/WildfireSync.java +++ b/src/main/java/com/wildfire/main/networking/WildfireSync.java @@ -18,7 +18,7 @@ package com.wildfire.main.networking; -import com.wildfire.main.GenderPlayer; +import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.WildfireGender; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -46,9 +46,9 @@ public static void handle(MinecraftClient client, ClientPlayNetworkHandler handl SyncPacket packet = new SyncPacket(buf); if(client.player == null || packet.uuid.equals(client.player.getUuid())) return; - GenderPlayer plr = WildfireGender.getOrAddPlayerById(packet.uuid); + PlayerConfig plr = WildfireGender.getOrAddPlayerById(packet.uuid); packet.updatePlayerFromPacket(plr); - plr.syncStatus = GenderPlayer.SyncStatus.SYNCED; + plr.syncStatus = PlayerConfig.SyncStatus.SYNCED; } @SuppressWarnings("unused") @@ -56,7 +56,7 @@ public static void handle(MinecraftServer server, ServerPlayerEntity player, Ser SyncPacket packet = new SyncPacket(buf); if(player == null || !player.getUuid().equals(packet.uuid)) return; - GenderPlayer plr = WildfireGender.getOrAddPlayerById(packet.uuid); + PlayerConfig plr = WildfireGender.getOrAddPlayerById(packet.uuid); packet.updatePlayerFromPacket(plr); sendToAllClients(player, plr); } @@ -65,15 +65,17 @@ public static void handle(MinecraftServer server, ServerPlayerEntity player, Ser * Sync a player's configuration to all nearby connected players * * @param toSync The {@link ServerPlayerEntity player} to sync - * @param genderPlayer The {@link GenderPlayer configuration} for the target player + * @param playerConfig The {@link PlayerConfig configuration} for the target player */ - public static void sendToAllClients(ServerPlayerEntity toSync, GenderPlayer genderPlayer) { - if(genderPlayer == null || toSync.getServer() == null) return; + public static void sendToAllClients(ServerPlayerEntity toSync, PlayerConfig playerConfig) { + if(playerConfig == null || toSync.getServer() == null) return; - PacketByteBuf packet = new SyncPacket(genderPlayer).getPacket(); + PacketByteBuf packet = new SyncPacket(playerConfig).getPacket(); PlayerLookup.tracking(toSync).forEach((sendTo) -> { if(sendTo.getUuid().equals(toSync.getUuid())) return; - sendPacketToClient(sendTo, packet); + if(ServerPlayNetworking.canSend(sendTo, SYNC_IDENTIFIER)) { + ServerPlayNetworking.send(sendTo, SYNC_IDENTIFIER, packet); + } }); } @@ -81,28 +83,25 @@ public static void sendToAllClients(ServerPlayerEntity toSync, GenderPlayer gend * Sync a player's configuration to another connected player * * @param sendTo The {@link ServerPlayerEntity player} to send the sync to - * @param toSync The {@link GenderPlayer configuration} for the player being synced + * @param toSync The {@link PlayerConfig configuration} for the player being synced */ - public static void sendToClient(ServerPlayerEntity sendTo, GenderPlayer toSync) { - sendPacketToClient(sendTo, new SyncPacket(toSync).getPacket()); + public static void sendToClient(ServerPlayerEntity sendTo, PlayerConfig toSync) { + PacketByteBuf packet = new SyncPacket(toSync).getPacket(); + if(ServerPlayNetworking.canSend(sendTo, SYNC_IDENTIFIER)) { + ServerPlayNetworking.send(sendTo, SYNC_IDENTIFIER, packet); + } } /** * Send the client player's configuration to the server for syncing to other players * - * @param plr The {@link GenderPlayer configuration} for the client player + * @param plr The {@link PlayerConfig configuration} for the client player */ @Environment(EnvType.CLIENT) - public static void sendToServer(GenderPlayer plr) { + public static void sendToServer(PlayerConfig plr) { if(plr == null || !plr.needsSync) return; PacketByteBuf buffer = new SyncPacket(plr).getPacket(); ClientPlayNetworking.send(SEND_GENDER_IDENTIFIER, buffer); plr.needsSync = false; } - - private static void sendPacketToClient(ServerPlayerEntity sendTo, PacketByteBuf packet) { - if(ServerPlayNetworking.canSend(sendTo, SYNC_IDENTIFIER)) { - ServerPlayNetworking.send(sendTo, SYNC_IDENTIFIER, packet); - } - } } diff --git a/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java b/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java new file mode 100644 index 00000000..cd4560c0 --- /dev/null +++ b/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java @@ -0,0 +1,67 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.mixins; + +import com.wildfire.api.IGenderArmor; +import com.wildfire.main.WildfireGender; +import com.wildfire.main.entitydata.PlayerConfig; +import com.wildfire.main.WildfireHelper; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.decoration.ArmorStandEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ArmorItem; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Hand; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ArmorStandEntity.class) +public abstract class ArmorStandEntityMixin { + @Inject( + method = "equip", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/entity/decoration/ArmorStandEntity;equipStack(Lnet/minecraft/entity/EquipmentSlot;Lnet/minecraft/item/ItemStack;)V", + shift = At.Shift.BEFORE + ) + ) + public void wildfiregender$equipArmorStandChestplate(PlayerEntity player, EquipmentSlot slot, ItemStack stack, Hand hand, CallbackInfoReturnable cir) { + if(player == null || player.getWorld().isClient()) return; + + Item item = stack.getItem(); + // Only apply to chestplates + if(!(item instanceof ArmorItem armorItem) || armorItem.getSlotType() != EquipmentSlot.CHEST) return; + + PlayerConfig playerConfig = WildfireGender.getPlayerById(player.getUuid()); + if(playerConfig == null) { + if(stack.getSubNbt("WildfireGender") != null) { + stack.removeSubNbt("WildfireGender"); + } + return; + } + + IGenderArmor armorConfig = WildfireHelper.getArmorConfig(stack); + if(armorConfig.armorStandsCopySettings()) { + WildfireHelper.writeToNbt(player, playerConfig, stack); + } + } +} diff --git a/src/main/java/com/wildfire/mixins/ArmorStandEntityRendererMixin.java b/src/main/java/com/wildfire/mixins/ArmorStandEntityRendererMixin.java new file mode 100644 index 00000000..d2cf7c4c --- /dev/null +++ b/src/main/java/com/wildfire/mixins/ArmorStandEntityRendererMixin.java @@ -0,0 +1,45 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.mixins; + +import com.wildfire.render.GenderArmorLayer; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.render.entity.ArmorStandEntityRenderer; +import net.minecraft.client.render.entity.EntityRendererFactory; +import net.minecraft.client.render.entity.LivingEntityRenderer; +import net.minecraft.client.render.entity.model.BipedEntityModel; +import net.minecraft.entity.decoration.ArmorStandEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Environment(EnvType.CLIENT) +@Mixin(ArmorStandEntityRenderer.class) +public abstract class ArmorStandEntityRendererMixin extends LivingEntityRenderer> { + public ArmorStandEntityRendererMixin(EntityRendererFactory.Context ctx, BipedEntityModel model, float shadow) { + super(ctx, model, shadow); + } + + @Inject(method = "", at = @At("TAIL")) + private void wildfiregender$armorStandBreastArmor(EntityRendererFactory.Context ctx, CallbackInfo ci) { + this.addFeature(new GenderArmorLayer<>(this, ctx.getModelManager())); + } +} diff --git a/src/main/java/com/wildfire/mixins/PlayerEntityMixin.java b/src/main/java/com/wildfire/mixins/BreastPhysicsTickMixin.java similarity index 53% rename from src/main/java/com/wildfire/mixins/PlayerEntityMixin.java rename to src/main/java/com/wildfire/mixins/BreastPhysicsTickMixin.java index f7af9ea8..f6833dc1 100644 --- a/src/main/java/com/wildfire/mixins/PlayerEntityMixin.java +++ b/src/main/java/com/wildfire/mixins/BreastPhysicsTickMixin.java @@ -18,40 +18,32 @@ package com.wildfire.mixins; -import com.mojang.authlib.GameProfile; -import com.wildfire.api.IGenderArmor; -import com.wildfire.main.GenderPlayer; -import com.wildfire.main.WildfireGender; -import com.wildfire.main.WildfireHelper; +import com.wildfire.main.entitydata.EntityConfig; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; -import net.minecraft.entity.EntityType; import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.decoration.ArmorStandEntity; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Environment(EnvType.CLIENT) -@Mixin(value = PlayerEntity.class, priority = 900) -public abstract class PlayerEntityMixin extends LivingEntity { - public PlayerEntityMixin(World world, BlockPos pos, float yaw, GameProfile profile) { - super(EntityType.PLAYER, world); - } - - @Inject(at = @At("TAIL"), method = "tick") - public void onTick(CallbackInfo info) { - if(!this.getWorld().isClient()) return; - GenderPlayer aPlr = WildfireGender.getPlayerById(this.getUuid()); - if(aPlr == null) return; - PlayerEntity plr = (PlayerEntity) (Object) this; - IGenderArmor armor = WildfireHelper.getArmorConfig(plr.getEquippedStack(EquipmentSlot.CHEST)); - - aPlr.getLeftBreastPhysics().update(plr, armor); - aPlr.getRightBreastPhysics().update(plr, armor); - } +@Mixin({ArmorStandEntity.class, PlayerEntity.class}) +public abstract class BreastPhysicsTickMixin { + @Inject(at = @At("TAIL"), method = "tick") + public void wildfiregender$tickBreastPhysics(CallbackInfo info) { + LivingEntity entity = (LivingEntity)(Object)this; + // Ignore ticks from the singleplayer integrated server + if(!entity.getWorld().isClient()) return; + + EntityConfig cfg = EntityConfig.getEntity(entity); + if(cfg == null) return; + if(entity instanceof ArmorStandEntity) { + cfg.readFromStack(entity.getEquippedStack(EquipmentSlot.CHEST)); + } + cfg.tickBreastPhysics(entity); + } } diff --git a/src/main/java/com/wildfire/mixins/LivingEntityMixin.java b/src/main/java/com/wildfire/mixins/LivingEntityMixin.java index 96c8e79b..3e570ef4 100644 --- a/src/main/java/com/wildfire/mixins/LivingEntityMixin.java +++ b/src/main/java/com/wildfire/mixins/LivingEntityMixin.java @@ -18,8 +18,8 @@ package com.wildfire.mixins; -import com.wildfire.main.GenderPlayer; import com.wildfire.main.WildfireGender; +import com.wildfire.main.entitydata.PlayerConfig; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.MinecraftClient; @@ -51,7 +51,7 @@ * `PlayerEntity#getHurtSound(DamageSource)`. */ @Mixin(LivingEntity.class) -public class LivingEntityMixin { +public abstract class LivingEntityMixin { @Environment(EnvType.CLIENT) @Inject( method = "onDamaged", @@ -86,7 +86,7 @@ public void serverGenderHurtSound(DamageSource source, float amount, CallbackInf @Unique private void playGenderHurtSound(PlayerEntity player) { - GenderPlayer genderPlayer = WildfireGender.getPlayerById(player.getUuid()); + PlayerConfig genderPlayer = WildfireGender.getPlayerById(player.getUuid()); if(genderPlayer == null || !genderPlayer.hasHurtSounds()) return; SoundEvent hurtSound = genderPlayer.getGender().getHurtSound(); diff --git a/src/main/java/com/wildfire/mixins/PlayerRenderMixin.java b/src/main/java/com/wildfire/mixins/PlayerRenderMixin.java index fb68a15e..e7815c9a 100644 --- a/src/main/java/com/wildfire/mixins/PlayerRenderMixin.java +++ b/src/main/java/com/wildfire/mixins/PlayerRenderMixin.java @@ -18,6 +18,7 @@ package com.wildfire.mixins; +import com.wildfire.render.GenderArmorLayer; import com.wildfire.render.GenderLayer; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -32,14 +33,15 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Environment(EnvType.CLIENT) -@Mixin(value = PlayerEntityRenderer.class, priority = 900) +@Mixin(PlayerEntityRenderer.class) public abstract class PlayerRenderMixin extends LivingEntityRenderer> { public PlayerRenderMixin(EntityRendererFactory.Context ctx, PlayerEntityModel model, float shadow) { super(ctx, model, shadow); } - @Inject(method = {""}, at = {@At("RETURN")}) - private void initFemaleGender(EntityRendererFactory.Context ctx, boolean slim, CallbackInfo ci) { - this.addFeature(new GenderLayer(this, ctx.getModelManager())); + @Inject(method = "", at = @At("TAIL")) + private void wildfiregender$addBreastLayers(EntityRendererFactory.Context ctx, boolean slim, CallbackInfo ci) { + this.addFeature(new GenderLayer<>(this)); + this.addFeature(new GenderArmorLayer<>(this, ctx.getModelManager())); } } \ No newline at end of file diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index f0211d0d..3ca02c6e 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -19,19 +19,20 @@ package com.wildfire.physics; import com.wildfire.api.IGenderArmor; -import com.wildfire.main.GenderPlayer; +import com.wildfire.main.entitydata.EntityConfig; import com.wildfire.main.WildfireHelper; import net.minecraft.client.network.AbstractClientPlayerEntity; -import net.minecraft.client.render.entity.PlayerEntityRenderer; import net.minecraft.entity.EntityPose; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.decoration.ArmorStandEntity; import net.minecraft.entity.passive.HorseEntity; import net.minecraft.entity.passive.PigEntity; import net.minecraft.entity.passive.StriderEntity; -import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.vehicle.BoatEntity; import net.minecraft.entity.vehicle.MinecartEntity; +import net.minecraft.item.ItemStack; import net.minecraft.util.math.MathHelper; -import net.minecraft.util.math.RotationAxis; import net.minecraft.util.math.Vec3d; public class BreastPhysics { @@ -45,71 +46,78 @@ public class BreastPhysics { private float breastSize = 0, preBreastSize = 0; private Vec3d prePos; - private final GenderPlayer genderPlayer; + private final EntityConfig entityConfig; - public BreastPhysics(GenderPlayer genderPlayer) { - this.genderPlayer = genderPlayer; + public BreastPhysics(EntityConfig entityConfig) { + this.entityConfig = entityConfig; } private int randomB = 1; private boolean alreadyFalling = false; - public void update(PlayerEntity plr, IGenderArmor armor) { + public void update(LivingEntity entity, IGenderArmor armor) { + if(entity instanceof ArmorStandEntity && !armor.armorStandsCopySettings()) { + // optimization: skip physics on armor stands that either don't have a chestplate, + // or have a chestplate we wouldn't copy player settings to + return; + } + this.wfg_preBounce = this.wfg_femaleBreast; this.wfg_preBounceX = this.wfg_femaleBreastX; this.wfg_preBounceRotation = this.wfg_bounceRotation; this.preBreastSize = this.breastSize; if(this.prePos == null) { - this.prePos = plr.getPos(); + this.prePos = entity.getPos(); return; } float h = 0; //tickDelta - float i = plr.getLeaningPitch(0); + float i = entity.getLeaningPitch(0); float j; float k; - AbstractClientPlayerEntity aPlr = (AbstractClientPlayerEntity) plr; float bodyXRotation = 0; float bodyYRotation = 0; - if (plr.isFallFlying()) { - j = (float) plr.getRoll() + h; + if (entity.isFallFlying()) { + j = (float) entity.getRoll() + h; k = MathHelper.clamp(j * j / 100.0F, 0.0F, 1.0F); - if (!plr.isUsingRiptide()) { - bodyXRotation = k * (-90.0F - plr.getPitch()); + if (!entity.isUsingRiptide()) { + bodyXRotation = k * (-90.0F - entity.getPitch()); } - Vec3d vec3d = plr.getRotationVec(h); - Vec3d vec3d2 = aPlr.lerpVelocity(h); - double d = vec3d2.horizontalLengthSquared(); - double e = vec3d.horizontalLengthSquared(); - if (d > 0.0 && e > 0.0) { - double l = (vec3d2.x * vec3d.x + vec3d2.z * vec3d.z) / Math.sqrt(d * e); - double m = vec3d2.x * vec3d.z - vec3d2.z * vec3d.x; - bodyYRotation = (float) (Math.signum(m) * Math.acos(l)); + if(entity instanceof AbstractClientPlayerEntity player) { + Vec3d vec3d = entity.getRotationVec(h); + Vec3d vec3d2 = player.lerpVelocity(h); + double d = vec3d2.horizontalLengthSquared(); + double e = vec3d.horizontalLengthSquared(); + if (d > 0.0 && e > 0.0) { + double l = (vec3d2.x * vec3d.x + vec3d2.z * vec3d.z) / Math.sqrt(d * e); + double m = vec3d2.x * vec3d.z - vec3d2.z * vec3d.x; + bodyYRotation = (float) (Math.signum(m) * Math.acos(l)); + } } } else if (i > 0.0F) { - j = aPlr.isTouchingWater() ? -90.0F - aPlr.getPitch() : -90.0F; + j = entity.isTouchingWater() ? -90.0F - entity.getPitch() : -90.0F; k = MathHelper.lerp(i, 0.0F, j); bodyXRotation = k; - } else if(plr.isSleeping()) { + } else if(entity.isSleeping()) { bodyXRotation = 90f; - } else if(plr.getPose() == EntityPose.CROUCHING) { + } else if(entity.getPose() == EntityPose.CROUCHING) { bodyXRotation = -15f; } - float breastWeight = genderPlayer.getBustSize() * 1.25f; - float targetBreastSize = genderPlayer.getBustSize(); + float breastWeight = entityConfig.getBustSize() * 1.25f; + float targetBreastSize = entityConfig.getBustSize(); - if (!genderPlayer.getGender().canHaveBreasts()) { + if (!entityConfig.getGender().canHaveBreasts()) { targetBreastSize = 0; } else { float tightness = MathHelper.clamp(armor.tightness(), 0, 1); - if(genderPlayer.getArmorPhysicsOverride()) tightness = 0; //override resistance + if(entityConfig.getArmorPhysicsOverride()) tightness = 0; //override resistance //Scale breast size by how tight the armor is, clamping at a max adjustment of shrinking by 0.15 targetBreastSize *= 1 - 0.15F * tightness; @@ -122,60 +130,60 @@ public void update(PlayerEntity plr, IGenderArmor armor) { } - Vec3d motion = plr.getPos().subtract(this.prePos); - this.prePos = plr.getPos(); + Vec3d motion = entity.getPos().subtract(this.prePos); + this.prePos = entity.getPos(); //System.out.println(motion); - float bounceIntensity = (targetBreastSize * 3f) * genderPlayer.getBounceMultiplier(); + float bounceIntensity = (targetBreastSize * 3f) * Math.round((entityConfig.getBounceMultiplier() * 3) * 100) / 100f; float resistance = MathHelper.clamp(armor.physicsResistance(), 0, 1); - if(genderPlayer.getArmorPhysicsOverride()) resistance = 0; //override resistance + if(entityConfig.getArmorPhysicsOverride()) resistance = 0; //override resistance //Adjust bounce intensity by physics resistance of the worn armor bounceIntensity *= 1 - resistance; - if(!genderPlayer.getBreasts().isUniboob()) { + if(!entityConfig.getBreasts().isUniboob()) { bounceIntensity = bounceIntensity * WildfireHelper.randFloat(0.5f, 1.5f); } - if(plr.fallDistance > 0 && !alreadyFalling) { - randomB = plr.getWorld().random.nextBoolean() ? -1 : 1; + if(entity.fallDistance > 0 && !alreadyFalling) { + randomB = entity.getWorld().random.nextBoolean() ? -1 : 1; alreadyFalling = true; } - if(plr.fallDistance == 0) alreadyFalling = false; + if(entity.fallDistance == 0) alreadyFalling = false; this.targetBounceY = (float) motion.y * bounceIntensity; this.targetBounceY += breastWeight; float horizVel = (float) Math.sqrt(Math.pow(motion.x, 2) + Math.pow(motion.z, 2)) * (bounceIntensity); //float horizLocal = -horizVel * ((plr.getRotationYawHead()-plr.renderYawOffset)<0?-1:1); - this.targetRotVel = -((plr.bodyYaw - plr.prevBodyYaw) / 15f) * bounceIntensity; + this.targetRotVel = -((entity.bodyYaw - entity.prevBodyYaw) / 15f) * bounceIntensity; //System.out.println("Body Rotation: " + (bodyXRotation) / 90); - float f2 = (float) plr.getVelocity().lengthSquared() / 0.2F; + float f2 = (float) entity.getVelocity().lengthSquared() / 0.2F; f2 = f2 * f2 * f2; if(f2 < 1.0F) f2 = 1.0F; - this.targetBounceY += MathHelper.cos(plr.limbAnimator.getPos() * 0.6662F + (float)Math.PI) * 0.5F * plr.limbAnimator.getSpeed() * 0.5F / f2; + this.targetBounceY += MathHelper.cos(entity.limbAnimator.getPos() * 0.6662F + (float)Math.PI) * 0.5F * entity.limbAnimator.getSpeed() * 0.5F / f2; //System.out.println(plr.rotationYaw); this.targetRotVel += (float) motion.y * bounceIntensity * randomB; - if(plr.getPose() == EntityPose.CROUCHING && !this.justSneaking) { + if(entity.getPose() == EntityPose.CROUCHING && !this.justSneaking) { this.justSneaking = true; this.targetBounceY += bounceIntensity; } - if(plr.getPose() != EntityPose.CROUCHING && this.justSneaking) { + if(entity.getPose() != EntityPose.CROUCHING && this.justSneaking) { this.justSneaking = false; this.targetBounceY += bounceIntensity; } //button option for extra entities - if(plr.getVehicle() != null) { - if(plr.getVehicle() instanceof BoatEntity boat) { - int rowTime = (int) boat.interpolatePaddlePhase(0, plr.limbAnimator.getPos()); - int rowTime2 = (int) boat.interpolatePaddlePhase(1, plr.limbAnimator.getPos()); + if(entity.getVehicle() != null) { + if(entity.getVehicle() instanceof BoatEntity boat) { + int rowTime = (int) boat.interpolatePaddlePhase(0, entity.limbAnimator.getPos()); + int rowTime2 = (int) boat.interpolatePaddlePhase(1, entity.limbAnimator.getPos()); float rotationL = (float) MathHelper.clampedLerp(-(float)Math.PI / 3F, -0.2617994F, (double) ((MathHelper.sin(-rowTime2) + 1.0F) / 2.0F)); float rotationR = (float) MathHelper.clampedLerp(-(float)Math.PI / 4F, (float)Math.PI / 4F, (double) ((MathHelper.sin(-rowTime + 1.0F) + 1.0F) / 2.0F)); @@ -184,39 +192,39 @@ public void update(PlayerEntity plr, IGenderArmor armor) { } } - if(plr.getVehicle() instanceof MinecartEntity cart) { + if(entity.getVehicle() instanceof MinecartEntity cart) { float speed = (float) cart.getVelocity().lengthSquared(); if(Math.random() * speed < 0.5f && speed > 0.2f) { this.targetBounceY = (Math.random() > 0.5 ? -bounceIntensity : bounceIntensity) / 6f; } } - if(plr.getVehicle() instanceof HorseEntity horse) { + if(entity.getVehicle() instanceof HorseEntity horse) { float movement = (float) horse.getVelocity().lengthSquared(); if(horse.age % clampMovement(movement) == 5 && movement > 0.1f) { this.targetBounceY = bounceIntensity / 4f; } } - if(plr.getVehicle() instanceof PigEntity pig) { + if(entity.getVehicle() instanceof PigEntity pig) { float movement = (float) pig.getVelocity().lengthSquared(); if(pig.age % clampMovement(movement) == 5 && movement > 0.08f) { this.targetBounceY = bounceIntensity / 4f; } } - if(plr.getVehicle() instanceof StriderEntity strider) { + if(entity.getVehicle() instanceof StriderEntity strider) { double heightOffset = (double)strider.getHeight() - 0.19 + (double)(0.12F * MathHelper.cos(strider.limbAnimator.getPos() * 1.5f) * 2F * Math.min(0.25F, strider.limbAnimator.getSpeed())); this.targetBounceY += ((float) (heightOffset * 3f) - 4.5f) * bounceIntensity; } } - if(plr.handSwinging && plr.age % 5 == 0 && plr.getPose() != EntityPose.SLEEPING) { + if(entity.handSwinging && entity.age % 5 == 0 && entity.getPose() != EntityPose.SLEEPING) { this.targetBounceY += (Math.random() > 0.5 ? -0.25f : 0.25f) * bounceIntensity; } - if(plr.getPose() == EntityPose.SLEEPING && !this.alreadySleeping) { + if(entity.getPose() == EntityPose.SLEEPING && !this.alreadySleeping) { this.targetBounceY = bounceIntensity; this.alreadySleeping = true; } - if(plr.getPose() != EntityPose.SLEEPING && this.alreadySleeping) { + if(entity.getPose() != EntityPose.SLEEPING && this.alreadySleeping) { this.targetBounceY = bounceIntensity; this.alreadySleeping = false; } @@ -227,7 +235,7 @@ public void update(PlayerEntity plr, IGenderArmor armor) { */ - float percent = genderPlayer.getFloppiness(); + float percent = entityConfig.getFloppiness(); float bounceAmount = 0.45f * (1f - percent) + 0.15f; //0.6f * percent - 0.15f; bounceAmount = MathHelper.clamp(bounceAmount, 0.15f, 0.6f); float delta = 2.25f - bounceAmount; diff --git a/src/main/java/com/wildfire/render/GenderArmorLayer.java b/src/main/java/com/wildfire/render/GenderArmorLayer.java new file mode 100644 index 00000000..cc2b8103 --- /dev/null +++ b/src/main/java/com/wildfire/render/GenderArmorLayer.java @@ -0,0 +1,184 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.render; + +import com.wildfire.main.WildfireGender; +import com.wildfire.main.entitydata.EntityConfig; +import com.wildfire.render.WildfireModelRenderer.BreastModelBox; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.model.ModelPart; +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.client.render.*; +import net.minecraft.client.render.entity.PlayerModelPart; +import net.minecraft.client.render.entity.feature.FeatureRendererContext; +import net.minecraft.client.render.entity.model.BipedEntityModel; +import net.minecraft.client.render.item.ItemRenderer; +import net.minecraft.client.render.model.BakedModelManager; +import net.minecraft.client.texture.Sprite; +import net.minecraft.client.texture.SpriteAtlasTexture; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.decoration.ArmorStandEntity; +import net.minecraft.item.ArmorItem; +import net.minecraft.item.ArmorMaterial; +import net.minecraft.item.DyeableArmorItem; +import net.minecraft.item.trim.ArmorTrim; +import net.minecraft.util.Identifier; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class GenderArmorLayer> extends GenderLayer { + + private final SpriteAtlasTexture armorTrimsAtlas; + protected final BreastModelBox lBoobArmor, rBoobArmor; + protected final BreastModelBox lTrim, rTrim; + private EntityConfig entityConfig; + + public GenderArmorLayer(FeatureRendererContext render, BakedModelManager bakery) { + super(render); + armorTrimsAtlas = bakery.getAtlas(TexturedRenderLayers.ARMOR_TRIMS_ATLAS_TEXTURE); + + lBoobArmor = new BreastModelBox(64, 32, 16, 17, -4F, 0.0F, 0F, 4, 5, 3, 0.0F, false); + rBoobArmor = new BreastModelBox(64, 32, 20, 17, 0, 0.0F, 0F, 4, 5, 3, 0.0F, false); + // apply a very slight delta to fix z-fighting with the armor + lTrim = new BreastModelBox(64, 32, 16, 17, -4F, 0.0F, 0F, 4, 5, 4, 0.001F, false); + rTrim = new BreastModelBox(64, 32, 20, 17, 0, 0.0F, 0F, 4, 5, 4, 0.001F, false); + } + + public Identifier getArmorResource(@Nonnull ArmorItem item, boolean legs, @Nullable String overlay) { + String material = item.getMaterial().getName(); + String namespace = "minecraft"; + int namespaceDelim = material.indexOf(":"); + if(namespaceDelim >= 0) { + namespace = material.substring(0, namespaceDelim); + material = material.substring(namespaceDelim + 1); + } + return new Identifier(namespace, "textures/models/armor/" + material + "_layer_" + (legs ? 2 : 1) + (overlay == null ? "" : "_" + overlay) + ".png"); + } + + @Override + public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, @Nonnull T ent, float limbAngle, float limbDistance, float partialTicks, float animationProgress, float headYaw, float headPitch) { + MinecraftClient client = MinecraftClient.getInstance(); + if(client.player == null) { + // we're currently in a menu, give up rendering before we crash the game + return; + } + + // If the entity has no armor to render, just immediately give up + // Note that we have to be very fast at abandoning rendering here, as this class is also attached to armor stands + if(ent.getEquippedStack(EquipmentSlot.CHEST).isEmpty()) return; + + try { + entityConfig = getConfig(ent); + if(entityConfig == null) return; + + if(!setupRender(ent, entityConfig, partialTicks)) return; + if(ent instanceof ArmorStandEntity && !genderArmor.armorStandsCopySettings()) return; + BipedEntityModel model = getContextModel(); + + // Render left + matrixStack.push(); + try { + setupTransformations(ent, model.body, matrixStack, true); + renderBreastArmor(ent, matrixStack, vertexConsumerProvider, packedLightIn, true); + } finally { + matrixStack.pop(); + } + + matrixStack.push(); + // Render right + try { + setupTransformations(ent, model.body, matrixStack, false); + renderBreastArmor(ent, matrixStack, vertexConsumerProvider, packedLightIn, false); + } finally { + matrixStack.pop(); + } + } catch (Exception e) { + WildfireGender.LOGGER.error("Failed to render breast armor", e); + } + } + + @Override + protected void setupTransformations(T entity, ModelPart body, MatrixStack matrixStack, boolean left) { + super.setupTransformations(entity, body, matrixStack, left); + if((entity instanceof AbstractClientPlayerEntity player && player.isPartVisible(PlayerModelPart.JACKET)) || + (entity instanceof ArmorStandEntity && entityConfig.hasJacketLayer())) { + matrixStack.translate(0, 0, -0.015f); + matrixStack.scale(1.05f, 1.05f, 1.05f); + } + } + + // TODO eventually expose some way for mods to override this, maybe through a default impl in IGenderArmor or similar + protected void renderBreastArmor(T entity, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, boolean left) { + if(armorStack.isEmpty() || !(armorStack.getItem() instanceof ArmorItem armorItem)) return; + + Identifier armorTexture = getArmorResource(armorItem, false, null); + Identifier overlayTexture = null; + boolean hasGlint = armorStack.hasGlint(); + float armorR = 1f, armorG = 1f, armorB = 1f; + if(armorItem instanceof DyeableArmorItem dyeableItem) { + //overlayTexture = getArmorResource(entity, armorStack, EquipmentSlot.CHEST, "overlay"); + int color = dyeableItem.getColor(armorStack); + armorR = (float) (color >> 16 & 255) / 255.0F; + armorG = (float) (color >> 8 & 255) / 255.0F; + armorB = (float) (color & 255) / 255.0F; + } + matrixStack.push(); + try { + matrixStack.translate(left ? 0.001f : -0.001f, 0.015f, -0.015f); + matrixStack.scale(1.05f, 1, 1); + BreastModelBox armor = left ? lBoobArmor : rBoobArmor; + RenderLayer armorType = RenderLayer.getArmorCutoutNoCull(armorTexture); + VertexConsumer armorVertexConsumer = ItemRenderer.getArmorGlintConsumer(vertexConsumerProvider, armorType, false, hasGlint); + renderBox(armor, matrixStack, armorVertexConsumer, packedLightIn, OverlayTexture.DEFAULT_UV, armorR, armorG, armorB, 1); + //noinspection ConstantValue + if(overlayTexture != null) { + RenderLayer overlayType = RenderLayer.getArmorCutoutNoCull(overlayTexture); + VertexConsumer overlayVertexConsumer = ItemRenderer.getArmorGlintConsumer(vertexConsumerProvider, overlayType, false, hasGlint); + renderBox(armor, matrixStack, overlayVertexConsumer, packedLightIn, OverlayTexture.DEFAULT_UV, 1, 1, 1, 1); + } + + ArmorTrim.getTrim(entity.getWorld().getRegistryManager(), armorStack, true).ifPresent((trim) -> { + renderArmorTrim(armorItem.getMaterial(), matrixStack, vertexConsumerProvider, packedLightIn, trim, hasGlint, left); + }); + } finally { + matrixStack.pop(); + } + } + + protected void renderArmorTrim(ArmorMaterial material, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, + ArmorTrim trim, boolean hasGlint, boolean left) { + BreastModelBox trimModelBox = left ? lTrim : rTrim; + Sprite sprite = this.armorTrimsAtlas.getSprite(trim.getGenericModelId(material)); + VertexConsumer vertexConsumer = sprite.getTextureSpecificVertexConsumer( + vertexConsumerProvider.getBuffer(TexturedRenderLayers.getArmorTrims(trim.getPattern().value().decal()))); + // Render the armor trim itself + renderBox(trimModelBox, matrixStack, vertexConsumer, packedLightIn, OverlayTexture.DEFAULT_UV, 1f, 1f, 1f, 1f); + // The enchantment glint however requires special handling; due to how Minecraft's enchant glint rendering works, rendering + // it at the same time as the trim itself results in the glint not rendering in sync with the rest of the armor. + // We *also* can't simply render the glint for both the trim and armor at the same time, due to the slight delta we apply + // to fix z-fighting between the trim and armor - and as such - a glint has to be rendered for each respective layer. + if(hasGlint) { + renderBox(trimModelBox, matrixStack, vertexConsumerProvider.getBuffer(RenderLayer.getArmorEntityGlint()), + packedLightIn, OverlayTexture.DEFAULT_UV, 1f, 1f, 1f, 1f); + } + } +} diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index ac96940c..a3207d4f 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -18,327 +18,273 @@ package com.wildfire.render; -import com.mojang.blaze3d.systems.RenderSystem; import com.wildfire.api.IGenderArmor; -import com.wildfire.main.Breasts; +import com.wildfire.main.entitydata.Breasts; +import com.wildfire.main.WildfireGender; import com.wildfire.main.WildfireHelper; +import com.wildfire.main.entitydata.EntityConfig; import com.wildfire.physics.BreastPhysics; import com.wildfire.render.WildfireModelRenderer.BreastModelBox; import com.wildfire.render.WildfireModelRenderer.OverlayModelBox; import com.wildfire.render.WildfireModelRenderer.PositionTextureVertex; import java.lang.Math; +import java.util.ConcurrentModificationException; import javax.annotation.Nonnull; -import com.wildfire.main.GenderPlayer; -import com.wildfire.main.WildfireGender; +import javax.annotation.Nullable; + import net.minecraft.block.Blocks; import net.minecraft.client.MinecraftClient; import net.minecraft.client.model.ModelPart; import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.client.render.*; import net.minecraft.client.render.entity.LivingEntityRenderer; -import net.minecraft.client.render.entity.PlayerEntityRenderer; import net.minecraft.client.render.entity.PlayerModelPart; import net.minecraft.client.render.entity.feature.FeatureRenderer; import net.minecraft.client.render.entity.feature.FeatureRendererContext; -import net.minecraft.client.render.entity.model.PlayerEntityModel; -import net.minecraft.client.render.item.ItemRenderer; -import net.minecraft.client.render.model.BakedModelManager; -import net.minecraft.client.texture.Sprite; -import net.minecraft.client.texture.SpriteAtlasTexture; +import net.minecraft.client.render.entity.model.BipedEntityModel; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.LivingEntity; import net.minecraft.entity.effect.StatusEffectUtil; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ArmorItem; -import net.minecraft.item.ArmorMaterial; -import net.minecraft.item.DyeableArmorItem; import net.minecraft.item.ItemStack; -import net.minecraft.item.trim.ArmorTrim; import net.minecraft.util.Identifier; import net.minecraft.util.math.*; import org.joml.*; -import javax.annotation.Nullable; -import java.util.HashMap; -import java.util.Map; - -public class GenderLayer extends FeatureRenderer> { - - private final SpriteAtlasTexture armorTrimsAtlas; +public class GenderLayer> extends FeatureRenderer { private BreastModelBox lBreast, rBreast; private final OverlayModelBox lBreastWear, rBreastWear; - private final BreastModelBox lBoobArmor, rBoobArmor; - private final BreastModelBox lTrim, rTrim; private float preBreastSize = 0f; - - public GenderLayer(FeatureRendererContext> render, - BakedModelManager bakery) { + private Breasts breasts; + protected ItemStack armorStack; + protected IGenderArmor genderArmor; + protected boolean isChestplateOccupied, bounceEnabled, breathingAnimation; + protected float breastOffsetX, breastOffsetY, breastOffsetZ, lTotal, lTotalX, rTotal, rTotalX, + leftBounceRotation, rightBounceRotation, breastSize, zOffset, outwardAngle; + + public GenderLayer(FeatureRendererContext render) { super(render); - armorTrimsAtlas = bakery.getAtlas(TexturedRenderLayers.ARMOR_TRIMS_ATLAS_TEXTURE); - lBreast = new BreastModelBox(64, 64, 16, 17, -4F, 0.0F, 0F, 4, 5, 4, 0.0F, false); rBreast = new BreastModelBox(64, 64, 20, 17, 0, 0.0F, 0F, 4, 5, 4, 0.0F, false); lBreastWear = new OverlayModelBox(true,64, 64, 17, 34, -4F, 0.0F, 0F, 4, 5, 3, 0.0F, false); rBreastWear = new OverlayModelBox(false,64, 64, 21, 34, 0, 0.0F, 0F, 4, 5, 3, 0.0F, false); + } - lBoobArmor = new BreastModelBox(64, 32, 16, 17, -4F, 0.0F, 0F, 4, 5, 3, 0.0F, false); - rBoobArmor = new BreastModelBox(64, 32, 20, 17, 0, 0.0F, 0F, 4, 5, 3, 0.0F, false); - // apply a very slight delta to fix z-fighting with the armor - lTrim = new BreastModelBox(64, 32, 16, 17, -4F, 0.0F, 0F, 4, 5, 5, 0.001F, false); - rTrim = new BreastModelBox(64, 32, 20, 17, 0, 0.0F, 0F, 4, 5, 5, 0.001F, false); + private @Nullable RenderLayer getRenderLayer(T entity) { + boolean bodyVisible = !entity.isInvisible(); + boolean translucent = !bodyVisible && !entity.isInvisibleTo(MinecraftClient.getInstance().player); + Identifier texture = getTexture(entity); + if(translucent) { + return RenderLayer.getItemEntityTranslucentCull(texture); + } else if(bodyVisible) { + return RenderLayer.getEntityTranslucent(texture); + } else if(entity.isGlowing()) { + return RenderLayer.getOutline(texture); + } + return null; } - public Identifier getArmorResource(ArmorItem item, boolean legs, @Nullable String overlay) { - return new Identifier("textures/models/armor/" + item.getMaterial().getName() + "_layer_" + (legs ? 2 : 1) + (overlay == null ? "" : "_" + overlay) + ".png"); + protected @Nullable EntityConfig getConfig(T entity) { + try { + return EntityConfig.getEntity(entity); + } catch(ConcurrentModificationException e) { + // likely a temporary failure, try again later + return null; + } } @Override - public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, @Nonnull AbstractClientPlayerEntity ent, float limbAngle, + public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, @Nonnull T ent, float limbAngle, float limbDistance, float partialTicks, float animationProgress, float headYaw, float headPitch) { - //Surround with a try/catch to fix for essential mod. - try { - GenderPlayer plr = WildfireGender.getPlayerById(ent.getUuid()); - if(plr == null) return; - - ItemStack armorStack = ent.getEquippedStack(EquipmentSlot.CHEST); - //Note: When the stack is empty the helper will fall back to an implementation that returns the proper data - IGenderArmor genderArmor = WildfireHelper.getArmorConfig(armorStack); - boolean isChestplateOccupied = genderArmor.coversBreasts() && !plr.getArmorPhysicsOverride(); - if (genderArmor.alwaysHidesBreasts() || !plr.showBreastsInArmor() && isChestplateOccupied) { - //If the armor always hides breasts or there is armor and the player configured breasts - // to be hidden when wearing armor, we can just exit early rather than doing any calculations - return; - } - if(!isChestplateOccupied && ent.isInvisibleTo(MinecraftClient.getInstance().player)) { - // nothing to render here, just exit early - return; - } - - PlayerEntityRenderer rend = (PlayerEntityRenderer) MinecraftClient.getInstance().getEntityRenderDispatcher().getRenderer(ent); - PlayerEntityModel model = rend.getModel(); - - Breasts breasts = plr.getBreasts(); - float breastOffsetX = Math.round((Math.round(breasts.getXOffset() * 100f) / 100f) * 10) / 10f; - float breastOffsetY = -Math.round((Math.round(breasts.getYOffset() * 100f) / 100f) * 10) / 10f; - float breastOffsetZ = -Math.round((Math.round(breasts.getZOffset() * 100f) / 100f) * 10) / 10f; - - BreastPhysics leftBreastPhysics = plr.getLeftBreastPhysics(); - final float bSize = leftBreastPhysics.getBreastSize(partialTicks); - float outwardAngle = (Math.round(breasts.getCleavage() * 100f) / 100f) * 100f; - outwardAngle = Math.min(outwardAngle, 10); + MinecraftClient client = MinecraftClient.getInstance(); + if(client.player == null) { + // we're currently in a menu, give up rendering before we crash the game + return; + } - float reducer = 0; - if (bSize < 0.84f) reducer++; - if (bSize < 0.72f) reducer++; + EntityConfig entityConfig = getConfig(ent); + if(entityConfig == null) return; - if (preBreastSize != bSize) { - lBreast = new BreastModelBox(64, 64, 16, 17, -4F, 0.0F, 0F, 4, 5, (int) (4 - breastOffsetZ - reducer), 0.0F, false); - rBreast = new BreastModelBox(64, 64, 20, 17, 0, 0.0F, 0F, 4, 5, (int) (4 - breastOffsetZ - reducer), 0.0F, false); - preBreastSize = bSize; + try { + if(!setupRender(ent, entityConfig, partialTicks)) return; + int combineTex = LivingEntityRenderer.getOverlay(ent, 0); + BipedEntityModel model = getContextModel(); + + // Render left + matrixStack.push(); + try { + setupTransformations(ent, model.body, matrixStack, true); + renderBreast(ent, matrixStack, vertexConsumerProvider, packedLightIn, combineTex, true); + } finally { + matrixStack.pop(); } - //Note: We only render if the entity is not visible to the player, so we can assume it is visible to the player - float overlayAlpha = ent.isInvisible() ? 0.15F : 1; - RenderSystem.setShaderColor(1f, 1f, 1f, 1f); - - float lTotal = MathHelper.lerp(partialTicks, leftBreastPhysics.getPreBounceY(), leftBreastPhysics.getBounceY()); - float lTotalX = MathHelper.lerp(partialTicks, leftBreastPhysics.getPreBounceX(), leftBreastPhysics.getBounceX()); - float leftBounceRotation = MathHelper.lerp(partialTicks, leftBreastPhysics.getPreBounceRotation(), leftBreastPhysics.getBounceRotation()); - float rTotal; - float rTotalX; - float rightBounceRotation; - if (breasts.isUniboob()) { - rTotal = lTotal; - rTotalX = lTotalX; - rightBounceRotation = leftBounceRotation; - } else { - BreastPhysics rightBreastPhysics = plr.getRightBreastPhysics(); - rTotal = MathHelper.lerp(partialTicks, rightBreastPhysics.getPreBounceY(), rightBreastPhysics.getBounceY()); - rTotalX = MathHelper.lerp(partialTicks, rightBreastPhysics.getPreBounceX(), rightBreastPhysics.getBounceX()); - rightBounceRotation = MathHelper.lerp(partialTicks, rightBreastPhysics.getPreBounceRotation(), rightBreastPhysics.getBounceRotation()); + // Render right + matrixStack.push(); + try { + setupTransformations(ent, model.body, matrixStack, false); + renderBreast(ent, matrixStack, vertexConsumerProvider, packedLightIn, combineTex, false); + } finally { + matrixStack.pop(); } - float breastSize = bSize * 1.5f; - if (breastSize > 0.7f) breastSize = 0.7f; - if (bSize > 0.7f) breastSize = bSize; - if (breastSize < 0.02f) return; - - float zOff = 0.0625f - (bSize * 0.0625f); - breastSize = bSize + 0.5f * Math.abs(bSize - 0.7f) * 2f; - - //matrixStack.translate(0, 0, zOff); - //System.out.println(bounceRotation); - - float resistance = MathHelper.clamp(genderArmor.physicsResistance(), 0, 1); - //Note: We only check if the breathing animation should be enabled if the chestplate's physics resistance - // is less than or equal to 0.5 so that if we won't be rendering it we can avoid doing extra calculations - boolean breathingAnimation = ((plr.getArmorPhysicsOverride() || resistance <= 0.5F) && - (!ent.isSubmergedInWater() || StatusEffectUtil.hasWaterBreathing(ent) || - ent.getWorld().getBlockState(new BlockPos(ent.getBlockX(), ent.getBlockY(), ent.getBlockZ())).isOf(Blocks.BUBBLE_COLUMN))); - boolean bounceEnabled = plr.hasBreastPhysics() && (!isChestplateOccupied || resistance < 1); //oh, you found this? - - int combineTex = LivingEntityRenderer.getOverlay(ent, 0); - RenderLayer type = RenderLayer.getEntityTranslucent(rend.getTexture(ent)); - renderBreastWithTransforms(ent, model.body, armorStack, matrixStack, vertexConsumerProvider, type, packedLightIn, combineTex, - overlayAlpha, bounceEnabled, lTotalX, lTotal, leftBounceRotation, breastSize, breastOffsetX, breastOffsetY, breastOffsetZ, zOff, - outwardAngle, breasts.isUniboob(), isChestplateOccupied, breathingAnimation, true); - renderBreastWithTransforms(ent, model.body, armorStack, matrixStack, vertexConsumerProvider, type, packedLightIn, combineTex, - overlayAlpha, bounceEnabled, rTotalX, rTotal, rightBounceRotation, breastSize, -breastOffsetX, breastOffsetY, breastOffsetZ, zOff, - -outwardAngle, breasts.isUniboob(), isChestplateOccupied, breathingAnimation, false); - RenderSystem.setShaderColor(1f, 1f, 1f, 1f); } catch(Exception e) { - e.printStackTrace(); + WildfireGender.LOGGER.error("Failed to render breast layer", e); } } - private void renderBreastWithTransforms(AbstractClientPlayerEntity entity, ModelPart body, ItemStack armorStack, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, - RenderLayer breastRenderType, int packedLightIn, int combineTex, float alpha, boolean bounceEnabled, float totalX, float total, float bounceRotation, - float breastSize, float breastOffsetX, float breastOffsetY, float breastOffsetZ, float zOff, float outwardAngle, boolean uniboob, - boolean isChestplateOccupied, boolean breathingAnimation, boolean left) { - matrixStack.push(); - //Surround with a try/catch to fix for essential mod. - try { - matrixStack.translate(body.pivotX * 0.0625f, body.pivotY * 0.0625f, body.pivotZ * 0.0625f); - if (body.roll != 0.0F) { - matrixStack.multiply(new Quaternionf().rotationXYZ(0f, 0f, body.roll)); - } - if (body.yaw != 0.0F) { - matrixStack.multiply(new Quaternionf().rotationXYZ(0f, body.yaw, 0f)); - } - if (body.pitch != 0.0F) { - matrixStack.multiply(new Quaternionf().rotationXYZ(body.pitch, 0f, 0f)); - } - - if (bounceEnabled) { - matrixStack.translate(totalX / 32f, 0, 0); - matrixStack.translate(0, total / 32f, 0); - } + /** + * Common logic for setting up breast rendering + * + * @return {@code true} if rendering should continue + */ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + protected boolean setupRender(T entity, EntityConfig entityConfig, float partialTicks) { + // Rendering breaks quite spectacularly on baby mobs, so just immediately give up before we even + // attempt rendering on such an entity. + if(entity.isBaby()) return false; + + armorStack = entity.getEquippedStack(EquipmentSlot.CHEST); + //Note: When the stack is empty the helper will fall back to an implementation that returns the proper data + genderArmor = WildfireHelper.getArmorConfig(armorStack); + isChestplateOccupied = genderArmor.coversBreasts() && !entityConfig.getArmorPhysicsOverride(); + if(genderArmor.alwaysHidesBreasts() || !entityConfig.showBreastsInArmor() && isChestplateOccupied) { + //If the armor always hides breasts or there is armor and the player configured breasts + // to be hidden when wearing armor, we can just exit early rather than doing any calculations + return false; + } - matrixStack.translate(breastOffsetX * 0.0625f, 0.05625f + (breastOffsetY * 0.0625f), zOff - 0.0625f * 2f + (breastOffsetZ * 0.0625f)); //shift down to correct position + RenderLayer type = getRenderLayer(entity); + if(type == null && !isChestplateOccupied) { + // the entity is invisible and doesn't have a chestplate equipped + return false; + } - if (!uniboob) { - matrixStack.translate(-0.0625f * 2 * (left ? 1 : -1), 0, 0); - } - if (bounceEnabled) { - matrixStack.multiply(new Quaternionf().rotationXYZ(0, (float)(bounceRotation * (Math.PI / 180f)), 0)); - } - if (!uniboob) { - matrixStack.translate(0.0625f * 2 * (left ? 1 : -1), 0, 0); - } + breasts = entityConfig.getBreasts(); + breastOffsetX = Math.round((Math.round(breasts.getXOffset() * 100f) / 100f) * 10) / 10f; + breastOffsetY = -Math.round((Math.round(breasts.getYOffset() * 100f) / 100f) * 10) / 10f; + breastOffsetZ = -Math.round((Math.round(breasts.getZOffset() * 100f) / 100f) * 10) / 10f; - float rotationMultiplier = 0; - if (bounceEnabled) { - matrixStack.translate(0, -0.035f * breastSize, 0); //shift down to correct position - rotationMultiplier = -total / 12f; - } - float totalRotation = breastSize + rotationMultiplier; - if (!bounceEnabled) { - totalRotation = breastSize; - } - if (totalRotation > breastSize + 0.2F) { - totalRotation = breastSize + 0.2F; - } - totalRotation = Math.min(totalRotation, 1); //hard limit for MAX + BreastPhysics leftBreastPhysics = entityConfig.getLeftBreastPhysics(); + final float bSize = leftBreastPhysics.getBreastSize(partialTicks); + outwardAngle = (Math.round(breasts.getCleavage() * 100f) / 100f) * 100f; + outwardAngle = Math.min(outwardAngle, 10); - if (isChestplateOccupied) { - matrixStack.translate(0, 0, 0.01f); - } + float reducer = 0; + if(bSize < 0.84f) reducer++; + if(bSize < 0.72f) reducer++; - matrixStack.multiply(new Quaternionf().rotationXYZ(0, (float)(outwardAngle * (Math.PI / 180f)), 0)); - matrixStack.multiply(new Quaternionf().rotationXYZ((float)(-35f * totalRotation * (Math.PI / 180f)), 0, 0)); + if(preBreastSize != bSize) { + lBreast = new BreastModelBox(64, 64, 16, 17, -4F, 0.0F, 0F, 4, 5, (int) (4 - breastOffsetZ - reducer), 0.0F, false); + rBreast = new BreastModelBox(64, 64, 20, 17, 0, 0.0F, 0F, 4, 5, (int) (4 - breastOffsetZ - reducer), 0.0F, false); + preBreastSize = bSize; + } - if (breathingAnimation) { - float f5 = -MathHelper.cos(entity.age * 0.09F) * 0.45F + 0.45F; - matrixStack.multiply(new Quaternionf().rotationXYZ((float)(f5 * (Math.PI / 180f)), 0, 0)); - } + lTotal = MathHelper.lerp(partialTicks, leftBreastPhysics.getPreBounceY(), leftBreastPhysics.getBounceY()); + lTotalX = MathHelper.lerp(partialTicks, leftBreastPhysics.getPreBounceX(), leftBreastPhysics.getBounceX()); + leftBounceRotation = MathHelper.lerp(partialTicks, leftBreastPhysics.getPreBounceRotation(), leftBreastPhysics.getBounceRotation()); + if(breasts.isUniboob()) { + rTotal = lTotal; + rTotalX = lTotalX; + rightBounceRotation = leftBounceRotation; + } else { + BreastPhysics rightBreastPhysics = entityConfig.getRightBreastPhysics(); + rTotal = MathHelper.lerp(partialTicks, rightBreastPhysics.getPreBounceY(), rightBreastPhysics.getBounceY()); + rTotalX = MathHelper.lerp(partialTicks, rightBreastPhysics.getPreBounceX(), rightBreastPhysics.getBounceX()); + rightBounceRotation = MathHelper.lerp(partialTicks, rightBreastPhysics.getPreBounceRotation(), rightBreastPhysics.getBounceRotation()); + } + breastSize = bSize * 1.5f; + if(breastSize > 0.7f) breastSize = 0.7f; + if(bSize > 0.7f) breastSize = bSize; + if(breastSize < 0.02f) return false; + + zOffset = 0.0625f - (bSize * 0.0625f); + breastSize = bSize + 0.5f * Math.abs(bSize - 0.7f) * 2f; + + float resistance = MathHelper.clamp(genderArmor.physicsResistance(), 0, 1); + //Note: We only check if the breathing animation should be enabled if the chestplate's physics resistance + // is less than or equal to 0.5 so that if we won't be rendering it we can avoid doing extra calculations + breathingAnimation = ((entityConfig.getArmorPhysicsOverride() || resistance <= 0.5F) && + (!entity.isSubmergedInWater() || StatusEffectUtil.hasWaterBreathing(entity) || + entity.getWorld().getBlockState(new BlockPos(entity.getBlockX(), entity.getBlockY(), entity.getBlockZ())).isOf(Blocks.BUBBLE_COLUMN))); + bounceEnabled = entityConfig.hasBreastPhysics() && (!isChestplateOccupied || resistance < 1); //oh, you found this? + return true; + } - matrixStack.scale(0.9995f, 1f, 1f); //z-fighting FIXXX + protected void setupTransformations(T entity, ModelPart body, MatrixStack matrixStack, boolean left) { + matrixStack.translate(body.pivotX * 0.0625f, body.pivotY * 0.0625f, body.pivotZ * 0.0625f); + if(body.roll != 0.0F) { + matrixStack.multiply(new Quaternionf().rotationXYZ(0f, 0f, body.roll)); + } + if(body.yaw != 0.0F) { + matrixStack.multiply(new Quaternionf().rotationXYZ(0f, body.yaw, 0f)); + } + if(body.pitch != 0.0F) { + matrixStack.multiply(new Quaternionf().rotationXYZ(body.pitch, 0f, 0f)); + } - renderBreast(entity, armorStack, matrixStack, vertexConsumerProvider, breastRenderType, packedLightIn, combineTex, alpha, left); - } catch(Exception e) { - e.printStackTrace(); - } finally { - matrixStack.pop(); + if(bounceEnabled) { + matrixStack.translate((left ? lTotalX : rTotalX) / 32f, 0, 0); + matrixStack.translate(0, (left ? lTotal : rTotal) / 32f, 0); } - } - private void renderBreast(AbstractClientPlayerEntity entity, ItemStack armorStack, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, RenderLayer breastRenderType, - int packedLightIn, int packedOverlayIn, float alpha, boolean left) { - VertexConsumer vertexConsumer = vertexConsumerProvider.getBuffer(breastRenderType); - // We don't want to render the player if they have invisibility active - if (!entity.isInvisibleTo(MinecraftClient.getInstance().player)) { - renderBox(left ? lBreast : rBreast, matrixStack, vertexConsumer, packedLightIn, packedOverlayIn, 1f, 1f, 1f, alpha); - if (entity.isPartVisible(PlayerModelPart.JACKET)) { - matrixStack.translate(0, 0, -0.015f); - matrixStack.scale(1.05f, 1.05f, 1.05f); - renderBox(left ? lBreastWear : rBreastWear, matrixStack, vertexConsumer, packedLightIn, packedOverlayIn, 1f, 1f, 1f, alpha); - } + matrixStack.translate((left ? breastOffsetX : -breastOffsetX) * 0.0625f, 0.05625f + (breastOffsetY * 0.0625f), zOffset - 0.0625f * 2f + (breastOffsetZ * 0.0625f)); //shift down to correct position + + if(!breasts.isUniboob()) { + matrixStack.translate(-0.0625f * 2 * (left ? 1 : -1), 0, 0); + } + if(bounceEnabled) { + matrixStack.multiply(new Quaternionf().rotationXYZ(0, (float)((left ? leftBounceRotation : rightBounceRotation) * (Math.PI / 180f)), 0)); + } + if(!breasts.isUniboob()) { + matrixStack.translate(0.0625f * 2 * (left ? 1 : -1), 0, 0); } - // But we do still want to render their chestplate to match vanilla behavior - if (!armorStack.isEmpty() && armorStack.getItem() instanceof ArmorItem armorItem) { - renderVanillaLikeBreastArmor(entity, matrixStack, vertexConsumerProvider, armorItem, armorStack, packedLightIn, left); + float rotationMultiplier = 0; + if(bounceEnabled) { + matrixStack.translate(0, -0.035f * breastSize, 0); //shift down to correct position + rotationMultiplier = -(left ? lTotal : rTotal) / 12f; } - } + float totalRotation = breastSize + rotationMultiplier; + if(!bounceEnabled) { + totalRotation = breastSize; + } + if(totalRotation > breastSize + 0.2F) { + totalRotation = breastSize + 0.2F; + } + totalRotation = Math.min(totalRotation, 1); //hard limit for MAX - // TODO eventually expose some way for mods to override this, maybe through a default impl in IGenderArmor or similar - private void renderVanillaLikeBreastArmor(PlayerEntity entity, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, ArmorItem armorItem, - ItemStack armorStack, int packedLightIn, boolean left) { - Identifier armorTexture = getArmorResource(armorItem, false, null); - Identifier overlayTexture = null; - boolean hasGlint = armorStack.hasGlint(); - float armorR = 1f, armorG = 1f, armorB = 1f; - if (armorItem instanceof DyeableArmorItem dyeableItem) { - //overlayTexture = getArmorResource(entity, armorStack, EquipmentSlot.CHEST, "overlay"); - int color = dyeableItem.getColor(armorStack); - armorR = (float) (color >> 16 & 255) / 255.0F; - armorG = (float) (color >> 8 & 255) / 255.0F; - armorB = (float) (color & 255) / 255.0F; + if(isChestplateOccupied) { + matrixStack.translate(0, 0, 0.01f); } - matrixStack.push(); - try { - matrixStack.translate(left ? 0.001f : -0.001f, 0.015f, -0.015f); - matrixStack.scale(1.05f, 1, 1); - WildfireModelRenderer.BreastModelBox armor = left ? lBoobArmor : rBoobArmor; - RenderLayer armorType = RenderLayer.getArmorCutoutNoCull(armorTexture); - VertexConsumer armorVertexConsumer = ItemRenderer.getArmorGlintConsumer(vertexConsumerProvider, armorType, false, hasGlint); - renderBox(armor, matrixStack, armorVertexConsumer, packedLightIn, OverlayTexture.DEFAULT_UV, armorR, armorG, armorB, 1); - //noinspection ConstantValue - if (overlayTexture != null) { - RenderLayer overlayType = RenderLayer.getArmorCutoutNoCull(overlayTexture); - VertexConsumer overlayVertexConsumer = ItemRenderer.getArmorGlintConsumer(vertexConsumerProvider, overlayType, false, hasGlint); - renderBox(armor, matrixStack, overlayVertexConsumer, packedLightIn, OverlayTexture.DEFAULT_UV, 1, 1, 1, 1); - } - ArmorTrim.getTrim(entity.getWorld().getRegistryManager(), armorStack, true).ifPresent((trim) -> { - renderArmorTrim(armorItem.getMaterial(), matrixStack, vertexConsumerProvider, packedLightIn, trim, hasGlint, left); - }); - } finally { - matrixStack.pop(); + matrixStack.multiply(new Quaternionf().rotationXYZ(0, (float)((left ? outwardAngle : -outwardAngle) * (Math.PI / 180f)), 0)); + matrixStack.multiply(new Quaternionf().rotationXYZ((float)(-35f * totalRotation * (Math.PI / 180f)), 0, 0)); + + if(breathingAnimation) { + float f5 = -MathHelper.cos(entity.age * 0.09F) * 0.45F + 0.45F; + matrixStack.multiply(new Quaternionf().rotationXYZ((float)(f5 * (Math.PI / 180f)), 0, 0)); } + + matrixStack.scale(0.9995f, 1f, 1f); //z-fighting FIXXX } - private void renderArmorTrim(ArmorMaterial material, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, - ArmorTrim trim, boolean hasGlint, boolean left) { - BreastModelBox trimModelBox = left ? lTrim : rTrim; - Sprite sprite = this.armorTrimsAtlas.getSprite(trim.getGenericModelId(material)); - VertexConsumer vertexConsumer = sprite.getTextureSpecificVertexConsumer( - vertexConsumerProvider.getBuffer(TexturedRenderLayers.getArmorTrims(trim.getPattern().value().decal()))); - // Render the armor trim itself - renderBox(trimModelBox, matrixStack, vertexConsumer, packedLightIn, OverlayTexture.DEFAULT_UV, 1f, 1f, 1f, 1f); - // The enchantment glint however requires special handling; due to how Minecraft's enchant glint rendering works, rendering - // it at the same time as the trim itself results in the glint not rendering in sync with the rest of the armor. - // We *also* can't simply render the glint for both the trim and armor at the same time, due to the slight delta we apply - // to fix z-fighting between the trim and armor - and as such - a glint has to be rendered for each respective layer. - if(hasGlint) { - renderBox(trimModelBox, matrixStack, vertexConsumerProvider.getBuffer(RenderLayer.getArmorEntityGlint()), - packedLightIn, OverlayTexture.DEFAULT_UV, 1f, 1f, 1f, 1f); + protected void renderBreast(T entity, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, int packedOverlayIn, boolean left) { + RenderLayer breastRenderType = getRenderLayer(entity); + if(breastRenderType == null) return; // only render if the player is visible in some capacity + float alpha = entity.isInvisible() ? 0.15F : 1; + VertexConsumer vertexConsumer = vertexConsumerProvider.getBuffer(breastRenderType); + renderBox(left ? lBreast : rBreast, matrixStack, vertexConsumer, packedLightIn, packedOverlayIn, 1f, 1f, 1f, alpha); + if(entity instanceof AbstractClientPlayerEntity player && player.isPartVisible(PlayerModelPart.JACKET)) { + matrixStack.translate(0, 0, -0.015f); + matrixStack.scale(1.05f, 1.05f, 1.05f); + renderBox(left ? lBreastWear : rBreastWear, matrixStack, vertexConsumer, packedLightIn, packedOverlayIn, 1f, 1f, 1f, alpha); } } - private static void renderBox(WildfireModelRenderer.ModelBox model, MatrixStack matrixStack, VertexConsumer bufferIn, int packedLightIn, int packedOverlayIn, + protected static void renderBox(WildfireModelRenderer.ModelBox model, MatrixStack matrixStack, VertexConsumer bufferIn, int packedLightIn, int packedOverlayIn, float red, float green, float blue, float alpha) { Matrix4f matrix4f = matrixStack.peek().getPositionMatrix(); Matrix3f matrix3f = matrixStack.peek().getNormalMatrix(); diff --git a/src/main/java/com/wildfire/render/armor/EmptyGenderArmor.java b/src/main/java/com/wildfire/render/armor/EmptyGenderArmor.java index a1cb2f57..b818693f 100644 --- a/src/main/java/com/wildfire/render/armor/EmptyGenderArmor.java +++ b/src/main/java/com/wildfire/render/armor/EmptyGenderArmor.java @@ -34,4 +34,9 @@ private EmptyGenderArmor() { public boolean coversBreasts() { return false; } -} \ No newline at end of file + + @Override + public boolean armorStandsCopySettings() { + return false; + } +} diff --git a/src/main/java/com/wildfire/render/armor/SimpleGenderArmor.java b/src/main/java/com/wildfire/render/armor/SimpleGenderArmor.java index 9c0ba521..a861a8d8 100644 --- a/src/main/java/com/wildfire/render/armor/SimpleGenderArmor.java +++ b/src/main/java/com/wildfire/render/armor/SimpleGenderArmor.java @@ -21,19 +21,27 @@ import com.wildfire.api.IGenderArmor; /** - * Base class to help define default implementations of {@link IGenderArmor}. + * Default implementations of {@link IGenderArmor} for vanilla armor types */ -public record SimpleGenderArmor(float physicsResistance, float tightness) implements IGenderArmor { +public record SimpleGenderArmor(float physicsResistance, float tightness, boolean armorStandsCopySettings) implements IGenderArmor { public static final SimpleGenderArmor FALLBACK = new SimpleGenderArmor(0.5F); public static final SimpleGenderArmor LEATHER = new SimpleGenderArmor(0.3F, 0.5F); public static final SimpleGenderArmor CHAIN_MAIL = new SimpleGenderArmor(0.5F, 0.2F); - public static final SimpleGenderArmor GOLD = new SimpleGenderArmor(0.85F); - public static final SimpleGenderArmor IRON = new SimpleGenderArmor(1); - public static final SimpleGenderArmor DIAMOND = new SimpleGenderArmor(1); - public static final SimpleGenderArmor NETHERITE = new SimpleGenderArmor(1); + public static final SimpleGenderArmor GOLD = new SimpleGenderArmor(0.85F, true); + public static final SimpleGenderArmor IRON = new SimpleGenderArmor(1, true); + public static final SimpleGenderArmor DIAMOND = new SimpleGenderArmor(1, true); + public static final SimpleGenderArmor NETHERITE = new SimpleGenderArmor(1, true); public SimpleGenderArmor(float physicsResistance) { - this(physicsResistance, 0); + this(physicsResistance, 0, false); + } + + public SimpleGenderArmor(float physicsResistance, boolean armorStandsCopySettings) { + this(physicsResistance, 0f, armorStandsCopySettings); + } + + public SimpleGenderArmor(float physicsResistance, float tightness) { + this(physicsResistance, tightness, false); } } \ No newline at end of file diff --git a/src/main/resources/wildfire_gender.mixins.json b/src/main/resources/wildfire_gender.mixins.json index e6dd8f4e..1a606acb 100644 --- a/src/main/resources/wildfire_gender.mixins.json +++ b/src/main/resources/wildfire_gender.mixins.json @@ -4,11 +4,13 @@ "package": "com.wildfire.mixins", "compatibilityLevel": "JAVA_17", "mixins": [ + "ArmorStandEntityMixin", "LivingEntityMixin" ], "client": [ - "PlayerEntityMixin", - "PlayerRenderMixin" + "ArmorStandEntityRendererMixin", + "PlayerRenderMixin", + "BreastPhysicsTickMixin" ], "injectors": { "defaultRequire": 1 From 6f0d93732ee97b38925b0cd876de4ccb27384f19 Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 26 Nov 2023 21:08:22 -0700 Subject: [PATCH 035/238] Switch to using an enum instead of a boolean for left/right rendering --- .../java/com/wildfire/render/BreastSide.java | 5 ++++ .../com/wildfire/render/GenderArmorLayer.java | 24 +++++++++---------- .../java/com/wildfire/render/GenderLayer.java | 17 ++++++------- 3 files changed, 26 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/wildfire/render/BreastSide.java diff --git a/src/main/java/com/wildfire/render/BreastSide.java b/src/main/java/com/wildfire/render/BreastSide.java new file mode 100644 index 00000000..3d094a7f --- /dev/null +++ b/src/main/java/com/wildfire/render/BreastSide.java @@ -0,0 +1,5 @@ +package com.wildfire.render; + +public enum BreastSide { + LEFT, RIGHT +} diff --git a/src/main/java/com/wildfire/render/GenderArmorLayer.java b/src/main/java/com/wildfire/render/GenderArmorLayer.java index cc2b8103..a4ccac30 100644 --- a/src/main/java/com/wildfire/render/GenderArmorLayer.java +++ b/src/main/java/com/wildfire/render/GenderArmorLayer.java @@ -97,8 +97,8 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume // Render left matrixStack.push(); try { - setupTransformations(ent, model.body, matrixStack, true); - renderBreastArmor(ent, matrixStack, vertexConsumerProvider, packedLightIn, true); + setupTransformations(ent, model.body, matrixStack, BreastSide.LEFT); + renderBreastArmor(ent, matrixStack, vertexConsumerProvider, packedLightIn, BreastSide.LEFT); } finally { matrixStack.pop(); } @@ -106,8 +106,8 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume matrixStack.push(); // Render right try { - setupTransformations(ent, model.body, matrixStack, false); - renderBreastArmor(ent, matrixStack, vertexConsumerProvider, packedLightIn, false); + setupTransformations(ent, model.body, matrixStack, BreastSide.RIGHT); + renderBreastArmor(ent, matrixStack, vertexConsumerProvider, packedLightIn, BreastSide.RIGHT); } finally { matrixStack.pop(); } @@ -117,8 +117,8 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume } @Override - protected void setupTransformations(T entity, ModelPart body, MatrixStack matrixStack, boolean left) { - super.setupTransformations(entity, body, matrixStack, left); + protected void setupTransformations(T entity, ModelPart body, MatrixStack matrixStack, BreastSide side) { + super.setupTransformations(entity, body, matrixStack, side); if((entity instanceof AbstractClientPlayerEntity player && player.isPartVisible(PlayerModelPart.JACKET)) || (entity instanceof ArmorStandEntity && entityConfig.hasJacketLayer())) { matrixStack.translate(0, 0, -0.015f); @@ -127,7 +127,7 @@ protected void setupTransformations(T entity, ModelPart body, MatrixStack matrix } // TODO eventually expose some way for mods to override this, maybe through a default impl in IGenderArmor or similar - protected void renderBreastArmor(T entity, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, boolean left) { + protected void renderBreastArmor(T entity, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, BreastSide side) { if(armorStack.isEmpty() || !(armorStack.getItem() instanceof ArmorItem armorItem)) return; Identifier armorTexture = getArmorResource(armorItem, false, null); @@ -143,9 +143,9 @@ protected void renderBreastArmor(T entity, MatrixStack matrixStack, VertexConsum } matrixStack.push(); try { - matrixStack.translate(left ? 0.001f : -0.001f, 0.015f, -0.015f); + matrixStack.translate(side == BreastSide.LEFT ? 0.001f : -0.001f, 0.015f, -0.015f); matrixStack.scale(1.05f, 1, 1); - BreastModelBox armor = left ? lBoobArmor : rBoobArmor; + BreastModelBox armor = side == BreastSide.LEFT ? lBoobArmor : rBoobArmor; RenderLayer armorType = RenderLayer.getArmorCutoutNoCull(armorTexture); VertexConsumer armorVertexConsumer = ItemRenderer.getArmorGlintConsumer(vertexConsumerProvider, armorType, false, hasGlint); renderBox(armor, matrixStack, armorVertexConsumer, packedLightIn, OverlayTexture.DEFAULT_UV, armorR, armorG, armorB, 1); @@ -157,7 +157,7 @@ protected void renderBreastArmor(T entity, MatrixStack matrixStack, VertexConsum } ArmorTrim.getTrim(entity.getWorld().getRegistryManager(), armorStack, true).ifPresent((trim) -> { - renderArmorTrim(armorItem.getMaterial(), matrixStack, vertexConsumerProvider, packedLightIn, trim, hasGlint, left); + renderArmorTrim(armorItem.getMaterial(), matrixStack, vertexConsumerProvider, packedLightIn, trim, hasGlint, side); }); } finally { matrixStack.pop(); @@ -165,8 +165,8 @@ protected void renderBreastArmor(T entity, MatrixStack matrixStack, VertexConsum } protected void renderArmorTrim(ArmorMaterial material, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, - ArmorTrim trim, boolean hasGlint, boolean left) { - BreastModelBox trimModelBox = left ? lTrim : rTrim; + ArmorTrim trim, boolean hasGlint, BreastSide side) { + BreastModelBox trimModelBox = side == BreastSide.LEFT ? lTrim : rTrim; Sprite sprite = this.armorTrimsAtlas.getSprite(trim.getGenericModelId(material)); VertexConsumer vertexConsumer = sprite.getTextureSpecificVertexConsumer( vertexConsumerProvider.getBuffer(TexturedRenderLayers.getArmorTrims(trim.getPattern().value().decal()))); diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index a3207d4f..966c6559 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -116,8 +116,8 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume // Render left matrixStack.push(); try { - setupTransformations(ent, model.body, matrixStack, true); - renderBreast(ent, matrixStack, vertexConsumerProvider, packedLightIn, combineTex, true); + setupTransformations(ent, model.body, matrixStack, BreastSide.LEFT); + renderBreast(ent, matrixStack, vertexConsumerProvider, packedLightIn, combineTex, BreastSide.LEFT); } finally { matrixStack.pop(); } @@ -125,8 +125,8 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume // Render right matrixStack.push(); try { - setupTransformations(ent, model.body, matrixStack, false); - renderBreast(ent, matrixStack, vertexConsumerProvider, packedLightIn, combineTex, false); + setupTransformations(ent, model.body, matrixStack, BreastSide.RIGHT); + renderBreast(ent, matrixStack, vertexConsumerProvider, packedLightIn, combineTex, BreastSide.RIGHT); } finally { matrixStack.pop(); } @@ -213,7 +213,8 @@ protected boolean setupRender(T entity, EntityConfig entityConfig, float partial return true; } - protected void setupTransformations(T entity, ModelPart body, MatrixStack matrixStack, boolean left) { + protected void setupTransformations(T entity, ModelPart body, MatrixStack matrixStack, BreastSide side) { + boolean left = side == BreastSide.LEFT; matrixStack.translate(body.pivotX * 0.0625f, body.pivotY * 0.0625f, body.pivotZ * 0.0625f); if(body.roll != 0.0F) { matrixStack.multiply(new Quaternionf().rotationXYZ(0f, 0f, body.roll)); @@ -271,16 +272,16 @@ protected void setupTransformations(T entity, ModelPart body, MatrixStack matrix matrixStack.scale(0.9995f, 1f, 1f); //z-fighting FIXXX } - protected void renderBreast(T entity, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, int packedOverlayIn, boolean left) { + private void renderBreast(T entity, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, int packedOverlayIn, BreastSide side) { RenderLayer breastRenderType = getRenderLayer(entity); if(breastRenderType == null) return; // only render if the player is visible in some capacity float alpha = entity.isInvisible() ? 0.15F : 1; VertexConsumer vertexConsumer = vertexConsumerProvider.getBuffer(breastRenderType); - renderBox(left ? lBreast : rBreast, matrixStack, vertexConsumer, packedLightIn, packedOverlayIn, 1f, 1f, 1f, alpha); + renderBox(side == BreastSide.LEFT ? lBreast : rBreast, matrixStack, vertexConsumer, packedLightIn, packedOverlayIn, 1f, 1f, 1f, alpha); if(entity instanceof AbstractClientPlayerEntity player && player.isPartVisible(PlayerModelPart.JACKET)) { matrixStack.translate(0, 0, -0.015f); matrixStack.scale(1.05f, 1.05f, 1.05f); - renderBox(left ? lBreastWear : rBreastWear, matrixStack, vertexConsumer, packedLightIn, packedOverlayIn, 1f, 1f, 1f, alpha); + renderBox(side == BreastSide.LEFT ? lBreastWear : rBreastWear, matrixStack, vertexConsumer, packedLightIn, packedOverlayIn, 1f, 1f, 1f, alpha); } } From d7f1c00ceb7425b3104026cb963ac20e1a41580d Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 26 Nov 2023 22:34:58 -0700 Subject: [PATCH 036/238] Remove missed unused imports --- src/main/java/com/wildfire/main/config/Configuration.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/com/wildfire/main/config/Configuration.java b/src/main/java/com/wildfire/main/config/Configuration.java index 0d4ee2ff..c5df000e 100644 --- a/src/main/java/com/wildfire/main/config/Configuration.java +++ b/src/main/java/com/wildfire/main/config/Configuration.java @@ -23,9 +23,6 @@ import com.google.gson.JsonObject; import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonWriter; -import com.wildfire.main.entitydata.Breasts; -import com.wildfire.main.entitydata.EntityConfig; -import com.wildfire.main.entitydata.PlayerConfig; import net.fabricmc.loader.api.FabricLoader; import java.io.File; From e6f2512d5f3c9c199c67bb9012395fe33967c2a8 Mon Sep 17 00:00:00 2001 From: WildfireRomeo <80865771+WildfireRomeo@users.noreply.github.com> Date: Mon, 27 Nov 2023 19:07:16 -0500 Subject: [PATCH 037/238] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7d058301..ef98a882 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -![Mod Banner](https://i.imgur.com/mtKmgT9.png) +![Mod Banner](https://i.imgur.com/WLCTnCK.png) # Wildfire's Female Gender Mod ### Description -The mod's primary purpose is to add breasts to your Minecraft player to give it a more unique appearance than the generic model, +The primary goal of this mod is to enhance your Minecraft player's appearance by adding breasts to your player model, providing a distinctive look compared to the standard model. It works on client-side without issues but if you wish to add syncing support then this mod must be present on the server aswell. ## Default Controls @@ -11,4 +11,4 @@ G - Open Wildfire's Gender Menu ## License -Wildfire's Female Gender Mod is licensed under GNU LGPLv3, a free and open-source license. For more information, please see the [license file](https://github.com/WildfireRomeo/WildfireFemaleGenderMod/blob/fabric-1.19.4/LICENSE). +Wildfire's Female Gender Mod is licensed under GNU LGPLv3, a free and open-source license. For more information, please see the [license file](https://github.com/WildfireRomeo/WildfireFemaleGenderMod/blob/fabric-1.20.2/LICENSE). From 10e0ac50c97e06bef9254bb8c164de31f5eb3a1c Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Tue, 28 Nov 2023 23:41:52 -0500 Subject: [PATCH 038/238] Bump next version to 3.2 --- gradle.properties | 2 +- src/main/resources/fabric.mod.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index ae88a371..8ac6f724 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ yarn_mappings=1.20.2+build.1 loader_version=0.14.22 # Mod Properties -mod_version = fabric-1.20.2-3.1 +mod_version = fabric-1.20.2-3.2 maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 6c104a58..98a4fa60 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -1,7 +1,7 @@ { "schemaVersion": 1, "id": "wildfire_gender", - "version": "1.20-3.1", + "version": "1.20-3.2", "name": "Wildfire's Female Gender Mod", "description": "Allows players to choose what gender they want to be.", "authors": [ From 1bac7c1e829a1965a14afcd5aff3853f10ccb9a7 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Tue, 28 Nov 2023 23:44:53 -0500 Subject: [PATCH 039/238] Removed unnecessary Configuration.java system out printing --- src/main/java/com/wildfire/main/config/Configuration.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/com/wildfire/main/config/Configuration.java b/src/main/java/com/wildfire/main/config/Configuration.java index c5df000e..37c0a3ac 100644 --- a/src/main/java/com/wildfire/main/config/Configuration.java +++ b/src/main/java/com/wildfire/main/config/Configuration.java @@ -62,9 +62,6 @@ public class Configuration { public Configuration(String saveLoc, String cfgName) { Path saveDir = FabricLoader.getInstance().getConfigDir(); - System.out.println("SAVE DIR: " + saveDir.toString()); - - System.out.println("SAVE DIR: " + saveDir.resolve(saveLoc).toString()); if(!Files.isDirectory(saveDir.resolve(saveLoc))) { try { Files.createDirectory(saveDir.resolve(saveLoc)); @@ -72,7 +69,6 @@ public Configuration(String saveLoc, String cfgName) { e.printStackTrace(); } } - //.getOrCreateGameRelativePath(FMLPaths.CONFIGDIR.get().resolve(saveLoc), saveLoc); CFG_FILE = saveDir.resolve(saveLoc).resolve(cfgName + ".json").toFile(); } From da56381c3e21b426fa6fecb699300cfc454e66a4 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Tue, 28 Nov 2023 23:56:20 -0500 Subject: [PATCH 040/238] Changed "Bounce Intensity" slider text to be consistent with "Breast Momentum" slider. --- .../gui/screen/WildfireCharacterSettingsScreen.java | 9 +++------ .../resources/assets/wildfire_gender/lang/cs_cz.json | 2 +- .../resources/assets/wildfire_gender/lang/de_ch.json | 2 +- .../resources/assets/wildfire_gender/lang/de_de.json | 2 +- .../resources/assets/wildfire_gender/lang/en_us.json | 2 +- .../resources/assets/wildfire_gender/lang/es_mx.json | 2 +- .../resources/assets/wildfire_gender/lang/fr_fr.json | 4 ++-- .../resources/assets/wildfire_gender/lang/lol_us.json | 2 +- .../resources/assets/wildfire_gender/lang/uk_ua.json | 2 +- .../resources/assets/wildfire_gender/lang/zh_cn.json | 2 +- 10 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java index 7b5f4b45..dd0524ee 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java @@ -92,12 +92,9 @@ public void init() { this.addDrawableChild(this.bounceSlider = new WildfireSlider(xPos, yPos + 60, 158, 20, Configuration.BOUNCE_MULTIPLIER, aPlr.getBounceMultiplier(), value -> { }, value -> { float bounceText = 3 * value; - float v = Math.round(bounceText * 10) / 10f; - bounceWarning = v > 1; - - if(v == 1.5f) return Text.translatable("wildfire_gender.slider.max_bounce"); - else if(v == 0f) return Text.translatable("wildfire_gender.slider.min_bounce"); - else return Text.translatable("wildfire_gender.slider.bounce", v); + int v = Math.round(bounceText * 100); + bounceWarning = v > 100; + return Text.translatable("wildfire_gender.slider.bounce", v); }, value -> { if (aPlr.updateBounceMultiplier(value)) { PlayerConfig.saveGenderInfo(aPlr); diff --git a/src/main/resources/assets/wildfire_gender/lang/cs_cz.json b/src/main/resources/assets/wildfire_gender/lang/cs_cz.json index 5d2a0ef4..d83d1105 100644 --- a/src/main/resources/assets/wildfire_gender/lang/cs_cz.json +++ b/src/main/resources/assets/wildfire_gender/lang/cs_cz.json @@ -38,7 +38,7 @@ "wildfire_gender.label.no": "Ne", "wildfire_gender.label.with_creator": "Hrajete na serveru s tvůrcem tohohle modu!", - "wildfire_gender.slider.bounce": "Intenzita houpání: %sx", + "wildfire_gender.slider.bounce": "Intenzita houpání: %s%%", "wildfire_gender.slider.floppy": "Hybnost: %s%%", "wildfire_gender.slider.min_bounce": "Proč mám vůbec zaplou fyziku?", "wildfire_gender.slider.max_bounce": "Anime fyzika!!!", diff --git a/src/main/resources/assets/wildfire_gender/lang/de_ch.json b/src/main/resources/assets/wildfire_gender/lang/de_ch.json index 899759a2..e2b8bfdc 100644 --- a/src/main/resources/assets/wildfire_gender/lang/de_ch.json +++ b/src/main/resources/assets/wildfire_gender/lang/de_ch.json @@ -7,7 +7,7 @@ "wildfire_gender.player_list.sync_status": "Synchronisierigsstatus", "wildfire_gender.player_list.state.loading": "Lade Spieler..", "wildfire_gender.player_list.state.synced": "Spieler glade!", - "wildfire_gender.player_list.bounce_multiplier": "Wackel Multiplikator: %sx", + "wildfire_gender.player_list.bounce_multiplier": "Wackel Multiplikator: %s%%", "wildfire_gender.player_list.breast_momentum": "Brust Schwung: %s%%", "wildfire_gender.player_list.female_sounds": "Wiibliche Soundeffekte: %s", diff --git a/src/main/resources/assets/wildfire_gender/lang/de_de.json b/src/main/resources/assets/wildfire_gender/lang/de_de.json index da32e5f3..500bf21f 100644 --- a/src/main/resources/assets/wildfire_gender/lang/de_de.json +++ b/src/main/resources/assets/wildfire_gender/lang/de_de.json @@ -31,7 +31,7 @@ "wildfire_gender.label.no": "Nein", "wildfire_gender.label.with_creator": "Du spielst auf einem Server mit dem Programmierer dieser Mod!", - "wildfire_gender.slider.bounce": "Wackel Intensität: %sx", + "wildfire_gender.slider.bounce": "Wackel Intensität: %s%%", "wildfire_gender.slider.floppy": "Brust Schwung: %s%%", "wildfire_gender.slider.min_bounce": "Warum ist die Physik überhaupt an?", "wildfire_gender.slider.max_bounce": "Anime Brust Physik!", diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index c27ccbcd..5fe95629 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -50,7 +50,7 @@ "wildfire_gender.label.no": "No", "wildfire_gender.label.with_creator": "You are playing on a server with the creator of this mod!", - "wildfire_gender.slider.bounce": "Bounce Intensity: %sx", + "wildfire_gender.slider.bounce": "Bounce Intensity: %s%%", "wildfire_gender.slider.floppy": "Breast Momentum: %s%%", "wildfire_gender.slider.min_bounce": "Why Are Physics Even On?", "wildfire_gender.slider.max_bounce": "Anime Breast Physics!!!", diff --git a/src/main/resources/assets/wildfire_gender/lang/es_mx.json b/src/main/resources/assets/wildfire_gender/lang/es_mx.json index e5bebcc0..6a190022 100644 --- a/src/main/resources/assets/wildfire_gender/lang/es_mx.json +++ b/src/main/resources/assets/wildfire_gender/lang/es_mx.json @@ -38,7 +38,7 @@ "wildfire_gender.label.no": "No", "wildfire_gender.label.with_creator": "¡Estás jugando en un servidor con la creadora del mod!", - "wildfire_gender.slider.bounce": "Intensidad de rebote: %sx", + "wildfire_gender.slider.bounce": "Intensidad de rebote: %s%%", "wildfire_gender.slider.floppy": "Ímpetu del busto: %s%%", "wildfire_gender.slider.min_bounce": "¿Para que encendiste las físicas?", "wildfire_gender.slider.max_bounce": "¡¡¡FÍSICAS DE ANIME!!!", diff --git a/src/main/resources/assets/wildfire_gender/lang/fr_fr.json b/src/main/resources/assets/wildfire_gender/lang/fr_fr.json index 030ba978..bd6146ce 100644 --- a/src/main/resources/assets/wildfire_gender/lang/fr_fr.json +++ b/src/main/resources/assets/wildfire_gender/lang/fr_fr.json @@ -7,7 +7,7 @@ "wildfire_gender.player_list.sync_status": "Sync Statut", "wildfire_gender.player_list.state.loading": "Chargement des Données...", "wildfire_gender.player_list.state.synced": "Joueur Synchronisé", - "wildfire_gender.player_list.bounce_multiplier": "Multiplicateur de Rebond: %sx", + "wildfire_gender.player_list.bounce_multiplier": "Multiplicateur de Rebond: %s%", "wildfire_gender.player_list.breast_momentum": "Élan de la Poitrine: %s%%", "wildfire_gender.player_list.female_sounds": "Sons Féminins: %s", @@ -38,7 +38,7 @@ "wildfire_gender.label.no": "Non", "wildfire_gender.label.with_creator": "Vous êtes en train de jouer sur un serveur avec la créatrice de ce mod!", - "wildfire_gender.slider.bounce": "Intensité de Rebond: %sx", + "wildfire_gender.slider.bounce": "Intensité de Rebond: %s%%", "wildfire_gender.slider.floppy": "Élan de la Poitrine: %s%%", "wildfire_gender.slider.min_bounce": "Pourquoi Activer la Physique?", "wildfire_gender.slider.max_bounce": "Anime Breast Physics!!!", diff --git a/src/main/resources/assets/wildfire_gender/lang/lol_us.json b/src/main/resources/assets/wildfire_gender/lang/lol_us.json index 3ea21618..44a980d8 100644 --- a/src/main/resources/assets/wildfire_gender/lang/lol_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/lol_us.json @@ -38,7 +38,7 @@ "wildfire_gender.label.no": "naw", "wildfire_gender.label.with_creator": "pett owna iz un serva gg", - "wildfire_gender.slider.bounce": "Crazy Bouncy: %sx", + "wildfire_gender.slider.bounce": "Crazy Bouncy: %s%%", "wildfire_gender.slider.floppy": "Haw huvy: %s%%", "wildfire_gender.slider.min_bounce": "Wat iz bouncy?", "wildfire_gender.slider.max_bounce": "BALONS!", diff --git a/src/main/resources/assets/wildfire_gender/lang/uk_ua.json b/src/main/resources/assets/wildfire_gender/lang/uk_ua.json index 15e68b0d..d0e46e96 100644 --- a/src/main/resources/assets/wildfire_gender/lang/uk_ua.json +++ b/src/main/resources/assets/wildfire_gender/lang/uk_ua.json @@ -38,7 +38,7 @@ "wildfire_gender.label.no": "Ні", "wildfire_gender.label.with_creator": "Ви граєте на одному ж сервері з творцем цього моду!", - "wildfire_gender.slider.bounce": "Інтенсивність пружності: %sx", + "wildfire_gender.slider.bounce": "Інтенсивність пружності: %s%%", "wildfire_gender.slider.floppy": "Імпульс грудей: %s%%", "wildfire_gender.slider.min_bounce": "Чому фізика взагалі включена?", "wildfire_gender.slider.max_bounce": "Аніме фізика грудей!!!", diff --git a/src/main/resources/assets/wildfire_gender/lang/zh_cn.json b/src/main/resources/assets/wildfire_gender/lang/zh_cn.json index 3e83cf50..a92c6f7f 100644 --- a/src/main/resources/assets/wildfire_gender/lang/zh_cn.json +++ b/src/main/resources/assets/wildfire_gender/lang/zh_cn.json @@ -38,7 +38,7 @@ "wildfire_gender.label.no": "否", "wildfire_gender.label.with_creator": "您正在与该模组的作者一起在服务器上玩!", - "wildfire_gender.slider.bounce": "弹跳强度:%sx", + "wildfire_gender.slider.bounce": "弹跳强度:%s%%", "wildfire_gender.slider.floppy": "乳房动量:%s%%", "wildfire_gender.slider.min_bounce": "为什么开启物理?", "wildfire_gender.slider.max_bounce": "动漫乳房物理!!!", From ea7aa2374f7b3c5dea122b26a376f394f6ca96ea Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Wed, 29 Nov 2023 00:17:32 -0500 Subject: [PATCH 041/238] Creator UUID changed Added Mod Menu integration (Discord server link) --- .../com/wildfire/gui/screen/WardrobeBrowserScreen.java | 2 +- src/main/resources/fabric.mod.json | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index 62216b0d..b4ccad68 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -46,7 +46,7 @@ public class WardrobeBrowserScreen extends BaseWildfireScreen { private static final Identifier BACKGROUND_FEMALE = new Identifier(WildfireGender.MODID, "textures/gui/wardrobe_bg2.png"); private static final Identifier BACKGROUND = new Identifier(WildfireGender.MODID, "textures/gui/wardrobe_bg3.png"); private static final Identifier TXTR_RIBBON = new Identifier(WildfireGender.MODID, "textures/bc_ribbon.png"); - private static final UUID CREATOR_UUID = UUID.fromString("33c937ae-6bfc-423e-a38e-3a613e7c1256"); + private static final UUID CREATOR_UUID = UUID.fromString("23b6feed-2dfe-4f2e-9429-863fd4adb946"); private static final boolean isBreastCancerAwarenessMonth = Calendar.getInstance().get(Calendar.MONTH) == Calendar.OCTOBER; public WardrobeBrowserScreen(Screen parent, UUID uuid) { diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 98a4fa60..19c1b9cb 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -45,5 +45,12 @@ "conflicts": { "skinlayers": "*", "allthetrims": "*" + }, + "custom": { + "modmenu": { + "links": { + "modmenu.discord": "https://discord.gg/4VDfQu4Wgb" + } + } } } \ No newline at end of file From 6651bcc3a9e9c0596d7de35dd5a16cc2a94cc83e Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Wed, 29 Nov 2023 01:14:38 -0500 Subject: [PATCH 042/238] Added mod banner.png to textures folder (*might* end up doing something with it later) --- .../assets/wildfire_gender/banner.png | Bin 0 -> 683780 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/main/resources/assets/wildfire_gender/banner.png diff --git a/src/main/resources/assets/wildfire_gender/banner.png b/src/main/resources/assets/wildfire_gender/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..2fa9373c4bd688dca3e8ebe4141fce7a2e68ce19 GIT binary patch literal 683780 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%0DJW5scXozLLrMY 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 z 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

o2a+Y3-n>4C-!)bhbj}i-7Fq9f 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�F9Twr{T^4 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%0A;g@fQ7(_g!;q8* z`ieX43JY$y76%myMw(n>kX!+eN1d-!I>sE*6qA6cFBKyzbWFkrPEG+9YPp!~9P#s{ zirLoOWq(9&Ldat@4)7Zr$!i6s{;4D{Wsm}#9ON&B57egY9H%kNGJtd)xsG?Vq>Xj5 zIMndL+aGV8;Fm2^0GwSE{wG)e2;(VY{F2Y6`#$25b3}Kt@)Ye5Nktm!ze7b6D4Z4V z2prN1@Y7a^L22vXKfAeqcj8aS*bBZ0eB0(K&U~F>C#lDu3vc`p$n>SO7EtHZzujPs z{vI4BoI|PZjDFw`P*qiDBWazV9z3x?olYM@9H#w(&NEwyl%SF78~uZ{<3vKoqI~cJ zDR|w4<(tpP4G#qV0scYW!FpiXae9VA_t@xx7Q?h z@cr()U!2@>hs+(@G_jdJrjEZeAzz?sv=MNEj^s{_VyQZJ`w%KgpFBB z+UGZ()REQLO0hBVqNon6fToFKxHs~R0$VCivz?J;fGE|?T8>-Z_;1Jic$y<#s07qi zRwe@BM5+wpV)1UCwd(;UD-+M;pZPm%4&4H|-cigIu6|F9!^J7y9o9MrwxcEr3WV6G z|EcA_H?||o>Dd4D9Q_QJhOcq?Z_klif%%V+C|!}?Q!G&m^l7_8iDRSK=g-zXHxKEM zH{R-tNyt9NfhOE5L=2QgWPd`UBxD`xIr7EwSgKRI*riXn_)Vs%huUyr9vz{v_9yVg z-Ui;zLYYN4GLWM<7!hLnM3V^yAq-tlxcOn@TX2=I&~Z^xcQZ9)mO+Ra`XRs^7}#<= zRaC%M44fVlOM|khyDK1kaB2M^LRS$^{wDAr3Td=VG+!5i)ycO!_Af<|$O z)4^(edBqwD>4g$DqpBPPQmE$yl#Fivc@O$a8KOu+ske8-?!h?j_JxB>$7fzaRhpK@*adl%MlW!h|=A*Xd|&xmOy6hVJQtgrSW=5Fl+_ z-z`KGe%V4YCg<=iT)y$xvSr<;Z-cy{sE zp|Atd6qXWO%Khx`I(yvjw54?;iMt=L1#;ev!kQzFha=qG+Bw8iWZG-zP34B&Z1+U*PS}!Ss+djIR&4tLBdfDX< z>4a`GykYl_d+tYipHtM$0`e$WUNN0#-=UMIw5JfCvy*N+Az`YaNsq$Jh#E%x%Tx{M zjC@6i2;|CK62zpjB&5%CFwgzgcAeyMWuHTPhK@Ta z-3Wo=yLmgjen8+GcPnxlfFO5^U$gsm4p~%g*MeKnlIKA$Uo;Uk0tRyS! z=Ay_XfiT;?L)BNUWpYFIbL?)HPk~G!n?z&0Ttro?a5a0+0;$zC%!iuV1OWeKD$J;8 zw%2tK5+M*VKq8RqE`Bi%;6`j<)78DLfS#7r)2U=z+?c$3gl|QPLt4<(a@igax--Ed z=Jy-=k}{~c@-X1w{~si<8!C=?>-29j#16T#Bgp(xNGSE4B+I)GF5D%N*cA53LgaLC2%I_1b> zcDg97&0+|z4kn>$flu`(Y`PfKKz1u*>pu7#Mbdx&V@^!!db`}=om4X21gDf}Xpat3 zm^y$>%12a|b3#FN^qjWj^oJo0aKli9L_sbx5>z;`RKnHDjAPGV&<#R13C`w5;r9m( zgP%=dfMG{C>yV{eKs=fwABqezPxRrk19&~8_9IgIKnYgN#+=$kB1SZFqD|pKnxVFV zB-W(|_$zHO;Mu7U+$V{`=U4EWi!A4oHbxf0Ny{(XSE?|7$~kibxFurUZyu6B7nMA{ zyjwv9DVMFZS0xl_Ggok8EErUhTtdkML3P3B)gLDwt$hrUc|T*@9CL&l^pE;4)xRP< zHt_5194Mh87U%RPI42UO)*(pM88S4s1zRQto@a>6VqFT8tkt;{qOY03R9|py{oRj*|zuL z{-sv8&O}ks{{VP|o7~}z8q*Sdh-yg97S`J*fh6VDTB$FojU-ZO6->1dV!Cu{wlo!) zOWd5~=Z*x`C8b+%#>I(af$IQx3SsI-h#XuhK5D#nlI#hVP8yvsc5SoFoEARrA0*MD zhph7*As`G(go@b0^?&|$-IiJp%uOb8M07J(uPlEP@hzf>z%Wo3q1Nj;^PZ}BYN-6j znp2O7MuN5gk==X{21l#5II!o2ELEi60@JgXxd9SC;xU4IsLPDwF2z%( zD#%Bc(FE_VQf{?kzPxb<9qo5#-S%DHU)J5bBYSW5#l-EuwzZr%UC??_B!|l4F#9keFgNg@RWxp$USed9-3u}7JR)J#LF z1b<)BNDW+$G_*VREs7u7WSbDp+D{cQWEQDIFzM-xQ8F3obhVM?Gcbbc@V0IKt=S0M z+avS2y4VfD2dzQf5#-Vnc-r1@*X#QI_M+|ej`T{(I~CcL&15#}9vhd-+wt$+2^qYJ;iSA4DI!uBL_wc3IDyzf3tVr zrTn+otuPKvlPpVhvq?ME+(nF-xjS2wpo`PU0Etx#-z3vlF)VGgc!guqx%NV}yoDDQ z+67Cbl4;q!KS4jnT-u;aD9}Iq$V4-AG%MkUk4~5|U3M^9S3<_zNJ^c8aWUn-Ortc! zFG5O7t(stT7}*_6_OPpE2Lmp9YTf!F?RjZ(co^A$12F?tBLVeL_>DCZ42q zO(1g4nUTA zpt8fPBf=+bTAmJmM#ydvT8lm-?gk_#Qh1pw2jrst^$CX4hx=ng*P6w76bnHC5H3_q ze=L<_4gzHE$yQi^5lTr$Eyj(3qH!XOW(z%&8PI&2!XggBa@oit(Oo1#EUvQ? zj6LZXNul#D0z#^WQ4}OFm2#!z3quWd(N1(>sE;hP$0RFjU*d?~>nLk*` zdA=8u=c^9v3(+FZoQ#}sx1IPwjO)TNLHVy2y<`GSVQZ>}E*m)AT^g{rmn5D~Q z8+?-6MWmX|36y81S3&zf|5)YA;ZO%vwK?eSfG$%22PI~ZP|yB%@=e7p98eun-#1*O z(sfy_P_ge(BuPeYWIT|U2b4qKZrg(XY}rnjTM3`w8bDH+e~K8H>=YahVl*bZ_#PUD z;2_yd&Pg~=w zo~n25;maSGRNycQl^wc*I#uj$&MkY=tl_CCJ2&?Z%n8A3-@Mw91mH$_WzpDWdCpVS zkrU2-o9j~pW5FnE#1L&haa!+;XR4w@H{13SBg=-v9=p}YFXPPar2UvWJuK?E9a+%* zxo0Ens@?NFfcN{xee=($iRYR*v7@OUuZ*#Z$>&J6>TcPpSG&?DDP z`X9sbHXr-~P8u@POXUqm$S5b#tsqD~AKs+3SZomWPb5#+^MYd~QKUX$-RpT)rkL*G zDdeK#?XUN}81T;>AuLSKh#v~I=QbGysB_2t-_EexyxhFpqljV*Tfh#VH2nM>1g@qy zU@i~U!^PEhOgfvR-+%Nz-E_I(iUal4|G#@kM~=toe_96R3=7``E>Deg`bntNsJx7% zz}hr|e@(LP`WXK{B1aN}ZZ$k z3@M^3DlGf-9C;m-C``$=f9jkvnD!#h?u8M4G~5XbohQAqOB>FI25fN&TI*CqUyl%$ zdaCa=bnFczOmsUgvP!-jM4d<}O2D~1%0BwLI4E`_p$k|0-x%jP?tH=$g}rc6ofvH; z(_-#!y;L+?Z_fR7L7uc$`yw&YM3D7`IU2{Vd6shDX1 zMvZWPN)CwtRZEP(qxo3ib*BtVm@b35Y=Y^VbkM%uc!|yf)IN3O;nLPIl zgxpNhp6s9aCKmfut~7Spyza&D!W_rwE7FZhlrxJvk)+vrS7O#p7AdlGa-m(5f`u<3 z&$|LG+Uc6D;G)*>JQHNuB|kJEOQJ9!$=PR&w#l+m%S^^}8xt4J97%&T)i*UYtK0tI z@Q`ETohg{CH@N1x0@y>c4*-cPzgOl!eR+=Or{-F|8M=U7TY>xL4EeSEi%ERhRd9pS zMhtn;yS@0~COjFh>ZtbkRjs^=I1Ghz=b1Z)f}e15Y4i|CuR$7C}C2iASX}f~)5FV?keuzbHCpudM8eS66)FT?moA0ZZ(we`OE~ zrsVYm-OWGiYdv#$3Iw7`87KD#4y~U4m((SnNje#OWlqgBv18Jc-R| zrrtW|+f}BIg*4BKJ+R8deV)l?(L=ENi9o@NeAhYuFQNo>X*5Gr1lxHsX42?(;b7sG zsfHi~bb$=&#v)ue+B>ul|HAt);<4)6Z*l28lf(nKJ;>DUWdM z?msxG>6R8WOFaJhPyY>;LjNGgU$Wb~+tuUP>QU{WgSzI%X0HL)n?GxZt_zX>!;W}U z(wewrYcyb%66^z7#Zg0W#bY=Ag8`FZ#do-mI?SiKOr_yyEjTf4mjBkz^@Drf=W(Ho z*tmbF2vN~eiLe}prYcpe-Y6$n#}MBLBEdcPN5NC60d0^7(7tc^BwaZjvO8fl`j*;9 z*H0TqbAzWWPGAwSJR!1$01XC~Z%V7P08L{|Mh@o3HjOKB^y+M^^ywc217DXnhLklXN4d|{W*gM)6We) zN7X+nb|ZZ)EMLe|3xj11cc>J_6r_+RI@R9_&hHS9p)bH#ODhoxK0ivshN99^fFR7p z0-aVI{|MI~7h%a8Tt_6j!5E;z{##9H{7WD{ho$XBD}-d0|SQr97>weT&qq{0LTZRYLw6j zNPi{5)xAe+`{f08sJ8mxK)ZosqP)NOEyY9eioTb}fkDxryduvC6Z}z?f&W^Ku>8!-wj=szXn%i&rmL1xh`}WU& z0e%_azH>{*@}5@<5*HvCpX0M#kMOm*K9A-CL72WAv&<{$_30ql2n_{9=fCBo^h`B2 z$1zP9DM@zS45pl?7+q+oppKBu!sUutwS6hHMr^yM_G@DX;PtUi@+YMVm{!O-pMN+aJWBt6#Sk1FnUa zfBGUL;%MKmI%Ph#PxzQc;fX8uhYY4ma9N!SCQ{P)kZ&r+@bbOSF@_8R<5c?dx*He-IChG`)p1u=0!Y z#l%_>sUNfYD=)-UypeMM zLoU&>^MV!q4Tz?Ruvz1touqWX+(qF}VFuW(K`Ny9VvsEGJ5kc8?9`jF8D~~j-7LF@ z@=+37KKMibxCC6-gJa_!rCOE*54c`(NKkf+<;Ny~?82!>!><$rpnl~tXh6fWNZL?F zn5XX}^9~F&D}lep?xly4=Nc*$!DWe(fyuHGJZ`rX_q7OPTQ~bJFNXsC;p5A;B?=< ztS*X2KER~A0;unTCCu00<(xH)Td2Ver5Y<1PJ-W)?MWb@TP65~@;8&Sk`OOsayjDy zfiK)0a68Vb$AaN#n~dKMYEX^w_QsuGz_;a4YIO*J!}&+65{3nR_{>$VD3#oE3tM=9 zSl9X=6kxad_P?@+eKj~R0c3E)?sw}mEM-GEU`9?EgR>&%dbo&L*iQ!%wN-YXOFnUD zkDnZ(G*7NQ`l!~t7*QSf_}ZLILEnQtLQ`?aDB#2d^q1)X`qgl!ERJYZY z^s^N4VBZ_Bn;5^lPig^H>(A@fT(N#~l}PrBPZ^Q4B)(TB-^_aLe}*eEav4xXb1NOpnanLoS0ABQvUbx& zhaX!nQ`WL;bkoFP%)~&R-K*VnQ$8D=={ctcc&mt{)zqlkV8d8z!{1l$dy~iu+EMtk z#6&v+LKo$kXNymk$C4TJE0gNLvM-?di+7@)rUzSvwGLRDpb*AGp~5|;OU(q_9j;F^ zhHT?s9z9KlTFr78Jn;hY*DMqQN`9uCviRV~DG-K0l8UosOFc>97OAzC7SYwcKylqR zEzRca!Jy&Qiy6WGhumPwONFn=okwt-cuBX#=k0GtUC<7VgWC{(gF`X5%h7VUscO<&Jtqi-!@8qrfyx6@YPvxGSpLp? zWR{b!Kjnwdb&SSK#j4~}??T@l72bf?&%T7L2S~`pMcG4(DnrHc0iD2<()+YDeQx1!_UfQiZ6zWeVUxc z8V(hx`WK*q@N2UuX*?$XK+;9cH7OEAMHys2ld*8%AwZEV<^BB+Q|3vBqPT{sA0_Lz(Xt8AsJbC*lTBJ{M9kxw985otFp&zlfiq=_LSnpMJ zHED0x|NR3;x~X{$VH!_VKm{VeDNTq$$fdNI%79`IM4%6LF?Iq51}H&9GXl)W%qdgI z{h~EZ3HA&q+=Rq#=3PjG2|TniZ&h{pei%)r*2RI5$Zv+nN|j2B?ROVXAn4?kH)P6o z8^Y2V<8s(p+|UmK7=J6pdK4`Y&Y`Io2w8J4Gzsuv7)()KK`84`#dvLh6+9d3C;9#S zj_*nHJJ6?(y(6=~>~I~}ZdG9sD z)Lr`w(ptRTe7X+tKeNM{0SRC^Q~S{JFZ{$`uTQ;CzcmZd|3>3BaRZ}JKG8j zfv4}_cfhy3)G%VMKRb6Ji`WWhPuVJ0o*o(ASNK5ZS{3L~z*Ei@tqJ;j&+gM&N~;mP z+*2Xa=S>x^(?8Ae(o=z(+-rxU^R$zcK840$rP7jWuRVL?0yXZ_rttIAti$7NXoDiy} ztu=IoAEj5lZ;60LqxOR1RAk1vjHqKb4{VOP$QvnJQTUmN=IRPR{~>d}xk2-s8(F2F z3_g_MZTfo+2?_;peBjz!pHgMcq`A%~1!DqLdjLwsgbS~b^|uI&fL6LN!RtOn6;c($ zfGSzAS~X94aRMV_%G@@q!Glb>`P)CTzom4xgSngo#ayy>tNTqPp+>3e5dixtxEA7Q z5e`(GN)KUAvI0uxWaVJW;Aa_FF$fgd2xkujJPC0L+4}evBMoVgA^;ZHsYoOE5GTZ) z73^6ISkiXbKw5cSvOTzbQ!(j?17d5FWiXo*?Vi1Gq#IB>H=q+4Q&3}W6LU#(sigIP+RTf)<*}( z<`(lzz$s27I8f}e5m@q{f=XH}kXUhrvhHy}E!>-3yr*qHmlShjtb{x--D3OBx_{Q# zrBEL)>(Wb##MzhjEfnq3&iWn>*51lIeTySsg5G{$3|omgApxF8*_YRf_BGO#%9b=K zDym?luv)5UqS`QcnP!II19~KD{@#Xth_;)E5^QbV5Nbi#b?XkK-A`w}xVZJkqp^g%`$OaS z?LY0ZF=X-YqwFd&kHN$3!)wTbHD;3gL}0Rw#+|j2*kB8pqXKU}7KOQBNZE;ioU!#& z!iz7SxzR6vuP=Op-WLeX`}Dcn*@faXlA^#wJ^R;|A#w2!(g>QP+!LP!>cL=R21yZ7 z1dh7$%2R)z3*qcJ(%5}ajBPHFVV>iF85}tkNzkSO#^gn0nmEp2(A|722$EVjL-64F zZ#P~K`C$;YFlIky=rF7a@`EL8Md*gg%63}(%y}}C{3K0&TJ2meb?i;z$XLZ1MLoXW zH}mgz{`=m#H}G<7666tSbQI8Z)sBvZxb>XGggps!s{)r{z$74wdHh zCR7c*{3}ZTeTu2;{vb~t8hq-&x|K@*q4#~nk()~c0c&yBrdek3`TldwI?u1W@<;b& z+ls}HoZ>E1IJ>{2h|P@Lt*&6{?WB@|ji}r{qJF6ThXu#jcQJzAY_2z6PR|cv`k%|g zLGv`04c5R81oJ#%#C@s(+ra)vC}15yXusiW``!tg^qnxuDyHAD218^Lw&FmL4cAN! z|L_3t$Y&yYR~Ta_D8k=Qs~n3;zGw^!E2;I{M7&^` zSTf^|6g4G6G2_+YKCRNg%?dnqxE#nsL;VAY2==a(P``5N|u4JlKCZ zg#75TJeltH-PNyVFch7ES8wC<5`CU?;J&BRN6Dp#_LBCJH&i&SZiv6Yc-9;6iyq{p z8F?oQ5vPQupTVi4OZmzGw>813;}U0x7#=jukI`UO#laVcZZo=(vm2cH%8uM7S_T>Z+}@wh}^*yJ%M0uVn9n> z-~h;PpID_KMznr&-w8Xg^AmeO9C&kdWZIUt{1o{4s#P=ThZMKB7RXXjux2g#v>{Vz zZ2;vnVN8D(1Ky#QwZoziSg!#rdawfQAEVL$;wAKHc<}O%Qfwk~^weHW#?+_!6xupU z#w~{RLAOOWoUSV3G*A~E0<8jm(%(Rc6+QC(hgJf?znth5MJkhq@(`s|f5pg4)spK% z82FG}xefQUFVv=x!F~;!A#w}};Y=s92N&7`3b+T)u~3s+bfmRWlS~VXu`>RqWIks6 z#6`!YdI|nhOYgZhCM2eUbNxb}i0xcCS|)9IxD1w^s#j4|?I0OPo6wQDbx5*6hlU&5 zCH1^cVHdK2W_1opZeZH;^J^x|D=&b!Xy=&D z6ls)8O-qkxtgAuEa;8lEMdo}*g;yAI5^^6BW;|Aat0Z3Ul@sgvEk)VLgX6^%CfhN2 zN@RD8shj_Z9`Bd8JDEXZtzJ@dzPu?`^&CHLnO4U^M{(6;Qo#1fUT-K*&`zzwjf*Tj z(B9nSt2pQfd(04oh=aKLZXY#-CkUuy*~q`@(} z_q2meHw#stD=WnWYfxFWDa5G&-I*LBz}H2dV= zsIXqO(;a-@O9Nwk4tl86Io|w=dWox-jM6!sv>B?-(a9p<|Lx*%Zs5Z=(j*B&jE>biy1RG(oAEm8{|oyEEcyYu6**L)*W!nur(lTAE(_QM;3#Gn0B}qr2J`l*q;y zbvAPf_D4Q;WFb=ytTehFWiK+l7ri>~s}ft_HKO+SSJ+MzuL9y=4E>_mrTj}7=qDp( z9&-AgL1BwbA&-&D(8XXa(=ENQTH=?W5Ce|ppJ(o%k@#VGP`nZxi0y|#v8#&ter8@A zq)0S(T`jEf?xd4!Mw=GZMQc4EF!77J{s@&o>B9{a>J$*@GiC4a1h4LKFo(bXF^Dc% z+x;=fi`4yjfm`Dc!im`3z6#q>xC4XlwB-;ona}Cr>p_3-qKiTVyXDofzaX9+6B5q^ zAs5fi%r|O6v9A@<1mnTPuq5Vm*-V(aqv*{nQ}UPGUDdkNhDO>!QYthaz-;j%Ay+_7 zX&-1rG(U2CCdwWGCHiJW;?>_36TKqZ8Xc&h_7<7`sV`brL)zifjexg)6w4H>RjGrh}6JEG} z-xS?dZMAGQQjB0StLTaDyDBR+`!*a+LqSp~7$uKqFO+kUvz@^4>3M0W*t6arf?ePV zWDV{r%tkvj_zTpE%3FUQASD>Zc07{TT{bonf=O$vGx`{UwOhI_Sb;w^XlUV0v~ahL zwd_q5-l$Q$93mpqhmcy`1Sa=PEV~jA?rs!p0 zW(^!tGgJ(1lC0}fePSg_!wpJ>mJyv~d!=y7)LAUmtqrHmNMsK|(-6Of+k>l0p~#{) zYY5tr?c+{Ri0<(4kG3(4ZzAN^UaYqJtC zp;3X?%|*@vM6fiGM1*O(l}Ud>`Pc-1iHlW*Z;JDhXG;;WW(UCM@hf&0r(h&EhJH23 z-cx{wAj}(yv}VAl{<)s`OJZS(Rj1D~( z7m#{MmTt-`zIiCCA=DM*>GAaOxuc0KT&sF2th6G^mb^3l-J?@e`Lfoak$fJBANC!c zM9FOa-7V`kr`B9KN5Er$1uqxVptThnDmgw69eYM=erHe@ekaT-G+kf97Nk;8tv~J2OR{O2ri<^U!7kG!dD`>rbmKg{oCPkVNd5A)Sl{=vB&w z`TAacqX$xV!ImJOcaI%(Q4mH z9b)Wsl=UuF7fF{82eyK8Awfy|p>hl6FHkBD>+M*>x$%3Upq6BOEcCeKAsN|>X&cbe zhKshmrUZYLEO5&S_Pvj}kt_&fPfJ3?DGjC22#IbHXeZk8fj{|_&qtYAIi=2;jU+lG zqpvw3D;mM3V+6g35Ryt{N$fplBLmpIjg({PPfb=1)BUW79`K~aMN&V>5e)HI5DURj z{#4F)+iEgNvWHWUO5qWa|BMEU&+0Esxlb41sq|ON{vA&uhtHQPq-=;2OWO>D6GWbX zOiEN1Eze073zg1f-Ijf(pVvUkN--P~Vuf+gd_c=OKR*J2+1tIChMorzQ2~u5k*kP7 zpx;8h*ehg3uK6J&H86$>F`6F-|8TYD5!Le6IoE8km@64{Qm>vC&_qW)T@Agbq_;yh zIA$gtF2@ueU12{=F!nId;N0$8fb|e4Vv)june9K`Tq#9k17~AOC&vycrQgQ{K%`@6 zl3*C^S5>fp3mm>f=;h}_qu=dz-V>e8Xx!#2^;a+QyWc{zw$~sDjXLBP zJbd_plWy;OhWdH7w)^qZah3Ds-#-&{c|fzp{mOmvF*Q?Gg*HeJn{3H*tLZ_OLjn_r zwjZ8z!FQMl;h*nt&kXdc26VT{8& zf&!guTsx8h8UhMOJcL5fkKX%uC}DQFqGWR;2PUe|&9HmTDiH*<%KSJBN6!w3dOmF@ zYpZ67TH)Fmwe!F@d3+*-wU9vGwx6C_Fots080;#){9h^jlKwRWHaf{%>2 zLqN=^2TiyL3*k>uz*G1%rUY&hYW*%VIA=kpMZI=RpxHWF+>Z@b$+_}-PhULY_zSDDp{#Q@Tzlx%8kf&f zI&YjP?^wpLP3!3FdcL^L4v7f?vgYJR;4o7XAM=oSE}4jdjey6`o5o%;9*8whhB?tb#Cf8_!A}7aBNdHd_0^dFJ-x>t8TXPlp zJ*kt@X3TR>7Ok6Q?s}-c3YT>Qtl-`|NcYvL->lqiYYWpc+1Mp3ak=~l`TPo7CCC(( zD6JQd4qfs%-ujVN!NoQ28K*gobe9=_3oV-RUE*KvSSeCSv`JaY46cDgvFHei(i-ew z(h}vGghSmWo*c-LyL0z#2eCe9MEoA|^}hN;W*HO_MnQ`mRB5f3DH(=wycnwjP>bmX z$(p!-_}Tpe9aEChaU5oVW z8-X0Ic+qxA8g2VT(e#iMN3ygW;!@QJ%cl)=OgeVh3(C;W^1AU|I{Ka9nZ=Rg8^c<< z#_}B}w{mt_yBvHvGxs9AV@qP0oZbIi-QbDrR#(VaTOZeV4X0oKQl2l_-;2B*Jv5a( zh~U>iFet?;tFL}Ta7a5Zep7}vd z*fqlU674Y;FH@CpCDXwoXsD9GikHx93glolZR>B@nfeE=#pUv$a8gI_Svej&GlkXA zj^qNIN>ZgYWk7*{cO655q*CH3zkz~MMHz+_L&`QD4C`gjJGJ*oTW!ZI((T~u=Ev7- z_wiM6QF3Q`*3pCdRraPJzhF*|09WV7ez$}|poH~1f;ALiViY-Ww+!1}*G{WAPIarF z{Yh#?RX2sbi8AA#+u^UbwqBN8hN00_26UTIA;VMT1k<%9RoV zIpNiXjsP@=6D}-!3_@+$OOlmR@U`A9KJBAMKB5trLD*Jqb7;*byZuVN+V1{betHF;ojd*MR!eiNFBH`69vwkYqU}Ruie{K74)n@2qrMxSWa`c>maeClSVYY!PXDyNglD{U@_eF56_mbT z7V!Sj?dveM$Q>JYfUxP4VN7;&8Qa!OoD?r$(O&&R*8B4Brho4(^i&;!GZ#q``rL>x z*$}lhDpb;wZI1^xItH>mOU1slKI!LI24Y^SA;jtdrhIOJ!lfwS{TsjTMUVGQP)lxjVvnlm z1aKr50bFv-^e}uY5-l-4U2-!&a=4!8TFE8_r2H#{N6Lp&kCse@|S&u z0SAf{jW!ImtCpLHsDM)9Qqk}4ox;|{O6k@#P{!~#KTs^|mr_nu3GR4{Pa69Rmcmm1 z3X5FP5H?DBG&1G}CW>%U)d^k;g1F$0i0q-QL}Io=H3pnE?Sc=k$Sx{Rz8oJ6`l;8h zy&F~!wk%~t$aXL=YYE-iWb#htDeNNjf)@R>=Z66W26<2zM{^tlD@|w*f{!R;=%O9Z z?y5?v+ppSMMfidW(v50b>XL_#>Jr3w$VtbN%4@zOyKs?#BTDAPSALqLkUtcpO_(fv zgG*hr_j?e@P1k?FS-@zrl!Y$V8#Ec|u8v>^RsVw&3zNP=q@;?zWfo6W3$nOW4UJHF ze-p?bX3AA!KT{{iCK9XWoDe4-0~^?y_K6J7H3ZOQ*U3z6k)vtypC^xZ{FJuI+&O0! z?|N@zcD~ksv>uyB0d3m3^i#PBt0I{Z9oZ>4>Q&2z!&R9HmM8r|A{t3_C6QTH zk{MGR|K7rmD>Sh4M9HBA^c+oS7YxKQ(eFWN+URjA&14CszlWyj*eHErHZi?}(0sC) zYwWZBu}L_H^C0i1n6)$lv2zeyX2h-86#gwIgjn8x34IDD-7Lp~Ve_a)6Dm)FgF(W^ zVg;udfx$X7hZ>@S?pC40iK9ZIM9nC-o;Az!uOGwFNGFm!fd{q+{;8|E{<`+U&}{$o z70)d)hoqf64wQTYcHOqJs&r^7B+(y2xHxV~N@cRj8Dyi!~kWG2P}1kgrr zG{mmTuR&N}J`yh(wrfDk`>?fVK&&q8HdnUAjQ0@v$Yb=Rn!4|s_;=nu$|5UV%_On~ z7}oPG$B&HLDr2Q5PggN*b8}~Gb^iA9wI4waC=_8j!5%3YEE65}g9MqNj2q?ly~KH3 z#3s0wSkVS=h1DI$L@w$7Fs$+D`-+(xjjZIe!-|f8N-l!kR0^M$si*6Mho!rVm$EcXv#Jvj;>EuqsIv#B zh|&GB0;@x45Btq;To3m@LLSDN0*BS?RMu-CjSXpndj6WRJ8}6d|FY&S;Mh<-2Nqjp zrT>MSTOQAT&EQY(N~R5Sz%`GDv{h<+AMQPVWBu1+gRkSl-sHR3BuKQ5DO(;YarwTsH$(KLVirqL)IR z->zQ#zJhMPxV#?pJ`7p}^d+8V(>z&l$_&S#pZ1{GwY{j{p*>L?sj>)6cNlfMjTgq5 zD}WZpYyWUn9(X3BJX&-pU66}XSZf0drz!(OkpmlW=?7>O$hN2m;`Ter3#?Ew4^sN` z2;|#Hz0#eouH)arH61k#KY`;uT^*QS0Dm0%H` zIUtu72pEbelYwGAJ#!+-Nl|JTXJHc^9a0)mi+?ZrvX^HMLScTzYT4uqwZQux=N-a{ z`ynbpnv0O&1S)}NT{$#mjnf$6<9z_qfB^<|5wqt7koP58G0O&wezV;@o!UN|;ka#> znNndUsG(dfz0(Kd$RR;7x0|J1z}7C$b8i)@ z|1EX0tdcBZ#ZYq<^T>5mOch6s&y4U%Of|(2eNO4BJG6)w&_xs*3eZ>v9ygMT z9n5n@%|ron)_ySYa|vo2;Th*z_og;3&wfo|D=3Sax6Z98k;E{#WVy1VBeQU8mkZ(z&= z%C?Pd+qP}nwrwYkZQHhuhF@$pY;3DBUS{Uq|8TJP;$5i@|1FMcnZtbU_6^Z()4*nR z^+D!lO%XPmnG)xK(%0M0{ol8<=Xs~F4sah$`yHYHv;U0@$fxk<`Fk@$4HG{wC{%C$ z72yvIN*TH>cLDji-H6O_NX7@_QH#D~hBs1*1fqW||H+ zlSqU&wkSv6T3w+i+#Uv#sYWGC3*^S4yN5_V5IZ*?bf|)hMl2^!?i?z`q>?@ik&kZD ze+5C$`G>XrbIGVP-G(@N1u1AX5uCOw$;Fz%BPO4*v9WC@`0aksm9e}03Dr(zhk675oZMR?RCInZ(nrYVx(Qha9M zAI3X`B^F0q6FOKTXE4Ls6@vni3pfE%CzNg~138~sQ^o+_t%YGV4HcYUIwei4EUXOK zuwEI^p*=xGubslg)tG*?L1Hr>J#7aIQ?Zpywqde(*Z~J%VJWXPZ!J?PZ5@El@kO-P zU*EO0g3Y)>m9rCP+;ri%?JxK)Lagfxn$eH^o>C34(ePymnzUd#Y?|29R!%o=O#hD5 z%uvc5RUQWDB9kOCb3%n=A*f!3b;q5;Vo0KhsMnXAF-atXQYqnRxJzi_-`u;SaV*{4 z23ktg?6gnP`VuXJ_bRxnKirvUgDjY62PT|1iancHro=*0O^G5eAQ3uCtlF1U09WQE zR;v$Xs84(tAti|S?+-ZF9f=L?yu;+WNM_Sc;mlcLg;tB z≷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

Write a player's gender config to NBT on the given item stack.

- * - *

This only copies enough data to render breasts similarly to how they'd appear on the given player, which includes:

- *
    - *
  • {@link EntityConfig#getBustSize() Breast size}
  • - *
  • {@link Breasts#getCleavage() Cleavage}
  • - *
  • {@link Breasts#isUniboob() Uniboob}
  • - *
  • {@link Breasts#getXOffset() X}, {@link Breasts#getYOffset() Y}, and {@link Breasts#getZOffset() Z} offsets
  • - *
  • Whether the {@link PlayerEntity#isPartVisible player's jacket layer is visible}
  • - *
- * - * @see EntityConfig#readFromStack + * Utility method returning an {@link Optional} containing the requested value from the provided {@link NbtCompound} */ - public static void writeToNbt(@NotNull PlayerEntity player, @NotNull PlayerConfig config, @NotNull ItemStack armor) { - NbtCompound nbt = new NbtCompound(); - nbt.putFloat("BreastSize", config.getGender().canHaveBreasts() && config.showBreastsInArmor() ? config.getBustSize() : 0f); - nbt.putFloat("Cleavage", config.getBreasts().getCleavage()); - nbt.putBoolean("Uniboob", config.getBreasts().isUniboob()); - nbt.putFloat("XOffset", config.getBreasts().getXOffset()); - nbt.putFloat("YOffset", config.getBreasts().getYOffset()); - nbt.putFloat("ZOffset", config.getBreasts().getZOffset()); - // note that we also copy this to properly copy the exact size, as the player model will push the breast armor - // layer out a bit if they have a visible jacket layer - nbt.putBoolean("Jacket", player.isPartVisible(PlayerModelPart.JACKET)); - NbtComponent.set(DataComponentTypes.CUSTOM_DATA, armor, armorNbt -> armorNbt.put("WildfireGender", nbt)); + public static Optional readNbt(NbtCompound compound, String key, Function reader) { + if(!compound.contains(key)) { + return Optional.empty(); + } + return Optional.of(reader.apply(key)); + } + + /** + * Variant of {@link #readNbt}, clamping a {@code float} value to the allowed range by the provided config key. + */ + public static Optional readNbt(NbtCompound compound, String key, FloatConfigKey configKey) { + return readNbt(compound, key, compound::getFloat) + .map(v -> MathHelper.clamp(v, configKey.getMinInclusive(), configKey.getMaxInclusive())); } -} \ No newline at end of file +} diff --git a/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java b/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java new file mode 100644 index 00000000..c19b892f --- /dev/null +++ b/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java @@ -0,0 +1,70 @@ +package com.wildfire.main.entitydata; + +import com.wildfire.main.WildfireHelper; +import com.wildfire.main.config.Configuration; +import net.minecraft.client.render.entity.PlayerModelPart; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.NbtComponent; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector3f; + +/** + *

Record class for storing player breast settings on armor equipped onto armor stands

+ * + *

Note that while this is treated similarly to any other {@link DataComponentTypes data component} for performance reasons, + * this is never written as its own component on item stacks, but instead uses the {@link DataComponentTypes#CUSTOM_DATA custom NBT data component} + * for compatibility with vanilla clients on servers.

+ */ +public record BreastDataComponent(float breastSize, float cleavage, Vector3f offsets, boolean jacket, @Nullable NbtComponent nbtComponent) { + public static @Nullable BreastDataComponent fromPlayer(@NotNull PlayerEntity player, @NotNull PlayerConfig config) { + if(!config.getGender().canHaveBreasts() || !config.showBreastsInArmor()) { + return null; + } + + return new BreastDataComponent(config.getBustSize(), config.getBreasts().getCleavage(), config.getBreasts().getOffsets(), + player.isPartVisible(PlayerModelPart.JACKET), null); + } + + public static @Nullable BreastDataComponent fromComponent(@Nullable NbtComponent component) { + if(component == null) { + return null; + } + + @SuppressWarnings("deprecation") NbtCompound root = component.getNbt(); + if(!root.contains("WildfireGender", NbtElement.COMPOUND_TYPE)) { + return null; + } + NbtCompound nbt = root.getCompound("WildfireGender"); + + float breastSize = WildfireHelper.readNbt(nbt, "BreastSize", Configuration.BUST_SIZE).orElse(0f); + float cleavage = WildfireHelper.readNbt(nbt, "Cleavage", Configuration.BREASTS_CLEAVAGE).orElseGet(Configuration.BREASTS_CLEAVAGE::getDefault); + boolean jacket = WildfireHelper.readNbt(nbt, "Jacket", nbt::getBoolean).orElse(true); + Vector3f offsets = new Vector3f( + WildfireHelper.readNbt(nbt, "XOffset", Configuration.BREASTS_OFFSET_X).orElse(0f), + WildfireHelper.readNbt(nbt, "YOffset", Configuration.BREASTS_OFFSET_Y).orElse(0f), + WildfireHelper.readNbt(nbt, "ZOffset", Configuration.BREASTS_OFFSET_Z).orElse(0f)); + + return new BreastDataComponent(breastSize, cleavage, offsets, jacket, component); + } + + public void write(ItemStack stack) { + if(stack.isEmpty()) { + throw new IllegalArgumentException("The provided ItemStack must not be empty"); + } + NbtCompound nbt = new NbtCompound(); + nbt.putFloat("BreastSize", breastSize); + nbt.putFloat("Cleavage", cleavage); + nbt.putFloat("XOffset", offsets.x); + nbt.putFloat("YOffset", offsets.y); + nbt.putFloat("ZOffset", offsets.z); + nbt.putBoolean("Jacket", jacket); + // see the class javadoc for why we're using the custom data component instead of using this class + // as its own data component type + NbtComponent.set(DataComponentTypes.CUSTOM_DATA, stack, stackNbt -> stackNbt.put("WildfireGender", nbt)); + } +} diff --git a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java index 2bd49921..44ce4ce1 100644 --- a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java +++ b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java @@ -33,11 +33,11 @@ import net.minecraft.entity.decoration.ArmorStandEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; -import net.minecraft.nbt.NbtCompound; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.HashMap; +import java.util.Objects; import java.util.UUID; /** @@ -63,6 +63,7 @@ public class EntityConfig { protected final BreastPhysics lBreastPhysics, rBreastPhysics; protected final Breasts breasts; protected boolean jacketLayer = true; + protected @Nullable BreastDataComponent fromComponent; EntityConfig(UUID uuid) { this.uuid = uuid; @@ -74,31 +75,33 @@ public class EntityConfig { /** * Copy gender settings included in the given {@link ItemStack item NBT} to the current entity * - * @see WildfireHelper#writeToNbt + * @see BreastDataComponent */ public void readFromStack(@NotNull ItemStack chestplate) { NbtComponent component = chestplate.get(DataComponentTypes.CUSTOM_DATA); + if(chestplate.isEmpty() || component == null) { + this.fromComponent = null; + this.gender = Gender.MALE; + return; + } else if(fromComponent != null && fromComponent.nbtComponent() != null + && Objects.equals(fromComponent.nbtComponent(), component)) { + // nothing's changed since the last time we checked, so there's no need to read from the + // underlying nbt tag again + return; + } - // #getNbt() is already marked as deprecated, despite the only other option (#copyNbt()) - // being a less performant option for what we need, which is simply a read-only view of - // the underlying nbt compound. - @SuppressWarnings("deprecation") - NbtCompound nbt = component != null && component.contains("WildfireGender") - ? component.getNbt().getCompound("WildfireGender") - : null; - - if(nbt == null) { + fromComponent = BreastDataComponent.fromComponent(component); + if(fromComponent == null) { this.gender = Gender.MALE; return; } - this.pBustSize = nbt.contains("BreastSize") ? nbt.getFloat("BreastSize") : 0f; - this.gender = this.pBustSize > 0.02f ? Gender.FEMALE : Gender.MALE; - if(nbt.contains("Cleavage")) breasts.updateCleavage(nbt.getFloat("Cleavage")); - if(nbt.contains("Uniboob")) breasts.updateUniboob(nbt.getBoolean("Uniboob")); - if(nbt.contains("XOffset")) breasts.updateXOffset(nbt.getFloat("XOffset")); - if(nbt.contains("YOffset")) breasts.updateYOffset(nbt.getFloat("YOffset")); - if(nbt.contains("ZOffset")) breasts.updateZOffset(nbt.getFloat("ZOffset")); - if(nbt.contains("Jacket")) jacketLayer = nbt.getBoolean("Jacket"); + + breastPhysics = false; + pBustSize = fromComponent.breastSize(); + gender = pBustSize >= 0.02f ? Gender.FEMALE : Gender.MALE; + breasts.updateCleavage(fromComponent.cleavage()); + breasts.updateOffsets(fromComponent.offsets()); + this.jacketLayer = fromComponent.jacket(); } /** diff --git a/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java b/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java index 2bdcfb60..235f746a 100644 --- a/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java +++ b/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java @@ -21,6 +21,7 @@ import com.llamalad7.mixinextras.sugar.Local; import com.wildfire.api.IGenderArmor; import com.wildfire.main.WildfireGender; +import com.wildfire.main.entitydata.BreastDataComponent; import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.WildfireHelper; import net.minecraft.component.DataComponentTypes; @@ -77,7 +78,10 @@ protected ArmorStandEntityMixin(EntityType entityType, W IGenderArmor armorConfig = WildfireHelper.getArmorConfig(stack); if(armorConfig.armorStandsCopySettings()) { - WildfireHelper.writeToNbt(player, playerConfig, stack); + BreastDataComponent component = BreastDataComponent.fromPlayer(player, playerConfig); + if(component != null) { + component.write(stack); + } } return stack; From 5ac5046146210098c313363adecc670d3e7a6fa8 Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 10 Mar 2024 19:45:32 -0600 Subject: [PATCH 067/238] improve sidedness for mod initializer this also changes hurt sounds to be purely client-sided to work around fabric api doing some relatively strict registry syncing, which I didn't consider when initially choosing to make this occur on both the client- and server-side. this won't fully solve the issue in singleplayer worlds opened to LAN, but there's not a whole lot we can do here outside of forcing fabric-api to simply not sync our sound registry entry with an ugly bodge mixin. --- .../java/com/wildfire/api/WildfireAPI.java | 3 +- .../wildfire/main/WildfireEventHandler.java | 8 +-- .../com/wildfire/main/WildfireGender.java | 42 +++++++++----- .../wildfire/main/WildfireGenderClient.java | 48 +++++++++++++++ .../wildfire/main/WildfireGenderServer.java | 51 ---------------- .../wildfire/mixins/LivingEntityMixin.java | 58 ++++--------------- src/main/resources/fabric.mod.json | 4 +- .../resources/wildfire_gender.mixins.json | 6 +- 8 files changed, 93 insertions(+), 127 deletions(-) create mode 100644 src/main/java/com/wildfire/main/WildfireGenderClient.java delete mode 100644 src/main/java/com/wildfire/main/WildfireGenderServer.java diff --git a/src/main/java/com/wildfire/api/WildfireAPI.java b/src/main/java/com/wildfire/api/WildfireAPI.java index 51f75106..db7e536a 100644 --- a/src/main/java/com/wildfire/api/WildfireAPI.java +++ b/src/main/java/com/wildfire/api/WildfireAPI.java @@ -18,6 +18,7 @@ package com.wildfire.api; +import com.wildfire.main.WildfireGenderClient; import com.wildfire.main.config.Configuration; import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.WildfireGender; @@ -80,7 +81,7 @@ public static void addGenderArmor(Item item, IGenderArmor genderArmor) { * @param markForSync true if you want to send the gender settings to the server upon loading. */ public static Future> loadGenderInfo(UUID uuid, boolean markForSync) { - return WildfireGender.loadGenderInfo(uuid, markForSync); + return WildfireGenderClient.loadGenderInfo(uuid, markForSync); } /** diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index 72b8c160..127aa4a1 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -58,12 +58,8 @@ private static void onEntityLoad(Entity entity, World world) { if(!world.isClient() || MinecraftClient.getInstance().player == null) return; if(entity instanceof AbstractClientPlayerEntity plr) { UUID uuid = plr.getUuid(); - PlayerConfig aPlr = WildfireGender.getPlayerById(plr.getUuid()); - if(aPlr == null) { - aPlr = new PlayerConfig(uuid); - WildfireGender.PLAYER_CACHE.put(uuid, aPlr); - WildfireGender.loadGenderInfo(uuid, uuid.equals(MinecraftClient.getInstance().player.getUuid())); - } + boolean isClientPlayer = uuid.equals(MinecraftClient.getInstance().player.getUuid()); + WildfireGenderClient.loadPlayerIfMissing(uuid, isClientPlayer); } } diff --git a/src/main/java/com/wildfire/main/WildfireGender.java b/src/main/java/com/wildfire/main/WildfireGender.java index eb2e7e0b..38e27ae9 100644 --- a/src/main/java/com/wildfire/main/WildfireGender.java +++ b/src/main/java/com/wildfire/main/WildfireGender.java @@ -20,38 +20,48 @@ import java.util.HashMap; import java.util.Map; -import java.util.Optional; import java.util.UUID; -import java.util.concurrent.Future; - import com.mojang.logging.LogUtils; import com.wildfire.main.entitydata.PlayerConfig; -import net.fabricmc.api.ClientModInitializer; -import net.minecraft.util.Util; +import com.wildfire.main.networking.WildfireSync; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.networking.v1.EntityTrackingEvents; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.network.ServerPlayerEntity; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; -public class WildfireGender implements ClientModInitializer { - public static final String VERSION = "3.1"; - public static final String MODID = "wildfire_gender"; +public class WildfireGender implements ModInitializer { + public static final String MODID = "wildfire_gender"; public static final Logger LOGGER = LogUtils.getLogger(); public static final Map PLAYER_CACHE = new HashMap<>(); @Override - public void onInitializeClient() { - WildfireEventHandler.registerClientEvents(); - } + public void onInitialize() { + WildfireSync.register(); + EntityTrackingEvents.START_TRACKING.register(this::onBeginTracking); + } public static @Nullable PlayerConfig getPlayerById(UUID id) { - return PLAYER_CACHE.get(id); + return PLAYER_CACHE.get(id); } public static @NotNull PlayerConfig getOrAddPlayerById(UUID id) { return PLAYER_CACHE.computeIfAbsent(id, PlayerConfig::new); } - public static Future> loadGenderInfo(UUID uuid, boolean markForSync) { - return Util.getIoWorkerExecutor().submit(() -> Optional.ofNullable(PlayerConfig.loadCachedPlayer(uuid, markForSync))); - } -} \ No newline at end of file + private void onBeginTracking(Entity tracked, ServerPlayerEntity syncTo) { + if(tracked instanceof PlayerEntity toSync) { + PlayerConfig genderToSync = WildfireGender.getPlayerById(toSync.getUuid()); + if(genderToSync == null) return; + // Note that we intentionally don't check if we've previously synced a player with this code path; + // because we use entity tracking to sync, it's entirely possible that one player would leave the + // tracking distance of another, change their settings, and then re-enter their tracking distance; + // we wouldn't sync while they're out of tracking distance, and as such, their settings would be out + // of sync until they relog. + WildfireSync.sendToClient(syncTo, genderToSync); + } + } +} diff --git a/src/main/java/com/wildfire/main/WildfireGenderClient.java b/src/main/java/com/wildfire/main/WildfireGenderClient.java new file mode 100644 index 00000000..c023febb --- /dev/null +++ b/src/main/java/com/wildfire/main/WildfireGenderClient.java @@ -0,0 +1,48 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.main; + +import com.wildfire.main.entitydata.PlayerConfig; +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.util.Util; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.Future; + +@Environment(EnvType.CLIENT) +public class WildfireGenderClient implements ClientModInitializer { + @Override + public void onInitializeClient() { + WildfireSounds.register(); + WildfireEventHandler.registerClientEvents(); + } + + public static Future> loadGenderInfo(UUID uuid, boolean markForSync) { + return Util.getIoWorkerExecutor().submit(() -> Optional.ofNullable(PlayerConfig.loadCachedPlayer(uuid, markForSync))); + } + + public static void loadPlayerIfMissing(UUID uuid, boolean markForSync) { + if(WildfireGender.PLAYER_CACHE.containsKey(uuid)) return; + WildfireGender.getOrAddPlayerById(uuid); + loadGenderInfo(uuid, markForSync); + } +} diff --git a/src/main/java/com/wildfire/main/WildfireGenderServer.java b/src/main/java/com/wildfire/main/WildfireGenderServer.java deleted file mode 100644 index d9b27031..00000000 --- a/src/main/java/com/wildfire/main/WildfireGenderServer.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -package com.wildfire.main; - -import com.wildfire.main.entitydata.PlayerConfig; -import com.wildfire.main.networking.WildfireSync; -import net.fabricmc.api.ModInitializer; -import net.fabricmc.fabric.api.networking.v1.EntityTrackingEvents; -import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; -import net.minecraft.entity.Entity; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.server.network.ServerPlayerEntity; - -public class WildfireGenderServer implements ModInitializer { - public void onInitialize() { - // while this class is named 'Server', this is actually a common code path, - // so we can safely register here for both sides. - WildfireSounds.register(); - WildfireSync.register(); - EntityTrackingEvents.START_TRACKING.register(this::onBeginTracking); - } - - private void onBeginTracking(Entity tracked, ServerPlayerEntity syncTo) { - if(tracked instanceof PlayerEntity toSync) { - PlayerConfig genderToSync = WildfireGender.getPlayerById(toSync.getUuid()); - if(genderToSync == null) return; - // Note that we intentionally don't check if we've previously synced a player with this code path; - // because we use entity tracking to sync, it's entirely possible that one player would leave the - // tracking distance of another, change their settings, and then re-enter their tracking distance; - // we wouldn't sync while they're out of tracking distance, and as such, their settings would be out - // of sync until they relog. - WildfireSync.sendToClient(syncTo, genderToSync); - } - } -} diff --git a/src/main/java/com/wildfire/mixins/LivingEntityMixin.java b/src/main/java/com/wildfire/mixins/LivingEntityMixin.java index 3e570ef4..0cbb67dc 100644 --- a/src/main/java/com/wildfire/mixins/LivingEntityMixin.java +++ b/src/main/java/com/wildfire/mixins/LivingEntityMixin.java @@ -28,31 +28,13 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.sound.SoundEvent; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -/* - * A note on why this implementation in particular was chosen: - * - * While this could be reduced down to one mixin (the `#onDamaged(DamageSource)` one in particular), and forego any sort - * of server-side handling, this is being done as (at least as of when this is being written,) the mod fails to ever - * perform an initial sync upon a player joining a dedicated server; as such, we're using client- *and* server-side mixins - * to provide some level of consistency, even if syncing isn't consistent. - * - * We're additionally playing *alongside* the vanilla hurt sound, largely for the sake of accessibility, as the vanilla - * hurt sound may provide important context as for why a player is taking damage, which could prove especially helpful - * for players with poor/no eyesight. - * - * Additionally, completely replacing the hurt sound server-side would essentially require the mod client-side as well - * to hear *any* hurt sounds from players with this setting enabled, which rules out mixins to methods such as - * `PlayerEntity#getHurtSound(DamageSource)`. - */ @Mixin(LivingEntity.class) +@Environment(EnvType.CLIENT) public abstract class LivingEntityMixin { - @Environment(EnvType.CLIENT) @Inject( method = "onDamaged", at = @At( @@ -60,39 +42,19 @@ public abstract class LivingEntityMixin { target = "Lnet/minecraft/entity/LivingEntity;playSound(Lnet/minecraft/sound/SoundEvent;FF)V" ) ) - public void clientGenderHurtSound(DamageSource damageSource, CallbackInfo ci) { + public void wildfiregender$playGenderHurtSound(DamageSource damageSource, CallbackInfo ci) { MinecraftClient client = MinecraftClient.getInstance(); if(client.player == null || client.world == null) return; - if((LivingEntity)(Object)this instanceof PlayerEntity player) { - if(player.getWorld().isClient() && player.getUuid().equals(client.player.getUuid())) { - this.playGenderHurtSound(player); - } - } - } - - @Inject( - method = "damage", - at = @At( - value = "INVOKE", - target = "Lnet/minecraft/entity/LivingEntity;playHurtSound(Lnet/minecraft/entity/damage/DamageSource;)V" - ) - ) - public void serverGenderHurtSound(DamageSource source, float amount, CallbackInfoReturnable cir) { - if((LivingEntity)(Object)this instanceof PlayerEntity player) { - if(!player.getWorld().isClient()) this.playGenderHurtSound(player); - } - } - - @Unique - private void playGenderHurtSound(PlayerEntity player) { - PlayerConfig genderPlayer = WildfireGender.getPlayerById(player.getUuid()); - if(genderPlayer == null || !genderPlayer.hasHurtSounds()) return; + if((LivingEntity)(Object)this instanceof PlayerEntity player && player.getWorld().isClient()) { + PlayerConfig genderPlayer = WildfireGender.getPlayerById(player.getUuid()); + if(genderPlayer == null || !genderPlayer.hasHurtSounds()) return; - SoundEvent hurtSound = genderPlayer.getGender().getHurtSound(); - if(hurtSound != null) { - float pitch = (player.getRandom().nextFloat() - player.getRandom().nextFloat()) * 0.2F + 1.0F; - player.playSound(hurtSound, 1f, pitch); + SoundEvent hurtSound = genderPlayer.getGender().getHurtSound(); + if(hurtSound != null) { + float pitch = (player.getRandom().nextFloat() - player.getRandom().nextFloat()) * 0.2F + 1.0F; + player.playSound(hurtSound, 1f, pitch); + } } } } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 7bb535b6..e86dd994 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -27,10 +27,10 @@ "environment": "*", "entrypoints": { "main": [ - "com.wildfire.main.WildfireGenderServer" + "com.wildfire.main.WildfireGender" ], "client": [ - "com.wildfire.main.WildfireGender" + "com.wildfire.main.WildfireGenderClient" ] }, "mixins": [ diff --git a/src/main/resources/wildfire_gender.mixins.json b/src/main/resources/wildfire_gender.mixins.json index 1a606acb..66406add 100644 --- a/src/main/resources/wildfire_gender.mixins.json +++ b/src/main/resources/wildfire_gender.mixins.json @@ -4,13 +4,13 @@ "package": "com.wildfire.mixins", "compatibilityLevel": "JAVA_17", "mixins": [ - "ArmorStandEntityMixin", - "LivingEntityMixin" + "ArmorStandEntityMixin" ], "client": [ "ArmorStandEntityRendererMixin", "PlayerRenderMixin", - "BreastPhysicsTickMixin" + "BreastPhysicsTickMixin", + "LivingEntityMixin" ], "injectors": { "defaultRequire": 1 From cd2f15208e4e119cd046418800fc99c56971f2c4 Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 30 Jan 2024 18:04:03 -0700 Subject: [PATCH 068/238] reduce code duplication in config classes also remove redundant double load/save --- .../main/config/AbstractConfiguration.java | 104 ++++++++++++++ .../config/BreastPresetConfiguration.java | 136 +++--------------- .../wildfire/main/config/Configuration.java | 113 +-------------- .../main/entitydata/PlayerConfig.java | 4 +- 4 files changed, 130 insertions(+), 227 deletions(-) create mode 100644 src/main/java/com/wildfire/main/config/AbstractConfiguration.java diff --git a/src/main/java/com/wildfire/main/config/AbstractConfiguration.java b/src/main/java/com/wildfire/main/config/AbstractConfiguration.java new file mode 100644 index 00000000..d1dd16a7 --- /dev/null +++ b/src/main/java/com/wildfire/main/config/AbstractConfiguration.java @@ -0,0 +1,104 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.main.config; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonWriter; +import com.wildfire.main.WildfireGender; +import net.fabricmc.api.EnvType; +import net.fabricmc.loader.api.FabricLoader; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +public abstract class AbstractConfiguration { + + private static final TypeAdapter ADAPTER = new Gson().getAdapter(JsonObject.class); + + private final File CFG_FILE; + public JsonObject SAVE_VALUES = new JsonObject(); + + protected AbstractConfiguration(String directory, String cfgName) { + Path saveDir = FabricLoader.getInstance().getConfigDir().resolve(directory); + if(supportsSaving() && !Files.isDirectory(saveDir)) { + try { + Files.createDirectory(saveDir); + } catch(IOException e) { + WildfireGender.LOGGER.error("Failed to create config directory", e); + } + } + CFG_FILE = saveDir.resolve(cfgName + ".json").toFile(); + } + + public static boolean supportsSaving() { + return FabricLoader.getInstance().getEnvironmentType() != EnvType.SERVER; + } + + public void set(ConfigKey key, TYPE value) { + key.save(SAVE_VALUES, value); + } + + public TYPE get(ConfigKey key) { + return key.read(SAVE_VALUES); + } + + public void setDefault(ConfigKey key) { + if(!SAVE_VALUES.has(key.key)) { + set(key, key.defaultValue); + } + } + + public void removeParameter(ConfigKey key) { + removeParameter(key.key); + } + + public void removeParameter(String key) { + SAVE_VALUES.remove(key); + } + + public void save() { + if(!supportsSaving()) return; + try(FileWriter writer = new FileWriter(CFG_FILE); JsonWriter jsonWriter = new JsonWriter(writer)) { + jsonWriter.setIndent("\t"); + ADAPTER.write(jsonWriter, SAVE_VALUES); + } catch (IOException e) { + WildfireGender.LOGGER.error("Failed to save config file", e); + } + } + + public void load() { + if(!supportsSaving()) return; + try(FileReader configurationFile = new FileReader(CFG_FILE)) { + JsonObject obj = new Gson().fromJson(configurationFile, JsonObject.class); + for(Map.Entry entry : obj.entrySet()) { + SAVE_VALUES.add(entry.getKey(), entry.getValue()); + } + } catch(IOException e) { + WildfireGender.LOGGER.error("Failed to load config file", e); + } + } +} diff --git a/src/main/java/com/wildfire/main/config/BreastPresetConfiguration.java b/src/main/java/com/wildfire/main/config/BreastPresetConfiguration.java index 0b26f768..241aee08 100644 --- a/src/main/java/com/wildfire/main/config/BreastPresetConfiguration.java +++ b/src/main/java/com/wildfire/main/config/BreastPresetConfiguration.java @@ -18,23 +18,14 @@ package com.wildfire.main.config; -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.TypeAdapter; -import com.google.gson.stream.JsonWriter; import net.fabricmc.loader.api.FabricLoader; import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.ArrayList; -import java.util.Map; -public class BreastPresetConfiguration { +public class BreastPresetConfiguration extends AbstractConfiguration { + + private static final String PRESETS_DIR = "WildfireGender/presets"; public static final StringConfigKey PRESET_NAME = new StringConfigKey("preset_name", ""); public static final FloatConfigKey BUST_SIZE = new FloatConfigKey("bust_size", 0.6F, 0, 0.8f); @@ -45,117 +36,28 @@ public class BreastPresetConfiguration { public static final BooleanConfigKey BREASTS_UNIBOOB = new BooleanConfigKey("breasts_uniboob", true); public static final FloatConfigKey BREASTS_CLEAVAGE = new FloatConfigKey("breasts_cleavage", 0, 0, 0.1F); - private static final TypeAdapter ADAPTER = new Gson().getAdapter(JsonObject.class); - - private final File CFG_FILE; - public JsonObject SAVE_VALUES = new JsonObject(); - public BreastPresetConfiguration(String cfgName) { - - Path saveDir = FabricLoader.getInstance().getConfigDir(); - System.out.println("Breast Presets Save Dir: " + saveDir.toString()); - if(!Files.isDirectory(saveDir.resolve("WildfireGender/presets"))) { - try { - Files.createDirectory(saveDir.resolve("WildfireGender/presets")); - } catch (IOException e) { - e.printStackTrace(); - } - } - //.getOrCreateGameRelativePath(FMLPaths.CONFIGDIR.get().resolve(saveLoc), saveLoc); - CFG_FILE = saveDir.resolve("WildfireGender/presets").resolve(cfgName + ".json").toFile(); + super(PRESETS_DIR, cfgName); } public static BreastPresetConfiguration[] getBreastPresetConfigurationFiles() { - ArrayList tmp = new ArrayList<>(); - - Path saveDir = FabricLoader.getInstance().getConfigDir(); - File presetFileLocation = saveDir.resolve("WildfireGender/presets").toFile(); - if(!presetFileLocation.exists()) { - presetFileLocation.mkdirs(); - } - File[] presetFiles = presetFileLocation.listFiles(); - if(presetFiles.length > 0) { - for (File f : presetFiles) { - BreastPresetConfiguration cfg = new BreastPresetConfiguration(f.getName().replace(".json", "")); - cfg.load(); // Load the preset values - tmp.add(cfg); + ArrayList presets = new ArrayList<>(); + File saveDir = FabricLoader.getInstance().getConfigDir().resolve(PRESETS_DIR).toFile(); + + if(!saveDir.exists()) { + saveDir.mkdirs(); + } + File[] presetFiles = saveDir.listFiles(); + if(presetFiles != null) { + for(File f : presetFiles) { + // strip the trailing '.json' + String name = f.getName().substring(0, f.getName().length() - 5); + BreastPresetConfiguration cfg = new BreastPresetConfiguration(name); + cfg.load(); // load from file + presets.add(cfg); } } - if(tmp.size() == 0) { - return new BreastPresetConfiguration[] {}; - } - return tmp.toArray(new BreastPresetConfiguration[tmp.size()]); - } - - public void finish() { - if(CFG_FILE.exists()) { - load(); //load file - updateConfig(); - } else { - //save(); //save all values to default in new file. - } - } - - public void set(ConfigKey key, TYPE value) { - key.save(SAVE_VALUES, value); - } - - public TYPE get(ConfigKey key) { - return key.read(SAVE_VALUES); - } - - public void removeParameter(ConfigKey key) { - removeParameter(key.key); - } - - public void removeParameter(String key) { - SAVE_VALUES.remove(key); - } - - public void updateConfig() { - JsonObject obj; - try (FileReader configurationFile = new FileReader(CFG_FILE)) { - obj = new Gson().fromJson(configurationFile, JsonObject.class); //GsonHelper.parse(configurationFile); - //Merge with existing values - for (Map.Entry entry : SAVE_VALUES.entrySet()) { - obj.add(entry.getKey(), entry.getValue()); - } - } catch(Exception ignored) { - return; - } - try (FileWriter writer = new FileWriter(CFG_FILE); - JsonWriter jsonWriter = new JsonWriter(writer)) { - ADAPTER.write(jsonWriter, obj); - //System.out.println("[Configuration] Saved Existing File!"); - } catch(Exception ignored) {} - } - - public void save() { - try (FileWriter writer = new FileWriter(CFG_FILE); - JsonWriter jsonWriter = new JsonWriter(writer)) { - jsonWriter.setIndent(" "); - ADAPTER.write(jsonWriter, SAVE_VALUES); - //System.out.println("[Configuration] Saved New File!"); - } catch (IOException e1) { - e1.printStackTrace(); - } - } - - //load file values to this class for use in the program - public void load() { - //System.out.println("[Configuration] Loading..."); - - try (FileReader configurationFile = new FileReader(CFG_FILE)) { - JsonObject obj = new Gson().fromJson(configurationFile, JsonObject.class); - for (Map.Entry entry : obj.entrySet()) { - String key = entry.getKey(); - SAVE_VALUES.add(key, entry.getValue()); - } - //System.out.println("[Configuration] Loaded!\n\n"); - } catch(Exception e) { - //System.out.println("[Configuration] Failed!\n\n"); - e.printStackTrace(); - } + return presets.toArray(BreastPresetConfiguration[]::new); } } diff --git a/src/main/java/com/wildfire/main/config/Configuration.java b/src/main/java/com/wildfire/main/config/Configuration.java index 37c0a3ac..930743c5 100644 --- a/src/main/java/com/wildfire/main/config/Configuration.java +++ b/src/main/java/com/wildfire/main/config/Configuration.java @@ -18,24 +18,12 @@ package com.wildfire.main.config; -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.TypeAdapter; -import com.google.gson.stream.JsonWriter; -import net.fabricmc.loader.api.FabricLoader; - -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Map; import java.util.UUID; -public class Configuration { +public class Configuration extends AbstractConfiguration { + + private static final String CONFIG_DIR = "WildfireGender"; public static final UUIDConfigKey USERNAME = new UUIDConfigKey("username", UUID.nameUUIDFromBytes("UNKNOWN".getBytes(StandardCharsets.UTF_8))); public static final GenderConfigKey GENDER = new GenderConfigKey("gender"); @@ -54,98 +42,7 @@ public class Configuration { public static final FloatConfigKey BOUNCE_MULTIPLIER = new FloatConfigKey("bounce_multiplier", 0.34F, 0, 0.5f); public static final FloatConfigKey FLOPPY_MULTIPLIER = new FloatConfigKey("floppy_multiplier", 0.75F, 0.25f, 1); - private static final TypeAdapter ADAPTER = new Gson().getAdapter(JsonObject.class); - - private final File CFG_FILE; - public JsonObject SAVE_VALUES = new JsonObject(); - - public Configuration(String saveLoc, String cfgName) { - - Path saveDir = FabricLoader.getInstance().getConfigDir(); - if(!Files.isDirectory(saveDir.resolve(saveLoc))) { - try { - Files.createDirectory(saveDir.resolve(saveLoc)); - } catch (IOException e) { - e.printStackTrace(); - } - } - CFG_FILE = saveDir.resolve(saveLoc).resolve(cfgName + ".json").toFile(); - } - - public void finish() { - if(CFG_FILE.exists()) { - load(); //load file - updateConfig(); - } else { - //save(); //save all values to default in new file. - } - } - - public void set(ConfigKey key, TYPE value) { - key.save(SAVE_VALUES, value); - } - - public void setDefault(ConfigKey key) { - if (!SAVE_VALUES.has(key.key)) { - set(key, key.defaultValue); - } - } - - public TYPE get(ConfigKey key) { - return key.read(SAVE_VALUES); - } - - public void removeParameter(ConfigKey key) { - removeParameter(key.key); - } - - public void removeParameter(String key) { - SAVE_VALUES.remove(key); - } - - public void updateConfig() { - JsonObject obj; - try (FileReader configurationFile = new FileReader(CFG_FILE)) { - obj = new Gson().fromJson(configurationFile, JsonObject.class); //GsonHelper.parse(configurationFile); - //Merge with existing values - for (Map.Entry entry : SAVE_VALUES.entrySet()) { - obj.add(entry.getKey(), entry.getValue()); - } - } catch(Exception ignored) { - return; - } - try (FileWriter writer = new FileWriter(CFG_FILE); - JsonWriter jsonWriter = new JsonWriter(writer)) { - ADAPTER.write(jsonWriter, obj); - //System.out.println("[Configuration] Saved Existing File!"); - } catch(Exception ignored) {} - } - - public void save() { - try (FileWriter writer = new FileWriter(CFG_FILE); - JsonWriter jsonWriter = new JsonWriter(writer)) { - jsonWriter.setIndent(" "); - ADAPTER.write(jsonWriter, SAVE_VALUES); - //System.out.println("[Configuration] Saved New File!"); - } catch (IOException e1) { - e1.printStackTrace(); - } - } - - //load file values to this class for use in the program - public void load() { - //System.out.println("[Configuration] Loading..."); - - try (FileReader configurationFile = new FileReader(CFG_FILE)) { - JsonObject obj = new Gson().fromJson(configurationFile, JsonObject.class); - for (Map.Entry entry : obj.entrySet()) { - String key = entry.getKey(); - SAVE_VALUES.add(key, entry.getValue()); - } - //System.out.println("[Configuration] Loaded!\n\n"); - } catch(Exception e) { - //System.out.println("[Configuration] Failed!\n\n"); - e.printStackTrace(); - } + public Configuration(String cfgName) { + super(CONFIG_DIR, cfgName); } } diff --git a/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java b/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java index ffac95ed..6fc905b4 100644 --- a/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java +++ b/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java @@ -48,7 +48,7 @@ public PlayerConfig(UUID uuid) { public PlayerConfig(UUID uuid, Gender gender) { super(uuid); this.gender = gender; - this.cfg = new Configuration("WildfireGender", this.uuid.toString()); + this.cfg = new Configuration(this.uuid.toString()); this.cfg.set(Configuration.USERNAME, this.uuid); this.cfg.setDefault(Configuration.GENDER); this.cfg.setDefault(Configuration.BUST_SIZE); @@ -65,7 +65,7 @@ public PlayerConfig(UUID uuid, Gender gender) { this.cfg.setDefault(Configuration.SHOW_IN_ARMOR); this.cfg.setDefault(Configuration.BOUNCE_MULTIPLIER); this.cfg.setDefault(Configuration.FLOPPY_MULTIPLIER); - this.cfg.finish(); + this.cfg.load(); } // this shouldn't ever be called on players, but just to be safe, override with a noop. From c79573002d5dd24950f18b0b5607e1cd36470f10 Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 11 Mar 2024 17:07:31 -0600 Subject: [PATCH 069/238] improve player config handling defer loading to the actual load task to not make it completely redundant --- src/main/java/com/wildfire/api/WildfireAPI.java | 3 +++ src/main/java/com/wildfire/main/WildfireGender.java | 5 +++-- .../com/wildfire/main/entitydata/EntityConfig.java | 5 +++-- .../com/wildfire/main/entitydata/PlayerConfig.java | 2 +- .../java/com/wildfire/render/GenderArmorLayer.java | 2 +- src/main/java/com/wildfire/render/GenderLayer.java | 12 +----------- 6 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/wildfire/api/WildfireAPI.java b/src/main/java/com/wildfire/api/WildfireAPI.java index db7e536a..4917fb5e 100644 --- a/src/main/java/com/wildfire/api/WildfireAPI.java +++ b/src/main/java/com/wildfire/api/WildfireAPI.java @@ -23,6 +23,8 @@ import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.WildfireGender; import com.wildfire.main.Gender; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; import org.jetbrains.annotations.NotNull; @@ -80,6 +82,7 @@ public static void addGenderArmor(Item item, IGenderArmor genderArmor) { * @param uuid the uuid of the target {@link PlayerEntity} * @param markForSync true if you want to send the gender settings to the server upon loading. */ + @Environment(EnvType.CLIENT) public static Future> loadGenderInfo(UUID uuid, boolean markForSync) { return WildfireGenderClient.loadGenderInfo(uuid, markForSync); } diff --git a/src/main/java/com/wildfire/main/WildfireGender.java b/src/main/java/com/wildfire/main/WildfireGender.java index 38e27ae9..b78cd763 100644 --- a/src/main/java/com/wildfire/main/WildfireGender.java +++ b/src/main/java/com/wildfire/main/WildfireGender.java @@ -18,9 +18,10 @@ package com.wildfire.main; -import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + import com.mojang.logging.LogUtils; import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.networking.WildfireSync; @@ -36,7 +37,7 @@ public class WildfireGender implements ModInitializer { public static final String MODID = "wildfire_gender"; public static final Logger LOGGER = LogUtils.getLogger(); - public static final Map PLAYER_CACHE = new HashMap<>(); + public static final Map PLAYER_CACHE = new ConcurrentHashMap<>(); @Override public void onInitialize() { diff --git a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java index 44ce4ce1..275ebb0b 100644 --- a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java +++ b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java @@ -36,9 +36,10 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.HashMap; import java.util.Objects; +import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; /** *

A stripped down version of a {@link PlayerConfig player's config}, intended for use with non-player entities.

@@ -49,7 +50,7 @@ */ public class EntityConfig { - public static final HashMap ENTITY_CACHE = new HashMap<>(); + public static final Map ENTITY_CACHE = new ConcurrentHashMap<>(); public final UUID uuid; protected Gender gender = Configuration.GENDER.getDefault(); diff --git a/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java b/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java index 6fc905b4..96cb5f81 100644 --- a/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java +++ b/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java @@ -65,7 +65,6 @@ public PlayerConfig(UUID uuid, Gender gender) { this.cfg.setDefault(Configuration.SHOW_IN_ARMOR); this.cfg.setDefault(Configuration.BOUNCE_MULTIPLIER); this.cfg.setDefault(Configuration.FLOPPY_MULTIPLIER); - this.cfg.load(); } // this shouldn't ever be called on players, but just to be safe, override with a noop. @@ -159,6 +158,7 @@ public static PlayerConfig loadCachedPlayer(UUID uuid, boolean markForSync) { if (plr != null) { plr.syncStatus = SyncStatus.CACHED; Configuration config = plr.getConfig(); + config.load(); plr.updateGender(config.get(Configuration.GENDER)); plr.updateBustSize(config.get(Configuration.BUST_SIZE)); plr.updateHurtSounds(config.get(Configuration.HURT_SOUNDS)); diff --git a/src/main/java/com/wildfire/render/GenderArmorLayer.java b/src/main/java/com/wildfire/render/GenderArmorLayer.java index 75879341..fae80221 100644 --- a/src/main/java/com/wildfire/render/GenderArmorLayer.java +++ b/src/main/java/com/wildfire/render/GenderArmorLayer.java @@ -91,7 +91,7 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume if(ArmorRendererRegistryImpl.get(chestplate.getItem()) != null) return; try { - entityConfig = getConfig(ent); + entityConfig = EntityConfig.getEntity(ent); if(entityConfig == null) return; if(!setupRender(ent, entityConfig, partialTicks)) return; diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index c3cdb6c4..e877c63d 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -29,7 +29,6 @@ import com.wildfire.render.WildfireModelRenderer.PositionTextureVertex; import java.lang.Math; -import java.util.ConcurrentModificationException; import java.util.function.Consumer; import net.fabricmc.api.EnvType; @@ -91,15 +90,6 @@ public GenderLayer(FeatureRendererContext render) { return null; } - protected @Nullable EntityConfig getConfig(T entity) { - try { - return EntityConfig.getEntity(entity); - } catch(ConcurrentModificationException e) { - // likely a temporary failure, try again later - return null; - } - } - @Override public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, @NotNull T ent, float limbAngle, float limbDistance, float partialTicks, float animationProgress, float headYaw, float headPitch) { @@ -109,7 +99,7 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume return; } - EntityConfig entityConfig = getConfig(ent); + EntityConfig entityConfig = EntityConfig.getEntity(ent); if(entityConfig == null) return; try { From 7a0e7f084fac40968239443845bd6b8a25c529e5 Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 11 Mar 2024 16:17:03 -0600 Subject: [PATCH 070/238] clamp float config values to their min/max values this is aimed at being a bit more user-friendly if the min/max values are modified, and a player's previous value would now be outside the allowed range for a key; previously, this would simply result in the key being reset to its default value. --- .../java/com/wildfire/main/config/FloatConfigKey.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/com/wildfire/main/config/FloatConfigKey.java b/src/main/java/com/wildfire/main/config/FloatConfigKey.java index 16930a8f..7fba727d 100644 --- a/src/main/java/com/wildfire/main/config/FloatConfigKey.java +++ b/src/main/java/com/wildfire/main/config/FloatConfigKey.java @@ -18,7 +18,9 @@ package com.wildfire.main.config; +import com.google.gson.JsonElement; import com.google.gson.JsonPrimitive; +import net.minecraft.util.math.MathHelper; public class FloatConfigKey extends NumberConfigKey { @@ -30,6 +32,14 @@ public FloatConfigKey(String key, float defaultValue, float minInclusive, float super(key, defaultValue, minInclusive, maxInclusive); } + @Override + protected Float read(JsonElement element) { + // note that we clamp float values instead of allowing them to be reset to their default to + // be a bit more user-friendly if the min/max value for this key is modified, and the player's + // previous config value would now be outside the allowed range for this key. + return MathHelper.clamp(super.read(element), getMinInclusive(), getMaxInclusive()); + } + @Override protected Float fromPrimitive(JsonPrimitive primitive) { return primitive.getAsFloat(); From 22338c7030be3d10ac8ce5d26ebdde4e22a320bd Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 10 Mar 2024 16:54:54 -0600 Subject: [PATCH 071/238] avoid recreating the same model boxes for each renderer --- .../com/wildfire/render/GenderArmorLayer.java | 23 +++++++----- .../java/com/wildfire/render/GenderLayer.java | 35 ++++++++++++------- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/wildfire/render/GenderArmorLayer.java b/src/main/java/com/wildfire/render/GenderArmorLayer.java index fae80221..323179e3 100644 --- a/src/main/java/com/wildfire/render/GenderArmorLayer.java +++ b/src/main/java/com/wildfire/render/GenderArmorLayer.java @@ -56,14 +56,11 @@ public class GenderArmorLayer> extends GenderLayer { private final SpriteAtlasTexture armorTrimsAtlas; - protected final BreastModelBox lBoobArmor, rBoobArmor; - protected final BreastModelBox lTrim, rTrim; + protected static final BreastModelBox lBoobArmor, rBoobArmor; + protected static final BreastModelBox lTrim, rTrim; private EntityConfig entityConfig; - public GenderArmorLayer(FeatureRendererContext render, BakedModelManager bakery) { - super(render); - armorTrimsAtlas = bakery.getAtlas(TexturedRenderLayers.ARMOR_TRIMS_ATLAS_TEXTURE); - + static { lBoobArmor = new BreastModelBox(64, 32, 16, 17, -4F, 0.0F, 0F, 4, 5, 3, 0.0F, false); rBoobArmor = new BreastModelBox(64, 32, 20, 17, 0, 0.0F, 0F, 4, 5, 3, 0.0F, false); // apply a very slight delta to fix z-fighting with the armor @@ -71,6 +68,11 @@ public GenderArmorLayer(FeatureRendererContext render, BakedModelManager b rTrim = new BreastModelBox(64, 32, 20, 17, 0, 0.0F, 0F, 4, 5, 4, 0.001F, false); } + public GenderArmorLayer(FeatureRendererContext render, BakedModelManager bakery) { + super(render); + armorTrimsAtlas = bakery.getAtlas(TexturedRenderLayers.ARMOR_TRIMS_ATLAS_TEXTURE); + } + @Override public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, @NotNull T ent, float limbAngle, float limbDistance, float partialTicks, float animationProgress, float headYaw, float headPitch) { MinecraftClient client = MinecraftClient.getInstance(); @@ -98,8 +100,8 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume if(ent instanceof ArmorStandEntity && !genderArmor.armorStandsCopySettings()) return; final RegistryEntry material = ((ArmorItem) chestplate.getItem()).getMaterial(); - final int color = armorStack.isIn(ItemTags.DYEABLE) ? DyedColorComponent.getColor(armorStack, -6265536) : Colors.WHITE; - final boolean glint = armorStack.hasGlint(); + final int color = chestplate.isIn(ItemTags.DYEABLE) ? DyedColorComponent.getColor(chestplate, -6265536) : Colors.WHITE; + final boolean glint = chestplate.hasGlint(); renderSides(ent, getContextModel(), matrixStack, side -> { material.value().layers().forEach(layer -> { @@ -124,6 +126,11 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume } } + @Override + protected void resizeBox(float breastSize) { + // this has no relevance to armor + } + @Override protected void setupTransformations(T entity, ModelPart body, MatrixStack matrixStack, BreastSide side) { super.setupTransformations(entity, body, matrixStack, side); diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index e877c63d..1b9fcac3 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -58,9 +58,9 @@ public class GenderLayer> extends FeatureRenderer { private BreastModelBox lBreast, rBreast; - private final OverlayModelBox lBreastWear, rBreastWear; + private static final OverlayModelBox lBreastWear, rBreastWear; - private float preBreastSize = 0f; + private float preBreastSize = 0f, preBreastOffsetZ; private Breasts breasts; protected ItemStack armorStack; protected IGenderArmor genderArmor; @@ -68,12 +68,16 @@ public class GenderLayer> protected float breastOffsetX, breastOffsetY, breastOffsetZ, lPhysPositionY, lPhysPositionX, rPhysPositionY, rTotalX, lPhysBounceRotation, rPhysBounceRotation, breastSize, zOffset, outwardAngle; + static { + lBreastWear = new OverlayModelBox(true, 64, 64, 17, 34, -4F, 0.0F, 0F, 4, 5, 3, 0.0F, false); + rBreastWear = new OverlayModelBox(false, 64, 64, 21, 34, 0, 0.0F, 0F, 4, 5, 3, 0.0F, false); + } + public GenderLayer(FeatureRendererContext render) { super(render); + // this can't be static or final as we need the ability to resize this during render time lBreast = new BreastModelBox(64, 64, 16, 17, -4F, 0.0F, 0F, 4, 5, 4, 0.0F, false); rBreast = new BreastModelBox(64, 64, 20, 17, 0, 0.0F, 0F, 4, 5, 4, 0.0F, false); - lBreastWear = new OverlayModelBox(true,64, 64, 17, 34, -4F, 0.0F, 0F, 4, 5, 3, 0.0F, false); - rBreastWear = new OverlayModelBox(false,64, 64, 21, 34, 0, 0.0F, 0F, 4, 5, 3, 0.0F, false); } private @Nullable RenderLayer getRenderLayer(T entity) { @@ -151,15 +155,7 @@ protected boolean setupRender(T entity, EntityConfig entityConfig, float partial outwardAngle = (Math.round(breasts.getCleavage() * 100f) / 100f) * 100f; outwardAngle = Math.min(outwardAngle, 10); - float reducer = -1; - if(bSize < 0.84f) reducer++; - if(bSize < 0.72f) reducer++; - - if(preBreastSize != bSize) { - lBreast = new BreastModelBox(64, 64, 16, 17, -4F, 0.0F, 0F, 4, 5, (int) (4 - breastOffsetZ - reducer), 0.0F, false); - rBreast = new BreastModelBox(64, 64, 20, 17, 0, 0.0F, 0F, 4, 5, (int) (4 - breastOffsetZ - reducer), 0.0F, false); - preBreastSize = bSize; - } + resizeBox(bSize); lPhysPositionY = MathHelper.lerp(partialTicks, leftBreastPhysics.getPrePositionY(), leftBreastPhysics.getPositionY()); lPhysPositionX = MathHelper.lerp(partialTicks, leftBreastPhysics.getPrePositionX(), leftBreastPhysics.getPositionX()); @@ -192,6 +188,19 @@ protected boolean setupRender(T entity, EntityConfig entityConfig, float partial return true; } + protected void resizeBox(float breastSize) { + float reducer = -1; + if(breastSize < 0.84f) reducer++; + if(breastSize < 0.72f) reducer++; + + if(preBreastSize != breastSize || preBreastOffsetZ != breastOffsetZ) { + lBreast = new BreastModelBox(64, 64, 16, 17, -4F, 0.0F, 0F, 4, 5, (int) (4 - breastOffsetZ - reducer), 0.0F, false); + rBreast = new BreastModelBox(64, 64, 20, 17, 0, 0.0F, 0F, 4, 5, (int) (4 - breastOffsetZ - reducer), 0.0F, false); + preBreastSize = breastSize; + preBreastOffsetZ = breastOffsetZ; + } + } + protected void setupTransformations(T entity, ModelPart body, MatrixStack matrixStack, BreastSide side) { matrixStack.translate(body.pivotX * 0.0625f, body.pivotY * 0.0625f, body.pivotZ * 0.0625f); if(body.roll != 0.0F) { From b418db86a5c35e4a8640cb178018032d4b9d5631 Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 10 Mar 2024 16:59:15 -0600 Subject: [PATCH 072/238] get the target RenderLayer from LivingEntityRenderer --- build.gradle | 4 ++++ .../java/com/wildfire/render/GenderLayer.java | 22 +++++++++---------- src/main/resources/fabric.mod.json | 1 + .../resources/wildfire_gender.accesswidener | 3 +++ 4 files changed, 18 insertions(+), 12 deletions(-) create mode 100644 src/main/resources/wildfire_gender.accesswidener diff --git a/build.gradle b/build.gradle index 217acd9d..fa7e309a 100644 --- a/build.gradle +++ b/build.gradle @@ -51,6 +51,10 @@ java { targetCompatibility = JavaVersion.VERSION_21 } +loom { + accessWidenerPath = file("src/main/resources/wildfire_gender.accesswidener") +} + jar { from("LICENSE") { rename { "${it}_${project.archivesBaseName}"} diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index 1b9fcac3..7b9e785e 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -48,7 +48,6 @@ import net.minecraft.entity.LivingEntity; import net.minecraft.entity.effect.StatusEffectUtil; import net.minecraft.item.ItemStack; -import net.minecraft.util.Identifier; import net.minecraft.util.math.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -58,6 +57,7 @@ public class GenderLayer> extends FeatureRenderer { private BreastModelBox lBreast, rBreast; + private final FeatureRendererContext context; private static final OverlayModelBox lBreastWear, rBreastWear; private float preBreastSize = 0f, preBreastOffsetZ; @@ -75,23 +75,21 @@ public class GenderLayer> public GenderLayer(FeatureRendererContext render) { super(render); + this.context = render; // this can't be static or final as we need the ability to resize this during render time lBreast = new BreastModelBox(64, 64, 16, 17, -4F, 0.0F, 0F, 4, 5, 4, 0.0F, false); rBreast = new BreastModelBox(64, 64, 20, 17, 0, 0.0F, 0F, 4, 5, 4, 0.0F, false); } private @Nullable RenderLayer getRenderLayer(T entity) { - boolean bodyVisible = !entity.isInvisible(); - boolean translucent = !bodyVisible && !entity.isInvisibleTo(MinecraftClient.getInstance().player); - Identifier texture = getTexture(entity); - if(translucent) { - return RenderLayer.getItemEntityTranslucentCull(texture); - } else if(bodyVisible) { - return RenderLayer.getEntityTranslucent(texture); - } else if(entity.isGlowing()) { - return RenderLayer.getOutline(texture); + if(context instanceof LivingEntityRenderer renderer) { + MinecraftClient client = MinecraftClient.getInstance(); + boolean bodyVisible = !entity.isInvisible(); + boolean translucent = !bodyVisible && client.player != null && !entity.isInvisibleTo(client.player); + boolean glowing = client.hasOutline(entity); + return renderer.getRenderLayer(entity, bodyVisible, translucent, glowing); } - return null; + throw new IllegalStateException("context renderer is not a LivingEntityRenderer"); } @Override @@ -99,7 +97,7 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume float limbDistance, float partialTicks, float animationProgress, float headYaw, float headPitch) { MinecraftClient client = MinecraftClient.getInstance(); if(client.player == null) { - // we're currently in a menu, give up rendering before we crash the game + // we're currently in a menu; we won't have any data loaded to begin with, so just give up early return; } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index e86dd994..a8482792 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -36,6 +36,7 @@ "mixins": [ "wildfire_gender.mixins.json" ], + "accessWidener": "wildfire_gender.accesswidener", "depends": { "fabricloader": ">=0.15", "fabric-networking-api-v1": "*", diff --git a/src/main/resources/wildfire_gender.accesswidener b/src/main/resources/wildfire_gender.accesswidener new file mode 100644 index 00000000..2d8c8acf --- /dev/null +++ b/src/main/resources/wildfire_gender.accesswidener @@ -0,0 +1,3 @@ +accessWidener v2 named + +accessible method net/minecraft/client/render/entity/LivingEntityRenderer getRenderLayer (Lnet/minecraft/entity/LivingEntity;ZZZ)Lnet/minecraft/client/render/RenderLayer; From 041d0c99b8bbfd2a81951e6d54fe985d22f399b1 Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 10 Mar 2024 17:34:58 -0600 Subject: [PATCH 073/238] fix rendering on small armor stands --- .../wildfire/main/entitydata/EntityConfig.java | 7 +------ .../com/wildfire/render/GenderArmorLayer.java | 4 ++-- .../java/com/wildfire/render/GenderLayer.java | 17 ++++++++++------- .../resources/wildfire_gender.accesswidener | 2 ++ 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java index 275ebb0b..f7efe925 100644 --- a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java +++ b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java @@ -108,17 +108,12 @@ public void readFromStack(@NotNull ItemStack chestplate) { /** * Get the configuration for a given entity * - * @return {@link EntityConfig}, {@link PlayerConfig} if given a {@link PlayerEntity player}, - * or {@code null} if given a baby entity + * @return The relevant {@link EntityConfig}, or {@link PlayerConfig} if given a {@link PlayerEntity player} */ public static @Nullable EntityConfig getEntity(@NotNull LivingEntity entity) { if(entity instanceof PlayerEntity) { return WildfireGender.getPlayerById(entity.getUuid()); } - if(entity.isBaby()) { - // rendering breaks quite spectacularly on baby mobs, so just immediately give up - return null; - } return ENTITY_CACHE.computeIfAbsent(entity.getUuid(), EntityConfig::new); } diff --git a/src/main/java/com/wildfire/render/GenderArmorLayer.java b/src/main/java/com/wildfire/render/GenderArmorLayer.java index 323179e3..bc9186dc 100644 --- a/src/main/java/com/wildfire/render/GenderArmorLayer.java +++ b/src/main/java/com/wildfire/render/GenderArmorLayer.java @@ -132,8 +132,8 @@ protected void resizeBox(float breastSize) { } @Override - protected void setupTransformations(T entity, ModelPart body, MatrixStack matrixStack, BreastSide side) { - super.setupTransformations(entity, body, matrixStack, side); + protected void setupTransformations(T entity, M model, MatrixStack matrixStack, BreastSide side) { + super.setupTransformations(entity, model, matrixStack, side); if((entity instanceof AbstractClientPlayerEntity player && player.isPartVisible(PlayerModelPart.JACKET)) || (entity instanceof ArmorStandEntity && entityConfig.hasJacketLayer())) { matrixStack.translate(0, 0, -0.015f); diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index 7b9e785e..0838f580 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -123,10 +123,6 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") protected boolean setupRender(T entity, EntityConfig entityConfig, float partialTicks) { - // Rendering breaks quite spectacularly on baby mobs, so just immediately give up before we even - // attempt rendering on such an entity. - if(entity.isBaby()) return false; - armorStack = entity.getEquippedStack(EquipmentSlot.CHEST); //Note: When the stack is empty the helper will fall back to an implementation that returns the proper data genderArmor = WildfireHelper.getArmorConfig(armorStack); @@ -199,7 +195,14 @@ protected void resizeBox(float breastSize) { } } - protected void setupTransformations(T entity, ModelPart body, MatrixStack matrixStack, BreastSide side) { + protected void setupTransformations(T entity, M model, MatrixStack matrixStack, BreastSide side) { + if(entity.isBaby()) { + float f1 = 1f / model.invertedChildBodyScale; + matrixStack.scale(f1, f1, f1); + matrixStack.translate(0f, model.childBodyYOffset / 16f, 0f); + } + + ModelPart body = model.body; matrixStack.translate(body.pivotX * 0.0625f, body.pivotY * 0.0625f, body.pivotZ * 0.0625f); if(body.roll != 0.0F) { matrixStack.multiply(new Quaternionf().rotationXYZ(0f, 0f, body.roll)); @@ -273,7 +276,7 @@ private void renderBreast(T entity, MatrixStack matrixStack, VertexConsumerProvi protected void renderSides(T entity, M model, MatrixStack matrixStack, Consumer renderer) { matrixStack.push(); try { - setupTransformations(entity, model.body, matrixStack, BreastSide.LEFT); + setupTransformations(entity, model, matrixStack, BreastSide.LEFT); renderer.accept(BreastSide.LEFT); } finally { matrixStack.pop(); @@ -281,7 +284,7 @@ protected void renderSides(T entity, M model, MatrixStack matrixStack, Consumer< matrixStack.push(); try { - setupTransformations(entity, model.body, matrixStack, BreastSide.RIGHT); + setupTransformations(entity, model, matrixStack, BreastSide.RIGHT); renderer.accept(BreastSide.RIGHT); } finally { matrixStack.pop(); diff --git a/src/main/resources/wildfire_gender.accesswidener b/src/main/resources/wildfire_gender.accesswidener index 2d8c8acf..0a01f901 100644 --- a/src/main/resources/wildfire_gender.accesswidener +++ b/src/main/resources/wildfire_gender.accesswidener @@ -1,3 +1,5 @@ accessWidener v2 named accessible method net/minecraft/client/render/entity/LivingEntityRenderer getRenderLayer (Lnet/minecraft/entity/LivingEntity;ZZZ)Lnet/minecraft/client/render/RenderLayer; +accessible field net/minecraft/client/render/entity/model/AnimalModel invertedChildBodyScale F +accessible field net/minecraft/client/render/entity/model/AnimalModel childBodyYOffset F From 56894b6f0c0fcaf451b5d6667351aefb34885249 Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 10 Mar 2024 17:39:42 -0600 Subject: [PATCH 074/238] prevent the player from overlapping with the gui this is primarily aimed at cosmetics from mods (like essential) which are sufficiently wide enough to overlap with the GUI, making it harder to read our button/slider labels, if not impossible. --- .../gui/screen/WardrobeBrowserScreen.java | 27 ++++++++----------- .../WildfireBreastCustomizationScreen.java | 19 ++++++------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index f5852c74..7f9787fa 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -26,23 +26,16 @@ import com.wildfire.gui.WildfireButton; import com.wildfire.main.entitydata.PlayerConfig; -import com.mojang.blaze3d.systems.RenderSystem; import com.wildfire.main.WildfireHelper; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.render.DiffuseLighting; -import net.minecraft.client.render.VertexConsumerProvider; -import net.minecraft.client.render.entity.EntityRenderDispatcher; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; -import org.joml.Quaternionf; @Environment(EnvType.CLIENT) public class WardrobeBrowserScreen extends BaseWildfireScreen { @@ -96,6 +89,17 @@ public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delt super.renderBackground(ctx, mouseX, mouseY, delta); Identifier backgroundTexture = getPlayer().getGender().canHaveBreasts() ? BACKGROUND_FEMALE : BACKGROUND; ctx.drawTexture(backgroundTexture, (this.width - 248) / 2, (this.height - 134) / 2, 0, 0, 248, 156); + + if(client != null && client.world != null) { + int xP = this.width / 2 - 82; + int yP = this.height / 2 + 40; + PlayerEntity ent = client.world.getPlayerByUuid(this.playerUUID); + if(ent != null) { + ctx.enableScissor(xP - 35, yP - 95, xP + 35, yP + 7); + drawEntityOnScreen(ctx, xP, yP, 45, (xP - mouseX), (yP - 76 - mouseY), ent); + ctx.disableScissor(); + } + } } @Override @@ -105,15 +109,6 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { int y = this.height / 2; ctx.drawText(textRenderer, title, x - 118, y - 62, 4473924, false); - if(client != null && client.world != null) { - int xP = this.width / 2 - 82; - int yP = this.height / 2 + 40; - PlayerEntity ent = client.world.getPlayerByUuid(this.playerUUID); - if(ent != null) { - drawEntityOnScreen(ctx, xP, yP, 45, (xP - mouseX), (yP - 76 - mouseY), ent); - } - } - if(client != null && client.player != null) { boolean withCreator = client.player.networkHandler.getPlayerList().stream() .anyMatch((player) -> player.getProfile().getId().equals(CREATOR_UUID)); diff --git a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java index e70dc9ce..37acefd5 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java @@ -18,7 +18,6 @@ package com.wildfire.gui.screen; -import com.mojang.blaze3d.systems.RenderSystem; import com.wildfire.gui.WildfireBreastPresetList; import com.wildfire.gui.WildfireButton; import com.wildfire.gui.WildfireSlider; @@ -180,6 +179,16 @@ public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delt if(currentTab == 1) { ctx.fill(PRESET_LIST.getX(), PRESET_LIST.getY(), PRESET_LIST.getRight(), PRESET_LIST.getBottom(), 0x55000000); } + + @SuppressWarnings("DataFlowIssue") + PlayerEntity ent = client.world.getPlayerByUuid(this.playerUUID); + if(ent != null) { + int xP = this.width / 2 - 102; + int yP = this.height / 2 + 275; + ctx.enableScissor(this.width / 2 - 235, this.height / 2 - 150, this.width / 2 + 25, yP + 35); + drawEntityOnScreen(ctx, xP, yP, 200, -20, -20, ent); + ctx.disableScissor(); + } } @Override @@ -188,14 +197,6 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { updatePresetTab(); super.render(ctx, mouseX, mouseY, delta); - RenderSystem.setShaderColor(1f, 1.0F, 1.0F, 1.0F); - - int xP = this.width / 2 - 102; - int yP = this.height / 2 + 275; - PlayerEntity ent = client.world.getPlayerByUuid(this.playerUUID); - if(ent != null) { - drawEntityOnScreen(ctx, xP, yP, 200, -20, -20, ent); - } int x = this.width / 2; int y = this.height / 2; From 8cef95d2f96da2f345b80e9fb82aa19f841288d9 Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 10 Mar 2024 17:56:45 -0600 Subject: [PATCH 075/238] suppress physics on armor stands --- .../java/com/wildfire/physics/BreastPhysics.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index 09d540d6..5bb9c490 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -23,7 +23,6 @@ import com.wildfire.main.WildfireHelper; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; -import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.entity.EntityPose; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.decoration.ArmorStandEntity; @@ -62,9 +61,18 @@ public BreastPhysics(EntityConfig entityConfig) { // as such, the best we can get here is marking this method as such. @Environment(EnvType.CLIENT) public void update(LivingEntity entity, IGenderArmor armor) { - if(entity instanceof ArmorStandEntity && !armor.armorStandsCopySettings()) { - // optimization: skip physics on armor stands that either don't have a chestplate, - // or have a chestplate we wouldn't copy player settings to + // always suppress the full physics calculations on armor stands + if(entity instanceof ArmorStandEntity) { + if(entityConfig.getGender().canHaveBreasts()) { + this.breastSize = entityConfig.getBustSize(); + if(!entityConfig.getArmorPhysicsOverride()) { + float tightness = MathHelper.clamp(armor.tightness(), 0, 1); + this.breastSize *= 1 - 0.15F * tightness; + } + this.preBreastSize = this.breastSize; + } else { + this.breastSize = 0f; + } return; } From 5c47fa731bb4fef2daca3f4c6049411264aef20e Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 11 Mar 2024 16:24:46 -0600 Subject: [PATCH 076/238] enforce static contexts for classes intended to be used as such --- .../wildfire/main/WildfireEventHandler.java | 6 +++++- .../com/wildfire/main/WildfireHelper.java | 6 +++++- .../com/wildfire/main/WildfireSounds.java | 8 ++++++-- .../main/networking/WildfireSync.java | 19 +++++++++++-------- .../render/WildfireModelRenderer.java | 5 ++++- 5 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index 127aa4a1..41202ba8 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -41,7 +41,11 @@ import java.util.UUID; @Environment(EnvType.CLIENT) -public class WildfireEventHandler { +public final class WildfireEventHandler { + private WildfireEventHandler() { + throw new UnsupportedOperationException(); + } + public static final KeyBinding toggleEditGUI = KeyBindingHelper.registerKeyBinding( new KeyBinding("key.wildfire_gender.gender_menu", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_G, "category.wildfire_gender.generic")); private static long timer = 0; diff --git a/src/main/java/com/wildfire/main/WildfireHelper.java b/src/main/java/com/wildfire/main/WildfireHelper.java index a942c439..7ad99a63 100644 --- a/src/main/java/com/wildfire/main/WildfireHelper.java +++ b/src/main/java/com/wildfire/main/WildfireHelper.java @@ -40,7 +40,11 @@ import java.util.concurrent.ThreadLocalRandom; import java.util.function.Function; -public class WildfireHelper { +public final class WildfireHelper { + private WildfireHelper() { + throw new UnsupportedOperationException(); + } + public static int randInt(int min, int max) { return ThreadLocalRandom.current().nextInt(min, max + 1); } diff --git a/src/main/java/com/wildfire/main/WildfireSounds.java b/src/main/java/com/wildfire/main/WildfireSounds.java index 6583e978..47ce0635 100644 --- a/src/main/java/com/wildfire/main/WildfireSounds.java +++ b/src/main/java/com/wildfire/main/WildfireSounds.java @@ -23,10 +23,14 @@ import net.minecraft.sound.SoundEvent; import net.minecraft.util.Identifier; -public class WildfireSounds { +public final class WildfireSounds { + private WildfireSounds() { + throw new UnsupportedOperationException(); + } + public static final SoundEvent FEMALE_HURT = SoundEvent.of(new Identifier(WildfireGender.MODID, "female_hurt")); - protected static void register() { + static void register() { Registry.register(Registries.SOUND_EVENT, FEMALE_HURT.getId(), FEMALE_HURT); } } diff --git a/src/main/java/com/wildfire/main/networking/WildfireSync.java b/src/main/java/com/wildfire/main/networking/WildfireSync.java index 828658b1..93fcf988 100644 --- a/src/main/java/com/wildfire/main/networking/WildfireSync.java +++ b/src/main/java/com/wildfire/main/networking/WildfireSync.java @@ -29,8 +29,13 @@ import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.minecraft.server.network.ServerPlayerEntity; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +public final class WildfireSync { + private WildfireSync() { + throw new UnsupportedOperationException(); + } -public class WildfireSync { @ApiStatus.Internal public static void register() { // note that each packet has to be registered on both sides for receiving and sending, regardless @@ -60,8 +65,8 @@ public static void registerClient() { * @param toSync The {@link ServerPlayerEntity player} to sync * @param playerConfig The {@link PlayerConfig configuration} for the target player */ - public static void sendToAllClients(ServerPlayerEntity toSync, PlayerConfig playerConfig) { - if(playerConfig == null || toSync.getServer() == null) return; + public static void sendToAllClients(@NotNull ServerPlayerEntity toSync, @NotNull PlayerConfig playerConfig) { + if(toSync.getServer() == null) return; PlayerLookup.tracking(toSync).stream() .filter(player -> !player.equals(toSync)) @@ -75,7 +80,7 @@ public static void sendToAllClients(ServerPlayerEntity toSync, PlayerConfig play * @param sendTo The {@link ServerPlayerEntity player} to send the sync to * @param toSync The {@link PlayerConfig configuration} for the player being synced */ - public static void sendToClient(ServerPlayerEntity sendTo, PlayerConfig toSync) { + public static void sendToClient(@NotNull ServerPlayerEntity sendTo, @NotNull PlayerConfig toSync) { if(ClientboundSyncPacket.canSend(sendTo)) { ServerPlayNetworking.send(sendTo, new ClientboundSyncPacket(toSync)); } @@ -87,10 +92,8 @@ public static void sendToClient(ServerPlayerEntity sendTo, PlayerConfig toSync) * @param plr The {@link PlayerConfig configuration} for the client player */ @Environment(EnvType.CLIENT) - public static void sendToServer(PlayerConfig plr) { - if(plr == null || !plr.needsSync || !ServerboundSyncPacket.canSend()) { - return; - } + public static void sendToServer(@NotNull PlayerConfig plr) { + if(!plr.needsSync || !ServerboundSyncPacket.canSend()) return; ClientPlayNetworking.send(new ServerboundSyncPacket(plr)); plr.needsSync = false; diff --git a/src/main/java/com/wildfire/render/WildfireModelRenderer.java b/src/main/java/com/wildfire/render/WildfireModelRenderer.java index 63f6388b..8cbc6a25 100644 --- a/src/main/java/com/wildfire/render/WildfireModelRenderer.java +++ b/src/main/java/com/wildfire/render/WildfireModelRenderer.java @@ -24,7 +24,10 @@ import org.joml.Vector3f; @Environment(EnvType.CLIENT) -public class WildfireModelRenderer { +public final class WildfireModelRenderer { + private WildfireModelRenderer() { + throw new UnsupportedOperationException(); + } public static class ModelBox { public final WildfireModelRenderer.TexturedQuad[] quads; From 7208c65e394b5f1ae09559f1a3b39a96d2e404ae Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 11 Mar 2024 16:49:31 -0600 Subject: [PATCH 077/238] refactor WildfireEventHandler to be a common class --- .../wildfire/main/WildfireEventHandler.java | 91 +++++++++++++++++-- .../com/wildfire/main/WildfireGender.java | 2 + .../wildfire/main/WildfireGenderClient.java | 2 + 3 files changed, 85 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index 41202ba8..ac0377a0 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -29,35 +29,69 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.networking.v1.EntityTrackingEvents; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.option.KeyBinding; -import net.minecraft.client.util.InputUtil; import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Util; import net.minecraft.world.World; import org.lwjgl.glfw.GLFW; import java.util.UUID; -@Environment(EnvType.CLIENT) public final class WildfireEventHandler { private WildfireEventHandler() { throw new UnsupportedOperationException(); } - public static final KeyBinding toggleEditGUI = KeyBindingHelper.registerKeyBinding( - new KeyBinding("key.wildfire_gender.gender_menu", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_G, "category.wildfire_gender.generic")); - private static long timer = 0; + private static final KeyBinding CONFIG_KEYBIND; + private static int timer = 0; + static { + if(FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { + // this has to be wrapped in a lambda to ensure that a dedicated server won't crash during startup + // while executing this static block + CONFIG_KEYBIND = Util.make(() -> { + KeyBinding keybind = new KeyBinding("key.wildfire_gender.gender_menu", GLFW.GLFW_KEY_G, "category.wildfire_gender.generic"); + KeyBindingHelper.registerKeyBinding(keybind); + return keybind; + }); + } else { + CONFIG_KEYBIND = null; + } + } + + /** + * Register all events applicable the server-side for both a dedicated server and singleplayer + */ + public static void registerCommonEvents() { + EntityTrackingEvents.START_TRACKING.register(WildfireEventHandler::onBeginTracking); + ServerPlayConnectionEvents.DISCONNECT.register(WildfireEventHandler::playerDisconnected); + } + + /** + * Register all client-side events + */ + @Environment(EnvType.CLIENT) public static void registerClientEvents() { ClientEntityEvents.ENTITY_LOAD.register(WildfireEventHandler::onEntityLoad); ClientEntityEvents.ENTITY_UNLOAD.register(WildfireEventHandler::onEntityUnload); ClientTickEvents.END_CLIENT_TICK.register(WildfireEventHandler::onClientTick); - ClientPlayConnectionEvents.DISCONNECT.register(WildfireEventHandler::disconnect); - WildfireSync.registerClient(); + ClientPlayConnectionEvents.DISCONNECT.register(WildfireEventHandler::clientDisconnect); } + /** + * Load data for a loaded player if applicable + */ + @Environment(EnvType.CLIENT) private static void onEntityLoad(Entity entity, World world) { if(!world.isClient() || MinecraftClient.getInstance().player == null) return; if(entity instanceof AbstractClientPlayerEntity plr) { @@ -67,11 +101,21 @@ private static void onEntityLoad(Entity entity, World world) { } } + /** + * Remove (non-player) entities from the cache when they're unloaded + */ + @Environment(EnvType.CLIENT) private static void onEntityUnload(Entity entity, World world) { - // note that we don't attempt to unload players; they're instead only ever unloaded once we leave a world + // note that we don't attempt to unload players; they're instead only ever unloaded once we leave a world, + // or once they disconnect EntityConfig.ENTITY_CACHE.remove(entity.getUuid()); } + /** + * Perform various actions that should happen once per client tick, such as syncing client player settings + * to the server. + */ + @Environment(EnvType.CLIENT) private static void onClientTick(MinecraftClient client) { if(client.world == null || client.player == null) return; @@ -83,13 +127,40 @@ private static void onClientTick(MinecraftClient client) { if(aPlr != null) WildfireSync.sendToServer(aPlr); } - if(toggleEditGUI.wasPressed() && client.currentScreen == null) { + if(CONFIG_KEYBIND.wasPressed() && client.currentScreen == null) { client.setScreen(new WardrobeBrowserScreen(null, client.player.getUuid())); } } - private static void disconnect(ClientPlayNetworkHandler networkHandler, MinecraftClient client) { + /** + * Clears all caches when the client player disconnects from a server/closes a singleplayer world + */ + @Environment(EnvType.CLIENT) + private static void clientDisconnect(ClientPlayNetworkHandler networkHandler, MinecraftClient client) { WildfireGender.PLAYER_CACHE.clear(); EntityConfig.ENTITY_CACHE.clear(); } + + /** + * Removes a disconnecting player from the cache on a server + */ + private static void playerDisconnected(ServerPlayNetworkHandler handler, MinecraftServer server) { + WildfireGender.PLAYER_CACHE.remove(handler.getPlayer().getUuid()); + } + + /** + * Send a sync packet when a player enters the render distance of another player + */ + private static void onBeginTracking(Entity tracked, ServerPlayerEntity syncTo) { + if(tracked instanceof PlayerEntity toSync) { + PlayerConfig genderToSync = WildfireGender.getPlayerById(toSync.getUuid()); + if(genderToSync == null) return; + // Note that we intentionally don't check if we've previously synced a player with this code path; + // because we use entity tracking to sync, it's entirely possible that one player would leave the + // tracking distance of another, change their settings, and then re-enter their tracking distance; + // we wouldn't sync while they're out of tracking distance, and as such, their settings would be out + // of sync until they relog. + WildfireSync.sendToClient(syncTo, genderToSync); + } + } } diff --git a/src/main/java/com/wildfire/main/WildfireGender.java b/src/main/java/com/wildfire/main/WildfireGender.java index b78cd763..c836c53a 100644 --- a/src/main/java/com/wildfire/main/WildfireGender.java +++ b/src/main/java/com/wildfire/main/WildfireGender.java @@ -27,6 +27,7 @@ import com.wildfire.main.networking.WildfireSync; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.networking.v1.EntityTrackingEvents; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.server.network.ServerPlayerEntity; @@ -42,6 +43,7 @@ public class WildfireGender implements ModInitializer { @Override public void onInitialize() { WildfireSync.register(); + WildfireEventHandler.registerCommonEvents(); EntityTrackingEvents.START_TRACKING.register(this::onBeginTracking); } diff --git a/src/main/java/com/wildfire/main/WildfireGenderClient.java b/src/main/java/com/wildfire/main/WildfireGenderClient.java index c023febb..b845fcd5 100644 --- a/src/main/java/com/wildfire/main/WildfireGenderClient.java +++ b/src/main/java/com/wildfire/main/WildfireGenderClient.java @@ -19,6 +19,7 @@ package com.wildfire.main; import com.wildfire.main.entitydata.PlayerConfig; +import com.wildfire.main.networking.WildfireSync; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -33,6 +34,7 @@ public class WildfireGenderClient implements ClientModInitializer { @Override public void onInitializeClient() { WildfireSounds.register(); + WildfireSync.registerClient(); WildfireEventHandler.registerClientEvents(); } From 06d50258c627d2acf050ca8adc4f6964ebd2c6c7 Mon Sep 17 00:00:00 2001 From: celeste Date: Wed, 10 Apr 2024 16:09:46 -0600 Subject: [PATCH 078/238] move gui utility methods to their own class --- src/main/java/com/wildfire/gui/GuiUtils.java | 95 +++++++++++++++++++ .../java/com/wildfire/gui/WildfireButton.java | 3 +- .../java/com/wildfire/gui/WildfireSlider.java | 3 +- .../gui/screen/BaseWildfireScreen.java | 31 +----- .../gui/screen/WardrobeBrowserScreen.java | 8 +- .../WildfireBreastCustomizationScreen.java | 3 +- .../WildfireCharacterSettingsScreen.java | 6 +- .../com/wildfire/main/WildfireHelper.java | 34 ------- 8 files changed, 107 insertions(+), 76 deletions(-) create mode 100644 src/main/java/com/wildfire/gui/GuiUtils.java diff --git a/src/main/java/com/wildfire/gui/GuiUtils.java b/src/main/java/com/wildfire/gui/GuiUtils.java new file mode 100644 index 00000000..db819ff6 --- /dev/null +++ b/src/main/java/com/wildfire/gui/GuiUtils.java @@ -0,0 +1,95 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.gui; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.ingame.InventoryScreen; +import net.minecraft.entity.LivingEntity; +import net.minecraft.text.Text; +import net.minecraft.util.Util; +import net.minecraft.util.math.MathHelper; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +import java.util.Objects; + +@Environment(EnvType.CLIENT) +public final class GuiUtils { + private GuiUtils() { + throw new UnsupportedOperationException(); + } + + // Reimplementation of DrawContext#drawCenteredTextWithShadow but with the text shadow removed + public static void drawCenteredText(DrawContext ctx, TextRenderer textRenderer, Text text, int x, int y, int color) { + int centeredX = x - textRenderer.getWidth(text) / 2; + ctx.drawText(textRenderer, text, centeredX, y, color, false); + } + + // Reimplementation of ClickableWidget#drawScrollableText but with the text shadow removed + public static void drawScrollableTextWithoutShadow(DrawContext context, TextRenderer textRenderer, Text text, int left, int top, int right, int bottom, int color) { + int i = textRenderer.getWidth(text); + int var10000 = top + bottom; + Objects.requireNonNull(textRenderer); + int j = (var10000 - 9) / 2 + 1; + int k = right - left; + if (i > k) { + int l = i - k; + double d = (double) Util.getMeasuringTimeMs() / 1000.0; + double e = Math.max((double)l * 0.5, 3.0); + double f = Math.sin(1.5707963267948966 * Math.cos(6.283185307179586 * d / e)) / 2.0 + 0.5; + double g = MathHelper.lerp(f, 0.0, l); + context.enableScissor(left, top, right, bottom); + context.drawText(textRenderer, text, left - (int)g, j, color, false); + context.disableScissor(); + } else { + drawCenteredText(context, textRenderer, text, (left + right) / 2, j, color); + } + } + + // Reimplementation of InventoryScreen#drawEntity, intended to allow for applying our own scissor calls, and + // accepting an origin point instead of X/Y bounds + public static void drawEntityOnScreen(DrawContext ctx, int x, int y, int size, float mouseX, float mouseY, LivingEntity entity) { + float i = (float) Math.atan(mouseX / 40.0F); + float j = (float) Math.atan(mouseY / 40.0F); + Quaternionf quaternionf = new Quaternionf().rotateZ((float) Math.PI); + Quaternionf quaternionf2 = new Quaternionf().rotateX(j * 20.0F * (float) (Math.PI / 180.0)); + quaternionf.mul(quaternionf2); + float k = entity.bodyYaw; + float l = entity.getYaw(); + float m = entity.getPitch(); + float n = entity.prevHeadYaw; + float o = entity.headYaw; + entity.bodyYaw = 180.0F + i * 20.0F; + entity.setYaw(180.0F + i * 40.0F); + entity.setPitch(-j * 20.0F); + entity.headYaw = entity.getYaw(); + entity.prevHeadYaw = entity.getYaw(); + // divide by entity scale to ensure that we always draw the entity at a consistent size + float renderSize = size / entity.getScale(); + InventoryScreen.drawEntity(ctx, x, y, renderSize, new Vector3f(), quaternionf, quaternionf2, entity); + entity.bodyYaw = k; + entity.setYaw(l); + entity.setPitch(m); + entity.prevHeadYaw = n; + entity.headYaw = o; + } +} diff --git a/src/main/java/com/wildfire/gui/WildfireButton.java b/src/main/java/com/wildfire/gui/WildfireButton.java index 93480c5b..369d31db 100644 --- a/src/main/java/com/wildfire/gui/WildfireButton.java +++ b/src/main/java/com/wildfire/gui/WildfireButton.java @@ -19,7 +19,6 @@ package com.wildfire.gui; import com.mojang.blaze3d.systems.RenderSystem; -import com.wildfire.main.WildfireHelper; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.MinecraftClient; @@ -58,7 +57,7 @@ protected void renderWidget(DrawContext ctx, int mouseX, int mouseY, float parti int textColor = active ? 0xFFFFFF : 0x666666; int i = this.getX() + 2; int j = this.getX() + this.getWidth() - 2; - WildfireHelper.drawScrollableTextWithoutShadow(ctx, font, this.getMessage(), i, this.getY(), j, this.getY() + this.getHeight(), textColor); + GuiUtils.drawScrollableTextWithoutShadow(ctx, font, this.getMessage(), i, this.getY(), j, this.getY() + this.getHeight(), textColor); RenderSystem.setShaderColor(1f, 1f, 1f, 1f); } diff --git a/src/main/java/com/wildfire/gui/WildfireSlider.java b/src/main/java/com/wildfire/gui/WildfireSlider.java index 74398a32..507ae57e 100644 --- a/src/main/java/com/wildfire/gui/WildfireSlider.java +++ b/src/main/java/com/wildfire/gui/WildfireSlider.java @@ -19,7 +19,6 @@ package com.wildfire.gui; import com.mojang.blaze3d.systems.RenderSystem; -import com.wildfire.main.WildfireHelper; import com.wildfire.main.config.FloatConfigKey; import it.unimi.dsi.fastutil.floats.Float2ObjectFunction; import it.unimi.dsi.fastutil.floats.FloatConsumer; @@ -123,7 +122,7 @@ protected void renderWidget(DrawContext ctx, int mouseX, int mouseY, float delta TextRenderer font = MinecraftClient.getInstance().textRenderer; int i = this.getX() + 2; int j = this.getX() + this.getWidth() - 2; - WildfireHelper.drawScrollableTextWithoutShadow(ctx, font, this.getMessage(), i, this.getY(), j, this.getY() + this.getHeight(), this.hovered || changed ? 0xFFFF55 : 0xFFFFFF); + GuiUtils.drawScrollableTextWithoutShadow(ctx, font, this.getMessage(), i, this.getY(), j, this.getY() + this.getHeight(), this.hovered || changed ? 0xFFFF55 : 0xFFFFFF); } } diff --git a/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java b/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java index 90013469..c254072d 100644 --- a/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java +++ b/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java @@ -52,33 +52,4 @@ public PlayerConfig getPlayer() { public boolean shouldPause() { return false; } - - // this is adapted from InventoryScreen#drawEntity to allow for applying our own scissor calls, and instead - // simply accept an origin point to render from - public static void drawEntityOnScreen(DrawContext ctx, int x, int y, int size, float mouseX, float mouseY, LivingEntity entity) { - float i = (float) Math.atan(mouseX / 40.0F); - float j = (float) Math.atan(mouseY / 40.0F); - Quaternionf quaternionf = new Quaternionf().rotateZ((float) Math.PI); - Quaternionf quaternionf2 = new Quaternionf().rotateX(j * 20.0F * (float) (Math.PI / 180.0)); - quaternionf.mul(quaternionf2); - float k = entity.bodyYaw; - float l = entity.getYaw(); - float m = entity.getPitch(); - float n = entity.prevHeadYaw; - float o = entity.headYaw; - entity.bodyYaw = 180.0F + i * 20.0F; - entity.setYaw(180.0F + i * 40.0F); - entity.setPitch(-j * 20.0F); - entity.headYaw = entity.getYaw(); - entity.prevHeadYaw = entity.getYaw(); - // divide by entity scale to ensure that we always draw the entity at a constant size, avoiding the entity - // being either too small or far too large for the gui - float renderSize = size / entity.getScale(); - InventoryScreen.drawEntity(ctx, x, y, renderSize, new Vector3f(0f), quaternionf, quaternionf2, entity); - entity.bodyYaw = k; - entity.setYaw(l); - entity.setPitch(m); - entity.prevHeadYaw = n; - entity.headYaw = o; - } -} \ No newline at end of file +} diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index 7f9787fa..f79fe2da 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -18,6 +18,7 @@ package com.wildfire.gui.screen; +import com.wildfire.gui.GuiUtils; import com.wildfire.main.Gender; import com.wildfire.main.WildfireGender; @@ -26,7 +27,6 @@ import com.wildfire.gui.WildfireButton; import com.wildfire.main.entitydata.PlayerConfig; -import com.wildfire.main.WildfireHelper; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.MinecraftClient; @@ -95,8 +95,8 @@ public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delt int yP = this.height / 2 + 40; PlayerEntity ent = client.world.getPlayerByUuid(this.playerUUID); if(ent != null) { - ctx.enableScissor(xP - 35, yP - 95, xP + 35, yP + 7); - drawEntityOnScreen(ctx, xP, yP, 45, (xP - mouseX), (yP - 76 - mouseY), ent); + ctx.enableScissor(xP - 35, yP - 93, xP + 35, yP + 6); + GuiUtils.drawEntityOnScreen(ctx, xP, yP, 45, (xP - mouseX), (yP - 76 - mouseY), ent); ctx.disableScissor(); } } @@ -116,7 +116,7 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { int creatorY = y + 65; // move down so we don't overlap with the breast cancer awareness month banner if(isBreastCancerAwarenessMonth) creatorY += 30; - WildfireHelper.drawCenteredText(ctx, this.textRenderer, Text.translatable("wildfire_gender.label.with_creator"), this.width / 2, creatorY, 0xFF00FF); + GuiUtils.drawCenteredText(ctx, this.textRenderer, Text.translatable("wildfire_gender.label.with_creator"), this.width / 2, creatorY, 0xFF00FF); } } diff --git a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java index 37acefd5..1e3e59b1 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java @@ -18,6 +18,7 @@ package com.wildfire.gui.screen; +import com.wildfire.gui.GuiUtils; import com.wildfire.gui.WildfireBreastPresetList; import com.wildfire.gui.WildfireButton; import com.wildfire.gui.WildfireSlider; @@ -186,7 +187,7 @@ public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delt int xP = this.width / 2 - 102; int yP = this.height / 2 + 275; ctx.enableScissor(this.width / 2 - 235, this.height / 2 - 150, this.width / 2 + 25, yP + 35); - drawEntityOnScreen(ctx, xP, yP, 200, -20, -20, ent); + GuiUtils.drawEntityOnScreen(ctx, xP, yP, 200, -20, -20, ent); ctx.disableScissor(); } } diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java index cc4025f1..0b4a22b4 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java @@ -18,9 +18,9 @@ package com.wildfire.gui.screen; +import com.wildfire.gui.GuiUtils; import com.wildfire.gui.WildfireSlider; import com.wildfire.main.WildfireGender; -import com.wildfire.main.WildfireHelper; import com.wildfire.main.config.Configuration; import java.util.UUID; @@ -139,11 +139,11 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { int yPos = y - 47; ctx.drawText(textRenderer, getTitle(), x - 79, yPos - 10, 4473924, false); if(plrEntity != null) { - WildfireHelper.drawCenteredText(ctx, this.textRenderer, plrEntity.getDisplayName(), x, yPos - 30, 0xFFFFFF); + GuiUtils.drawCenteredText(ctx, this.textRenderer, plrEntity.getDisplayName(), x, yPos - 30, 0xFFFFFF); } if(bounceWarning) { - WildfireHelper.drawCenteredText(ctx, this.textRenderer, Text.translatable("wildfire_gender.tooltip.bounce_warning").formatted(Formatting.ITALIC), x, y + 90, 0xFF6666); + GuiUtils.drawCenteredText(ctx, this.textRenderer, Text.translatable("wildfire_gender.tooltip.bounce_warning").formatted(Formatting.ITALIC), x, y + 90, 0xFF6666); } } diff --git a/src/main/java/com/wildfire/main/WildfireHelper.java b/src/main/java/com/wildfire/main/WildfireHelper.java index 7ad99a63..0b6238b9 100644 --- a/src/main/java/com/wildfire/main/WildfireHelper.java +++ b/src/main/java/com/wildfire/main/WildfireHelper.java @@ -22,20 +22,13 @@ import com.wildfire.api.WildfireAPI; import com.wildfire.render.armor.SimpleGenderArmor; import com.wildfire.render.armor.EmptyGenderArmor; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gui.DrawContext; import com.wildfire.main.config.FloatConfigKey; import net.minecraft.entity.EquipmentSlot; import net.minecraft.item.*; import net.minecraft.nbt.NbtCompound; import net.minecraft.registry.entry.RegistryEntry; -import net.minecraft.text.Text; -import net.minecraft.util.Util; import net.minecraft.util.math.MathHelper; -import java.util.Objects; import java.util.Optional; import java.util.concurrent.ThreadLocalRandom; import java.util.function.Function; @@ -90,33 +83,6 @@ public static IGenderArmor getArmorConfig(ItemStack stack) { } } - @Environment(EnvType.CLIENT) - public static void drawCenteredText(DrawContext ctx, TextRenderer textRenderer, Text text, int x, int y, int color) { - int centeredX = x - textRenderer.getWidth(text) / 2; - ctx.drawText(textRenderer, text, centeredX, y, color, false); - } - - @Environment(EnvType.CLIENT) - public static void drawScrollableTextWithoutShadow(DrawContext context, TextRenderer textRenderer, Text text, int left, int top, int right, int bottom, int color) { - int i = textRenderer.getWidth(text); - int var10000 = top + bottom; - Objects.requireNonNull(textRenderer); - int j = (var10000 - 9) / 2 + 1; - int k = right - left; - if (i > k) { - int l = i - k; - double d = (double) Util.getMeasuringTimeMs() / 1000.0; - double e = Math.max((double)l * 0.5, 3.0); - double f = Math.sin(1.5707963267948966 * Math.cos(6.283185307179586 * d / e)) / 2.0 + 0.5; - double g = MathHelper.lerp(f, 0.0, (double)l); - context.enableScissor(left, top, right, bottom); - context.drawText(textRenderer, text, left - (int)g, j, color, false); - context.disableScissor(); - } else { - drawCenteredText(context, textRenderer, text, (left + right) / 2, j, color); - } - } - /** * Utility method returning an {@link Optional} containing the requested value from the provided {@link NbtCompound} */ From a907cd5e6c735198cfed3e75231f2438be51afc2 Mon Sep 17 00:00:00 2001 From: celeste Date: Wed, 10 Apr 2024 16:52:07 -0600 Subject: [PATCH 079/238] slightly reword IGenderArmor javadocs this primarly affects the #armorStandsCopySettings() method, making the intended use case of this much clearer. --- .../java/com/wildfire/api/IGenderArmor.java | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/wildfire/api/IGenderArmor.java b/src/main/java/com/wildfire/api/IGenderArmor.java index 4a72d1fd..cac2270e 100644 --- a/src/main/java/com/wildfire/api/IGenderArmor.java +++ b/src/main/java/com/wildfire/api/IGenderArmor.java @@ -35,8 +35,11 @@ default boolean coversBreasts() { } /** - * Determines if this {@link IGenderArmor} should always hide the wearer's breasts when worn even if they have {@code showBreastsInArmor} set to {@code true}. This is - * useful for armors that may have custom rendering that is not compatible with how the breasts render and would just lead to clipping. + *

Determines if this {@link IGenderArmor} should always hide the wearer's breasts when worn even if they have + * {@code showBreastsInArmor} set to {@code true}.

+ * + *

This is intended for armors that may have custom rendering that is not compatible with how breasts render + * and would just lead to clipping or other unintended behavior.

* * @return {@code true} to hide the breasts regardless of what {@code showBreastsInArmor} is set to. * @@ -47,7 +50,8 @@ default boolean alwaysHidesBreasts() { } /** - * The percent of physical resistance this {@link IGenderArmor} provides to the wearer's breasts when calculating the corresponding physics. + * The percent of physical resistance this {@link IGenderArmor} provides to the wearer's breasts when calculating + * the corresponding physics. * * @return Value between {@code 0} (no resistance, full physics) and {@code 1} (total resistance, no physics). * @@ -58,8 +62,8 @@ default float physicsResistance() { } /** - * Value representing how "tight" this {@link IGenderArmor} is. Tightness "compresses" the breasts against the wearer causing the breasts to appear up to {@code 15%} - * smaller. + * Value representing how "tight" this {@link IGenderArmor} is. Tightness "compresses" the breasts against the wearer, + * causing the breasts to appear up to 15% smaller. * * @return Value between {@code 0} (no tightness, no size reduction) and {@code 1} (full tightness, {@code 15%} size reduction). * @@ -70,15 +74,24 @@ default float tightness() { } /** - * Determines whether armor stands should copy the breast settings of the player equipping this chestplate - * onto it. + *

Determines whether armor stands should copy the breast settings of the player equipping this chestplate + * onto it.

+ * + *

If this returns {@code true}, an equipping player's breast settings will be copied onto the + * item stack's {@link net.minecraft.component.DataComponentTypes#CUSTOM_DATA custom NBT data component} + * under the tag {@code WildfireGender}.

* - * @implNote If this returns {@code true}, any time a player equips this chestplate onto an armor stand, their - * breast settings will be copied onto the item stack's NBT under the key {@code WildfireGender}. + *

This is designed for armor types that are metallic in nature, and not armor types that would (realistically) + * be flexible enough to accommodate for a player's breasts on their own (such as Leather and Chain).

* - * @return Defaults to returning {@code true} if this armor {@link #coversBreasts() covers the breasts} + * @return {@code true} to copy the equipping player's breast settings onto this armor type when equipped onto + * armor stands, and render the relevant breast settings on the armor stand. + * + * @implNote Defaults to returning {@code true} if this armor {@link #coversBreasts() covers the breasts} * (and {@link #alwaysHidesBreasts() doesn't hide them}), and {@link #physicsResistance() has * complete physics resistance}. + * + * @see com.wildfire.main.entitydata.BreastDataComponent */ default boolean armorStandsCopySettings() { return !alwaysHidesBreasts() && coversBreasts() && physicsResistance() == 1f; From e90e2a18b38e7bf42566882f719e1bcaf6427284 Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 18 Apr 2024 15:42:19 -0600 Subject: [PATCH 080/238] slightly reduce default bounce multiplier to remove >100% warning 0.34 is shown as 102% in the gui, which results in the unnatural bounce intensity warning, which *probably* shouldn't appear with default values --- src/main/java/com/wildfire/main/config/Configuration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/main/config/Configuration.java b/src/main/java/com/wildfire/main/config/Configuration.java index 930743c5..5eb6ecf3 100644 --- a/src/main/java/com/wildfire/main/config/Configuration.java +++ b/src/main/java/com/wildfire/main/config/Configuration.java @@ -39,7 +39,7 @@ public class Configuration extends AbstractConfiguration { public static final BooleanConfigKey BREAST_PHYSICS = new BooleanConfigKey("breast_physics", true); public static final BooleanConfigKey ARMOR_PHYSICS_OVERRIDE = new BooleanConfigKey("armor_physics_override", false); public static final BooleanConfigKey SHOW_IN_ARMOR = new BooleanConfigKey("show_in_armor", true); - public static final FloatConfigKey BOUNCE_MULTIPLIER = new FloatConfigKey("bounce_multiplier", 0.34F, 0, 0.5f); + public static final FloatConfigKey BOUNCE_MULTIPLIER = new FloatConfigKey("bounce_multiplier", 0.333F, 0, 0.5f); public static final FloatConfigKey FLOPPY_MULTIPLIER = new FloatConfigKey("floppy_multiplier", 0.75F, 0.25f, 1); public Configuration(String cfgName) { From 5844f1046bd4f2669d071baeca00901764e215e4 Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 18 Apr 2024 18:26:00 -0600 Subject: [PATCH 081/238] use the fabric api render layer register event instead of mixins --- .github/workflows/fabric.yml | 2 +- .../wildfire/main/WildfireEventHandler.java | 27 ++++++++++- .../mixins/ArmorStandEntityRendererMixin.java | 45 ------------------ .../wildfire/mixins/PlayerRenderMixin.java | 47 ------------------- .../resources/wildfire_gender.mixins.json | 2 - 5 files changed, 27 insertions(+), 96 deletions(-) delete mode 100644 src/main/java/com/wildfire/mixins/ArmorStandEntityRendererMixin.java delete mode 100644 src/main/java/com/wildfire/mixins/PlayerRenderMixin.java diff --git a/.github/workflows/fabric.yml b/.github/workflows/fabric.yml index 00b23907..053ed99b 100644 --- a/.github/workflows/fabric.yml +++ b/.github/workflows/fabric.yml @@ -12,7 +12,7 @@ jobs: - name: Extract build version information id: ref run: .github/extract_refs.sh - - name: Set up JDK 17 + - name: Setup JDK uses: actions/setup-java@v4 with: distribution: 'temurin' diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index ac0377a0..dcab67b3 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -23,12 +23,15 @@ import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.networking.ServerboundSyncPacket; import com.wildfire.main.networking.WildfireSync; +import com.wildfire.render.GenderArmorLayer; +import com.wildfire.render.GenderLayer; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.client.rendering.v1.LivingEntityFeatureRendererRegistrationCallback; import net.fabricmc.fabric.api.networking.v1.EntityTrackingEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.fabricmc.loader.api.FabricLoader; @@ -36,7 +39,13 @@ import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.render.entity.ArmorStandEntityRenderer; +import net.minecraft.client.render.entity.EntityRendererFactory; +import net.minecraft.client.render.entity.LivingEntityRenderer; +import net.minecraft.client.render.entity.PlayerEntityRenderer; import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayNetworkHandler; @@ -86,6 +95,22 @@ public static void registerClientEvents() { ClientEntityEvents.ENTITY_UNLOAD.register(WildfireEventHandler::onEntityUnload); ClientTickEvents.END_CLIENT_TICK.register(WildfireEventHandler::onClientTick); ClientPlayConnectionEvents.DISCONNECT.register(WildfireEventHandler::clientDisconnect); + LivingEntityFeatureRendererRegistrationCallback.EVENT.register(WildfireEventHandler::registerRenderLayers); + } + + /** + * Attach breast render layers to players and armor stands + */ + @Environment(EnvType.CLIENT) + private static void registerRenderLayers(EntityType entityType, LivingEntityRenderer entityRenderer, + LivingEntityFeatureRendererRegistrationCallback.RegistrationHelper registrationHelper, + EntityRendererFactory.Context context) { + if(entityRenderer instanceof PlayerEntityRenderer playerRenderer) { + registrationHelper.register(new GenderLayer<>(playerRenderer)); + registrationHelper.register(new GenderArmorLayer<>(playerRenderer, context.getModelManager())); + } else if(entityRenderer instanceof ArmorStandEntityRenderer armorStandRenderer) { + registrationHelper.register(new GenderArmorLayer<>(armorStandRenderer, context.getModelManager())); + } } /** @@ -102,7 +127,7 @@ private static void onEntityLoad(Entity entity, World world) { } /** - * Remove (non-player) entities from the cache when they're unloaded + * Remove (non-player) entities from the client cache when they're unloaded */ @Environment(EnvType.CLIENT) private static void onEntityUnload(Entity entity, World world) { diff --git a/src/main/java/com/wildfire/mixins/ArmorStandEntityRendererMixin.java b/src/main/java/com/wildfire/mixins/ArmorStandEntityRendererMixin.java deleted file mode 100644 index d2cf7c4c..00000000 --- a/src/main/java/com/wildfire/mixins/ArmorStandEntityRendererMixin.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -package com.wildfire.mixins; - -import com.wildfire.render.GenderArmorLayer; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.client.render.entity.ArmorStandEntityRenderer; -import net.minecraft.client.render.entity.EntityRendererFactory; -import net.minecraft.client.render.entity.LivingEntityRenderer; -import net.minecraft.client.render.entity.model.BipedEntityModel; -import net.minecraft.entity.decoration.ArmorStandEntity; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Environment(EnvType.CLIENT) -@Mixin(ArmorStandEntityRenderer.class) -public abstract class ArmorStandEntityRendererMixin extends LivingEntityRenderer> { - public ArmorStandEntityRendererMixin(EntityRendererFactory.Context ctx, BipedEntityModel model, float shadow) { - super(ctx, model, shadow); - } - - @Inject(method = "", at = @At("TAIL")) - private void wildfiregender$armorStandBreastArmor(EntityRendererFactory.Context ctx, CallbackInfo ci) { - this.addFeature(new GenderArmorLayer<>(this, ctx.getModelManager())); - } -} diff --git a/src/main/java/com/wildfire/mixins/PlayerRenderMixin.java b/src/main/java/com/wildfire/mixins/PlayerRenderMixin.java deleted file mode 100644 index e7815c9a..00000000 --- a/src/main/java/com/wildfire/mixins/PlayerRenderMixin.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -package com.wildfire.mixins; - -import com.wildfire.render.GenderArmorLayer; -import com.wildfire.render.GenderLayer; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.client.network.AbstractClientPlayerEntity; -import net.minecraft.client.render.entity.EntityRendererFactory; -import net.minecraft.client.render.entity.LivingEntityRenderer; -import net.minecraft.client.render.entity.PlayerEntityRenderer; -import net.minecraft.client.render.entity.model.PlayerEntityModel; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Environment(EnvType.CLIENT) -@Mixin(PlayerEntityRenderer.class) -public abstract class PlayerRenderMixin extends LivingEntityRenderer> { - public PlayerRenderMixin(EntityRendererFactory.Context ctx, PlayerEntityModel model, float shadow) { - super(ctx, model, shadow); - } - - @Inject(method = "", at = @At("TAIL")) - private void wildfiregender$addBreastLayers(EntityRendererFactory.Context ctx, boolean slim, CallbackInfo ci) { - this.addFeature(new GenderLayer<>(this)); - this.addFeature(new GenderArmorLayer<>(this, ctx.getModelManager())); - } -} \ No newline at end of file diff --git a/src/main/resources/wildfire_gender.mixins.json b/src/main/resources/wildfire_gender.mixins.json index 66406add..7c74d197 100644 --- a/src/main/resources/wildfire_gender.mixins.json +++ b/src/main/resources/wildfire_gender.mixins.json @@ -7,8 +7,6 @@ "ArmorStandEntityMixin" ], "client": [ - "ArmorStandEntityRendererMixin", - "PlayerRenderMixin", "BreastPhysicsTickMixin", "LivingEntityMixin" ], From b16757a392c0939d0bf23852837a87cb7b12a753 Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 23 Apr 2024 12:35:39 -0600 Subject: [PATCH 082/238] bump version requirement to full release --- gradle.properties | 8 ++++---- .../java/com/wildfire/main/entitydata/EntityConfig.java | 3 +-- src/main/resources/fabric.mod.json | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/gradle.properties b/gradle.properties index 45b50b62..e437ee57 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,9 +4,9 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://fabricmc.net/develop # Note that this variable does *not* define the minimum required version in fabric.mod.json! -minecraft_version=1.20.5-pre1 -yarn_mappings=1.20.5-pre1+build.3 -loader_version=0.15.9 +minecraft_version=1.20.5 +yarn_mappings=1.20.5+build.1 +loader_version=0.15.10 # Mod Properties mod_version = 3.2.1 @@ -14,4 +14,4 @@ maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod # Dependencies -fabric_version=0.96.15+1.20.5 +fabric_version=0.97.5+1.20.5 diff --git a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java index f7efe925..1bb63cd3 100644 --- a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java +++ b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java @@ -84,8 +84,7 @@ public void readFromStack(@NotNull ItemStack chestplate) { this.fromComponent = null; this.gender = Gender.MALE; return; - } else if(fromComponent != null && fromComponent.nbtComponent() != null - && Objects.equals(fromComponent.nbtComponent(), component)) { + } else if(fromComponent != null && Objects.equals(component, fromComponent.nbtComponent())) { // nothing's changed since the last time we checked, so there's no need to read from the // underlying nbt tag again return; diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index a8482792..bc5fc71f 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -45,7 +45,7 @@ "fabric-rendering-v1": "*", "fabric-resource-loader-v0": "*", "fabric-registry-sync-v0": "*", - "minecraft": ">=1.20.5-alpha.24.14.a <=1.20.5", + "minecraft": ">=1.20.5 <1.21", "java": ">=21" }, "conflicts": { From 5c184f7ef1c711731412fbad5e16057afc29a548 Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 29 Apr 2024 11:21:58 -0600 Subject: [PATCH 083/238] 1.20.6 --- gradle.properties | 6 +++--- src/main/java/com/wildfire/main/WildfireGender.java | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index e437ee57..8a942972 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,8 +4,8 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://fabricmc.net/develop # Note that this variable does *not* define the minimum required version in fabric.mod.json! -minecraft_version=1.20.5 -yarn_mappings=1.20.5+build.1 +minecraft_version=1.20.6 +yarn_mappings=1.20.6+build.1 loader_version=0.15.10 # Mod Properties @@ -14,4 +14,4 @@ maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod # Dependencies -fabric_version=0.97.5+1.20.5 +fabric_version=0.97.8+1.20.6 diff --git a/src/main/java/com/wildfire/main/WildfireGender.java b/src/main/java/com/wildfire/main/WildfireGender.java index c836c53a..806cd217 100644 --- a/src/main/java/com/wildfire/main/WildfireGender.java +++ b/src/main/java/com/wildfire/main/WildfireGender.java @@ -27,7 +27,6 @@ import com.wildfire.main.networking.WildfireSync; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.networking.v1.EntityTrackingEvents; -import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.server.network.ServerPlayerEntity; From f1399ef1f09277d68ae14e9552c82a051fa91c91 Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 10 Mar 2024 18:08:50 -0600 Subject: [PATCH 084/238] improve rotation physics while riding other entities this is primarily meant to fix weird physics issues when riding entities like boats, which cause the player's breasts to constantly attempt to rotate opposite of their head yaw at certain angles, even when sitting still pose changes (like sneaking and sleeping) have also been changed to use a single lastPose field, instead of a separate boolean field for each --- .../com/wildfire/physics/BreastPhysics.java | 85 +++++++++++++------ 1 file changed, 58 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index 5bb9c490..8ca0684c 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -23,12 +23,11 @@ import com.wildfire.main.WildfireHelper; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityPose; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.decoration.ArmorStandEntity; -import net.minecraft.entity.passive.HorseEntity; -import net.minecraft.entity.passive.PigEntity; -import net.minecraft.entity.passive.StriderEntity; +import net.minecraft.entity.passive.*; import net.minecraft.entity.vehicle.BoatEntity; import net.minecraft.entity.vehicle.MinecartEntity; import net.minecraft.util.math.MathHelper; @@ -43,19 +42,60 @@ public class BreastPhysics { //Rotation private float bounceRotVel = 0, targetRotVel = 0, rotVelocity = 0, wfg_bounceRotation, wfg_preBounceRotation; - private boolean justSneaking = false, alreadySleeping = false; - private float breastSize = 0, preBreastSize = 0; + private EntityPose lastPose; private Vec3d prePos; + private final EntityConfig entityConfig; + private int randomB = 1; + private boolean alreadyFalling = false; public BreastPhysics(EntityConfig entityConfig) { this.entityConfig = entityConfig; } - private int randomB = 1; - private boolean alreadyFalling = false; + private static boolean vehicleSuppressesRotation(Entity vehicle) { + return + // while you aren't able to normally ride chickens in vanilla, it is still possible through + // means like /ride, and as chickens attempt to force the rider's body yaw to the same yaw + // as the chicken (which is likely intended only for baby zombies), this results in unintended + // behavior with what we're doing + vehicle instanceof ChickenEntity + // unsaddled horses (and llamas, which also extend AbstractDonkeyEntity?) also break rotation + // physics, despite acting similarly to other entities where the rider's body yaw is allowed to + // (somewhat) freely move around + || vehicle instanceof AbstractHorseEntity horseLike && !horseLike.isSaddled() + // camels also suffer from largely the same issue as unsaddled horses when sitting or rising + || vehicle instanceof CamelEntity camel && camel.isStationary(); + } + + private static boolean shouldUseVehicleYaw(LivingEntity rider, Entity vehicle) { + return vehicle.hasControllingPassenger() + // boats will typically be caught by the above #hasControllingPassenger() check, but still + // special case these to catch any weird modded cases that might arise + || vehicle instanceof BoatEntity + // general catch-all for other entities that force the rider's body yaw to match theirs, + // such as horses + || vehicle.getBodyYaw() == rider.getBodyYaw(); + } + + private static float calcRotation(LivingEntity entity, float bounceIntensity) { + Entity vehicle = entity.getVehicle(); + if(vehicle != null) { + if(vehicleSuppressesRotation(vehicle)) { + return 0f; + } else if(shouldUseVehicleYaw(entity, vehicle)) { + if(vehicle instanceof LivingEntity livingVehicle) { + return -((livingVehicle.bodyYaw - livingVehicle.prevBodyYaw) / 15f) * bounceIntensity; + } else { + return -((vehicle.getYaw() - vehicle.prevYaw) / 15f) * bounceIntensity; + } + } + } + + return -((entity.bodyYaw - entity.prevBodyYaw) / 15f) * bounceIntensity; + } // this class cannot be blanket marked as client-side only, as this is referenced in the constructor for EntityConfig; // as such, the best we can get here is marking this method as such. @@ -161,24 +201,23 @@ public void update(LivingEntity entity, IGenderArmor armor) { this.targetBounceY = (float) motion.y * bounceIntensity; this.targetBounceY += breastWeight; float horizVel = (float) Math.sqrt(Math.pow(motion.x, 2) + Math.pow(motion.z, 2)) * (bounceIntensity); - this.targetRotVel = -((entity.bodyYaw - entity.prevBodyYaw) / 15f) * bounceIntensity; + + this.targetRotVel = this.calcRotation(entity, bounceIntensity); + this.targetRotVel += (float) motion.y * bounceIntensity * randomB; float f2 = (float) entity.getVelocity().lengthSquared() / 0.2F; f2 = f2 * f2 * f2; if(f2 < 1.0F) f2 = 1.0F; - this.targetBounceY += MathHelper.cos(entity.limbAnimator.getPos() * 0.6662F + (float)Math.PI) * 0.5F * entity.limbAnimator.getSpeed() * 0.5F / f2; - this.targetRotVel += (float) motion.y * bounceIntensity * randomB; - - - if(entity.getPose() == EntityPose.CROUCHING && !this.justSneaking) { - this.justSneaking = true; - this.targetBounceY += bounceIntensity; - } - if(entity.getPose() != EntityPose.CROUCHING && this.justSneaking) { - this.justSneaking = false; - this.targetBounceY += bounceIntensity; + EntityPose pose = entity.getPose(); + if(pose != lastPose) { + if(pose == EntityPose.CROUCHING || lastPose == EntityPose.CROUCHING) { + this.targetBounceY += bounceIntensity; + } else if(pose == EntityPose.SLEEPING || lastPose == EntityPose.SLEEPING) { + this.targetBounceY = bounceIntensity; + } + lastPose = pose; } //button option for extra entities @@ -222,14 +261,6 @@ public void update(LivingEntity entity, IGenderArmor armor) { if(entity.handSwinging && entity.age % 5 == 0 && entity.getPose() != EntityPose.SLEEPING) { this.targetBounceY += (Math.random() > 0.5 ? -0.25f : 0.25f) * bounceIntensity; } - if(entity.getPose() == EntityPose.SLEEPING && !this.alreadySleeping) { - this.targetBounceY = bounceIntensity; - this.alreadySleeping = true; - } - if(entity.getPose() != EntityPose.SLEEPING && this.alreadySleeping) { - this.targetBounceY = bounceIntensity; - this.alreadySleeping = false; - } /*if(plr.getPose() == EntityPose.SWIMMING) { //System.out.println(1 - plr.getRotationVec(tickDelta).getY()); rotationMultiplier = 1 - (float) plr.getRotationVec(tickDelta).getY(); From b8f4e46218e4801875689eb93085f1ad783094ff Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 10 Mar 2024 18:13:48 -0600 Subject: [PATCH 085/238] general improvements to vehicle physics lowered movement speed threshold on pigs and horses to ensure that their physics modifications now actually apply in most cases minecarts should appear to affect physics a lot less randomly as it did before, while still behaving in largely the same manner switch to else-if branches to prevent doing extra instanceof checks that we already know will fail --- .../com/wildfire/physics/BreastPhysics.java | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index 8ca0684c..6376903c 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -231,33 +231,32 @@ public void update(LivingEntity entity, IGenderArmor armor) { if(rotationL < -1 || rotationR < -0.6f) { this.targetBounceY = bounceIntensity / 3.25f; } - } - - if(entity.getVehicle() instanceof MinecartEntity cart) { + } else if(entity.getVehicle() instanceof MinecartEntity cart) { float speed = (float) cart.getVelocity().lengthSquared(); if(Math.random() * speed < 0.5f && speed > 0.2f) { this.targetBounceY = (Math.random() > 0.5 ? -bounceIntensity : bounceIntensity) / 6f; + this.targetBounceY += breastWeight; } - } - if(entity.getVehicle() instanceof HorseEntity horse) { + } else if(entity.getVehicle() instanceof HorseEntity horse) { float movement = (float) horse.getVelocity().lengthSquared(); - if(horse.age % clampMovement(movement) == 5 && movement > 0.1f) { + if(horse.age % clampMovement(movement) == 5 && movement > 0.05f) { this.targetBounceY = bounceIntensity / 4f; + this.targetBounceY += breastWeight; } - } - if(entity.getVehicle() instanceof PigEntity pig) { + } else if(entity.getVehicle() instanceof PigEntity pig) { float movement = (float) pig.getVelocity().lengthSquared(); - if(pig.age % clampMovement(movement) == 5 && movement > 0.08f) { - this.targetBounceY = bounceIntensity / 4f; + if(pig.age % clampMovement(movement) == 5 && movement > 0.002f) { + this.targetBounceY = (bounceIntensity * MathHelper.clamp(movement * 75, 0.1f, 1f)) / 4f; + this.targetBounceY += breastWeight; } - } - if(entity.getVehicle() instanceof StriderEntity strider) { + } else if(entity.getVehicle() instanceof StriderEntity strider) { double heightOffset = (double)strider.getHeight() - 0.19 + (double)(0.12F * MathHelper.cos(strider.limbAnimator.getPos() * 1.5f) * 2F * Math.min(0.25F, strider.limbAnimator.getSpeed())); this.targetBounceY += ((float) (heightOffset * 3f) - 4.5f) * bounceIntensity; } } + if(entity.handSwinging && entity.age % 5 == 0 && entity.getPose() != EntityPose.SLEEPING) { this.targetBounceY += (Math.random() > 0.5 ? -0.25f : 0.25f) * bounceIntensity; } From 081b882cca1fc3843feaffc546709141232e7a0f Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 10 Mar 2024 19:13:54 -0600 Subject: [PATCH 086/238] rework arm swing physics the effects of the swinging motion are now modified by haste and mining fatigue, making the effect stronger or weaker respectively --- .../com/wildfire/physics/BreastPhysics.java | 26 ++++++++++++++----- .../resources/wildfire_gender.accesswidener | 1 + 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index 6376903c..2a23bb95 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -30,6 +30,8 @@ import net.minecraft.entity.passive.*; import net.minecraft.entity.vehicle.BoatEntity; import net.minecraft.entity.vehicle.MinecartEntity; +import net.minecraft.util.Arm; +import net.minecraft.util.Hand; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; @@ -257,8 +259,21 @@ public void update(LivingEntity entity, IGenderArmor armor) { } } - if(entity.handSwinging && entity.age % 5 == 0 && entity.getPose() != EntityPose.SLEEPING) { - this.targetBounceY += (Math.random() > 0.5 ? -0.25f : 0.25f) * bounceIntensity; + int swingDuration = entity.getHandSwingDuration(); + if(entity.handSwinging && swingDuration > 1 && entity.age % 5 == 0 && pose != EntityPose.SLEEPING) { + float amplifier = 0f; + if(swingDuration < 6) { + amplifier = 0.15f * (6 - swingDuration); + } else if(swingDuration > 6) { + amplifier = 0.067f * -(swingDuration - 6); + } + // Cap our amplifier at the swing durations of Mining Fatigue III/Haste II + amplifier = MathHelper.clamp(1 + amplifier, 0.6f, 1.3f); + this.targetBounceY += (Math.random() > 0.5 ? -0.25f : 0.25f) * bounceIntensity * amplifier; + + Arm swingingArm = entity.preferredHand == Hand.MAIN_HAND ? entity.getMainArm() : + entity.getMainArm() == Arm.RIGHT ? Arm.LEFT : Arm.RIGHT; + this.targetRotVel += (swingingArm == Arm.RIGHT ? 1.185f : -1.185f) * bounceIntensity * amplifier; } /*if(plr.getPose() == EntityPose.SWIMMING) { //System.out.println(1 - plr.getRotationVec(tickDelta).getY()); @@ -281,10 +296,9 @@ public void update(LivingEntity entity, IGenderArmor armor) { if(bounceVel > 2.5f) { targetBounceY -= distanceFromMax; } - if(targetBounceY < -1.5f) targetBounceY = -1.5f; - if(targetBounceY > 2.5f) targetBounceY = 2.5f; - if(targetRotVel < -25f) targetRotVel = -25f; - if(targetRotVel > 25f) targetRotVel = 25f; + + targetBounceY = MathHelper.clamp(targetBounceY, -1.5f, 2.5f); + targetRotVel = MathHelper.clamp(targetRotVel, -25f, 25f); this.velocity = MathHelper.lerp(bounceAmount, this.velocity, (this.targetBounceY - this.bounceVel) * delta); this.bounceVel += this.velocity * percent * 1.1625f; diff --git a/src/main/resources/wildfire_gender.accesswidener b/src/main/resources/wildfire_gender.accesswidener index 0a01f901..3aa162d7 100644 --- a/src/main/resources/wildfire_gender.accesswidener +++ b/src/main/resources/wildfire_gender.accesswidener @@ -3,3 +3,4 @@ accessWidener v2 named accessible method net/minecraft/client/render/entity/LivingEntityRenderer getRenderLayer (Lnet/minecraft/entity/LivingEntity;ZZZ)Lnet/minecraft/client/render/RenderLayer; accessible field net/minecraft/client/render/entity/model/AnimalModel invertedChildBodyScale F accessible field net/minecraft/client/render/entity/model/AnimalModel childBodyYOffset F +accessible method net/minecraft/entity/LivingEntity getHandSwingDuration ()I From d43da06e463b1dc4b4845f9b2ecce56dbbc9156e Mon Sep 17 00:00:00 2001 From: celeste Date: Sat, 23 Mar 2024 17:08:19 -0600 Subject: [PATCH 087/238] improve arm swing rotation physics a bit more this commit is a bit more experimental than the previous commits, but it does appear to behave largely as intended. - rotate back toward the swinging arm if the previous swing animation is interrupted, either with another swing or with haste - apply swing rotation every tick, and reduce the intensity of the effect by a significant amount to compensate; this is to make the following change a bit more reasonable - account for swing progress when determining which direction to rotate the player's breasts, rotating them back toward the swinging arm in the latter half of the swing animation --- .../com/wildfire/physics/BreastPhysics.java | 76 +++++++++++++++++-- 1 file changed, 68 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index 2a23bb95..3f0c71a1 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -47,6 +47,7 @@ public class BreastPhysics { private float breastSize = 0, preBreastSize = 0; private EntityPose lastPose; + private int lastSwingDuration = 6, lastSwingTick = 0; private Vec3d prePos; private final EntityConfig entityConfig; @@ -68,7 +69,7 @@ private static boolean vehicleSuppressesRotation(Entity vehicle) { // physics, despite acting similarly to other entities where the rider's body yaw is allowed to // (somewhat) freely move around || vehicle instanceof AbstractHorseEntity horseLike && !horseLike.isSaddled() - // camels also suffer from largely the same issue as unsaddled horses when sitting or rising + // camels also suffer from largely the same issue as unsaddled horses when sitting or standing up || vehicle instanceof CamelEntity camel && camel.isStationary(); } @@ -204,7 +205,7 @@ public void update(LivingEntity entity, IGenderArmor armor) { this.targetBounceY += breastWeight; float horizVel = (float) Math.sqrt(Math.pow(motion.x, 2) + Math.pow(motion.z, 2)) * (bounceIntensity); - this.targetRotVel = this.calcRotation(entity, bounceIntensity); + this.targetRotVel = calcRotation(entity, bounceIntensity); this.targetRotVel += (float) motion.y * bounceIntensity * randomB; float f2 = (float) entity.getVelocity().lengthSquared() / 0.2F; @@ -260,21 +261,47 @@ public void update(LivingEntity entity, IGenderArmor armor) { } int swingDuration = entity.getHandSwingDuration(); - if(entity.handSwinging && swingDuration > 1 && entity.age % 5 == 0 && pose != EntityPose.SLEEPING) { + // Require that either the current swing duration is 2 ticks, or the swing duration from the previous tick is, + // as any faster and the arm effectively doesn't swing at all; we check the previous tick's swing duration for + // reasons explained later on in this block + if((swingDuration > 1 || lastSwingDuration > 1) && pose != EntityPose.SLEEPING) { float amplifier = 0f; if(swingDuration < 6) { amplifier = 0.15f * (6 - swingDuration); } else if(swingDuration > 6) { - amplifier = 0.067f * -(swingDuration - 6); + amplifier = -0.067f * (swingDuration - 6); } // Cap our amplifier at the swing durations of Mining Fatigue III/Haste II amplifier = MathHelper.clamp(1 + amplifier, 0.6f, 1.3f); - this.targetBounceY += (Math.random() > 0.5 ? -0.25f : 0.25f) * bounceIntensity * amplifier; - Arm swingingArm = entity.preferredHand == Hand.MAIN_HAND ? entity.getMainArm() : - entity.getMainArm() == Arm.RIGHT ? Arm.LEFT : Arm.RIGHT; - this.targetRotVel += (swingingArm == Arm.RIGHT ? 1.185f : -1.185f) * bounceIntensity * amplifier; + if(entity.handSwinging && entity.age % 5 == 0) { + this.targetBounceY += (Math.random() > 0.5 ? -0.25f : 0.25f) * amplifier * bounceIntensity; + } + + int swingTickDelta = entity.handSwingTicks - lastSwingTick; + float swingProgress = distanceFromMedian(0, lastSwingDuration, MathHelper.clamp(lastSwingTick, 0, lastSwingDuration)); + Arm swingingArm = entity.preferredHand == Hand.MAIN_HAND ? entity.getMainArm() : entity.getMainArm().getOpposite(); + + if(swingTickDelta < 0 && lastSwingTick != lastSwingDuration - 1) { + // Add a bit of counter-rotation back toward the currently swinging arm if the previous arm swing + // animation is interrupted + // Note that we don't check if the player's arm is currently swinging here to account for cases like + // haste being used to reset a player's swing; one notable example of this is Wynncraft's spell casting, + // which applies haste to the player when a spell is successfully cast. + this.targetRotVel += (swingingArm == Arm.RIGHT ? -2.5f : 2.5f) * Math.abs(swingProgress) * bounceIntensity; + } else if(entity.handSwinging && swingDuration > 1) { + // Otherwise if the swing animation isn't interrupted, attempt to rotate slightly in the direction + // that the body is currently moving + Arm swingingToward = swingProgress > 0f ? swingingArm.getOpposite() : swingingArm; + this.targetRotVel += (swingingToward == Arm.RIGHT ? 0.2f : -0.2f) * amplifier * bounceIntensity; + } + lastSwingTick = entity.handSwingTicks; } + if(!entity.handSwinging) { + lastSwingTick = 0; + } + lastSwingDuration = Math.max(swingDuration, 1); + /*if(plr.getPose() == EntityPose.SWIMMING) { //System.out.println(1 - plr.getRotationVec(tickDelta).getY()); rotationMultiplier = 1 - (float) plr.getRotationVec(tickDelta).getY(); @@ -352,4 +379,37 @@ private int clampMovement(float movement) { if(val < 1) val = 1; return val; } + + /** + * Return the distance from the median of the two provided boundary points from a given point + * + * @param p1 Lower boundary point + * @param p2 Upper boundary point + * @param point The target point within the range of {@code p1} and {@code p2} to get the distance from the median of + * + * @return A {@code float} of how far the provided point is from the median of the two boundary points, with + * {@code 1f} being at the median exactly, and {@code 0f} being at either of the two provided boundary + * points.
+ * If the provided point is in the latter half of the range between the two boundary points, the returned + * float will be negative. + * + * @throws IllegalArgumentException If {@code p2} is greater than {@code p1}, or if {@code atPoint} is out of bounds + */ + @SuppressWarnings("SameParameterValue") + private static float distanceFromMedian(final int p1, final int p2, float point) { + // sanity checks + if(p1 >= p2) { + throw new IllegalArgumentException("p2 must be greater than p1"); + } + if(point < p1 || point > p2) { + throw new IllegalArgumentException("point must be within bounds of p1 and p2"); + } + + if(point == p1 || point == p2) { + return 0; + } + float median = (p2 - p1) / 2f; + if(point > median) point = -(median - point); + return point / median; + } } From 4d2125db60e51e28c5ee56bb08d216478d217dee Mon Sep 17 00:00:00 2001 From: celeste Date: Sat, 25 May 2024 17:34:55 -0600 Subject: [PATCH 088/238] consistently apply swing physics even with haste fix incorrect rotate logic --- .../java/com/wildfire/physics/BreastPhysics.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index 3f0c71a1..9c7b3b19 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -274,8 +274,11 @@ public void update(LivingEntity entity, IGenderArmor armor) { // Cap our amplifier at the swing durations of Mining Fatigue III/Haste II amplifier = MathHelper.clamp(1 + amplifier, 0.6f, 1.3f); - if(entity.handSwinging && entity.age % 5 == 0) { - this.targetBounceY += (Math.random() > 0.5 ? -0.25f : 0.25f) * amplifier * bounceIntensity; + // consistently apply even with short swing durations, such as with haste + int everyNthTick = MathHelper.clamp(swingDuration - 1, 1, 5); + if(entity.handSwinging && entity.age % everyNthTick == 0) { + float hasteMult = MathHelper.clamp(everyNthTick / 5f, 0.4f, 1f); + this.targetBounceY += (Math.random() > 0.5 ? -0.25f : 0.25f) * amplifier * bounceIntensity * hasteMult; } int swingTickDelta = entity.handSwingTicks - lastSwingTick; @@ -290,10 +293,10 @@ public void update(LivingEntity entity, IGenderArmor armor) { // which applies haste to the player when a spell is successfully cast. this.targetRotVel += (swingingArm == Arm.RIGHT ? -2.5f : 2.5f) * Math.abs(swingProgress) * bounceIntensity; } else if(entity.handSwinging && swingDuration > 1) { - // Otherwise if the swing animation isn't interrupted, attempt to rotate slightly in the direction - // that the body is currently moving + // Otherwise if the swing animation isn't interrupted, attempt to rotate slightly counter to the + // direction that the body is currently moving Arm swingingToward = swingProgress > 0f ? swingingArm.getOpposite() : swingingArm; - this.targetRotVel += (swingingToward == Arm.RIGHT ? 0.2f : -0.2f) * amplifier * bounceIntensity; + this.targetRotVel += (swingingToward == Arm.RIGHT ? -0.2f : 0.2f) * amplifier * bounceIntensity; } lastSwingTick = entity.handSwingTicks; } @@ -409,7 +412,7 @@ private static float distanceFromMedian(final int p1, final int p2, float point) return 0; } float median = (p2 - p1) / 2f; - if(point > median) point = -(median - point); + if(point > median) point = -(point - median); return point / median; } } From 622c53734fde1839e50d4d02b7c129cdd63026d4 Mon Sep 17 00:00:00 2001 From: celeste Date: Wed, 22 May 2024 13:10:16 -0600 Subject: [PATCH 089/238] 24w21b --- gradle.properties | 8 +++--- .../gui/WildfireBreastPresetList.java | 8 +++--- .../gui/screen/WardrobeBrowserScreen.java | 6 ++-- .../WildfireCharacterSettingsScreen.java | 2 +- .../com/wildfire/main/WildfireSounds.java | 2 +- .../main/entitydata/EntityConfig.java | 5 ++++ .../networking/ClientboundSyncPacket.java | 3 +- .../networking/ServerboundSyncPacket.java | 3 +- .../com/wildfire/render/GenderArmorLayer.java | 28 +++++++------------ .../java/com/wildfire/render/GenderLayer.java | 27 +++++++++--------- .../render/WildfireModelRenderer.java | 3 +- src/main/resources/fabric.mod.json | 2 +- 12 files changed, 48 insertions(+), 49 deletions(-) diff --git a/gradle.properties b/gradle.properties index 8a942972..6dc98844 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,9 +4,9 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://fabricmc.net/develop # Note that this variable does *not* define the minimum required version in fabric.mod.json! -minecraft_version=1.20.6 -yarn_mappings=1.20.6+build.1 -loader_version=0.15.10 +minecraft_version=24w21b +yarn_mappings=24w21b+build.1 +loader_version=0.15.11 # Mod Properties mod_version = 3.2.1 @@ -14,4 +14,4 @@ maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod # Dependencies -fabric_version=0.97.8+1.20.6 +fabric_version=0.99.1+1.21 diff --git a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java index deb822ee..6082eaad 100644 --- a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java +++ b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java @@ -30,7 +30,7 @@ public class BreastPresetListEntry { public BreastPresetListEntry(String name, BreastPresetConfiguration data) { this.name = name; this.data = data; - this.ident = new Identifier(WildfireGender.MODID, "textures/presets/iknowthisisnull.png"); + this.ident = Identifier.of(WildfireGender.MODID, "textures/presets/iknowthisisnull.png"); } } @@ -38,9 +38,9 @@ public BreastPresetListEntry(String name, BreastPresetConfiguration data) { private BreastPresetListEntry[] BREAST_PRESETS = new BreastPresetListEntry[] { }; - private static final Identifier TXTR_SYNC = new Identifier(WildfireGender.MODID, "textures/sync.png"); - private static final Identifier TXTR_UNKNOWN = new Identifier(WildfireGender.MODID, "textures/unknown.png"); - private static final Identifier TXTR_CACHED = new Identifier(WildfireGender.MODID, "textures/cached.png"); + private static final Identifier TXTR_SYNC = Identifier.of(WildfireGender.MODID, "textures/sync.png"); + private static final Identifier TXTR_UNKNOWN = Identifier.of(WildfireGender.MODID, "textures/unknown.png"); + private static final Identifier TXTR_CACHED = Identifier.of(WildfireGender.MODID, "textures/cached.png"); private final int listWidth; private final WildfireBreastCustomizationScreen parent; diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index f79fe2da..29bb4940 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -39,9 +39,9 @@ @Environment(EnvType.CLIENT) public class WardrobeBrowserScreen extends BaseWildfireScreen { - private static final Identifier BACKGROUND_FEMALE = new Identifier(WildfireGender.MODID, "textures/gui/wardrobe_bg2.png"); - private static final Identifier BACKGROUND = new Identifier(WildfireGender.MODID, "textures/gui/wardrobe_bg3.png"); - private static final Identifier TXTR_RIBBON = new Identifier(WildfireGender.MODID, "textures/bc_ribbon.png"); + private static final Identifier BACKGROUND_FEMALE = Identifier.of(WildfireGender.MODID, "textures/gui/wardrobe_bg2.png"); + private static final Identifier BACKGROUND = Identifier.of(WildfireGender.MODID, "textures/gui/wardrobe_bg3.png"); + private static final Identifier TXTR_RIBBON = Identifier.of(WildfireGender.MODID, "textures/bc_ribbon.png"); private static final UUID CREATOR_UUID = UUID.fromString("23b6feed-2dfe-4f2e-9429-863fd4adb946"); private static final boolean isBreastCancerAwarenessMonth = Calendar.getInstance().get(Calendar.MONTH) == Calendar.OCTOBER; diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java index 0b4a22b4..d1679ce4 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java @@ -42,7 +42,7 @@ public class WildfireCharacterSettingsScreen extends BaseWildfireScreen { private static final Text ENABLED = Text.translatable("wildfire_gender.label.enabled").formatted(Formatting.GREEN); private static final Text DISABLED = Text.translatable("wildfire_gender.label.disabled").formatted(Formatting.RED); - private static final Identifier BACKGROUND = new Identifier(WildfireGender.MODID, "textures/gui/settings_bg.png"); + private static final Identifier BACKGROUND = Identifier.of(WildfireGender.MODID, "textures/gui/settings_bg.png"); private WildfireSlider bounceSlider, floppySlider; private boolean bounceWarning; diff --git a/src/main/java/com/wildfire/main/WildfireSounds.java b/src/main/java/com/wildfire/main/WildfireSounds.java index 47ce0635..07285fe7 100644 --- a/src/main/java/com/wildfire/main/WildfireSounds.java +++ b/src/main/java/com/wildfire/main/WildfireSounds.java @@ -28,7 +28,7 @@ private WildfireSounds() { throw new UnsupportedOperationException(); } - public static final SoundEvent FEMALE_HURT = SoundEvent.of(new Identifier(WildfireGender.MODID, "female_hurt")); + public static final SoundEvent FEMALE_HURT = SoundEvent.of(Identifier.of(WildfireGender.MODID, "female_hurt")); static void register() { Registry.register(Registries.SOUND_EVENT, FEMALE_HURT.getId(), FEMALE_HURT); diff --git a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java index 1bb63cd3..6c3b325d 100644 --- a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java +++ b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java @@ -61,6 +61,11 @@ public class EntityConfig { protected boolean showBreastsInArmor = Configuration.SHOW_IN_ARMOR.getDefault(); // note: hurt sounds and armor physics override are not defined here, as they have no relevance // to entities, and are instead entirely in PlayerConfig + + // TODO ideally these physics objects would be made entirely client-sided, but this class is + // used on both the client and server (primarily through PlayerConfig), making it very + // difficult to do so without some major changes to split this up further into a common class + // with a client extension class (e.g. the PlayerEntity & AbstractClientPlayerEntity classes) protected final BreastPhysics lBreastPhysics, rBreastPhysics; protected final Breasts breasts; protected boolean jacketLayer = true; diff --git a/src/main/java/com/wildfire/main/networking/ClientboundSyncPacket.java b/src/main/java/com/wildfire/main/networking/ClientboundSyncPacket.java index 3137a435..64412276 100644 --- a/src/main/java/com/wildfire/main/networking/ClientboundSyncPacket.java +++ b/src/main/java/com/wildfire/main/networking/ClientboundSyncPacket.java @@ -29,10 +29,11 @@ import net.minecraft.network.codec.PacketCodec; import net.minecraft.network.packet.CustomPayload; import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; public final class ClientboundSyncPacket extends AbstractSyncPacket implements CustomPayload { - public static final CustomPayload.Id ID = CustomPayload.id("wildfire_gender:sync"); + public static final Id ID = new CustomPayload.Id<>(Identifier.of(WildfireGender.MODID, "sync")); public static final PacketCodec CODEC = PacketCodec.of(ClientboundSyncPacket::encode, ClientboundSyncPacket::new); ClientboundSyncPacket(PlayerConfig plr) { diff --git a/src/main/java/com/wildfire/main/networking/ServerboundSyncPacket.java b/src/main/java/com/wildfire/main/networking/ServerboundSyncPacket.java index 18148d7d..d767ed1a 100644 --- a/src/main/java/com/wildfire/main/networking/ServerboundSyncPacket.java +++ b/src/main/java/com/wildfire/main/networking/ServerboundSyncPacket.java @@ -29,10 +29,11 @@ import net.minecraft.network.codec.PacketCodec; import net.minecraft.network.packet.CustomPayload; import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; public final class ServerboundSyncPacket extends AbstractSyncPacket implements CustomPayload { - public static final Id ID = CustomPayload.id("wildfire_gender:send_gender_info"); + public static final Id ID = new CustomPayload.Id<>(Identifier.of(WildfireGender.MODID, "send_gender_info")); public static final PacketCodec CODEC = PacketCodec.of(ServerboundSyncPacket::encode, ServerboundSyncPacket::new); ServerboundSyncPacket(PlayerConfig plr) { diff --git a/src/main/java/com/wildfire/render/GenderArmorLayer.java b/src/main/java/com/wildfire/render/GenderArmorLayer.java index bc9186dc..9ada68c0 100644 --- a/src/main/java/com/wildfire/render/GenderArmorLayer.java +++ b/src/main/java/com/wildfire/render/GenderArmorLayer.java @@ -25,7 +25,6 @@ import net.fabricmc.api.Environment; import net.fabricmc.fabric.impl.client.rendering.ArmorRendererRegistryImpl; import net.minecraft.client.MinecraftClient; -import net.minecraft.client.model.ModelPart; import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.client.render.*; import net.minecraft.client.render.entity.PlayerModelPart; @@ -47,7 +46,6 @@ import net.minecraft.item.trim.ArmorTrim; import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.registry.tag.ItemTags; -import net.minecraft.util.Colors; import net.minecraft.util.Identifier; import net.minecraft.util.math.ColorHelper; import org.jetbrains.annotations.NotNull; @@ -74,7 +72,8 @@ public GenderArmorLayer(FeatureRendererContext render, BakedModelManager b } @Override - public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, @NotNull T ent, float limbAngle, float limbDistance, float partialTicks, float animationProgress, float headYaw, float headPitch) { + public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, @NotNull T ent, float limbAngle, + float limbDistance, float partialTicks, float animationProgress, float headYaw, float headPitch) { MinecraftClient client = MinecraftClient.getInstance(); if(client.player == null) { // we're currently in a menu, give up rendering before we crash the game @@ -100,20 +99,13 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume if(ent instanceof ArmorStandEntity && !genderArmor.armorStandsCopySettings()) return; final RegistryEntry material = ((ArmorItem) chestplate.getItem()).getMaterial(); - final int color = chestplate.isIn(ItemTags.DYEABLE) ? DyedColorComponent.getColor(chestplate, -6265536) : Colors.WHITE; + final int color = chestplate.isIn(ItemTags.DYEABLE) ? ColorHelper.Argb.fullAlpha(DyedColorComponent.getColor(chestplate, -6265536)) : -1; final boolean glint = chestplate.hasGlint(); renderSides(ent, getContextModel(), matrixStack, side -> { material.value().layers().forEach(layer -> { - float r, g, b; - if(layer.isDyeable() && color != Colors.WHITE) { - r = (float)ColorHelper.Argb.getRed(color) / 255f; - g = (float)ColorHelper.Argb.getGreen(color) / 255f; - b = (float)ColorHelper.Argb.getBlue(color) / 255f; - } else { - r = g = b = 1f; - } - renderBreastArmor(layer.getTexture(false), matrixStack, vertexConsumerProvider, packedLightIn, side, r, g, b, glint); + int layerColor = layer.isDyeable() ? color : -1; + renderBreastArmor(layer.getTexture(false), matrixStack, vertexConsumerProvider, packedLightIn, side, layerColor, glint); }); ArmorTrim trim = armorStack.get(DataComponentTypes.TRIM); @@ -145,11 +137,11 @@ protected void setupTransformations(T entity, M model, MatrixStack matrixStack, // TODO eventually expose some way for mods to override this, maybe through a default impl in IGenderArmor or similar protected void renderBreastArmor(Identifier texture, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, - int light, BreastSide side, float r, float g, float b, boolean glint) { + int light, BreastSide side, int color, boolean glint) { BreastModelBox armor = side.isLeft ? lBoobArmor : rBoobArmor; RenderLayer armorType = RenderLayer.getArmorCutoutNoCull(texture); - VertexConsumer armorVertexConsumer = ItemRenderer.getArmorGlintConsumer(vertexConsumerProvider, armorType, false, glint); - renderBox(armor, matrixStack, armorVertexConsumer, light, OverlayTexture.DEFAULT_UV, r, g, b, 1); + VertexConsumer armorVertexConsumer = ItemRenderer.getArmorGlintConsumer(vertexConsumerProvider, armorType, glint); + renderBox(armor, matrixStack, armorVertexConsumer, light, OverlayTexture.DEFAULT_UV, ColorHelper.Argb.fullAlpha(color)); } protected void renderArmorTrim(RegistryEntry material, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, @@ -159,14 +151,14 @@ protected void renderArmorTrim(RegistryEntry material, MatrixStac VertexConsumer vertexConsumer = sprite.getTextureSpecificVertexConsumer( vertexConsumerProvider.getBuffer(TexturedRenderLayers.getArmorTrims(trim.getPattern().value().decal()))); // Render the armor trim itself - renderBox(trimModelBox, matrixStack, vertexConsumer, packedLightIn, OverlayTexture.DEFAULT_UV, 1f, 1f, 1f, 1f); + renderBox(trimModelBox, matrixStack, vertexConsumer, packedLightIn, OverlayTexture.DEFAULT_UV, -1); // The enchantment glint however requires special handling; due to how Minecraft's enchant glint rendering works, rendering // it at the same time as the trim itself results in the glint not rendering in sync with the rest of the armor. // We *also* can't simply render the glint for both the trim and armor at the same time, due to the slight delta we apply // to fix z-fighting between the trim and armor - and as such - a glint has to be rendered for each respective layer. if(hasGlint) { renderBox(trimModelBox, matrixStack, vertexConsumerProvider.getBuffer(RenderLayer.getArmorEntityGlint()), - packedLightIn, OverlayTexture.DEFAULT_UV, 1f, 1f, 1f, 1f); + packedLightIn, OverlayTexture.DEFAULT_UV, -1); } } } diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index 0838f580..8a382ae7 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -260,16 +260,18 @@ protected void setupTransformations(T entity, M model, MatrixStack matrixStack, matrixStack.scale(0.9995f, 1f, 1f); //z-fighting FIXXX } - private void renderBreast(T entity, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, int packedOverlayIn, BreastSide side) { + private void renderBreast(T entity, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, + int packedOverlayIn, BreastSide side) { RenderLayer breastRenderType = getRenderLayer(entity); if(breastRenderType == null) return; // only render if the player is visible in some capacity - float alpha = entity.isInvisible() ? 0.15F : 1; + int alpha = entity.isInvisible() ? ColorHelper.channelFromFloat(0.15f) : 255; + int color = ColorHelper.Argb.getArgb(alpha, 255, 255, 255); VertexConsumer vertexConsumer = vertexConsumerProvider.getBuffer(breastRenderType); - renderBox(side.isLeft ? lBreast : rBreast, matrixStack, vertexConsumer, packedLightIn, packedOverlayIn, 1f, 1f, 1f, alpha); + renderBox(side.isLeft ? lBreast : rBreast, matrixStack, vertexConsumer, packedLightIn, packedOverlayIn, color); if(entity instanceof AbstractClientPlayerEntity player && player.isPartVisible(PlayerModelPart.JACKET)) { matrixStack.translate(0, 0, -0.015f); matrixStack.scale(1.05f, 1.05f, 1.05f); - renderBox(side.isLeft ? lBreastWear : rBreastWear, matrixStack, vertexConsumer, packedLightIn, packedOverlayIn, 1f, 1f, 1f, alpha); + renderBox(side.isLeft ? lBreastWear : rBreastWear, matrixStack, vertexConsumer, packedLightIn, packedOverlayIn, color); } } @@ -291,23 +293,22 @@ protected void renderSides(T entity, M model, MatrixStack matrixStack, Consumer< } } - protected static void renderBox(WildfireModelRenderer.ModelBox model, MatrixStack matrixStack, VertexConsumer bufferIn, int packedLightIn, int packedOverlayIn, - float red, float green, float blue, float alpha) { + protected static void renderBox(WildfireModelRenderer.ModelBox model, MatrixStack matrixStack, VertexConsumer bufferIn, + int packedLightIn, int packedOverlayIn, int color) { Matrix4f matrix4f = matrixStack.peek().getPositionMatrix(); Matrix3f matrix3f = matrixStack.peek().getNormalMatrix(); - for (WildfireModelRenderer.TexturedQuad quad : model.quads) { - Vector3f vector3f = new Vector3f(quad.normal.x, quad.normal.y, quad.normal.z); - vector3f.mul(matrix3f); + for(WildfireModelRenderer.TexturedQuad quad : model.quads) { + Vector3f vector3f = new Vector3f(quad.normal.x, quad.normal.y, quad.normal.z).mul(matrix3f); float normalX = vector3f.x; float normalY = vector3f.y; float normalZ = vector3f.z; - for (PositionTextureVertex vertex : quad.vertexPositions) { + for(PositionTextureVertex vertex : quad.vertexPositions) { float j = vertex.x() / 16.0F; float k = vertex.y() / 16.0F; float l = vertex.z() / 16.0F; - Vector4f vector4f = new Vector4f(j, k, l, 1.0F); - vector4f.mul(matrix4f); - bufferIn.vertex(vector4f.x, vector4f.y, vector4f.z, red, green, blue, alpha, vertex.texturePositionX(), vertex.texturePositionY(), packedOverlayIn, packedLightIn, normalX, normalY, normalZ); + Vector4f vector4f = new Vector4f(j, k, l, 1.0F).mul(matrix4f); + bufferIn.vertex(vector4f.x(), vector4f.y(), vector4f.z(), color, vertex.u(), vertex.v(), + packedOverlayIn, packedLightIn, normalX, normalY, normalZ); } } } diff --git a/src/main/java/com/wildfire/render/WildfireModelRenderer.java b/src/main/java/com/wildfire/render/WildfireModelRenderer.java index 8cbc6a25..72308d5c 100644 --- a/src/main/java/com/wildfire/render/WildfireModelRenderer.java +++ b/src/main/java/com/wildfire/render/WildfireModelRenderer.java @@ -239,8 +239,7 @@ public SkinnedModelPlane(int tW, int tH, int texU, int texV, float x, float y, f } } - public record PositionTextureVertex(float x, float y, float z, float texturePositionX, float texturePositionY) { - + public record PositionTextureVertex(float x, float y, float z, float u, float v) { public PositionTextureVertex withTexturePosition(float texU, float texV) { return new PositionTextureVertex(x, y, z, texU, texV); } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index bc5fc71f..aa462a16 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -45,7 +45,7 @@ "fabric-rendering-v1": "*", "fabric-resource-loader-v0": "*", "fabric-registry-sync-v0": "*", - "minecraft": ">=1.20.5 <1.21", + "minecraft": ">=1.21-alpha.24.21.b <1.21", "java": ">=21" }, "conflicts": { From 2678d2a3440cb7b3a3391bc9aefe3755c22a600c Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 23 May 2024 14:59:03 -0600 Subject: [PATCH 090/238] remove accidental double sync code --- .../java/com/wildfire/main/WildfireGender.java | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/main/java/com/wildfire/main/WildfireGender.java b/src/main/java/com/wildfire/main/WildfireGender.java index 806cd217..47b70001 100644 --- a/src/main/java/com/wildfire/main/WildfireGender.java +++ b/src/main/java/com/wildfire/main/WildfireGender.java @@ -26,10 +26,6 @@ import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.networking.WildfireSync; import net.fabricmc.api.ModInitializer; -import net.fabricmc.fabric.api.networking.v1.EntityTrackingEvents; -import net.minecraft.entity.Entity; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.server.network.ServerPlayerEntity; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -43,7 +39,6 @@ public class WildfireGender implements ModInitializer { public void onInitialize() { WildfireSync.register(); WildfireEventHandler.registerCommonEvents(); - EntityTrackingEvents.START_TRACKING.register(this::onBeginTracking); } public static @Nullable PlayerConfig getPlayerById(UUID id) { @@ -53,17 +48,4 @@ public void onInitialize() { public static @NotNull PlayerConfig getOrAddPlayerById(UUID id) { return PLAYER_CACHE.computeIfAbsent(id, PlayerConfig::new); } - - private void onBeginTracking(Entity tracked, ServerPlayerEntity syncTo) { - if(tracked instanceof PlayerEntity toSync) { - PlayerConfig genderToSync = WildfireGender.getPlayerById(toSync.getUuid()); - if(genderToSync == null) return; - // Note that we intentionally don't check if we've previously synced a player with this code path; - // because we use entity tracking to sync, it's entirely possible that one player would leave the - // tracking distance of another, change their settings, and then re-enter their tracking distance; - // we wouldn't sync while they're out of tracking distance, and as such, their settings would be out - // of sync until they relog. - WildfireSync.sendToClient(syncTo, genderToSync); - } - } } From da5d2fdf5848069627ceb84a53df7c8dcb878a7b Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 23 May 2024 15:00:18 -0600 Subject: [PATCH 091/238] rename some render variables to better match yarn names --- gradle.properties | 2 +- .../com/wildfire/render/GenderArmorLayer.java | 6 ++--- .../java/com/wildfire/render/GenderLayer.java | 22 +++++++++---------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/gradle.properties b/gradle.properties index 6dc98844..d1d92769 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.jvmargs=-Xmx1G # check these on https://fabricmc.net/develop # Note that this variable does *not* define the minimum required version in fabric.mod.json! minecraft_version=24w21b -yarn_mappings=24w21b+build.1 +yarn_mappings=24w21b+build.4 loader_version=0.15.11 # Mod Properties diff --git a/src/main/java/com/wildfire/render/GenderArmorLayer.java b/src/main/java/com/wildfire/render/GenderArmorLayer.java index 9ada68c0..a7472f7a 100644 --- a/src/main/java/com/wildfire/render/GenderArmorLayer.java +++ b/src/main/java/com/wildfire/render/GenderArmorLayer.java @@ -72,7 +72,7 @@ public GenderArmorLayer(FeatureRendererContext render, BakedModelManager b } @Override - public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, @NotNull T ent, float limbAngle, + public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light, @NotNull T ent, float limbAngle, float limbDistance, float partialTicks, float animationProgress, float headYaw, float headPitch) { MinecraftClient client = MinecraftClient.getInstance(); if(client.player == null) { @@ -105,12 +105,12 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume renderSides(ent, getContextModel(), matrixStack, side -> { material.value().layers().forEach(layer -> { int layerColor = layer.isDyeable() ? color : -1; - renderBreastArmor(layer.getTexture(false), matrixStack, vertexConsumerProvider, packedLightIn, side, layerColor, glint); + renderBreastArmor(layer.getTexture(false), matrixStack, vertexConsumerProvider, light, side, layerColor, glint); }); ArmorTrim trim = armorStack.get(DataComponentTypes.TRIM); if(trim != null) { - renderArmorTrim(material, matrixStack, vertexConsumerProvider, packedLightIn, trim, glint, side); + renderArmorTrim(material, matrixStack, vertexConsumerProvider, light, trim, glint, side); } }); } catch(Exception e) { diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index 8a382ae7..ca2ad3f3 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -93,7 +93,7 @@ public GenderLayer(FeatureRendererContext render) { } @Override - public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, @NotNull T ent, float limbAngle, + public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light, @NotNull T ent, float limbAngle, float limbDistance, float partialTicks, float animationProgress, float headYaw, float headPitch) { MinecraftClient client = MinecraftClient.getInstance(); if(client.player == null) { @@ -106,10 +106,10 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume try { if(!setupRender(ent, entityConfig, partialTicks)) return; - int combineTex = LivingEntityRenderer.getOverlay(ent, 0); + int overlay = LivingEntityRenderer.getOverlay(ent, 0); renderSides(ent, getContextModel(), matrixStack, side -> { - renderBreast(ent, matrixStack, vertexConsumerProvider, packedLightIn, combineTex, side); + renderBreast(ent, matrixStack, vertexConsumerProvider, light, overlay, side); }); } catch(Exception e) { WildfireGender.LOGGER.error("Failed to render breast layer", e); @@ -260,18 +260,18 @@ protected void setupTransformations(T entity, M model, MatrixStack matrixStack, matrixStack.scale(0.9995f, 1f, 1f); //z-fighting FIXXX } - private void renderBreast(T entity, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, - int packedOverlayIn, BreastSide side) { + private void renderBreast(T entity, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light, + int overlay, BreastSide side) { RenderLayer breastRenderType = getRenderLayer(entity); if(breastRenderType == null) return; // only render if the player is visible in some capacity int alpha = entity.isInvisible() ? ColorHelper.channelFromFloat(0.15f) : 255; int color = ColorHelper.Argb.getArgb(alpha, 255, 255, 255); VertexConsumer vertexConsumer = vertexConsumerProvider.getBuffer(breastRenderType); - renderBox(side.isLeft ? lBreast : rBreast, matrixStack, vertexConsumer, packedLightIn, packedOverlayIn, color); + renderBox(side.isLeft ? lBreast : rBreast, matrixStack, vertexConsumer, light, overlay, color); if(entity instanceof AbstractClientPlayerEntity player && player.isPartVisible(PlayerModelPart.JACKET)) { matrixStack.translate(0, 0, -0.015f); matrixStack.scale(1.05f, 1.05f, 1.05f); - renderBox(side.isLeft ? lBreastWear : rBreastWear, matrixStack, vertexConsumer, packedLightIn, packedOverlayIn, color); + renderBox(side.isLeft ? lBreastWear : rBreastWear, matrixStack, vertexConsumer, light, overlay, color); } } @@ -293,8 +293,8 @@ protected void renderSides(T entity, M model, MatrixStack matrixStack, Consumer< } } - protected static void renderBox(WildfireModelRenderer.ModelBox model, MatrixStack matrixStack, VertexConsumer bufferIn, - int packedLightIn, int packedOverlayIn, int color) { + protected static void renderBox(WildfireModelRenderer.ModelBox model, MatrixStack matrixStack, VertexConsumer vertexConsumer, + int light, int overlay, int color) { Matrix4f matrix4f = matrixStack.peek().getPositionMatrix(); Matrix3f matrix3f = matrixStack.peek().getNormalMatrix(); for(WildfireModelRenderer.TexturedQuad quad : model.quads) { @@ -307,8 +307,8 @@ protected static void renderBox(WildfireModelRenderer.ModelBox model, MatrixStac float k = vertex.y() / 16.0F; float l = vertex.z() / 16.0F; Vector4f vector4f = new Vector4f(j, k, l, 1.0F).mul(matrix4f); - bufferIn.vertex(vector4f.x(), vector4f.y(), vector4f.z(), color, vertex.u(), vertex.v(), - packedOverlayIn, packedLightIn, normalX, normalY, normalZ); + vertexConsumer.vertex(vector4f.x(), vector4f.y(), vector4f.z(), color, vertex.u(), vertex.v(), + overlay, light, normalX, normalY, normalZ); } } } From 83cf8c5888e3b65d9e211bccd4655ee75591af59 Mon Sep 17 00:00:00 2001 From: celeste Date: Wed, 29 May 2024 11:01:01 -0600 Subject: [PATCH 092/238] 1.21-pre1 --- gradle.properties | 6 +++--- .../main/entitydata/BreastDataComponent.java | 2 +- .../com/wildfire/main/entitydata/Breasts.java | 4 +++- .../main/entitydata/EntityConfig.java | 5 ++--- .../main/entitydata/PlayerConfig.java | 19 ++++++++++++------- .../com/wildfire/render/GenderArmorLayer.java | 2 +- .../java/com/wildfire/render/GenderLayer.java | 12 ++++++------ 7 files changed, 28 insertions(+), 22 deletions(-) diff --git a/gradle.properties b/gradle.properties index d1d92769..b52ed40c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,8 +4,8 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://fabricmc.net/develop # Note that this variable does *not* define the minimum required version in fabric.mod.json! -minecraft_version=24w21b -yarn_mappings=24w21b+build.4 +minecraft_version=1.21-pre1 +yarn_mappings=1.21-pre1+build.3 loader_version=0.15.11 # Mod Properties @@ -14,4 +14,4 @@ maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod # Dependencies -fabric_version=0.99.1+1.21 +fabric_version=0.99.2+1.21 diff --git a/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java b/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java index c19b892f..652d4537 100644 --- a/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java +++ b/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java @@ -2,10 +2,10 @@ import com.wildfire.main.WildfireHelper; import com.wildfire.main.config.Configuration; -import net.minecraft.client.render.entity.PlayerModelPart; import net.minecraft.component.DataComponentTypes; import net.minecraft.component.type.NbtComponent; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerModelPart; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtElement; diff --git a/src/main/java/com/wildfire/main/entitydata/Breasts.java b/src/main/java/com/wildfire/main/entitydata/Breasts.java index b849221b..c1f1a241 100644 --- a/src/main/java/com/wildfire/main/entitydata/Breasts.java +++ b/src/main/java/com/wildfire/main/entitydata/Breasts.java @@ -30,7 +30,9 @@ @SuppressWarnings("UnusedReturnValue") public final class Breasts { - private float xOffset = Configuration.BREASTS_OFFSET_X.getDefault(), yOffset = Configuration.BREASTS_OFFSET_Y.getDefault(), zOffset = Configuration.BREASTS_OFFSET_Z.getDefault(); + private float xOffset = Configuration.BREASTS_OFFSET_X.getDefault(), + yOffset = Configuration.BREASTS_OFFSET_Y.getDefault(), + zOffset = Configuration.BREASTS_OFFSET_Z.getDefault(); private float cleavage = Configuration.BREASTS_CLEAVAGE.getDefault(); private boolean uniboob = Configuration.BREASTS_UNIBOOB.getDefault(); diff --git a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java index 6c3b325d..cf9967b7 100644 --- a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java +++ b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java @@ -58,8 +58,7 @@ public class EntityConfig { protected boolean breastPhysics = Configuration.BREAST_PHYSICS.getDefault(); protected float bounceMultiplier = Configuration.BOUNCE_MULTIPLIER.getDefault(); protected float floppyMultiplier = Configuration.FLOPPY_MULTIPLIER.getDefault(); - protected boolean showBreastsInArmor = Configuration.SHOW_IN_ARMOR.getDefault(); - // note: hurt sounds and armor physics override are not defined here, as they have no relevance + // note: hurt sounds, armor physics override, and show in armor are not defined here, as they have no relevance // to entities, and are instead entirely in PlayerConfig // TODO ideally these physics objects would be made entirely client-sided, but this class is @@ -71,7 +70,7 @@ public class EntityConfig { protected boolean jacketLayer = true; protected @Nullable BreastDataComponent fromComponent; - EntityConfig(UUID uuid) { + protected EntityConfig(UUID uuid) { this.uuid = uuid; this.breasts = new Breasts(); lBreastPhysics = new BreastPhysics(this); diff --git a/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java b/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java index 96cb5f81..766b9c65 100644 --- a/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java +++ b/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java @@ -38,16 +38,21 @@ public class PlayerConfig extends EntityConfig { public SyncStatus syncStatus = SyncStatus.UNKNOWN; private final Configuration cfg; - private boolean hurtSounds = Configuration.HURT_SOUNDS.getDefault(); - private boolean armorPhysOverride = Configuration.ARMOR_PHYSICS_OVERRIDE.getDefault(); - - public PlayerConfig(UUID uuid) { - this(uuid, Configuration.GENDER.getDefault()); + protected boolean hurtSounds = Configuration.HURT_SOUNDS.getDefault(); + protected boolean armorPhysOverride = Configuration.ARMOR_PHYSICS_OVERRIDE.getDefault(); + protected boolean showBreastsInArmor = Configuration.SHOW_IN_ARMOR.getDefault(); + + /** + * @deprecated Use {@link #updateGender(Gender)} instead + */ + @Deprecated + public PlayerConfig(UUID uuid, Gender gender) { + this(uuid); + updateGender(gender); } - public PlayerConfig(UUID uuid, Gender gender) { + public PlayerConfig(UUID uuid) { super(uuid); - this.gender = gender; this.cfg = new Configuration(this.uuid.toString()); this.cfg.set(Configuration.USERNAME, this.uuid); this.cfg.setDefault(Configuration.GENDER); diff --git a/src/main/java/com/wildfire/render/GenderArmorLayer.java b/src/main/java/com/wildfire/render/GenderArmorLayer.java index a7472f7a..a66373e9 100644 --- a/src/main/java/com/wildfire/render/GenderArmorLayer.java +++ b/src/main/java/com/wildfire/render/GenderArmorLayer.java @@ -27,7 +27,6 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.client.render.*; -import net.minecraft.client.render.entity.PlayerModelPart; import net.minecraft.client.render.entity.feature.FeatureRendererContext; import net.minecraft.client.render.entity.model.BipedEntityModel; import net.minecraft.client.render.item.ItemRenderer; @@ -40,6 +39,7 @@ import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.decoration.ArmorStandEntity; +import net.minecraft.entity.player.PlayerModelPart; import net.minecraft.item.ArmorItem; import net.minecraft.item.ArmorMaterial; import net.minecraft.item.ItemStack; diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index ca2ad3f3..e5b64f81 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -39,7 +39,6 @@ import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.client.render.*; import net.minecraft.client.render.entity.LivingEntityRenderer; -import net.minecraft.client.render.entity.PlayerModelPart; import net.minecraft.client.render.entity.feature.FeatureRenderer; import net.minecraft.client.render.entity.feature.FeatureRendererContext; import net.minecraft.client.render.entity.model.BipedEntityModel; @@ -47,6 +46,7 @@ import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.effect.StatusEffectUtil; +import net.minecraft.entity.player.PlayerModelPart; import net.minecraft.item.ItemStack; import net.minecraft.util.math.*; import org.jetbrains.annotations.NotNull; @@ -60,12 +60,12 @@ public class GenderLayer> private final FeatureRendererContext context; private static final OverlayModelBox lBreastWear, rBreastWear; - private float preBreastSize = 0f, preBreastOffsetZ; + private float preBreastSize, preBreastOffsetZ; private Breasts breasts; protected ItemStack armorStack; protected IGenderArmor genderArmor; protected boolean isChestplateOccupied, bounceEnabled, breathingAnimation; - protected float breastOffsetX, breastOffsetY, breastOffsetZ, lPhysPositionY, lPhysPositionX, rPhysPositionY, rTotalX, + protected float breastOffsetX, breastOffsetY, breastOffsetZ, lPhysPositionY, lPhysPositionX, rPhysPositionY, rPhysPositionX, lPhysBounceRotation, rPhysBounceRotation, breastSize, zOffset, outwardAngle; static { @@ -156,12 +156,12 @@ protected boolean setupRender(T entity, EntityConfig entityConfig, float partial lPhysBounceRotation = MathHelper.lerp(partialTicks, leftBreastPhysics.getPreBounceRotation(), leftBreastPhysics.getBounceRotation()); if(breasts.isUniboob()) { rPhysPositionY = lPhysPositionY; - rTotalX = lPhysPositionX; + rPhysPositionX = lPhysPositionX; rPhysBounceRotation = lPhysBounceRotation; } else { BreastPhysics rightBreastPhysics = entityConfig.getRightBreastPhysics(); rPhysPositionY = MathHelper.lerp(partialTicks, rightBreastPhysics.getPrePositionY(), rightBreastPhysics.getPositionY()); - rTotalX = MathHelper.lerp(partialTicks, rightBreastPhysics.getPrePositionX(), rightBreastPhysics.getPositionX()); + rPhysPositionX = MathHelper.lerp(partialTicks, rightBreastPhysics.getPrePositionX(), rightBreastPhysics.getPositionX()); rPhysBounceRotation = MathHelper.lerp(partialTicks, rightBreastPhysics.getPreBounceRotation(), rightBreastPhysics.getBounceRotation()); } breastSize = bSize * 1.5f; @@ -215,7 +215,7 @@ protected void setupTransformations(T entity, M model, MatrixStack matrixStack, } if(bounceEnabled) { - matrixStack.translate((side.isLeft ? lPhysPositionX : rTotalX) / 32f, 0, 0); + matrixStack.translate((side.isLeft ? lPhysPositionX : rPhysPositionX) / 32f, 0, 0); matrixStack.translate(0, (side.isLeft ? lPhysPositionY : rPhysPositionY) / 32f, 0); } From e21a0d109c8769e42cb63783850dc61696b2decb Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 31 May 2024 11:50:02 -0600 Subject: [PATCH 093/238] catch the client player config being null in uis this should really only happen if the player is disconnected while in the menus, in which case, we only really care about avoiding the render loop throwing an error, with buttons still strictly expecting the player config to not be null. --- .../gui/WildfireBreastPresetList.java | 16 +++++++++------ .../gui/screen/BaseWildfireScreen.java | 8 ++------ .../gui/screen/WardrobeBrowserScreen.java | 7 +++++-- .../WildfireBreastCustomizationScreen.java | 20 +++++++++++-------- .../WildfireCharacterSettingsScreen.java | 4 +++- 5 files changed, 32 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java index 6082eaad..e91f8df8 100644 --- a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java +++ b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java @@ -3,6 +3,7 @@ import com.wildfire.gui.screen.WildfireBreastCustomizationScreen; import com.wildfire.main.WildfireGender; import com.wildfire.main.config.BreastPresetConfiguration; +import com.wildfire.main.entitydata.PlayerConfig; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.MinecraftClient; @@ -14,6 +15,7 @@ import net.minecraft.util.Identifier; import java.util.ArrayList; +import java.util.Objects; @Environment(EnvType.CLIENT) public class WildfireBreastPresetList extends EntryListWidget { @@ -125,12 +127,14 @@ private Entry(final BreastPresetListEntry nInfo) { this.nInfo = nInfo; this.thumbnail = nInfo.ident; btnOpenGUI = new WildfireButton(0, 0, getRowWidth() - 6, itemHeight, Text.empty(), button -> { - parent.getPlayer().updateBustSize(nInfo.data.get(BreastPresetConfiguration.BUST_SIZE)); - parent.getPlayer().getBreasts().updateXOffset(nInfo.data.get(BreastPresetConfiguration.BREASTS_OFFSET_X)); - parent.getPlayer().getBreasts().updateYOffset(nInfo.data.get(BreastPresetConfiguration.BREASTS_OFFSET_Y)); - parent.getPlayer().getBreasts().updateZOffset(nInfo.data.get(BreastPresetConfiguration.BREASTS_OFFSET_Z)); - parent.getPlayer().getBreasts().updateCleavage(nInfo.data.get(BreastPresetConfiguration.BREASTS_CLEAVAGE)); - parent.getPlayer().getBreasts().updateUniboob(nInfo.data.get(BreastPresetConfiguration.BREASTS_UNIBOOB)); + PlayerConfig plr = Objects.requireNonNull(parent.getPlayer(), "getPlayer()"); + plr.updateBustSize(nInfo.data.get(BreastPresetConfiguration.BUST_SIZE)); + plr.getBreasts().updateXOffset(nInfo.data.get(BreastPresetConfiguration.BREASTS_OFFSET_X)); + plr.getBreasts().updateYOffset(nInfo.data.get(BreastPresetConfiguration.BREASTS_OFFSET_Y)); + plr.getBreasts().updateZOffset(nInfo.data.get(BreastPresetConfiguration.BREASTS_OFFSET_Z)); + plr.getBreasts().updateCleavage(nInfo.data.get(BreastPresetConfiguration.BREASTS_CLEAVAGE)); + plr.getBreasts().updateUniboob(nInfo.data.get(BreastPresetConfiguration.BREASTS_UNIBOOB)); + PlayerConfig.saveGenderInfo(plr); }); } diff --git a/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java b/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java index c254072d..a2ac1060 100644 --- a/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java +++ b/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java @@ -24,13 +24,9 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; -import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.screen.ingame.InventoryScreen; -import net.minecraft.entity.LivingEntity; import net.minecraft.text.Text; -import org.joml.Quaternionf; -import org.joml.Vector3f; +import org.jetbrains.annotations.Nullable; @Environment(EnvType.CLIENT) public abstract class BaseWildfireScreen extends Screen { @@ -44,7 +40,7 @@ protected BaseWildfireScreen(Text title, Screen parent, UUID uuid) { this.playerUUID = uuid; } - public PlayerConfig getPlayer() { + public @Nullable PlayerConfig getPlayer() { return WildfireGender.getPlayerById(this.playerUUID); } diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index 29bb4940..f17f759a 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -23,6 +23,7 @@ import com.wildfire.main.WildfireGender; import java.util.Calendar; +import java.util.Objects; import java.util.UUID; import com.wildfire.gui.WildfireButton; @@ -52,7 +53,7 @@ public WardrobeBrowserScreen(Screen parent, UUID uuid) { @Override public void init() { int y = this.height / 2; - PlayerConfig plr = getPlayer(); + PlayerConfig plr = Objects.requireNonNull(getPlayer(), "getPlayer()"); this.addDrawableChild(new WildfireButton(this.width / 2 - 42, y - 52, 158, 20, getGenderLabel(plr.getGender()), button -> { Gender gender = switch (plr.getGender()) { @@ -87,7 +88,9 @@ private Text getGenderLabel(Gender gender) { @Override public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delta) { super.renderBackground(ctx, mouseX, mouseY, delta); - Identifier backgroundTexture = getPlayer().getGender().canHaveBreasts() ? BACKGROUND_FEMALE : BACKGROUND; + PlayerConfig plr = getPlayer(); + if(plr == null) return; + Identifier backgroundTexture = plr.getGender().canHaveBreasts() ? BACKGROUND_FEMALE : BACKGROUND; ctx.drawTexture(backgroundTexture, (this.width - 248) / 2, (this.height - 134) / 2, 0, 0, 248, 156); if(client != null && client.world != null) { diff --git a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java index 1e3e59b1..11f67576 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java @@ -37,6 +37,7 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.text.Text; +import java.util.Objects; import java.util.UUID; @Environment(EnvType.CLIENT) @@ -57,7 +58,7 @@ public WildfireBreastCustomizationScreen(Screen parent, UUID uuid) { public void init() { int j = this.height / 2 - 11; - PlayerConfig plr = getPlayer(); + PlayerConfig plr = Objects.requireNonNull(getPlayer(), "getPlayer()"); Breasts breasts = plr.getBreasts(); FloatConsumer onSave = value -> { //Just save as we updated the actual value in value change @@ -146,20 +147,23 @@ public void init() { private void createNewPreset(String presetName) { BreastPresetConfiguration cfg = new BreastPresetConfiguration(presetName); + PlayerConfig plr = Objects.requireNonNull(getPlayer(), "getPlayer()"); cfg.set(BreastPresetConfiguration.PRESET_NAME, presetName); - cfg.set(BreastPresetConfiguration.BUST_SIZE, this.getPlayer().getBustSize()); - cfg.set(BreastPresetConfiguration.BREASTS_UNIBOOB, this.getPlayer().getBreasts().isUniboob()); - cfg.set(BreastPresetConfiguration.BREASTS_CLEAVAGE, this.getPlayer().getBreasts().getCleavage()); - cfg.set(BreastPresetConfiguration.BREASTS_OFFSET_X, this.getPlayer().getBreasts().getXOffset()); - cfg.set(BreastPresetConfiguration.BREASTS_OFFSET_Y, this.getPlayer().getBreasts().getYOffset()); - cfg.set(BreastPresetConfiguration.BREASTS_OFFSET_Z, this.getPlayer().getBreasts().getZOffset()); + cfg.set(BreastPresetConfiguration.BUST_SIZE, plr.getBustSize()); + cfg.set(BreastPresetConfiguration.BREASTS_UNIBOOB, plr.getBreasts().isUniboob()); + cfg.set(BreastPresetConfiguration.BREASTS_CLEAVAGE, plr.getBreasts().getCleavage()); + cfg.set(BreastPresetConfiguration.BREASTS_OFFSET_X, plr.getBreasts().getXOffset()); + cfg.set(BreastPresetConfiguration.BREASTS_OFFSET_Y, plr.getBreasts().getYOffset()); + cfg.set(BreastPresetConfiguration.BREASTS_OFFSET_Z, plr.getBreasts().getZOffset()); cfg.save(); PRESET_LIST.refreshList(); } private void updatePresetTab() { - boolean canHaveBreasts = getPlayer().getGender().canHaveBreasts(); + PlayerConfig plr = getPlayer(); + if(plr == null) return; + boolean canHaveBreasts = plr.getGender().canHaveBreasts(); breastSlider.visible = canHaveBreasts && currentTab == 0; xOffsetBoobSlider.visible = canHaveBreasts && currentTab == 0; yOffsetBoobSlider.visible = canHaveBreasts && currentTab == 0; diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java index d1679ce4..458f726c 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java @@ -22,6 +22,8 @@ import com.wildfire.gui.WildfireSlider; import com.wildfire.main.WildfireGender; import com.wildfire.main.config.Configuration; + +import java.util.Objects; import java.util.UUID; import com.wildfire.gui.WildfireButton; @@ -53,7 +55,7 @@ protected WildfireCharacterSettingsScreen(Screen parent, UUID uuid) { @Override public void init() { - PlayerConfig aPlr = getPlayer(); + PlayerConfig aPlr = Objects.requireNonNull(getPlayer(), "getPlayer()"); int x = this.width / 2; int y = this.height / 2; int yPos = y - 47; From 6e871d6876ef5b2b28a62498c8a8464f6b459e55 Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 31 May 2024 12:07:54 -0600 Subject: [PATCH 094/238] don't try to load if the config file doesn't exist --- .../java/com/wildfire/main/config/AbstractConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/main/config/AbstractConfiguration.java b/src/main/java/com/wildfire/main/config/AbstractConfiguration.java index d1dd16a7..3fe4f31c 100644 --- a/src/main/java/com/wildfire/main/config/AbstractConfiguration.java +++ b/src/main/java/com/wildfire/main/config/AbstractConfiguration.java @@ -91,7 +91,7 @@ public void save() { } public void load() { - if(!supportsSaving()) return; + if(!supportsSaving() || !CFG_FILE.exists()) return; try(FileReader configurationFile = new FileReader(CFG_FILE)) { JsonObject obj = new Gson().fromJson(configurationFile, JsonObject.class); for(Map.Entry entry : obj.entrySet()) { From 15fe8564facb66be07a8a03666fadf720bcabdc0 Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 31 May 2024 14:20:49 -0600 Subject: [PATCH 095/238] pre2 --- gradle.properties | 6 +++--- .../java/com/wildfire/gui/WildfireBreastPresetList.java | 2 +- src/main/java/com/wildfire/physics/BreastPhysics.java | 4 +--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/gradle.properties b/gradle.properties index b52ed40c..727fcabb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,8 +4,8 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://fabricmc.net/develop # Note that this variable does *not* define the minimum required version in fabric.mod.json! -minecraft_version=1.21-pre1 -yarn_mappings=1.21-pre1+build.3 +minecraft_version=1.21-pre2 +yarn_mappings=1.21-pre2+build.2 loader_version=0.15.11 # Mod Properties @@ -14,4 +14,4 @@ maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod # Dependencies -fabric_version=0.99.2+1.21 +fabric_version=0.99.4+1.21 diff --git a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java index e91f8df8..1594d6ee 100644 --- a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java +++ b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java @@ -64,7 +64,7 @@ protected void drawSelectionHighlight(DrawContext context, int y, int entryWidth @Override protected void drawMenuListBackground(DrawContext context) {} - // copy of EntryListWidget#renderList without the added margin between entries + // copy of super without the added margin between entries @Override protected void renderList(DrawContext context, int mouseX, int mouseY, float delta) { int left = this.getRowLeft(); diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index 9c7b3b19..6d6abba3 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -378,9 +378,7 @@ public float getPreBounceRotation() { } private int clampMovement(float movement) { - int val = (int) (10 - movement*2f); - if(val < 1) val = 1; - return val; + return Math.max((int) (10 - movement*2f), 1); } /** From 5020054093b7f747ac7873a954a380d765f14eea Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 2 Jun 2024 00:48:19 -0600 Subject: [PATCH 096/238] fix still incorrect distance from median logic as it turns out, I am both incapable of math, *and* validating that my implementation is correct *both* times that I previously went over this exact same block of code. --- .../com/wildfire/physics/BreastPhysics.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index 6d6abba3..8053e196 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -388,13 +388,14 @@ private int clampMovement(float movement) { * @param p2 Upper boundary point * @param point The target point within the range of {@code p1} and {@code p2} to get the distance from the median of * - * @return A {@code float} of how far the provided point is from the median of the two boundary points, with - * {@code 1f} being at the median exactly, and {@code 0f} being at either of the two provided boundary - * points.
+ * @return A {@code float} indicating how far the provided {@code point} is from the median of the two boundary + * points, with {@code 1f} being at the median exactly, and {@code 0f} being at either of the two + * provided boundary points.
* If the provided point is in the latter half of the range between the two boundary points, the returned * float will be negative. * - * @throws IllegalArgumentException If {@code p2} is greater than {@code p1}, or if {@code atPoint} is out of bounds + * @throws IllegalArgumentException If {@code p1} is equal to or greater than {@code p2}, + * or if {@code point} is not within the specified range. */ @SuppressWarnings("SameParameterValue") private static float distanceFromMedian(final int p1, final int p2, float point) { @@ -403,14 +404,20 @@ private static float distanceFromMedian(final int p1, final int p2, float point) throw new IllegalArgumentException("p2 must be greater than p1"); } if(point < p1 || point > p2) { - throw new IllegalArgumentException("point must be within bounds of p1 and p2"); + throw new IllegalArgumentException(point + " is not within bounds of (" + p1 + ", " + p2 + ")"); } if(point == p1 || point == p2) { return 0; } + // subtract p1 to get the actual inner range, then divide to get the median float median = (p2 - p1) / 2f; - if(point > median) point = -(point - median); + point -= p1; + if(point > median) { + // invert the provided point to instead become smaller the further we are away from the median + // in the latter half of the specified range + point = -(median - (point - median)); + } return point / median; } } From 503af6e39246a11cc98f64400066c9a2dab0bc67 Mon Sep 17 00:00:00 2001 From: celeste Date: Sat, 8 Jun 2024 13:36:00 -0600 Subject: [PATCH 097/238] pre4, bump version range to allow 1.21.x releases (hopefully 1.21.1 doesnt immediately release and break this) --- gradle.properties | 6 +++--- src/main/resources/fabric.mod.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index 727fcabb..aab0f990 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,8 +4,8 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://fabricmc.net/develop # Note that this variable does *not* define the minimum required version in fabric.mod.json! -minecraft_version=1.21-pre2 -yarn_mappings=1.21-pre2+build.2 +minecraft_version=1.21-pre4 +yarn_mappings=1.21-pre4+build.3 loader_version=0.15.11 # Mod Properties @@ -14,4 +14,4 @@ maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod # Dependencies -fabric_version=0.99.4+1.21 +fabric_version=0.100.0+1.21 diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index aa462a16..84350e8b 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -45,7 +45,7 @@ "fabric-rendering-v1": "*", "fabric-resource-loader-v0": "*", "fabric-registry-sync-v0": "*", - "minecraft": ">=1.21-alpha.24.21.b <1.21", + "minecraft": ">=1.21-alpha.24.21.b <1.22", "java": ">=21" }, "conflicts": { From 65a2b7947d39c8bd313d0561ed04e28a8bf93b53 Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 13 Jun 2024 09:46:43 -0600 Subject: [PATCH 098/238] bump to full release --- gradle.properties | 6 +++--- src/main/resources/fabric.mod.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index aab0f990..57acd116 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,8 +4,8 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://fabricmc.net/develop # Note that this variable does *not* define the minimum required version in fabric.mod.json! -minecraft_version=1.21-pre4 -yarn_mappings=1.21-pre4+build.3 +minecraft_version=1.21 +yarn_mappings=1.21+build.1 loader_version=0.15.11 # Mod Properties @@ -14,4 +14,4 @@ maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod # Dependencies -fabric_version=0.100.0+1.21 +fabric_version=0.100.1+1.21 diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 84350e8b..f6dc0d3d 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -45,7 +45,7 @@ "fabric-rendering-v1": "*", "fabric-resource-loader-v0": "*", "fabric-registry-sync-v0": "*", - "minecraft": ">=1.21-alpha.24.21.b <1.22", + "minecraft": ">=1.21 <1.22", "java": ">=21" }, "conflicts": { From 7400e4647626804e28c882a1631e47bfb5455dd7 Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 1 Jul 2024 12:25:15 -0600 Subject: [PATCH 099/238] remove all the trims as a conflict they've since added compatibility on their end, so this conflict warning isn't accurate anymore. --- src/main/resources/fabric.mod.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index f6dc0d3d..427eb800 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -49,8 +49,7 @@ "java": ">=21" }, "conflicts": { - "skinlayers": "*", - "allthetrims": "*" + "skinlayers": "*" }, "custom": { "modmenu": { @@ -59,4 +58,4 @@ } } } -} \ No newline at end of file +} From e307086d1ee68e42c1e7b80f6b7ff66daaad9424 Mon Sep 17 00:00:00 2001 From: "bubble(good7777865)" <94590633+good7777865@users.noreply.github.com> Date: Thu, 4 Jul 2024 23:24:33 +0900 Subject: [PATCH 100/238] Translated to Korean. Added ko_kr.json --- src/main/resources/assets/wildfire_gender/lang/ko_kr.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/main/resources/assets/wildfire_gender/lang/ko_kr.json diff --git a/src/main/resources/assets/wildfire_gender/lang/ko_kr.json b/src/main/resources/assets/wildfire_gender/lang/ko_kr.json new file mode 100644 index 00000000..e69de29b From c9885cf7003f0aa306c1f79426e4c1c766f52256 Mon Sep 17 00:00:00 2001 From: "bubble(good7777865)" <94590633+good7777865@users.noreply.github.com> Date: Thu, 4 Jul 2024 23:26:35 +0900 Subject: [PATCH 101/238] Update ko_kr.json --- .../assets/wildfire_gender/lang/ko_kr.json | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/src/main/resources/assets/wildfire_gender/lang/ko_kr.json b/src/main/resources/assets/wildfire_gender/lang/ko_kr.json index e69de29b..f13dee0d 100644 --- a/src/main/resources/assets/wildfire_gender/lang/ko_kr.json +++ b/src/main/resources/assets/wildfire_gender/lang/ko_kr.json @@ -0,0 +1,61 @@ +{ + "category.wildfire_gender.generic": "Wildfire의 여성 성별 모드", + "key.wildfire_gender.gender_menu": "여성 성별 메뉴", + "toast.wildfire_gender.get_started": "'%s' 눌러 설정을 엽니다.", + + "wildfire_gender.player_list.title": "여성 성별 모드", + "wildfire_gender.player_list.settings_button": "설정", + "wildfire_gender.player_list.sync_status": "동기화 상태", + "wildfire_gender.player_list.state.loading": "데이더 가져오는 중...", + "wildfire_gender.player_list.state.synced": "동기화 완료", + "wildfire_gender.player_list.bounce_multiplier": "흔들림 정도: %s배", + "wildfire_gender.player_list.breast_momentum": "가슴 흔들림 정도: %s%%", + "wildfire_gender.player_list.female_sounds": "여성적 소리: %s", + + "wildfire_gender.wardrobe.title": "Wildfire의 여성 성별 모드", + "wildfire_gender.breast_customization.tab_customization": "커스터마이즈", + "wildfire_gender.breast_customization.tab_presets": "기본값", + + "wildfire_gender.breast_customization.presets.add_new": "새로 추가...", + "wildfire_gender.breast_customization.presets.delete": "삭제", + + "wildfire_gender.wardrobe.slider.breast_size": "가슴 크기: %s%%", + "wildfire_gender.wardrobe.slider.separation": "분할 정도: %s", + "wildfire_gender.wardrobe.slider.height": "높이: %s", + "wildfire_gender.wardrobe.slider.depth": "깊이: %s", + "wildfire_gender.wardrobe.slider.rotation": "분할 각도: %s도", + + "wildfire_gender.appearance_settings.title": "생김새 설정", + "wildfire_gender.char_settings.title": "캐릭터 설정", + "wildfire_gender.char_settings.physics": "가슴 흔들림: %s", + + "wildfire_gender.char_settings.override_armor_physics": "갑옷 흔들림: %s", + "wildfire_gender.tooltip.override_armor_physics.line1": "가슴 흔들림은 장착된 갑옷에 의해 더 이상 감소/억제되지 않습니다.", + "wildfire_gender.tooltip.override_armor_physics.line2": "이것은 갑옷을 숨기는 리소스팩 또는 갑옷을 변경하는 리소스팩과 함께 사용하기 위한 것입니다.", + + "wildfire_gender.char_settings.hide_in_armor": "갑옷을 입었을떄 숨기기: %s", + "wildfire_gender.char_settings.hurt_sounds": "여성적 소리: %s", + "wildfire_gender.tooltip.hurt_sounds": "성별이 여성 또는 기타로 설정되면 캐릭터가 데미지을 입을 때 여성적 상처음을 재생합니다.", + + "wildfire_gender.breast_customization.dual_physics": "개별 흔들림: %s", + + "wildfire_gender.label.gender": "성별", + "wildfire_gender.label.female": "여자", + "wildfire_gender.label.male": "남자", + "wildfire_gender.label.other": "기타", + + "wildfire_gender.label.enabled": "켜짐", + "wildfire_gender.label.disabled": "꺼짐", + "wildfire_gender.label.yes": "켜짐", + "wildfire_gender.label.no": "꺼짐", + "wildfire_gender.label.with_creator": "당신은 이 모드를 만든 사람과 같은 서버에 있습니다!", + + "wildfire_gender.slider.bounce": "흔들림 강도: %s%%", + "wildfire_gender.slider.floppy": "가슴 흔들림 정도: %s%%", + "wildfire_gender.slider.min_bounce": "이럴거면 흔들림이 왜 켜져있죠?", + "wildfire_gender.slider.max_bounce": "강력한 흔들림!!!", + "wildfire_gender.tooltip.bounce_warning": "흔들림 정도를 높은 값으로 설정하면 굉장히 부자연스러워 보입니다!", + + "wildfire_gender.cancer_awareness.title": "이번 달은 유방암 인식의 달이에요!", + "wildfire_gender.coming_soon": "곧 나옵니다!" +} From 6de893feb230c9a2b0c191913f1720d869870519 Mon Sep 17 00:00:00 2001 From: "bubble(good7777865)" <94590633+good7777865@users.noreply.github.com> Date: Thu, 4 Jul 2024 23:57:41 +0900 Subject: [PATCH 102/238] Fixed ko_kr.json --- .../assets/wildfire_gender/lang/ko_kr.json | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/resources/assets/wildfire_gender/lang/ko_kr.json b/src/main/resources/assets/wildfire_gender/lang/ko_kr.json index f13dee0d..66593f53 100644 --- a/src/main/resources/assets/wildfire_gender/lang/ko_kr.json +++ b/src/main/resources/assets/wildfire_gender/lang/ko_kr.json @@ -1,9 +1,9 @@ { - "category.wildfire_gender.generic": "Wildfire의 여성 성별 모드", - "key.wildfire_gender.gender_menu": "여성 성별 메뉴", + "category.wildfire_gender.generic": "Wildfire의 성별 모드", + "key.wildfire_gender.gender_menu": "성별 선택 메뉴", "toast.wildfire_gender.get_started": "'%s' 눌러 설정을 엽니다.", - "wildfire_gender.player_list.title": "여성 성별 모드", + "wildfire_gender.player_list.title": "성별 모드", "wildfire_gender.player_list.settings_button": "설정", "wildfire_gender.player_list.sync_status": "동기화 상태", "wildfire_gender.player_list.state.loading": "데이더 가져오는 중...", @@ -12,9 +12,9 @@ "wildfire_gender.player_list.breast_momentum": "가슴 흔들림 정도: %s%%", "wildfire_gender.player_list.female_sounds": "여성적 소리: %s", - "wildfire_gender.wardrobe.title": "Wildfire의 여성 성별 모드", + "wildfire_gender.wardrobe.title": "Wildfire의 성별 모드", "wildfire_gender.breast_customization.tab_customization": "커스터마이즈", - "wildfire_gender.breast_customization.tab_presets": "기본값", + "wildfire_gender.breast_customization.tab_presets": "사전 설정", "wildfire_gender.breast_customization.presets.add_new": "새로 추가...", "wildfire_gender.breast_customization.presets.delete": "삭제", @@ -31,17 +31,17 @@ "wildfire_gender.char_settings.override_armor_physics": "갑옷 흔들림: %s", "wildfire_gender.tooltip.override_armor_physics.line1": "가슴 흔들림은 장착된 갑옷에 의해 더 이상 감소/억제되지 않습니다.", - "wildfire_gender.tooltip.override_armor_physics.line2": "이것은 갑옷을 숨기는 리소스팩 또는 갑옷을 변경하는 리소스팩과 함께 사용하기 위한 것입니다.", + "wildfire_gender.tooltip.override_armor_physics.line2": "이것은 갑옷을 숨기거나 갑옷을 변경하는 리소스팩과 함께 사용하기 위한 것입니다.", - "wildfire_gender.char_settings.hide_in_armor": "갑옷을 입었을떄 숨기기: %s", + "wildfire_gender.char_settings.hide_in_armor": "갑옷을 입었을때 숨기기: %s", "wildfire_gender.char_settings.hurt_sounds": "여성적 소리: %s", - "wildfire_gender.tooltip.hurt_sounds": "성별이 여성 또는 기타로 설정되면 캐릭터가 데미지을 입을 때 여성적 상처음을 재생합니다.", + "wildfire_gender.tooltip.hurt_sounds": "성별이 여성 또는 기타로 설정되면 캐릭터가 데미지를 입을 때 여성적 상처음을 재생합니다.", "wildfire_gender.breast_customization.dual_physics": "개별 흔들림: %s", "wildfire_gender.label.gender": "성별", - "wildfire_gender.label.female": "여자", - "wildfire_gender.label.male": "남자", + "wildfire_gender.label.female": "여성", + "wildfire_gender.label.male": "남성", "wildfire_gender.label.other": "기타", "wildfire_gender.label.enabled": "켜짐", From dde91c1d23c7f744063f7e05e12b80c7bf68fcdc Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 19 Jul 2024 18:33:30 -0600 Subject: [PATCH 103/238] switch to using codecs for networking and armor stand nbt --- src/main/java/com/wildfire/main/Gender.java | 9 ++ .../com/wildfire/main/WildfireHelper.java | 28 +--- .../main/entitydata/BreastDataComponent.java | 86 ++++++++---- .../com/wildfire/main/entitydata/Breasts.java | 31 +++++ .../main/networking/AbstractSyncPacket.java | 130 ++++++++---------- .../networking/ClientboundSyncPacket.java | 15 +- .../networking/ServerboundSyncPacket.java | 15 +- .../mixins/ArmorStandEntityMixin.java | 19 +-- 8 files changed, 185 insertions(+), 148 deletions(-) diff --git a/src/main/java/com/wildfire/main/Gender.java b/src/main/java/com/wildfire/main/Gender.java index 72565c7d..52b88d56 100644 --- a/src/main/java/com/wildfire/main/Gender.java +++ b/src/main/java/com/wildfire/main/Gender.java @@ -18,11 +18,17 @@ package com.wildfire.main; +import io.netty.buffer.ByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.network.codec.PacketCodecs; import net.minecraft.sound.SoundEvent; import net.minecraft.text.Text; import net.minecraft.util.Formatting; +import net.minecraft.util.function.ValueLists; import org.jetbrains.annotations.Nullable; +import java.util.function.IntFunction; + public enum Gender { // NOTE: The order of these should remain unchanged! Changing these WILL modify player configs! @@ -30,6 +36,9 @@ public enum Gender { MALE(Text.translatable("wildfire_gender.label.male").formatted(Formatting.BLUE), false, null), OTHER(Text.translatable("wildfire_gender.label.other").formatted(Formatting.GREEN), true, WildfireSounds.FEMALE_HURT); + public static final IntFunction BY_ID = ValueLists.createIdToValueFunction(Gender::ordinal, values(), ValueLists.OutOfBoundsHandling.WRAP); + public static final PacketCodec CODEC = PacketCodecs.indexed(BY_ID, Gender::ordinal); + private final Text name; private final boolean canHaveBreasts; private final @Nullable SoundEvent hurtSound; diff --git a/src/main/java/com/wildfire/main/WildfireHelper.java b/src/main/java/com/wildfire/main/WildfireHelper.java index 0b6238b9..2ed24171 100644 --- a/src/main/java/com/wildfire/main/WildfireHelper.java +++ b/src/main/java/com/wildfire/main/WildfireHelper.java @@ -22,16 +22,14 @@ import com.wildfire.api.WildfireAPI; import com.wildfire.render.armor.SimpleGenderArmor; import com.wildfire.render.armor.EmptyGenderArmor; -import com.wildfire.main.config.FloatConfigKey; import net.minecraft.entity.EquipmentSlot; -import net.minecraft.item.*; -import net.minecraft.nbt.NbtCompound; +import net.minecraft.item.ArmorItem; +import net.minecraft.item.ArmorMaterial; +import net.minecraft.item.ArmorMaterials; +import net.minecraft.item.ItemStack; import net.minecraft.registry.entry.RegistryEntry; -import net.minecraft.util.math.MathHelper; -import java.util.Optional; import java.util.concurrent.ThreadLocalRandom; -import java.util.function.Function; public final class WildfireHelper { private WildfireHelper() { @@ -82,22 +80,4 @@ public static IGenderArmor getArmorConfig(ItemStack stack) { return EmptyGenderArmor.INSTANCE; } } - - /** - * Utility method returning an {@link Optional} containing the requested value from the provided {@link NbtCompound} - */ - public static Optional readNbt(NbtCompound compound, String key, Function reader) { - if(!compound.contains(key)) { - return Optional.empty(); - } - return Optional.of(reader.apply(key)); - } - - /** - * Variant of {@link #readNbt}, clamping a {@code float} value to the allowed range by the provided config key. - */ - public static Optional readNbt(NbtCompound compound, String key, FloatConfigKey configKey) { - return readNbt(compound, key, compound::getFloat) - .map(v -> MathHelper.clamp(v, configKey.getMinInclusive(), configKey.getMaxInclusive())); - } } diff --git a/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java b/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java index 652d4537..4d6b6300 100644 --- a/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java +++ b/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java @@ -1,18 +1,27 @@ package com.wildfire.main.entitydata; -import com.wildfire.main.WildfireHelper; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; import com.wildfire.main.config.Configuration; +import com.wildfire.main.config.FloatConfigKey; import net.minecraft.component.DataComponentTypes; import net.minecraft.component.type.NbtComponent; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerModelPart; import net.minecraft.item.ItemStack; -import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtElement; +import net.minecraft.nbt.NbtOps; +import net.minecraft.registry.RegistryOps; +import net.minecraft.registry.RegistryWrapper; +import net.minecraft.util.math.MathHelper; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.joml.Vector3f; +import java.util.function.Function; + /** *

Record class for storing player breast settings on armor equipped onto armor stands

* @@ -21,6 +30,35 @@ * for compatibility with vanilla clients on servers.

*/ public record BreastDataComponent(float breastSize, float cleavage, Vector3f offsets, boolean jacket, @Nullable NbtComponent nbtComponent) { + + private static Codec boundedFloat(FloatConfigKey configKey) { + return Codec.FLOAT.xmap(val -> MathHelper.clamp(val, configKey.getMinInclusive(), configKey.getMaxInclusive()), Function.identity()); + } + + private static final String KEY = "WildfireGender"; + private static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + boundedFloat(Configuration.BUST_SIZE) + .optionalFieldOf("BreastSize", 0f) + .forGetter(BreastDataComponent::breastSize), + boundedFloat(Configuration.BREASTS_CLEAVAGE) + .optionalFieldOf("Cleavage", Configuration.BREASTS_CLEAVAGE.getDefault()) + .forGetter(BreastDataComponent::cleavage), + Codec.BOOL + .optionalFieldOf("Jacket", true) + .forGetter(BreastDataComponent::jacket), + boundedFloat(Configuration.BREASTS_OFFSET_X) + .optionalFieldOf("XOffset", 0f) + .forGetter(component -> component.offsets.x), + boundedFloat(Configuration.BREASTS_OFFSET_Y) + .optionalFieldOf("YOffset", 0f) + .forGetter(component -> component.offsets.y), + boundedFloat(Configuration.BREASTS_OFFSET_Z) + .optionalFieldOf("ZOffset", 0f) + .forGetter(component -> component.offsets.y) + ).apply(instance, (breastSize, cleavage, jacket, x, y, z) -> new BreastDataComponent(breastSize, cleavage, new Vector3f(x, y, z), jacket, null)) + ); + private static final MapCodec MAP_CODEC = CODEC.fieldOf(KEY); + public static @Nullable BreastDataComponent fromPlayer(@NotNull PlayerEntity player, @NotNull PlayerConfig config) { if(!config.getGender().canHaveBreasts() || !config.showBreastsInArmor()) { return null; @@ -35,36 +73,34 @@ public record BreastDataComponent(float breastSize, float cleavage, Vector3f off return null; } - @SuppressWarnings("deprecation") NbtCompound root = component.getNbt(); - if(!root.contains("WildfireGender", NbtElement.COMPOUND_TYPE)) { + DataResult result = component.get(MAP_CODEC); + if(result.isError()) { return null; } - NbtCompound nbt = root.getCompound("WildfireGender"); - - float breastSize = WildfireHelper.readNbt(nbt, "BreastSize", Configuration.BUST_SIZE).orElse(0f); - float cleavage = WildfireHelper.readNbt(nbt, "Cleavage", Configuration.BREASTS_CLEAVAGE).orElseGet(Configuration.BREASTS_CLEAVAGE::getDefault); - boolean jacket = WildfireHelper.readNbt(nbt, "Jacket", nbt::getBoolean).orElse(true); - Vector3f offsets = new Vector3f( - WildfireHelper.readNbt(nbt, "XOffset", Configuration.BREASTS_OFFSET_X).orElse(0f), - WildfireHelper.readNbt(nbt, "YOffset", Configuration.BREASTS_OFFSET_Y).orElse(0f), - WildfireHelper.readNbt(nbt, "ZOffset", Configuration.BREASTS_OFFSET_Z).orElse(0f)); - return new BreastDataComponent(breastSize, cleavage, offsets, jacket, component); + return result.getOrThrow().withComponent(component); } - public void write(ItemStack stack) { + public void write(RegistryWrapper.WrapperLookup lookup, ItemStack stack) { if(stack.isEmpty()) { throw new IllegalArgumentException("The provided ItemStack must not be empty"); } - NbtCompound nbt = new NbtCompound(); - nbt.putFloat("BreastSize", breastSize); - nbt.putFloat("Cleavage", cleavage); - nbt.putFloat("XOffset", offsets.x); - nbt.putFloat("YOffset", offsets.y); - nbt.putFloat("ZOffset", offsets.z); - nbt.putBoolean("Jacket", jacket); - // see the class javadoc for why we're using the custom data component instead of using this class - // as its own data component type - NbtComponent.set(DataComponentTypes.CUSTOM_DATA, stack, stackNbt -> stackNbt.put("WildfireGender", nbt)); + + RegistryOps op = lookup.getOps(NbtOps.INSTANCE); + DataResult result = stack.getOrDefault(DataComponentTypes.CUSTOM_DATA, NbtComponent.DEFAULT).with(op, MAP_CODEC, this); + if(result.isSuccess()) { + stack.set(DataComponentTypes.CUSTOM_DATA, result.getOrThrow()); + } + } + + public static void removeFromStack(ItemStack stack) { + NbtComponent component = stack.get(DataComponentTypes.CUSTOM_DATA); + if(component != null && component.contains(KEY)) { + NbtComponent.set(DataComponentTypes.CUSTOM_DATA, stack, nbt -> nbt.remove(KEY)); + } + } + + private BreastDataComponent withComponent(NbtComponent component) { + return new BreastDataComponent(breastSize, cleavage, offsets, jacket, component); } } diff --git a/src/main/java/com/wildfire/main/entitydata/Breasts.java b/src/main/java/com/wildfire/main/entitydata/Breasts.java index c1f1a241..a50b25ff 100644 --- a/src/main/java/com/wildfire/main/entitydata/Breasts.java +++ b/src/main/java/com/wildfire/main/entitydata/Breasts.java @@ -20,6 +20,9 @@ import com.wildfire.main.config.ConfigKey; import com.wildfire.main.config.Configuration; +import io.netty.buffer.ByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.network.codec.PacketCodecs; import org.joml.Vector3f; import java.util.function.Consumer; @@ -30,6 +33,23 @@ @SuppressWarnings("UnusedReturnValue") public final class Breasts { + public static final PacketCodec CODEC = PacketCodec.tuple( + PacketCodecs.FLOAT, Breasts::getXOffset, + PacketCodecs.FLOAT, Breasts::getYOffset, + PacketCodecs.FLOAT, Breasts::getZOffset, + PacketCodecs.BOOL, Breasts::isUniboob, + PacketCodecs.FLOAT, Breasts::getCleavage, + (x, y, z, uniboob, cleavage) -> { + Breasts breasts = new Breasts(); + breasts.xOffset = x; + breasts.yOffset = y; + breasts.zOffset = z; + breasts.cleavage = cleavage; + breasts.uniboob = uniboob; + return breasts; + } + ); + private float xOffset = Configuration.BREASTS_OFFSET_X.getDefault(), yOffset = Configuration.BREASTS_OFFSET_Y.getDefault(), zOffset = Configuration.BREASTS_OFFSET_Z.getDefault(); @@ -137,4 +157,15 @@ public boolean isUniboob() { public boolean updateUniboob(boolean value) { return updateValue(Configuration.BREASTS_UNIBOOB, value, v -> this.uniboob = v); } + + /** + * Copy settings from the provided {@link Breasts breasts data} onto the current instance + */ + public void copyFrom(Breasts breasts) { + this.xOffset = breasts.xOffset; + this.yOffset = breasts.yOffset; + this.zOffset = breasts.zOffset; + this.cleavage = breasts.cleavage; + this.uniboob = breasts.uniboob; + } } diff --git a/src/main/java/com/wildfire/main/networking/AbstractSyncPacket.java b/src/main/java/com/wildfire/main/networking/AbstractSyncPacket.java index 5a260dcf..07dcdad8 100644 --- a/src/main/java/com/wildfire/main/networking/AbstractSyncPacket.java +++ b/src/main/java/com/wildfire/main/networking/AbstractSyncPacket.java @@ -18,96 +18,82 @@ package com.wildfire.main.networking; +import com.mojang.datafixers.util.Function6; import com.wildfire.main.entitydata.Breasts; import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.Gender; -import net.minecraft.network.PacketByteBuf; -import org.joml.Vector3f; +import io.netty.buffer.ByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.network.codec.PacketCodecs; +import net.minecraft.util.Uuids; import java.util.UUID; abstract class AbstractSyncPacket { - protected final UUID uuid; - private final Gender gender; - private final float bustSize; - - //physics variables - private final boolean breastPhysics; - private final boolean showInArmor; - private final float bounceMultiplier; - private final float floppyMultiplier; - - private final Vector3f offsets; - private final boolean uniboob; - private final float cleavage; - private final boolean hurtSounds; - - protected AbstractSyncPacket(PlayerConfig plr) { - this.uuid = plr.uuid; - this.gender = plr.getGender(); - this.bustSize = plr.getBustSize(); - this.hurtSounds = plr.hasHurtSounds(); - - //physics variables - this.breastPhysics = plr.hasBreastPhysics(); - this.showInArmor = plr.showBreastsInArmor(); - this.bounceMultiplier = plr.getBounceMultiplier(); - this.floppyMultiplier = plr.getFloppiness(); - - Breasts breasts = plr.getBreasts(); - this.offsets = breasts.getOffsets(); - this.uniboob = breasts.isUniboob(); - this.cleavage = breasts.getCleavage(); + protected static PacketCodec codec(SyncPacketConstructor constructor) { + return PacketCodec.tuple( + Uuids.PACKET_CODEC, p -> p.uuid, + Gender.CODEC, p -> p.gender, + PacketCodecs.FLOAT, p -> p.bustSize, + PacketCodecs.BOOL, p -> p.hurtSounds, + BreastPhysics.CODEC, p -> p.physics, + Breasts.CODEC, p -> p.breasts, + constructor + ); } - protected AbstractSyncPacket(PacketByteBuf buffer) { - this.uuid = buffer.readUuid(); - this.gender = buffer.readEnumConstant(Gender.class); - this.bustSize = buffer.readFloat(); - this.hurtSounds = buffer.readBoolean(); - - //physics variables - this.breastPhysics = buffer.readBoolean(); - this.showInArmor = buffer.readBoolean(); - this.bounceMultiplier = buffer.readFloat(); - this.floppyMultiplier = buffer.readFloat(); - - this.offsets = buffer.readVector3f(); - this.uniboob = buffer.readBoolean(); - this.cleavage = buffer.readFloat(); + protected final UUID uuid; + protected final Gender gender; + protected final float bustSize; + protected final boolean hurtSounds; + protected final BreastPhysics physics; + protected final Breasts breasts; + + protected AbstractSyncPacket(UUID uuid, Gender gender, float bustSize, boolean hurtSounds, BreastPhysics physics, Breasts breasts) { + this.uuid = uuid; + this.gender = gender; + this.bustSize = bustSize; + this.hurtSounds = hurtSounds; + this.physics = physics; + this.breasts = breasts; } - protected void encode(PacketByteBuf buffer) { - buffer.writeUuid(this.uuid); - buffer.writeEnumConstant(this.gender); - buffer.writeFloat(this.bustSize); - buffer.writeBoolean(this.hurtSounds); - buffer.writeBoolean(this.breastPhysics); - buffer.writeBoolean(this.showInArmor); - buffer.writeFloat(this.bounceMultiplier); - buffer.writeFloat(this.floppyMultiplier); - - buffer.writeVector3f(offsets); - buffer.writeBoolean(this.uniboob); - buffer.writeFloat(this.cleavage); + protected AbstractSyncPacket(PlayerConfig plr) { + this(plr.uuid, plr.getGender(), plr.getBustSize(), plr.hasHurtSounds(), new BreastPhysics(plr), plr.getBreasts()); } protected void updatePlayerFromPacket(PlayerConfig plr) { plr.updateGender(gender); plr.updateBustSize(bustSize); plr.updateHurtSounds(hurtSounds); + physics.applyTo(plr); + plr.getBreasts().copyFrom(breasts); + } + + protected record BreastPhysics(boolean physics, boolean showInArmor, float bounceMultiplier, float floppyMultiplier) { + + public static final PacketCodec CODEC = PacketCodec.tuple( + PacketCodecs.BOOL, BreastPhysics::physics, + PacketCodecs.BOOL, BreastPhysics::showInArmor, + PacketCodecs.FLOAT, BreastPhysics::bounceMultiplier, + PacketCodecs.FLOAT, BreastPhysics::floppyMultiplier, + BreastPhysics::new + ); + + private BreastPhysics(PlayerConfig plr) { + this(plr.hasBreastPhysics(), plr.showBreastsInArmor(), plr.getBounceMultiplier(), plr.getFloppiness()); + } + + private void applyTo(PlayerConfig plr) { + plr.updateBreastPhysics(physics); + plr.updateShowBreastsInArmor(showInArmor); + plr.updateBounceMultiplier(bounceMultiplier); + plr.updateFloppiness(floppyMultiplier); + } + } - //physics - plr.updateBreastPhysics(breastPhysics); - plr.updateShowBreastsInArmor(showInArmor); - plr.updateBounceMultiplier(bounceMultiplier); - plr.updateFloppiness(floppyMultiplier); - //System.out.println(plr.username + " - " + plr.gender); - - Breasts breasts = plr.getBreasts(); - breasts.updateOffsets(offsets); - breasts.updateUniboob(uniboob); - breasts.updateCleavage(cleavage); + @FunctionalInterface + protected interface SyncPacketConstructor extends Function6 { } } diff --git a/src/main/java/com/wildfire/main/networking/ClientboundSyncPacket.java b/src/main/java/com/wildfire/main/networking/ClientboundSyncPacket.java index 64412276..623bd57d 100644 --- a/src/main/java/com/wildfire/main/networking/ClientboundSyncPacket.java +++ b/src/main/java/com/wildfire/main/networking/ClientboundSyncPacket.java @@ -18,30 +18,33 @@ package com.wildfire.main.networking; +import com.wildfire.main.Gender; import com.wildfire.main.WildfireGender; +import com.wildfire.main.entitydata.Breasts; import com.wildfire.main.entitydata.PlayerConfig; +import io.netty.buffer.ByteBuf; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; -import net.minecraft.network.PacketByteBuf; -import net.minecraft.network.RegistryByteBuf; import net.minecraft.network.codec.PacketCodec; import net.minecraft.network.packet.CustomPayload; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.util.Identifier; +import java.util.UUID; + public final class ClientboundSyncPacket extends AbstractSyncPacket implements CustomPayload { public static final Id ID = new CustomPayload.Id<>(Identifier.of(WildfireGender.MODID, "sync")); - public static final PacketCodec CODEC = PacketCodec.of(ClientboundSyncPacket::encode, ClientboundSyncPacket::new); + public static final PacketCodec CODEC = codec(ClientboundSyncPacket::new); - ClientboundSyncPacket(PlayerConfig plr) { + public ClientboundSyncPacket(PlayerConfig plr) { super(plr); } - ClientboundSyncPacket(PacketByteBuf buffer) { - super(buffer); + private ClientboundSyncPacket(UUID uuid, Gender gender, float bustSize, boolean hurtSounds, BreastPhysics physics, Breasts breasts) { + super(uuid, gender, bustSize, hurtSounds, physics, breasts); } @Override diff --git a/src/main/java/com/wildfire/main/networking/ServerboundSyncPacket.java b/src/main/java/com/wildfire/main/networking/ServerboundSyncPacket.java index d767ed1a..c874896f 100644 --- a/src/main/java/com/wildfire/main/networking/ServerboundSyncPacket.java +++ b/src/main/java/com/wildfire/main/networking/ServerboundSyncPacket.java @@ -18,30 +18,33 @@ package com.wildfire.main.networking; +import com.wildfire.main.Gender; import com.wildfire.main.WildfireGender; +import com.wildfire.main.entitydata.Breasts; import com.wildfire.main.entitydata.PlayerConfig; +import io.netty.buffer.ByteBuf; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; -import net.minecraft.network.PacketByteBuf; -import net.minecraft.network.RegistryByteBuf; import net.minecraft.network.codec.PacketCodec; import net.minecraft.network.packet.CustomPayload; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.util.Identifier; +import java.util.UUID; + public final class ServerboundSyncPacket extends AbstractSyncPacket implements CustomPayload { public static final Id ID = new CustomPayload.Id<>(Identifier.of(WildfireGender.MODID, "send_gender_info")); - public static final PacketCodec CODEC = PacketCodec.of(ServerboundSyncPacket::encode, ServerboundSyncPacket::new); + public static final PacketCodec CODEC = codec(ServerboundSyncPacket::new); - ServerboundSyncPacket(PlayerConfig plr) { + public ServerboundSyncPacket(PlayerConfig plr) { super(plr); } - ServerboundSyncPacket(PacketByteBuf buffer) { - super(buffer); + private ServerboundSyncPacket(UUID uuid, Gender gender, float bustSize, boolean hurtSounds, BreastPhysics physics, Breasts breasts) { + super(uuid, gender, bustSize, hurtSounds, physics, breasts); } @Override diff --git a/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java b/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java index 235f746a..ef29054b 100644 --- a/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java +++ b/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java @@ -24,8 +24,6 @@ import com.wildfire.main.entitydata.BreastDataComponent; import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.WildfireHelper; -import net.minecraft.component.DataComponentTypes; -import net.minecraft.component.type.NbtComponent; import net.minecraft.entity.EntityType; import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.LivingEntity; @@ -34,7 +32,6 @@ import net.minecraft.item.ItemStack; import net.minecraft.world.World; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.ModifyArg; @@ -44,14 +41,6 @@ protected ArmorStandEntityMixin(EntityType entityType, W super(entityType, world); } - @Unique - private void wildfiregender$removeBreastDataFromStack(ItemStack stack) { - NbtComponent component = stack.get(DataComponentTypes.CUSTOM_DATA); - if(component != null && component.contains("WildfireGender")) { - NbtComponent.set(DataComponentTypes.CUSTOM_DATA, stack, nbt -> nbt.remove("WildfireGender")); - } - } - @ModifyArg( method = "equip", at = @At( @@ -72,7 +61,7 @@ protected ArmorStandEntityMixin(EntityType entityType, W // that may still have the tag from older versions, or from potential cross-mod interactions // which allow for removing items from armor stands without calling the vanilla // #equip and/or #onBreak methods - wildfiregender$removeBreastDataFromStack(stack); + BreastDataComponent.removeFromStack(stack); return stack; } @@ -80,7 +69,7 @@ protected ArmorStandEntityMixin(EntityType entityType, W if(armorConfig.armorStandsCopySettings()) { BreastDataComponent component = BreastDataComponent.fromPlayer(player, playerConfig); if(component != null) { - component.write(stack); + component.write(player.getWorld().getRegistryManager(), stack); } } @@ -97,7 +86,7 @@ protected ArmorStandEntityMixin(EntityType entityType, W ) public ItemStack wildfiregender$removeBreastDataOnReplace(ItemStack stack, @Local(argsOnly = true) PlayerEntity player) { if(!player.getWorld().isClient()) { - wildfiregender$removeBreastDataFromStack(stack); + BreastDataComponent.removeFromStack(stack); } return stack; } @@ -112,7 +101,7 @@ protected ArmorStandEntityMixin(EntityType entityType, W ) public ItemStack wildfiregender$removeBreastDataOnBreak(ItemStack stack) { if(!getWorld().isClient()) { - wildfiregender$removeBreastDataFromStack(stack); + BreastDataComponent.removeFromStack(stack); } return stack; } From 7f6df51257e650071555dd5b7f421cd19af4fabd Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 21 Jul 2024 16:41:11 -0600 Subject: [PATCH 104/238] remove unnecessary else --- .../com/wildfire/main/WildfireHelper.java | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/wildfire/main/WildfireHelper.java b/src/main/java/com/wildfire/main/WildfireHelper.java index 2ed24171..c74b74ad 100644 --- a/src/main/java/com/wildfire/main/WildfireHelper.java +++ b/src/main/java/com/wildfire/main/WildfireHelper.java @@ -50,34 +50,34 @@ public static IGenderArmor getArmorConfig(ItemStack stack) { if (WildfireAPI.getGenderArmors().get(stack.getItem()) != null) { return WildfireAPI.getGenderArmors().get(stack.getItem()); - } else { - //TODO: Fabric Alternative to Capabilities? Maybe someone can help with this? - if (stack.getItem() instanceof ArmorItem armorItem && armorItem.getSlotType() == EquipmentSlot.CHEST) { - //Start by checking if it is a vanilla chestplate as we have custom configurations for those we check against - // the armor material instead of the item instance in case any mods define custom armor items using vanilla - // materials as then we can make a better guess at what we want the default implementation to be - RegistryEntry material = armorItem.getMaterial(); - if (material == ArmorMaterials.LEATHER) { - return SimpleGenderArmor.LEATHER; - } else if (material == ArmorMaterials.CHAIN) { - return SimpleGenderArmor.CHAIN_MAIL; - } else if (material == ArmorMaterials.GOLD) { - return SimpleGenderArmor.GOLD; - } else if (material == ArmorMaterials.IRON) { - return SimpleGenderArmor.IRON; - } else if (material == ArmorMaterials.DIAMOND) { - return SimpleGenderArmor.DIAMOND; - } else if (material == ArmorMaterials.NETHERITE) { - return SimpleGenderArmor.NETHERITE; - } - //Otherwise just fallback to our default armor implementation - return SimpleGenderArmor.FALLBACK; + } + + //TODO: Fabric Alternative to Capabilities? Maybe someone can help with this? + if (stack.getItem() instanceof ArmorItem armorItem && armorItem.getSlotType() == EquipmentSlot.CHEST) { + //Start by checking if it is a vanilla chestplate as we have custom configurations for those we check against + // the armor material instead of the item instance in case any mods define custom armor items using vanilla + // materials as then we can make a better guess at what we want the default implementation to be + RegistryEntry material = armorItem.getMaterial(); + if (material == ArmorMaterials.LEATHER) { + return SimpleGenderArmor.LEATHER; + } else if (material == ArmorMaterials.CHAIN) { + return SimpleGenderArmor.CHAIN_MAIL; + } else if (material == ArmorMaterials.GOLD) { + return SimpleGenderArmor.GOLD; + } else if (material == ArmorMaterials.IRON) { + return SimpleGenderArmor.IRON; + } else if (material == ArmorMaterials.DIAMOND) { + return SimpleGenderArmor.DIAMOND; + } else if (material == ArmorMaterials.NETHERITE) { + return SimpleGenderArmor.NETHERITE; } - //If it is not an armor item default as if "nothing is being worn that covers the breast area" - // this might not be fully accurate and may need some tweaks but in general is likely relatively - // close to the truth of if it should render or not. This covers cases such as the elytra and - // other wearables - return EmptyGenderArmor.INSTANCE; + //Otherwise just fallback to our default armor implementation + return SimpleGenderArmor.FALLBACK; } + //If it is not an armor item default as if "nothing is being worn that covers the breast area" + // this might not be fully accurate and may need some tweaks but in general is likely relatively + // close to the truth of if it should render or not. This covers cases such as the elytra and + // other wearables + return EmptyGenderArmor.INSTANCE; } } From b90c2672ac812da533a7e59c4fe90b36c88d62d9 Mon Sep 17 00:00:00 2001 From: Alexsandr Feniksov Date: Tue, 13 Aug 2024 23:12:22 +0300 Subject: [PATCH 105/238] Create ru_ru.json --- .../assets/wildfire_gender/lang/ru_ru.json | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 src/main/resources/assets/wildfire_gender/lang/ru_ru.json diff --git a/src/main/resources/assets/wildfire_gender/lang/ru_ru.json b/src/main/resources/assets/wildfire_gender/lang/ru_ru.json new file mode 100644 index 00000000..c3fb194d --- /dev/null +++ b/src/main/resources/assets/wildfire_gender/lang/ru_ru.json @@ -0,0 +1,60 @@ +{ + "category.wildfire_gender.generic": "Wildfire's Female Gender Mod", + "key.wildfire_gender.gender_menu": "Меню", + + "wildfire_gender.hurt.female": "Female Player Hurt", + + "wildfire_gender.player_list.title": "Female Gender Mod", + "wildfire_gender.player_list.settings_button": "Настройки", + "wildfire_gender.player_list.sync_status": "Состояние синхронизации", + "wildfire_gender.player_list.state.loading": "Загрузка данных...", + "wildfire_gender.player_list.state.synced": "Игроки синхронизированны", + + "wildfire_gender.wardrobe.title": "Меню кастомизации", + "wildfire_gender.wardrobe.slider.breast_size": "Размер груди: %s%%", + "wildfire_gender.wardrobe.slider.separation": "Расстояние: %s", + "wildfire_gender.wardrobe.slider.height": "Высота: %s", + "wildfire_gender.wardrobe.slider.depth": "Глубина: %s", + "wildfire_gender.wardrobe.slider.rotation": "Вращение: %s градусов", + + "wildfire_gender.appearance_settings.title": "Настройки внешности", + "wildfire_gender.char_settings.title": "Настройки персонажа", + "wildfire_gender.char_settings.physics": "Физика груди: %s", + "wildfire_gender.tooltip.breast_physics": "Обеспечивает физику груди", + "wildfire_gender.char_settings.hide_in_armor": "Скрывать под броней: %s", + "wildfire_gender.tooltip.hide_in_armor": "Скрывает грудь при ношении Брони", + "wildfire_gender.char_settings.hurt_sounds": "Звуки при ранении: %s", + "wildfire_gender.tooltip.hurt_sounds": "Женские звуки при ранении", + + "wildfire_gender.breast_customization.dual_physics": "Двойная физика: %s", + + "wildfire_gender.player_list.bounce_multiplier": "Множитель отскока: %sx", + "wildfire_gender.player_list.breast_momentum": "Импульс груди: %s%%", + "wildfire_gender.player_list.female_sounds": "Женские звуки при ранении: %s", + + "wildfire_gender.settings.title": "Меню настроек Wildfire's", + + "wildfire_gender.acknowledge.confirm": "Окей", + + "wildfire_gender.label.gender": "Пол", + "wildfire_gender.label.female": "Женщина", + "wildfire_gender.label.male": "Мужчина", + "wildfire_gender.label.other": "Другой", + + "wildfire_gender.label.enabled": "Вкл", + "wildfire_gender.label.disabled": "Выкл", + "wildfire_gender.label.yes": "Да", + "wildfire_gender.label.no": "Нет", + "wildfire_gender.label.exit": "X", + "wildfire_gender.label.too_far": "Слишком Далеко", + "wildfire_gender.label.with_creator": "Вы играете на одном сервере с создателем этого мода!", + + "wildfire_gender.slider.bounce": "Упругость: %sx", + "wildfire_gender.slider.floppy": "Импульс груди: %s%%", + "wildfire_gender.slider.min_bounce": "Почему вообще включена Физика?", + "wildfire_gender.slider.max_bounce": "Физика груди как в Аниме!!!", + "wildfire_gender.tooltip.bounce_warning": "Установка высокого значения «Упругость» будет выглядеть очень неестественно!", + + "wildfire_gender.cancer_awareness.title": "Эй, сегодня месяц повышения осведомленности о раке молочной железы!", + "wildfire_gender.cancer_awareness.description": "Нажмите здесь, чтобы сделать пожертвование в фонд §dСьюзан Г. Комен§f!" +} \ No newline at end of file From 5e2c5b7ad7aa93f43a862b7904ac63faf0ae4da2 Mon Sep 17 00:00:00 2001 From: celeste Date: Wed, 14 Aug 2024 14:08:25 -0600 Subject: [PATCH 106/238] fix physics not counting skeleton/zombie horses as horses this also means that llamas are affected by this block, but that's an issue i don't find to be *as* bad as what this is fixing. --- src/main/java/com/wildfire/physics/BreastPhysics.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index 8053e196..484a90f1 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -240,7 +240,7 @@ public void update(LivingEntity entity, IGenderArmor armor) { this.targetBounceY = (Math.random() > 0.5 ? -bounceIntensity : bounceIntensity) / 6f; this.targetBounceY += breastWeight; } - } else if(entity.getVehicle() instanceof HorseEntity horse) { + } else if(entity.getVehicle() instanceof AbstractHorseEntity horse) { float movement = (float) horse.getVelocity().lengthSquared(); if(horse.age % clampMovement(movement) == 5 && movement > 0.05f) { this.targetBounceY = bounceIntensity / 4f; From aab3b843c683599076e86cfb07e7185be33d8c81 Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 22 Aug 2024 23:16:51 -0600 Subject: [PATCH 107/238] fix missing pre size set in simplified armor stand physics --- src/main/java/com/wildfire/physics/BreastPhysics.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index 484a90f1..e744817a 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -114,7 +114,7 @@ public void update(LivingEntity entity, IGenderArmor armor) { } this.preBreastSize = this.breastSize; } else { - this.breastSize = 0f; + this.preBreastSize = this.breastSize = 0f; } return; } From b299170bd3cf6148c72a4cbd9116de8dd3036981 Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 22 Aug 2024 23:19:51 -0600 Subject: [PATCH 108/238] make mixin classes package private non-accessor mixins shouldn't be accessed from non-mixin code, and this simply more strictly enforces that --- src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java | 2 +- src/main/java/com/wildfire/mixins/BreastPhysicsTickMixin.java | 2 +- src/main/java/com/wildfire/mixins/LivingEntityMixin.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java b/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java index ef29054b..b5099c70 100644 --- a/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java +++ b/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java @@ -36,7 +36,7 @@ import org.spongepowered.asm.mixin.injection.ModifyArg; @Mixin(ArmorStandEntity.class) -public abstract class ArmorStandEntityMixin extends LivingEntity { +abstract class ArmorStandEntityMixin extends LivingEntity { protected ArmorStandEntityMixin(EntityType entityType, World world) { super(entityType, world); } diff --git a/src/main/java/com/wildfire/mixins/BreastPhysicsTickMixin.java b/src/main/java/com/wildfire/mixins/BreastPhysicsTickMixin.java index f6833dc1..84d1e0a2 100644 --- a/src/main/java/com/wildfire/mixins/BreastPhysicsTickMixin.java +++ b/src/main/java/com/wildfire/mixins/BreastPhysicsTickMixin.java @@ -32,7 +32,7 @@ @Environment(EnvType.CLIENT) @Mixin({ArmorStandEntity.class, PlayerEntity.class}) -public abstract class BreastPhysicsTickMixin { +abstract class BreastPhysicsTickMixin { @Inject(at = @At("TAIL"), method = "tick") public void wildfiregender$tickBreastPhysics(CallbackInfo info) { LivingEntity entity = (LivingEntity)(Object)this; diff --git a/src/main/java/com/wildfire/mixins/LivingEntityMixin.java b/src/main/java/com/wildfire/mixins/LivingEntityMixin.java index 0cbb67dc..2bdf07ae 100644 --- a/src/main/java/com/wildfire/mixins/LivingEntityMixin.java +++ b/src/main/java/com/wildfire/mixins/LivingEntityMixin.java @@ -34,7 +34,7 @@ @Mixin(LivingEntity.class) @Environment(EnvType.CLIENT) -public abstract class LivingEntityMixin { +abstract class LivingEntityMixin { @Inject( method = "onDamaged", at = @At( From ad85d3de7604c8dc7cefdff2d915db21390ada59 Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 2 Sep 2024 21:25:07 -0600 Subject: [PATCH 109/238] change fall physics angle to randomize on initial upward velocity this is intended to make double jumps and the like appear slightly more natural, as one wouldn't expect that the breasts would continue to consistently rotate the exact same way the entire time until coming to a complete stop on the ground. --- .../java/com/wildfire/physics/BreastPhysics.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index e744817a..908ea111 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -52,7 +52,7 @@ public class BreastPhysics { private final EntityConfig entityConfig; private int randomB = 1; - private boolean alreadyFalling = false; + private double lastVerticalMoveVelocity; public BreastPhysics(EntityConfig entityConfig) { this.entityConfig = entityConfig; @@ -194,12 +194,14 @@ public void update(LivingEntity entity, IGenderArmor armor) { if(!entityConfig.getBreasts().isUniboob()) { bounceIntensity = bounceIntensity * WildfireHelper.randFloat(0.5f, 1.5f); } - if(entity.fallDistance > 0 && !alreadyFalling) { + + double vertVelocity = entity.getVelocity().y; + // Randomize which side the breast will angle toward when the player jumps/has upward velocity applied to them, + // or stops falling + if((lastVerticalMoveVelocity <= 0 && vertVelocity > 0) || (lastVerticalMoveVelocity < 0 && vertVelocity == 0)) { randomB = entity.getWorld().random.nextBoolean() ? -1 : 1; - alreadyFalling = true; } - if(entity.fallDistance == 0) alreadyFalling = false; - + lastVerticalMoveVelocity = vertVelocity; this.targetBounceY = (float) motion.y * bounceIntensity; this.targetBounceY += breastWeight; From d7ea9b081fe390c320f9fff72a318482058f9bcc Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 10 Oct 2024 13:01:00 -0600 Subject: [PATCH 110/238] 1.21.2-pre3 --- build.gradle | 4 +- gradle.properties | 8 +- gradle/wrapper/gradle-wrapper.properties | 2 +- .../java/com/wildfire/api/WildfireAPI.java | 5 +- .../gui/WildfireBreastPresetList.java | 5 +- .../gui/screen/WardrobeBrowserScreen.java | 5 +- .../WildfireCharacterSettingsScreen.java | 3 +- .../wildfire/main/WildfireEventHandler.java | 8 +- .../com/wildfire/main/WildfireGender.java | 4 +- .../wildfire/main/WildfireGenderClient.java | 11 ++- .../com/wildfire/main/WildfireHelper.java | 47 ++++----- .../com/wildfire/main/WildfireSounds.java | 2 +- .../main/entitydata/BreastDataComponent.java | 2 +- .../main/entitydata/EntityConfig.java | 4 +- .../LivingEntityRenderStateMixin.java | 45 +++++++++ .../LivingEntityRendererMixin.java | 51 ++++++++++ .../com/wildfire/physics/BreastPhysics.java | 4 +- .../com/wildfire/render/GenderArmorLayer.java | 73 ++++++++------ .../java/com/wildfire/render/GenderLayer.java | 95 ++++++++++++------- .../render/RenderStateEntityCapture.java | 28 ++++++ src/main/resources/fabric.mod.json | 2 +- .../resources/wildfire_gender.accesswidener | 3 - .../resources/wildfire_gender.mixins.json | 4 +- 23 files changed, 286 insertions(+), 129 deletions(-) create mode 100644 src/main/java/com/wildfire/mixins/renderstate/LivingEntityRenderStateMixin.java create mode 100644 src/main/java/com/wildfire/mixins/renderstate/LivingEntityRendererMixin.java create mode 100644 src/main/java/com/wildfire/render/RenderStateEntityCapture.java diff --git a/build.gradle b/build.gradle index fa7e309a..edb328d0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '1.6-SNAPSHOT' + id 'fabric-loom' version '1.8-SNAPSHOT' id 'maven-publish' } @@ -17,7 +17,7 @@ repositories { dependencies { // To change the versions see the gradle.properties file minecraft "com.mojang:minecraft:${project.minecraft_version}" - mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" + mappings "net.fabricmc:yarn:${project.minecraft_version}+build.${project.yarn_build}:v2" modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" modImplementation fabricApi.module("fabric-networking-api-v1", project.fabric_version) diff --git a/gradle.properties b/gradle.properties index 57acd116..78d58cd4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,9 +4,9 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://fabricmc.net/develop # Note that this variable does *not* define the minimum required version in fabric.mod.json! -minecraft_version=1.21 -yarn_mappings=1.21+build.1 -loader_version=0.15.11 +minecraft_version=1.21.2-pre3 +yarn_build=4 +loader_version=0.16.7 # Mod Properties mod_version = 3.2.1 @@ -14,4 +14,4 @@ maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod # Dependencies -fabric_version=0.100.1+1.21 +fabric_version=0.105.4+1.21.2 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 20db9ad5..707e499a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/com/wildfire/api/WildfireAPI.java b/src/main/java/com/wildfire/api/WildfireAPI.java index 4917fb5e..94ab9074 100644 --- a/src/main/java/com/wildfire/api/WildfireAPI.java +++ b/src/main/java/com/wildfire/api/WildfireAPI.java @@ -32,9 +32,8 @@ import java.util.HashMap; import java.util.Map; -import java.util.Optional; import java.util.UUID; -import java.util.concurrent.Future; +import java.util.concurrent.CompletableFuture; @SuppressWarnings("unused") public class WildfireAPI { @@ -83,7 +82,7 @@ public static void addGenderArmor(Item item, IGenderArmor genderArmor) { * @param markForSync true if you want to send the gender settings to the server upon loading. */ @Environment(EnvType.CLIENT) - public static Future> loadGenderInfo(UUID uuid, boolean markForSync) { + public static CompletableFuture<@Nullable PlayerConfig> loadGenderInfo(UUID uuid, boolean markForSync) { return WildfireGenderClient.loadGenderInfo(uuid, markForSync); } diff --git a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java index 1594d6ee..193186e0 100644 --- a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java +++ b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java @@ -11,6 +11,7 @@ import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; import net.minecraft.client.gui.widget.EntryListWidget; +import net.minecraft.client.render.RenderLayer; import net.minecraft.text.Text; import net.minecraft.util.Identifier; @@ -81,7 +82,7 @@ protected void renderList(DrawContext context, int mouseX, int mouseY, float del } @Override - protected int getRowTop(int index) { + public int getRowTop(int index) { return this.getY() - (int)this.getScrollAmount() + index * this.itemHeight + this.headerHeight; } @@ -146,7 +147,7 @@ public void render(DrawContext ctx, int index, int y, int x, int entryWidth, int TextRenderer font = MinecraftClient.getInstance().textRenderer; //ctx.fill(x, y, x + entryWidth, y + entryHeight, 0x55005555); - ctx.drawTexture(thumbnail, x + 2, y + 2, 0, 0, 28, 28, 28,28); + ctx.drawTexture(RenderLayer::getGuiTextured, thumbnail, x + 2, y + 2, 0, 0, 28, 28, 28, 28); ctx.drawText(font, Text.of(nInfo.name), x + 34, y + 4, 0xFFFFFFFF, false); //ctx.drawText(font, Text.translatable("07/25/2023 1:19 AM").formatted(Formatting.ITALIC), x + 34, y + 20, 0xFF888888, false); diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index f17f759a..20711820 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -33,6 +33,7 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.render.RenderLayer; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.text.Text; import net.minecraft.util.Formatting; @@ -91,7 +92,7 @@ public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delt PlayerConfig plr = getPlayer(); if(plr == null) return; Identifier backgroundTexture = plr.getGender().canHaveBreasts() ? BACKGROUND_FEMALE : BACKGROUND; - ctx.drawTexture(backgroundTexture, (this.width - 248) / 2, (this.height - 134) / 2, 0, 0, 248, 156); + ctx.drawTexture(RenderLayer::getGuiTextured, backgroundTexture, (this.width - 248) / 2, (this.height - 134) / 2, 0, 0, 248, 156, 256, 256); if(client != null && client.world != null) { int xP = this.width / 2 - 82; @@ -127,7 +128,7 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { int bcaY = y - 45; ctx.fill(x - 159, bcaY + 106, x + 159, bcaY + 136, 0x55000000); ctx.drawTextWithShadow(textRenderer, Text.translatable("wildfire_gender.cancer_awareness.title").formatted(Formatting.BOLD, Formatting.ITALIC), this.width / 2 - 148, bcaY + 117, 0xFFFFFF); - ctx.drawTexture(TXTR_RIBBON, x + 130, bcaY + 109, 26, 26, 0, 0, 20, 20, 20, 20); + ctx.drawTexture(RenderLayer::getGuiTextured, TXTR_RIBBON, x + 130, bcaY + 109, 0, 0, 26, 26, 20, 20, 20, 20); } } } \ No newline at end of file diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java index 458f726c..282a30d2 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java @@ -34,6 +34,7 @@ import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.render.RenderLayer; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.text.Text; import net.minecraft.util.Formatting; @@ -128,7 +129,7 @@ public void init() { @Override public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delta) { super.renderBackground(ctx, mouseX, mouseY, delta); - ctx.drawTexture(BACKGROUND, (this.width - 172) / 2, (this.height - 124) / 2, 0, 0, 172, 144); + ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND, (this.width - 172) / 2, (this.height - 124) / 2, 0, 0, 172, 144, 256, 256); } @Override diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index dcab67b3..76419a7a 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -79,7 +79,7 @@ private WildfireEventHandler() { } /** - * Register all events applicable the server-side for both a dedicated server and singleplayer + * Register all events applicable to the server-side for both a dedicated server and singleplayer */ public static void registerCommonEvents() { EntityTrackingEvents.START_TRACKING.register(WildfireEventHandler::onBeginTracking); @@ -102,14 +102,14 @@ public static void registerClientEvents() { * Attach breast render layers to players and armor stands */ @Environment(EnvType.CLIENT) - private static void registerRenderLayers(EntityType entityType, LivingEntityRenderer entityRenderer, + private static void registerRenderLayers(EntityType entityType, LivingEntityRenderer entityRenderer, LivingEntityFeatureRendererRegistrationCallback.RegistrationHelper registrationHelper, EntityRendererFactory.Context context) { if(entityRenderer instanceof PlayerEntityRenderer playerRenderer) { registrationHelper.register(new GenderLayer<>(playerRenderer)); - registrationHelper.register(new GenderArmorLayer<>(playerRenderer, context.getModelManager())); + registrationHelper.register(new GenderArmorLayer<>(playerRenderer, context.getModelManager(), context.getEquipmentModelLoader())); } else if(entityRenderer instanceof ArmorStandEntityRenderer armorStandRenderer) { - registrationHelper.register(new GenderArmorLayer<>(armorStandRenderer, context.getModelManager())); + registrationHelper.register(new GenderArmorLayer<>(armorStandRenderer, context.getModelManager(), context.getEquipmentModelLoader())); } } diff --git a/src/main/java/com/wildfire/main/WildfireGender.java b/src/main/java/com/wildfire/main/WildfireGender.java index 47b70001..8aba972d 100644 --- a/src/main/java/com/wildfire/main/WildfireGender.java +++ b/src/main/java/com/wildfire/main/WildfireGender.java @@ -18,9 +18,9 @@ package com.wildfire.main; +import java.util.HashMap; import java.util.Map; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; import com.mojang.logging.LogUtils; import com.wildfire.main.entitydata.PlayerConfig; @@ -33,7 +33,7 @@ public class WildfireGender implements ModInitializer { public static final String MODID = "wildfire_gender"; public static final Logger LOGGER = LogUtils.getLogger(); - public static final Map PLAYER_CACHE = new ConcurrentHashMap<>(); + public static final Map PLAYER_CACHE = new HashMap<>(); @Override public void onInitialize() { diff --git a/src/main/java/com/wildfire/main/WildfireGenderClient.java b/src/main/java/com/wildfire/main/WildfireGenderClient.java index b845fcd5..0439576d 100644 --- a/src/main/java/com/wildfire/main/WildfireGenderClient.java +++ b/src/main/java/com/wildfire/main/WildfireGenderClient.java @@ -24,13 +24,16 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.util.Util; +import org.jetbrains.annotations.Nullable; -import java.util.Optional; import java.util.UUID; -import java.util.concurrent.Future; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; @Environment(EnvType.CLIENT) public class WildfireGenderClient implements ClientModInitializer { + private static final Executor LOAD_EXECUTOR = Util.getIoWorkerExecutor().named("wildfire_gender$loadPlayerData"); + @Override public void onInitializeClient() { WildfireSounds.register(); @@ -38,8 +41,8 @@ public void onInitializeClient() { WildfireEventHandler.registerClientEvents(); } - public static Future> loadGenderInfo(UUID uuid, boolean markForSync) { - return Util.getIoWorkerExecutor().submit(() -> Optional.ofNullable(PlayerConfig.loadCachedPlayer(uuid, markForSync))); + public static CompletableFuture<@Nullable PlayerConfig> loadGenderInfo(UUID uuid, boolean markForSync) { + return CompletableFuture.supplyAsync(() -> PlayerConfig.loadCachedPlayer(uuid, markForSync), LOAD_EXECUTOR); } public static void loadPlayerIfMissing(UUID uuid, boolean markForSync) { diff --git a/src/main/java/com/wildfire/main/WildfireHelper.java b/src/main/java/com/wildfire/main/WildfireHelper.java index c74b74ad..e174a966 100644 --- a/src/main/java/com/wildfire/main/WildfireHelper.java +++ b/src/main/java/com/wildfire/main/WildfireHelper.java @@ -22,16 +22,26 @@ import com.wildfire.api.WildfireAPI; import com.wildfire.render.armor.SimpleGenderArmor; import com.wildfire.render.armor.EmptyGenderArmor; +import net.minecraft.component.DataComponentTypes; import net.minecraft.entity.EquipmentSlot; -import net.minecraft.item.ArmorItem; -import net.minecraft.item.ArmorMaterial; -import net.minecraft.item.ArmorMaterials; import net.minecraft.item.ItemStack; -import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.item.equipment.EquipmentModels; +import net.minecraft.util.Identifier; +import java.util.Map; import java.util.concurrent.ThreadLocalRandom; public final class WildfireHelper { + // TODO migrate this from being hardcoded to being provided by resource packs instead? + private static final Map VANILLA_ARMORS = Map.of( + EquipmentModels.LEATHER, SimpleGenderArmor.LEATHER, + EquipmentModels.CHAINMAIL, SimpleGenderArmor.CHAIN_MAIL, + EquipmentModels.IRON, SimpleGenderArmor.IRON, + EquipmentModels.GOLD, SimpleGenderArmor.GOLD, + EquipmentModels.DIAMOND, SimpleGenderArmor.DIAMOND, + EquipmentModels.NETHERITE, SimpleGenderArmor.NETHERITE + ); + private WildfireHelper() { throw new UnsupportedOperationException(); } @@ -53,31 +63,12 @@ public static IGenderArmor getArmorConfig(ItemStack stack) { } //TODO: Fabric Alternative to Capabilities? Maybe someone can help with this? - if (stack.getItem() instanceof ArmorItem armorItem && armorItem.getSlotType() == EquipmentSlot.CHEST) { - //Start by checking if it is a vanilla chestplate as we have custom configurations for those we check against - // the armor material instead of the item instance in case any mods define custom armor items using vanilla - // materials as then we can make a better guess at what we want the default implementation to be - RegistryEntry material = armorItem.getMaterial(); - if (material == ArmorMaterials.LEATHER) { - return SimpleGenderArmor.LEATHER; - } else if (material == ArmorMaterials.CHAIN) { - return SimpleGenderArmor.CHAIN_MAIL; - } else if (material == ArmorMaterials.GOLD) { - return SimpleGenderArmor.GOLD; - } else if (material == ArmorMaterials.IRON) { - return SimpleGenderArmor.IRON; - } else if (material == ArmorMaterials.DIAMOND) { - return SimpleGenderArmor.DIAMOND; - } else if (material == ArmorMaterials.NETHERITE) { - return SimpleGenderArmor.NETHERITE; - } - //Otherwise just fallback to our default armor implementation - return SimpleGenderArmor.FALLBACK; + var equippable = stack.get(DataComponentTypes.EQUIPPABLE); + if(equippable != null && equippable.slot() == EquipmentSlot.CHEST) { + var model = equippable.model(); + return model.map(VANILLA_ARMORS::get).orElse(SimpleGenderArmor.FALLBACK); } - //If it is not an armor item default as if "nothing is being worn that covers the breast area" - // this might not be fully accurate and may need some tweaks but in general is likely relatively - // close to the truth of if it should render or not. This covers cases such as the elytra and - // other wearables + return EmptyGenderArmor.INSTANCE; } } diff --git a/src/main/java/com/wildfire/main/WildfireSounds.java b/src/main/java/com/wildfire/main/WildfireSounds.java index 07285fe7..8835b07a 100644 --- a/src/main/java/com/wildfire/main/WildfireSounds.java +++ b/src/main/java/com/wildfire/main/WildfireSounds.java @@ -31,6 +31,6 @@ private WildfireSounds() { public static final SoundEvent FEMALE_HURT = SoundEvent.of(Identifier.of(WildfireGender.MODID, "female_hurt")); static void register() { - Registry.register(Registries.SOUND_EVENT, FEMALE_HURT.getId(), FEMALE_HURT); + Registry.register(Registries.SOUND_EVENT, FEMALE_HURT.id(), FEMALE_HURT); } } diff --git a/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java b/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java index 4d6b6300..00a16c56 100644 --- a/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java +++ b/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java @@ -23,7 +23,7 @@ import java.util.function.Function; /** - *

Record class for storing player breast settings on armor equipped onto armor stands

+ *

Data component-like class for storing player breast settings on armor equipped onto armor stands

* *

Note that while this is treated similarly to any other {@link DataComponentTypes data component} for performance reasons, * this is never written as its own component on item stacks, but instead uses the {@link DataComponentTypes#CUSTOM_DATA custom NBT data component} diff --git a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java index cf9967b7..c591745b 100644 --- a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java +++ b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java @@ -36,10 +36,10 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.HashMap; import java.util.Objects; import java.util.Map; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; /** *

A stripped down version of a {@link PlayerConfig player's config}, intended for use with non-player entities.

@@ -50,7 +50,7 @@ */ public class EntityConfig { - public static final Map ENTITY_CACHE = new ConcurrentHashMap<>(); + public static final Map ENTITY_CACHE = new HashMap<>(); public final UUID uuid; protected Gender gender = Configuration.GENDER.getDefault(); diff --git a/src/main/java/com/wildfire/mixins/renderstate/LivingEntityRenderStateMixin.java b/src/main/java/com/wildfire/mixins/renderstate/LivingEntityRenderStateMixin.java new file mode 100644 index 00000000..eca86a2b --- /dev/null +++ b/src/main/java/com/wildfire/mixins/renderstate/LivingEntityRenderStateMixin.java @@ -0,0 +1,45 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.mixins.renderstate; + +import com.wildfire.render.RenderStateEntityCapture; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.render.entity.state.LivingEntityRenderState; +import net.minecraft.entity.LivingEntity; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Implements; +import org.spongepowered.asm.mixin.Interface; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; + +@Mixin(LivingEntityRenderState.class) +@Implements(@Interface(iface = RenderStateEntityCapture.class, prefix = "wildfire_gender$")) +@Environment(EnvType.CLIENT) +abstract class LivingEntityRenderStateMixin { + private @Unique @Nullable LivingEntity wildfire_gender$entity = null; + + public @Nullable LivingEntity wildfire_gender$getEntity() { + return wildfire_gender$entity; + } + + public void wildfire_gender$setEntity(LivingEntity entity) { + this.wildfire_gender$entity = entity; + } +} diff --git a/src/main/java/com/wildfire/mixins/renderstate/LivingEntityRendererMixin.java b/src/main/java/com/wildfire/mixins/renderstate/LivingEntityRendererMixin.java new file mode 100644 index 00000000..058e64b5 --- /dev/null +++ b/src/main/java/com/wildfire/mixins/renderstate/LivingEntityRendererMixin.java @@ -0,0 +1,51 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.mixins.renderstate; + +import com.wildfire.render.RenderStateEntityCapture; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.render.entity.LivingEntityRenderer; +import net.minecraft.client.render.entity.state.LivingEntityRenderState; +import net.minecraft.entity.LivingEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(LivingEntityRenderer.class) +@Environment(EnvType.CLIENT) +abstract class LivingEntityRendererMixin { + /* + * This is a terrible, horrible, no good, very bad bodge solution, which ideally we wouldn't need to do to begin with, + * but unfortunately with how this mod works, and with the changes made to entity renderers, we do. + * + * As of 24w33a (1.21.2), Mojang has replaced the entity reference provided to feature renderer instances with an + * _incredibly_ simplified render state object; while the player render state does provide a way for us to get + * the entity (by means of a provided entity ID), armor stands (and by extension all other entity types) lack + * this same entity ID property. + * + * As such, the only real way to actually get the rendered entity for non-player entities is to capture it in a + * variable further up the chain, before the game actually runs any feature renderers. + */ + @Inject(method = "updateRenderState(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/client/render/entity/state/LivingEntityRenderState;F)V", at = @At("TAIL")) + public void wildfiregender$captureEntityRenderState(LivingEntity entity, LivingEntityRenderState state, float tickDelta, CallbackInfo ci) { + ((RenderStateEntityCapture)state).setEntity(entity); + } +} diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index 908ea111..523227cb 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -228,8 +228,8 @@ public void update(LivingEntity entity, IGenderArmor armor) { //button option for extra entities if(entity.getVehicle() != null) { if(entity.getVehicle() instanceof BoatEntity boat) { - int rowTime = (int) boat.interpolatePaddlePhase(0, entity.limbAnimator.getPos()); - int rowTime2 = (int) boat.interpolatePaddlePhase(1, entity.limbAnimator.getPos()); + int rowTime = (int) boat.lerpPaddlePhase(0, entity.limbAnimator.getPos()); + int rowTime2 = (int) boat.lerpPaddlePhase(1, entity.limbAnimator.getPos()); float rotationL = (float) MathHelper.clampedLerp(-(float)Math.PI / 3F, -0.2617994F, (double) ((MathHelper.sin(-rowTime2) + 1.0F) / 2.0F)); float rotationR = (float) MathHelper.clampedLerp(-(float)Math.PI / 4F, (float)Math.PI / 4F, (double) ((MathHelper.sin(-rowTime + 1.0F) + 1.0F) / 2.0F)); diff --git a/src/main/java/com/wildfire/render/GenderArmorLayer.java b/src/main/java/com/wildfire/render/GenderArmorLayer.java index a66373e9..8293098d 100644 --- a/src/main/java/com/wildfire/render/GenderArmorLayer.java +++ b/src/main/java/com/wildfire/render/GenderArmorLayer.java @@ -27,8 +27,10 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.client.render.*; +import net.minecraft.client.render.entity.equipment.EquipmentModelLoader; import net.minecraft.client.render.entity.feature.FeatureRendererContext; import net.minecraft.client.render.entity.model.BipedEntityModel; +import net.minecraft.client.render.entity.state.BipedEntityRenderState; import net.minecraft.client.render.item.ItemRenderer; import net.minecraft.client.render.model.BakedModelManager; import net.minecraft.client.texture.Sprite; @@ -40,20 +42,20 @@ import net.minecraft.entity.LivingEntity; import net.minecraft.entity.decoration.ArmorStandEntity; import net.minecraft.entity.player.PlayerModelPart; -import net.minecraft.item.ArmorItem; -import net.minecraft.item.ArmorMaterial; import net.minecraft.item.ItemStack; -import net.minecraft.item.trim.ArmorTrim; -import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.item.equipment.EquipmentModel; +import net.minecraft.item.equipment.trim.ArmorTrim; import net.minecraft.registry.tag.ItemTags; import net.minecraft.util.Identifier; import net.minecraft.util.math.ColorHelper; -import org.jetbrains.annotations.NotNull; + +import java.util.Objects; @Environment(EnvType.CLIENT) -public class GenderArmorLayer> extends GenderLayer { +public class GenderArmorLayer> extends GenderLayer { private final SpriteAtlasTexture armorTrimsAtlas; + private final EquipmentModelLoader equipmentModelLoader; protected static final BreastModelBox lBoobArmor, rBoobArmor; protected static final BreastModelBox lTrim, rTrim; private EntityConfig entityConfig; @@ -66,24 +68,27 @@ public class GenderArmorLayer render, BakedModelManager bakery) { + public GenderArmorLayer(FeatureRendererContext render, BakedModelManager bakery, EquipmentModelLoader equipmentModelLoader) { super(render); - armorTrimsAtlas = bakery.getAtlas(TexturedRenderLayers.ARMOR_TRIMS_ATLAS_TEXTURE); + this.armorTrimsAtlas = bakery.getAtlas(TexturedRenderLayers.ARMOR_TRIMS_ATLAS_TEXTURE); + this.equipmentModelLoader = equipmentModelLoader; } @Override - public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light, @NotNull T ent, float limbAngle, - float limbDistance, float partialTicks, float animationProgress, float headYaw, float headPitch) { + public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light, S state, float limbAngle, float limbDistance) { MinecraftClient client = MinecraftClient.getInstance(); if(client.player == null) { // we're currently in a menu, give up rendering before we crash the game return; } - final ItemStack chestplate = ent.getEquippedStack(EquipmentSlot.CHEST); - // If the entity has no armor to render, just immediately give up - // Note that we have to be very fast at abandoning rendering here, as this class is also attached to armor stands - if(chestplate.isEmpty() || !(chestplate.getItem() instanceof ArmorItem)) return; + LivingEntity ent = getEntity(state); + if(ent == null) return; + + final ItemStack chestplate = state.equippedChestStack; + // Check if the worn item in the chest slot is actually equippable in the chest slot, and has a model to render + var component = chestplate.get(DataComponentTypes.EQUIPPABLE); + if(component == null || component.slot() != EquipmentSlot.CHEST || component.model().isEmpty()) return; // And similarly just entirely give up if the item has a renderer registered with Fabric API // This will likely result in the player's breasts sticking out through the armor layer unless the mod in question // implements an IGenderArmor to prevent them from rendering entirely, but oh well; at least we won't be @@ -95,22 +100,27 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume entityConfig = EntityConfig.getEntity(ent); if(entityConfig == null) return; - if(!setupRender(ent, entityConfig, partialTicks)) return; + if(!setupRender(state, entityConfig)) return; if(ent instanceof ArmorStandEntity && !genderArmor.armorStandsCopySettings()) return; - final RegistryEntry material = ((ArmorItem) chestplate.getItem()).getMaterial(); - final int color = chestplate.isIn(ItemTags.DYEABLE) ? ColorHelper.Argb.fullAlpha(DyedColorComponent.getColor(chestplate, -6265536)) : -1; - final boolean glint = chestplate.hasGlint(); - - renderSides(ent, getContextModel(), matrixStack, side -> { - material.value().layers().forEach(layer -> { - int layerColor = layer.isDyeable() ? color : -1; - renderBreastArmor(layer.getTexture(false), matrixStack, vertexConsumerProvider, light, side, layerColor, glint); + int color = chestplate.isIn(ItemTags.DYEABLE) ? DyedColorComponent.getColor(chestplate, -1) : -1; + boolean glint = chestplate.hasGlint(); + + renderSides(state, getContextModel(), matrixStack, side -> { + var modelId = component.model().orElseThrow(); + equipmentModelLoader.get(modelId).getLayers(EquipmentModel.LayerType.HUMANOID).forEach(layer -> { + // mojang what the Optional hell is this + int layerColor = layer.dyeable().map(dye -> { + int defaultColor = dye.colorWhenUndyed().map(ColorHelper::fullAlpha).orElse(-1); + return color != -1 ? color : defaultColor; + }).orElse(-1); + var texture = layer.getFullTextureId(EquipmentModel.LayerType.HUMANOID); + renderBreastArmor(texture, matrixStack, vertexConsumerProvider, light, side, layerColor, glint); }); - ArmorTrim trim = armorStack.get(DataComponentTypes.TRIM); + var trim = armorStack.get(DataComponentTypes.TRIM); if(trim != null) { - renderArmorTrim(material, matrixStack, vertexConsumerProvider, light, trim, glint, side); + renderArmorTrim(modelId, matrixStack, vertexConsumerProvider, light, trim, glint, side); } }); } catch(Exception e) { @@ -124,8 +134,9 @@ protected void resizeBox(float breastSize) { } @Override - protected void setupTransformations(T entity, M model, MatrixStack matrixStack, BreastSide side) { - super.setupTransformations(entity, model, matrixStack, side); + protected void setupTransformations(S state, M model, MatrixStack matrixStack, BreastSide side) { + super.setupTransformations(state, model, matrixStack, side); + LivingEntity entity = Objects.requireNonNull(getEntity(state), "getEntity()"); if((entity instanceof AbstractClientPlayerEntity player && player.isPartVisible(PlayerModelPart.JACKET)) || (entity instanceof ArmorStandEntity && entityConfig.hasJacketLayer())) { matrixStack.translate(0, 0, -0.015f); @@ -141,15 +152,15 @@ protected void renderBreastArmor(Identifier texture, MatrixStack matrixStack, Ve BreastModelBox armor = side.isLeft ? lBoobArmor : rBoobArmor; RenderLayer armorType = RenderLayer.getArmorCutoutNoCull(texture); VertexConsumer armorVertexConsumer = ItemRenderer.getArmorGlintConsumer(vertexConsumerProvider, armorType, glint); - renderBox(armor, matrixStack, armorVertexConsumer, light, OverlayTexture.DEFAULT_UV, ColorHelper.Argb.fullAlpha(color)); + renderBox(armor, matrixStack, armorVertexConsumer, light, OverlayTexture.DEFAULT_UV, ColorHelper.fullAlpha(color)); } - protected void renderArmorTrim(RegistryEntry material, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, + protected void renderArmorTrim(Identifier model, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, ArmorTrim trim, boolean hasGlint, BreastSide side) { BreastModelBox trimModelBox = side.isLeft ? lTrim : rTrim; - Sprite sprite = this.armorTrimsAtlas.getSprite(trim.getGenericModelId(material)); + Sprite sprite = this.armorTrimsAtlas.getSprite(trim.getTexture(EquipmentModel.LayerType.HUMANOID, model)); VertexConsumer vertexConsumer = sprite.getTextureSpecificVertexConsumer( - vertexConsumerProvider.getBuffer(TexturedRenderLayers.getArmorTrims(trim.getPattern().value().decal()))); + vertexConsumerProvider.getBuffer(TexturedRenderLayers.getArmorTrims(trim.pattern().value().decal()))); // Render the armor trim itself renderBox(trimModelBox, matrixStack, vertexConsumer, packedLightIn, OverlayTexture.DEFAULT_UV, -1); // The enchantment glint however requires special handling; due to how Minecraft's enchant glint rendering works, rendering diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index e5b64f81..7060d039 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -29,6 +29,7 @@ import com.wildfire.render.WildfireModelRenderer.PositionTextureVertex; import java.lang.Math; +import java.util.Objects; import java.util.function.Consumer; import net.fabricmc.api.EnvType; @@ -42,24 +43,25 @@ import net.minecraft.client.render.entity.feature.FeatureRenderer; import net.minecraft.client.render.entity.feature.FeatureRendererContext; import net.minecraft.client.render.entity.model.BipedEntityModel; +import net.minecraft.client.render.entity.state.BipedEntityRenderState; import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.effect.StatusEffectUtil; import net.minecraft.entity.player.PlayerModelPart; import net.minecraft.item.ItemStack; +import net.minecraft.util.Identifier; import net.minecraft.util.math.*; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.joml.*; @Environment(EnvType.CLIENT) -public class GenderLayer> extends FeatureRenderer { +public class GenderLayer> extends FeatureRenderer { private BreastModelBox lBreast, rBreast; - private final FeatureRendererContext context; private static final OverlayModelBox lBreastWear, rBreastWear; + private final FeatureRendererContext context; + private float preBreastSize, preBreastOffsetZ; private Breasts breasts; protected ItemStack armorStack; @@ -73,7 +75,7 @@ public class GenderLayer> rBreastWear = new OverlayModelBox(false, 64, 64, 21, 34, 0, 0.0F, 0F, 4, 5, 3, 0.0F, false); } - public GenderLayer(FeatureRendererContext render) { + public GenderLayer(FeatureRendererContext render) { super(render); this.context = render; // this can't be static or final as we need the ability to resize this during render time @@ -81,35 +83,57 @@ public GenderLayer(FeatureRendererContext render) { rBreast = new BreastModelBox(64, 64, 20, 17, 0, 0.0F, 0F, 4, 5, 4, 0.0F, false); } - private @Nullable RenderLayer getRenderLayer(T entity) { - if(context instanceof LivingEntityRenderer renderer) { - MinecraftClient client = MinecraftClient.getInstance(); - boolean bodyVisible = !entity.isInvisible(); - boolean translucent = !bodyVisible && client.player != null && !entity.isInvisibleTo(client.player); - boolean glowing = client.hasOutline(entity); - return renderer.getRenderLayer(entity, bodyVisible, translucent, glowing); + /** + * Convenience method for getting the captured entity object from a render state + */ + protected @Nullable LivingEntity getEntity(S state) { + return ((RenderStateEntityCapture)state).getEntity(); + } + + /** + * Copy of {@code LivingEntityRenderer#getRenderLayer} + */ + private @Nullable RenderLayer getRenderLayer(S state) { + boolean bodyVisible = !state.invisible; + boolean translucent = state.invisible && !state.invisibleToPlayer; + boolean glowing = state.hasOutline; + + Identifier texture; + if(this.context instanceof LivingEntityRenderer livingEntityRenderer) { + texture = livingEntityRenderer.getTexture(state); + } else { + throw new IllegalStateException("context renderer is not a LivingEntityRenderer subclass"); + } + + if(translucent) { + return RenderLayer.getItemEntityTranslucentCull(texture); + } else if(bodyVisible) { + return this.getContextModel().getLayer(texture); + } else { + return glowing ? RenderLayer.getOutline(texture) : null; } - throw new IllegalStateException("context renderer is not a LivingEntityRenderer"); } @Override - public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light, @NotNull T ent, float limbAngle, - float limbDistance, float partialTicks, float animationProgress, float headYaw, float headPitch) { + public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light, S state, float limbAngle, float limbDistance) { MinecraftClient client = MinecraftClient.getInstance(); if(client.player == null) { // we're currently in a menu; we won't have any data loaded to begin with, so just give up early return; } + LivingEntity ent = getEntity(state); + if(ent == null) return; + EntityConfig entityConfig = EntityConfig.getEntity(ent); if(entityConfig == null) return; try { - if(!setupRender(ent, entityConfig, partialTicks)) return; - int overlay = LivingEntityRenderer.getOverlay(ent, 0); + if(!setupRender(state, entityConfig)) return; + int overlay = LivingEntityRenderer.getOverlay(state, 0); - renderSides(ent, getContextModel(), matrixStack, side -> { - renderBreast(ent, matrixStack, vertexConsumerProvider, light, overlay, side); + renderSides(state, getContextModel(), matrixStack, side -> { + renderBreast(state, matrixStack, vertexConsumerProvider, light, overlay, side); }); } catch(Exception e) { WildfireGender.LOGGER.error("Failed to render breast layer", e); @@ -122,8 +146,11 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume * @return {@code true} if rendering should continue */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") - protected boolean setupRender(T entity, EntityConfig entityConfig, float partialTicks) { - armorStack = entity.getEquippedStack(EquipmentSlot.CHEST); + protected boolean setupRender(S state, EntityConfig entityConfig) { + float partialTicks = MinecraftClient.getInstance().getRenderTickCounter().getTickDelta(true); + LivingEntity entity = Objects.requireNonNull(getEntity(state), "getEntity()"); + + armorStack = state.equippedChestStack; //Note: When the stack is empty the helper will fall back to an implementation that returns the proper data genderArmor = WildfireHelper.getArmorConfig(armorStack); isChestplateOccupied = genderArmor.coversBreasts() && !entityConfig.getArmorPhysicsOverride(); @@ -133,7 +160,7 @@ protected boolean setupRender(T entity, EntityConfig entityConfig, float partial return false; } - RenderLayer type = getRenderLayer(entity); + RenderLayer type = getRenderLayer(state); if(type == null && !isChestplateOccupied) { // the entity is invisible and doesn't have a chestplate equipped return false; @@ -195,11 +222,10 @@ protected void resizeBox(float breastSize) { } } - protected void setupTransformations(T entity, M model, MatrixStack matrixStack, BreastSide side) { - if(entity.isBaby()) { - float f1 = 1f / model.invertedChildBodyScale; - matrixStack.scale(f1, f1, f1); - matrixStack.translate(0f, model.childBodyYOffset / 16f, 0f); + protected void setupTransformations(S state, M model, MatrixStack matrixStack, BreastSide side) { + if(state.baby) { + matrixStack.scale(state.ageScale, state.ageScale, state.ageScale); + matrixStack.translate(0f, 0.75f, 0f); } ModelPart body = model.body; @@ -253,19 +279,20 @@ protected void setupTransformations(T entity, M model, MatrixStack matrixStack, matrixStack.multiply(new Quaternionf().rotationXYZ((float)(-35f * totalRotation * (Math.PI / 180f)), 0, 0)); if(breathingAnimation) { - float f5 = -MathHelper.cos(entity.age * 0.09F) * 0.45F + 0.45F; + float f5 = -MathHelper.cos(state.age * 0.09F) * 0.45F + 0.45F; matrixStack.multiply(new Quaternionf().rotationXYZ((float)(f5 * (Math.PI / 180f)), 0, 0)); } matrixStack.scale(0.9995f, 1f, 1f); //z-fighting FIXXX } - private void renderBreast(T entity, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light, + private void renderBreast(S state, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light, int overlay, BreastSide side) { - RenderLayer breastRenderType = getRenderLayer(entity); + LivingEntity entity = Objects.requireNonNull(getEntity(state), "getEntity()"); + RenderLayer breastRenderType = getRenderLayer(state); if(breastRenderType == null) return; // only render if the player is visible in some capacity int alpha = entity.isInvisible() ? ColorHelper.channelFromFloat(0.15f) : 255; - int color = ColorHelper.Argb.getArgb(alpha, 255, 255, 255); + int color = ColorHelper.getArgb(alpha, 255, 255, 255); VertexConsumer vertexConsumer = vertexConsumerProvider.getBuffer(breastRenderType); renderBox(side.isLeft ? lBreast : rBreast, matrixStack, vertexConsumer, light, overlay, color); if(entity instanceof AbstractClientPlayerEntity player && player.isPartVisible(PlayerModelPart.JACKET)) { @@ -275,10 +302,10 @@ private void renderBreast(T entity, MatrixStack matrixStack, VertexConsumerProvi } } - protected void renderSides(T entity, M model, MatrixStack matrixStack, Consumer renderer) { + protected void renderSides(S state, M model, MatrixStack matrixStack, Consumer renderer) { matrixStack.push(); try { - setupTransformations(entity, model, matrixStack, BreastSide.LEFT); + setupTransformations(state, model, matrixStack, BreastSide.LEFT); renderer.accept(BreastSide.LEFT); } finally { matrixStack.pop(); @@ -286,7 +313,7 @@ protected void renderSides(T entity, M model, MatrixStack matrixStack, Consumer< matrixStack.push(); try { - setupTransformations(entity, model, matrixStack, BreastSide.RIGHT); + setupTransformations(state, model, matrixStack, BreastSide.RIGHT); renderer.accept(BreastSide.RIGHT); } finally { matrixStack.pop(); diff --git a/src/main/java/com/wildfire/render/RenderStateEntityCapture.java b/src/main/java/com/wildfire/render/RenderStateEntityCapture.java new file mode 100644 index 00000000..43fc8510 --- /dev/null +++ b/src/main/java/com/wildfire/render/RenderStateEntityCapture.java @@ -0,0 +1,28 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.render; + +import net.minecraft.entity.LivingEntity; +import org.jetbrains.annotations.Nullable; + +// See LivingEntityRenderStateMixin for the actual implementation +public interface RenderStateEntityCapture { + @Nullable LivingEntity getEntity(); + void setEntity(LivingEntity entity); +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 427eb800..b77299ee 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -45,7 +45,7 @@ "fabric-rendering-v1": "*", "fabric-resource-loader-v0": "*", "fabric-registry-sync-v0": "*", - "minecraft": ">=1.21 <1.22", + "minecraft": ">=1.21.2-beta.2 <1.22", "java": ">=21" }, "conflicts": { diff --git a/src/main/resources/wildfire_gender.accesswidener b/src/main/resources/wildfire_gender.accesswidener index 3aa162d7..b69a6683 100644 --- a/src/main/resources/wildfire_gender.accesswidener +++ b/src/main/resources/wildfire_gender.accesswidener @@ -1,6 +1,3 @@ accessWidener v2 named -accessible method net/minecraft/client/render/entity/LivingEntityRenderer getRenderLayer (Lnet/minecraft/entity/LivingEntity;ZZZ)Lnet/minecraft/client/render/RenderLayer; -accessible field net/minecraft/client/render/entity/model/AnimalModel invertedChildBodyScale F -accessible field net/minecraft/client/render/entity/model/AnimalModel childBodyYOffset F accessible method net/minecraft/entity/LivingEntity getHandSwingDuration ()I diff --git a/src/main/resources/wildfire_gender.mixins.json b/src/main/resources/wildfire_gender.mixins.json index 7c74d197..142cb3b6 100644 --- a/src/main/resources/wildfire_gender.mixins.json +++ b/src/main/resources/wildfire_gender.mixins.json @@ -8,7 +8,9 @@ ], "client": [ "BreastPhysicsTickMixin", - "LivingEntityMixin" + "LivingEntityMixin", + "renderstate.LivingEntityRendererMixin", + "renderstate.LivingEntityRenderStateMixin" ], "injectors": { "defaultRequire": 1 From 90c8734a0868443840d2b10b128bcec347643b95 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Mon, 14 Oct 2024 20:59:43 -0400 Subject: [PATCH 111/238] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a6dba592..9c6c5a06 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ classes/ #Minecraft run/ runV2/ +dependencies/ # vscode From c116f5c9851af44234e86925c229b4e182f05c62 Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 18 Oct 2024 00:51:36 -0600 Subject: [PATCH 112/238] implement armor resource pack configs --- build.gradle | 2 +- doc/armor_configs.md | 97 ++++++++++++++++ .../com/wildfire/api/IBreastArmorTexture.java | 104 ++++++++++++++++++ .../java/com/wildfire/api/IGenderArmor.java | 57 +++++++++- src/main/java/com/wildfire/api/Vec2i.java | 45 ++++++++ .../java/com/wildfire/api/WildfireAPI.java | 23 +++- .../wildfire/api/impl/BreastArmorTexture.java | 42 +++++++ .../com/wildfire/api/impl/GenderArmor.java | 54 +++++++++ .../wildfire/main/WildfireGenderClient.java | 4 + .../com/wildfire/main/WildfireHelper.java | 75 ++++++++----- .../main/entitydata/BreastDataComponent.java | 21 ++-- .../mixins/ArmorStandEntityMixin.java | 15 +-- .../com/wildfire/render/GenderArmorLayer.java | 36 ++++-- .../render/armor/EmptyGenderArmor.java | 42 ------- .../render/armor/SimpleGenderArmor.java | 47 -------- .../resources/GenderArmorResourceManager.java | 66 +++++++++++ .../wildfire_gender_data/chainmail.json | 4 + .../wildfire_gender_data/diamond.json | 3 + .../wildfire_gender_data/elytra.json | 3 + .../minecraft/wildfire_gender_data/gold.json | 4 + .../minecraft/wildfire_gender_data/iron.json | 3 + .../wildfire_gender_data/leather.json | 4 + .../wildfire_gender_data/netherite.json | 3 + 23 files changed, 595 insertions(+), 159 deletions(-) create mode 100644 doc/armor_configs.md create mode 100644 src/main/java/com/wildfire/api/IBreastArmorTexture.java create mode 100644 src/main/java/com/wildfire/api/Vec2i.java create mode 100644 src/main/java/com/wildfire/api/impl/BreastArmorTexture.java create mode 100644 src/main/java/com/wildfire/api/impl/GenderArmor.java delete mode 100644 src/main/java/com/wildfire/render/armor/EmptyGenderArmor.java delete mode 100644 src/main/java/com/wildfire/render/armor/SimpleGenderArmor.java create mode 100644 src/main/java/com/wildfire/resources/GenderArmorResourceManager.java create mode 100644 src/main/resources/assets/minecraft/wildfire_gender_data/chainmail.json create mode 100644 src/main/resources/assets/minecraft/wildfire_gender_data/diamond.json create mode 100644 src/main/resources/assets/minecraft/wildfire_gender_data/elytra.json create mode 100644 src/main/resources/assets/minecraft/wildfire_gender_data/gold.json create mode 100644 src/main/resources/assets/minecraft/wildfire_gender_data/iron.json create mode 100644 src/main/resources/assets/minecraft/wildfire_gender_data/leather.json create mode 100644 src/main/resources/assets/minecraft/wildfire_gender_data/netherite.json diff --git a/build.gradle b/build.gradle index edb328d0..61633abf 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,7 @@ dependencies { modImplementation fabricApi.module("fabric-key-binding-api-v1", project.fabric_version) modImplementation fabricApi.module("fabric-lifecycle-events-v1", project.fabric_version) modImplementation fabricApi.module("fabric-rendering-v1", project.fabric_version) - modRuntimeOnly fabricApi.module("fabric-resource-loader-v0", project.fabric_version) + modImplementation fabricApi.module("fabric-resource-loader-v0", project.fabric_version) modRuntimeOnly fabricApi.module("fabric-registry-sync-v0", project.fabric_version) // Allow logging into an actual Minecraft account in a dev env diff --git a/doc/armor_configs.md b/doc/armor_configs.md new file mode 100644 index 00000000..59520e12 --- /dev/null +++ b/doc/armor_configs.md @@ -0,0 +1,97 @@ +# Armor Resource Configs + +Armor physics values can be modified by a resource pack by placing a JSON file at `assets/NAMESPACE/wildfire_gender_data/MODEL.json`, +where `NAMESPACE` and `MODEL` are replaced with the respective values (such as `assets/minecraft/wildfire_gender_data/iron.json`). + +The full schema with default values is as follows: + +```json5 +{ + // both values are a range of 0.0 and 1.0 (inclusive) + "resistance": 0.5, + "tightness": 0.0, + "covers_breasts": true, + "hide_breasts": false, + "render_on_armor_stands": null, // true if resistance == 1 + "texture": { + "texture_size": {"x": 64, "y": 32}, + "dimensions": {"x": 4, "y": 5}, + "left_uv": {"x": 16, "y": 17}, + "right_uv": {"x": -1, "y": -1} // defaults to left_uv added with the x value of dimensions + } +} +``` + +## Values + +### `resistance` + +A number value between `0.0` and `1.0` (inclusive) determining how much this armor resists the wearer's breast physics as +a percentage value, where values like `0.5` equate to 50%. + +Defaults to `0.5` (50%) if not set. + +### `tightness` + +A number value between `0.0` and `1.0` (inclusive) determining how much this armor "compresses" the wearer's breasts against +their chest as a percentage value, making them appear up to 15% smaller at `1.0` (100%). + +Defaults to `0.0` (0%) if not set. + +### `covers_breasts` + +Boolean value determining if this armor piece covers the wearer's breasts; this is intended to be set to `false` for equippable +chest slot items with an open front, such as the Elytra. Note that this does *not* prevent the item from rendering over the wearer's +breasts. + +Defaults to `true` if not set. + +### `hide_breasts` + +Boolean value determining if the wearer's breasts should be hidden entirely while this armor piece is worn; this is +intended for armor that use custom rendering which is largely incompatible with how this mod's breasts render. + +Defaults to `false` if not set. + +### `render_on_armor_stands` + +Boolean value determining if armor stands should render the breast settings of the player equipping this armor piece +onto them. + +This is designed for armor types which are metallic in nature (such as Iron and Gold), and not ones which would be +flexible enough to accommodate for the wearer's breasts on their own (such as Leather and Chain). + +Defaults to `true` *only* if `resistance` is `1.0` if unset. + +### `texture` + +Object containing various texture-related overrides; note that all values that specify `{"x": ..., "y": ...}` +*must* contain both `x` and `y` if specified. + +#### `texture_size` + +Controls the armor sprite's texture size. + +Note that if your sprite is simply an upscaled resolution of the vanilla sprite size (such as 128x64, 256x128, etc.) +then you do *not* need to modify this or any other texture values. + +Defaults to `{"x": 64, "y": 32}` if unset. + +#### `dimensions` + +Controls how large an area the breasts should grab from the sprite for *each breast*; this means that this value's +X is *half* of the total sprite area to render. + +Defaults to `{"x": 4, "y": 5}` if unset. + +#### `left_uv` + +Controls where the left breast should start rendering this armor from. + +Defaults to `{"x": 16, "y": 17}` if unset. + +#### `right_uv` + +Controls where the right breast should start rendering this armor from. + +Defaults to `left_uv` added with the X value of `dimensions` if unset. diff --git a/src/main/java/com/wildfire/api/IBreastArmorTexture.java b/src/main/java/com/wildfire/api/IBreastArmorTexture.java new file mode 100644 index 00000000..5388e1c8 --- /dev/null +++ b/src/main/java/com/wildfire/api/IBreastArmorTexture.java @@ -0,0 +1,104 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.api; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import com.wildfire.api.impl.BreastArmorTexture; +import org.jetbrains.annotations.NotNull; + +/** + * Defines the texture data for a given armor piece when covering an entity's breasts + */ +public interface IBreastArmorTexture { + + Vec2i DEFAULT_TEXTURE_SIZE = new Vec2i(64, 32); + Vec2i DEFAULT_DIMENSIONS = new Vec2i(4, 5); + Vec2i DEFAULT_LEFT_UV = new Vec2i(16, 17); + Vec2i DEFAULT_RIGHT_UV = DEFAULT_LEFT_UV.add(DEFAULT_DIMENSIONS.x(), 0); + + Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Vec2i.CODEC + .optionalFieldOf("texture_size", BreastArmorTexture.DEFAULT.textureSize()) + .forGetter(IBreastArmorTexture::textureSize), + Vec2i.CODEC + .optionalFieldOf("left_uv", BreastArmorTexture.DEFAULT.leftUv()) + .forGetter(IBreastArmorTexture::leftUv), + Vec2i.CODEC + .optionalFieldOf("right_uv", new Vec2i(-1, -1)) + .forGetter(IBreastArmorTexture::rightUv), + Vec2i.CODEC + .optionalFieldOf("dimensions", BreastArmorTexture.DEFAULT.dimensions()) + .forGetter(IBreastArmorTexture::dimensions) + ).apply(instance, (size, leftUv, rightUv, dimensions) -> { + var right = rightUv; + if(right.x() == -1 && right.y() == -1) { + right = leftUv.add(dimensions.x(), 0); + } + return new BreastArmorTexture(size, leftUv, right, dimensions); + })); + + /** + * The size of the armor sprite in pixels + * + * @implNote Defaults to {@code Vec2i(64, 32)} + * + * @return A {@link Vec2i} indicating how large the texture file is + */ + default @NotNull Vec2i textureSize() { + return DEFAULT_TEXTURE_SIZE; + } + + /** + * How large of an area from the sprite should be used for each breast + * + * @apiNote The X value of this should be halved from the total chest size to account for each breast side + * rendering independently of each other. + * + * @implNote Defaults to {@code Vec2i(4, 5)} + * + * @return A {@link Vec2i} indicating how large of an area should be grabbed from the texture sprite to display over + * the wearer's breasts + */ + default @NotNull Vec2i dimensions() { + return DEFAULT_DIMENSIONS; + } + + /** + * Where the left breast should grab the texture from on the sprite + * + * @implNote Defaults to {@code Vec2i(16, 17)} + * + * @return A {@link Vec2i} indicating the UV to use for the left breast + */ + default @NotNull Vec2i leftUv() { + return DEFAULT_LEFT_UV; + } + + /** + * Where the right breast should grab the texture from on the sprite + * + * @implNote Defaults to {@code Vec2i(20, 17)} + * + * @return A {@link Vec2i} indicating the UV to use for the right breast + */ + default @NotNull Vec2i rightUv() { + return DEFAULT_RIGHT_UV; + } +} diff --git a/src/main/java/com/wildfire/api/IGenderArmor.java b/src/main/java/com/wildfire/api/IGenderArmor.java index cac2270e..f76cf5bf 100644 --- a/src/main/java/com/wildfire/api/IGenderArmor.java +++ b/src/main/java/com/wildfire/api/IGenderArmor.java @@ -18,11 +18,45 @@ package com.wildfire.api; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import com.wildfire.api.impl.BreastArmorTexture; +import com.wildfire.api.impl.GenderArmor; +import com.wildfire.main.WildfireHelper; +import net.minecraft.util.TriState; +import org.jetbrains.annotations.NotNull; + /** * Implement this on a custom class for your chestplates or items that go in the chest slot to configure how it interacts with breast rendering. */ public interface IGenderArmor { + Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + WildfireHelper.boundedFloat(0f, 1f) + .optionalFieldOf("resistance", GenderArmor.DEFAULT.physicsResistance()) + .forGetter(IGenderArmor::physicsResistance), + WildfireHelper.boundedFloat(0f, 1f) + .optionalFieldOf("tightness", GenderArmor.DEFAULT.tightness()) + .forGetter(IGenderArmor::tightness), + Codec.BOOL + .optionalFieldOf("covers_breasts", GenderArmor.DEFAULT.coversBreasts()) + .forGetter(IGenderArmor::coversBreasts), + Codec.BOOL + .optionalFieldOf("hide_breasts", GenderArmor.DEFAULT.alwaysHidesBreasts()) + .forGetter(IGenderArmor::alwaysHidesBreasts), + WildfireHelper.TRISTATE + .optionalFieldOf("render_on_armor_stands", TriState.DEFAULT) + .forGetter(armor -> armor.armorStandsCopySettings() ? TriState.TRUE : TriState.FALSE), + IBreastArmorTexture.CODEC + .optionalFieldOf("texture", GenderArmor.DEFAULT.texture()) + .forGetter(IGenderArmor::texture) + ).apply(instance, (resistance, tightness, covers, hideBreasts, armorStands, texture) -> { + if(!covers) { + return GenderArmor.EMPTY; + } + return new GenderArmor(resistance, tightness, true, hideBreasts, armorStands.asBoolean(resistance == 1f), texture); + })); + /** * Determines whether this {@link IGenderArmor} "covers" the breasts or if it has an open front ({@code false}) like the elytra. * @@ -55,10 +89,10 @@ default boolean alwaysHidesBreasts() { * * @return Value between {@code 0} (no resistance, full physics) and {@code 1} (total resistance, no physics). * - * @implNote Defaults to {@code 0} (no resistance, full physics). + * @implNote Defaults to {@code 0.5f} (50% physics resistance). */ default float physicsResistance() { - return 0; + return 0.5f; } /** @@ -77,9 +111,8 @@ default float tightness() { *

Determines whether armor stands should copy the breast settings of the player equipping this chestplate * onto it.

* - *

If this returns {@code true}, an equipping player's breast settings will be copied onto the - * item stack's {@link net.minecraft.component.DataComponentTypes#CUSTOM_DATA custom NBT data component} - * under the tag {@code WildfireGender}.

+ *

If this returns {@code true}, the equipping player's breast settings will also be rendered when this + * armor piece is equipped onto an armor stand.

* *

This is designed for armor types that are metallic in nature, and not armor types that would (realistically) * be flexible enough to accommodate for a player's breasts on their own (such as Leather and Chain).

@@ -96,4 +129,18 @@ default float tightness() { default boolean armorStandsCopySettings() { return !alwaysHidesBreasts() && coversBreasts() && physicsResistance() == 1f; } + + /** + * Overrides certain values when this armor piece is being rendered + * + * @return The relevant {@link IBreastArmorTexture} + * + * @implNote Defaults to {@link BreastArmorTexture#DEFAULT} + * + * @see IBreastArmorTexture + * @see BreastArmorTexture + */ + default @NotNull IBreastArmorTexture texture() { + return BreastArmorTexture.DEFAULT; + } } \ No newline at end of file diff --git a/src/main/java/com/wildfire/api/Vec2i.java b/src/main/java/com/wildfire/api/Vec2i.java new file mode 100644 index 00000000..44fbd250 --- /dev/null +++ b/src/main/java/com/wildfire/api/Vec2i.java @@ -0,0 +1,45 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.api; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +/** + * Simplified immutable copy of a {@link org.joml.Vector2i Vector2i} + */ +public record Vec2i(int x, int y) { + // TODO a [x, y] list would be preferred but I don't understand codecs enough to try to get that to work + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.INT.fieldOf("x").forGetter(Vec2i::x), + Codec.INT.fieldOf("y").forGetter(Vec2i::y) + ).apply(instance, Vec2i::new)); + + /** + * Returns a new {@link Vec2i} with the sum of the provided values and this {@link Vec2i} + * + * @param x The value to add to this vector's X value + * @param y The value to add to this vector's Y value + * + * @return A new {@link Vec2i} with the added values + */ + public Vec2i add(int x, int y) { + return new Vec2i(this.x + x, this.y + y); + } +} diff --git a/src/main/java/com/wildfire/api/WildfireAPI.java b/src/main/java/com/wildfire/api/WildfireAPI.java index 94ab9074..6b20835b 100644 --- a/src/main/java/com/wildfire/api/WildfireAPI.java +++ b/src/main/java/com/wildfire/api/WildfireAPI.java @@ -27,6 +27,7 @@ import net.fabricmc.api.Environment; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -43,10 +44,17 @@ public class WildfireAPI { /** * Add custom physics resistance attributes to a chestplate * + * @apiNote This method should be considered "soft deprecated", and may be marked for removal in favor + * of resource pack configurations in the future. + * + * @implNote Implementations added through this method are presently ignored if a resource pack defines armor data + * at {@code NAMESPACE:wildfire_gender_data/MODEL.json}, and are only used as a default implementation. + * * @param item the item that you are linking this {@link IGenderArmor} to * @param genderArmor the class implementing the {@link IGenderArmor} to apply to the item * @see IGenderArmor */ + @ApiStatus.Obsolete public static void addGenderArmor(Item item, IGenderArmor genderArmor) { GENDER_ARMORS.put(item, genderArmor); } @@ -73,13 +81,17 @@ public static void addGenderArmor(Item item, IGenderArmor genderArmor) { return cfg.getGender(); } + // FIXME this method currently has the limitation of only actually affecting players that are currently cached /** *

Load the cached Gender Settings file for the specified {@link UUID}

* *

You should avoid using this unless you need to, as the mod will do this for you when loading a player entity.

* + * @apiNote This method currently has the limitation of only affecting players that are {@link #getPlayerById in the mod's cache}, + * and won't load anything otherwise. + * * @param uuid the uuid of the target {@link PlayerEntity} - * @param markForSync true if you want to send the gender settings to the server upon loading. + * @param markForSync {@code true} if player data should be synced to the server upon being loaded; this only has an effect on the client player. */ @Environment(EnvType.CLIENT) public static CompletableFuture<@Nullable PlayerConfig> loadGenderInfo(UUID uuid, boolean markForSync) { @@ -89,8 +101,15 @@ public static void addGenderArmor(Item item, IGenderArmor genderArmor) { /** * Get every registered {@link IGenderArmor custom armor configuration} * - * @implNote This does not provide vanilla armor configurations; see {@link com.wildfire.render.armor.SimpleGenderArmor} for that. + * @apiNote This method should be considered "soft deprecated", and may be marked for removal in favor + * of resource pack configurations in the future. + * + * @implNote This does not include armors registered through resource packs; + * see {@link com.wildfire.resources.GenderArmorResourceManager} for that. + * + * @see #addGenderArmor */ + @ApiStatus.Obsolete public static Map getGenderArmors() { return GENDER_ARMORS; } diff --git a/src/main/java/com/wildfire/api/impl/BreastArmorTexture.java b/src/main/java/com/wildfire/api/impl/BreastArmorTexture.java new file mode 100644 index 00000000..dfd777e6 --- /dev/null +++ b/src/main/java/com/wildfire/api/impl/BreastArmorTexture.java @@ -0,0 +1,42 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.api.impl; + +import com.wildfire.api.IBreastArmorTexture; +import org.jetbrains.annotations.NotNull; +import com.wildfire.api.Vec2i; + +/** + * Default record implementation of {@link IBreastArmorTexture} used for resource pack entries + * + * @see IBreastArmorTexture + */ +public record BreastArmorTexture(@NotNull Vec2i textureSize, @NotNull Vec2i leftUv, @NotNull Vec2i rightUv, @NotNull Vec2i dimensions) implements IBreastArmorTexture { + /** + * Default breast texture values, intended for armors compatible with the vanilla armor renderer. + */ + public static final IBreastArmorTexture DEFAULT = new Default(); + + /** + * Dummy implementation of {@link IBreastArmorTexture}; simply defers to the default interface implementations for all methods. + */ + public static final class Default implements IBreastArmorTexture { + private Default() {} + } +} diff --git a/src/main/java/com/wildfire/api/impl/GenderArmor.java b/src/main/java/com/wildfire/api/impl/GenderArmor.java new file mode 100644 index 00000000..9011516c --- /dev/null +++ b/src/main/java/com/wildfire/api/impl/GenderArmor.java @@ -0,0 +1,54 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.api.impl; + +import com.wildfire.api.IBreastArmorTexture; +import com.wildfire.api.IGenderArmor; + +/** + * Default record implementation of {@link IGenderArmor} used for resource pack entries + * + * @see IGenderArmor + */ +public record GenderArmor( + float physicsResistance, + float tightness, + boolean coversBreasts, + boolean alwaysHidesBreasts, + boolean armorStandsCopySettings, + IBreastArmorTexture texture +) implements IGenderArmor { + /** + * Default implementation used to represent armor types that lack any configuration + */ + public static final IGenderArmor DEFAULT = new Default(); + + /** + * Default implementation used when the player {@link net.minecraft.item.ItemStack#isEmpty() isn't wearing a chestplate}, + * or if the worn chestplate specifies that it doesn't cover the breasts. + */ + public static final IGenderArmor EMPTY = new GenderArmor(0f, 0f, false, false, false, BreastArmorTexture.DEFAULT); + + /** + * Dummy implementation of {@link IGenderArmor}; simply defers to the default interface implementations for all methods. + */ + public static final class Default implements IGenderArmor { + private Default() {} + } +} diff --git a/src/main/java/com/wildfire/main/WildfireGenderClient.java b/src/main/java/com/wildfire/main/WildfireGenderClient.java index 0439576d..d7375467 100644 --- a/src/main/java/com/wildfire/main/WildfireGenderClient.java +++ b/src/main/java/com/wildfire/main/WildfireGenderClient.java @@ -20,9 +20,12 @@ import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.networking.WildfireSync; +import com.wildfire.resources.GenderArmorResourceManager; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.resource.ResourceManagerHelper; +import net.minecraft.resource.ResourceType; import net.minecraft.util.Util; import org.jetbrains.annotations.Nullable; @@ -39,6 +42,7 @@ public void onInitializeClient() { WildfireSounds.register(); WildfireSync.registerClient(); WildfireEventHandler.registerClientEvents(); + ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(GenderArmorResourceManager.INSTANCE); } public static CompletableFuture<@Nullable PlayerConfig> loadGenderInfo(UUID uuid, boolean markForSync) { diff --git a/src/main/java/com/wildfire/main/WildfireHelper.java b/src/main/java/com/wildfire/main/WildfireHelper.java index e174a966..d453262f 100644 --- a/src/main/java/com/wildfire/main/WildfireHelper.java +++ b/src/main/java/com/wildfire/main/WildfireHelper.java @@ -18,34 +18,52 @@ package com.wildfire.main; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.codecs.PrimitiveCodec; import com.wildfire.api.IGenderArmor; import com.wildfire.api.WildfireAPI; -import com.wildfire.render.armor.SimpleGenderArmor; -import com.wildfire.render.armor.EmptyGenderArmor; +import com.wildfire.main.config.FloatConfigKey; import net.minecraft.component.DataComponentTypes; -import net.minecraft.entity.EquipmentSlot; import net.minecraft.item.ItemStack; -import net.minecraft.item.equipment.EquipmentModels; -import net.minecraft.util.Identifier; +import com.wildfire.api.impl.GenderArmor; +import com.wildfire.resources.GenderArmorResourceManager; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.util.TriState; +import net.minecraft.util.math.MathHelper; -import java.util.Map; import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Function; public final class WildfireHelper { - // TODO migrate this from being hardcoded to being provided by resource packs instead? - private static final Map VANILLA_ARMORS = Map.of( - EquipmentModels.LEATHER, SimpleGenderArmor.LEATHER, - EquipmentModels.CHAINMAIL, SimpleGenderArmor.CHAIN_MAIL, - EquipmentModels.IRON, SimpleGenderArmor.IRON, - EquipmentModels.GOLD, SimpleGenderArmor.GOLD, - EquipmentModels.DIAMOND, SimpleGenderArmor.DIAMOND, - EquipmentModels.NETHERITE, SimpleGenderArmor.NETHERITE - ); - private WildfireHelper() { throw new UnsupportedOperationException(); } + public static final PrimitiveCodec TRISTATE = new PrimitiveCodec<>() { + @Override + public DataResult read(final DynamicOps ops, final T input) { + return DataResult.success(ops.getBooleanValue(input) + .map(v -> v ? TriState.TRUE : TriState.FALSE) + .result().orElse(TriState.DEFAULT)); + } + + @Override + public T write(final DynamicOps ops, final TriState value) { + if(value == TriState.DEFAULT) { + return ops.empty(); + } + return ops.createBoolean(value == TriState.TRUE); + } + + @Override + public String toString() { + return "TriState"; + } + }; + public static int randInt(int min, int max) { return ThreadLocalRandom.current().nextInt(min, max + 1); } @@ -53,22 +71,23 @@ public static float randFloat(float min, float max) { return (float) ThreadLocalRandom.current().nextDouble(min, (double) max + 1); } + @Environment(EnvType.CLIENT) public static IGenderArmor getArmorConfig(ItemStack stack) { - if (stack.isEmpty()) { - return EmptyGenderArmor.INSTANCE; + if(stack.isEmpty()) { + return GenderArmor.EMPTY; } - if (WildfireAPI.getGenderArmors().get(stack.getItem()) != null) { - return WildfireAPI.getGenderArmors().get(stack.getItem()); - } + return GenderArmorResourceManager.get(stack).orElseGet(() -> { + var fallback = stack.contains(DataComponentTypes.EQUIPPABLE) ? GenderArmor.DEFAULT : GenderArmor.EMPTY; + return WildfireAPI.getGenderArmors().getOrDefault(stack.getItem(), fallback); + }); + } - //TODO: Fabric Alternative to Capabilities? Maybe someone can help with this? - var equippable = stack.get(DataComponentTypes.EQUIPPABLE); - if(equippable != null && equippable.slot() == EquipmentSlot.CHEST) { - var model = equippable.model(); - return model.map(VANILLA_ARMORS::get).orElse(SimpleGenderArmor.FALLBACK); - } + public static Codec boundedFloat(float minInclusive, float maxInclusive) { + return Codec.FLOAT.xmap(val -> MathHelper.clamp(val, minInclusive, maxInclusive), Function.identity()); + } - return EmptyGenderArmor.INSTANCE; + public static Codec boundedFloat(FloatConfigKey configKey) { + return boundedFloat(configKey.getMinInclusive(), configKey.getMaxInclusive()); } } diff --git a/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java b/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java index 00a16c56..51d62fc5 100644 --- a/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java +++ b/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java @@ -4,8 +4,8 @@ import com.mojang.serialization.DataResult; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; +import com.wildfire.main.WildfireHelper; import com.wildfire.main.config.Configuration; -import com.wildfire.main.config.FloatConfigKey; import net.minecraft.component.DataComponentTypes; import net.minecraft.component.type.NbtComponent; import net.minecraft.entity.player.PlayerEntity; @@ -15,44 +15,37 @@ import net.minecraft.nbt.NbtOps; import net.minecraft.registry.RegistryOps; import net.minecraft.registry.RegistryWrapper; -import net.minecraft.util.math.MathHelper; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.joml.Vector3f; -import java.util.function.Function; - /** *

Data component-like class for storing player breast settings on armor equipped onto armor stands

* *

Note that while this is treated similarly to any other {@link DataComponentTypes data component} for performance reasons, * this is never written as its own component on item stacks, but instead uses the {@link DataComponentTypes#CUSTOM_DATA custom NBT data component} - * for compatibility with vanilla clients on servers.

+ * (under the {@code WildfireGender} key) for compatibility with vanilla clients on servers.

*/ public record BreastDataComponent(float breastSize, float cleavage, Vector3f offsets, boolean jacket, @Nullable NbtComponent nbtComponent) { - private static Codec boundedFloat(FloatConfigKey configKey) { - return Codec.FLOAT.xmap(val -> MathHelper.clamp(val, configKey.getMinInclusive(), configKey.getMaxInclusive()), Function.identity()); - } - private static final String KEY = "WildfireGender"; private static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( - boundedFloat(Configuration.BUST_SIZE) + WildfireHelper.boundedFloat(Configuration.BUST_SIZE) .optionalFieldOf("BreastSize", 0f) .forGetter(BreastDataComponent::breastSize), - boundedFloat(Configuration.BREASTS_CLEAVAGE) + WildfireHelper.boundedFloat(Configuration.BREASTS_CLEAVAGE) .optionalFieldOf("Cleavage", Configuration.BREASTS_CLEAVAGE.getDefault()) .forGetter(BreastDataComponent::cleavage), Codec.BOOL .optionalFieldOf("Jacket", true) .forGetter(BreastDataComponent::jacket), - boundedFloat(Configuration.BREASTS_OFFSET_X) + WildfireHelper.boundedFloat(Configuration.BREASTS_OFFSET_X) .optionalFieldOf("XOffset", 0f) .forGetter(component -> component.offsets.x), - boundedFloat(Configuration.BREASTS_OFFSET_Y) + WildfireHelper.boundedFloat(Configuration.BREASTS_OFFSET_Y) .optionalFieldOf("YOffset", 0f) .forGetter(component -> component.offsets.y), - boundedFloat(Configuration.BREASTS_OFFSET_Z) + WildfireHelper.boundedFloat(Configuration.BREASTS_OFFSET_Z) .optionalFieldOf("ZOffset", 0f) .forGetter(component -> component.offsets.y) ).apply(instance, (breastSize, cleavage, jacket, x, y, z) -> new BreastDataComponent(breastSize, cleavage, new Vector3f(x, y, z), jacket, null)) diff --git a/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java b/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java index b5099c70..2f78d7a5 100644 --- a/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java +++ b/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java @@ -19,11 +19,9 @@ package com.wildfire.mixins; import com.llamalad7.mixinextras.sugar.Local; -import com.wildfire.api.IGenderArmor; import com.wildfire.main.WildfireGender; import com.wildfire.main.entitydata.BreastDataComponent; import com.wildfire.main.entitydata.PlayerConfig; -import com.wildfire.main.WildfireHelper; import net.minecraft.entity.EntityType; import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.LivingEntity; @@ -51,7 +49,7 @@ protected ArmorStandEntityMixin(EntityType entityType, W ) public ItemStack wildfiregender$attachBreastData(ItemStack stack, @Local(argsOnly = true) EquipmentSlot slot, @Local(argsOnly = true) PlayerEntity player) { - if(player == null || player.getWorld().isClient() || slot != EquipmentSlot.CHEST) { + if(player == null || getWorld().isClient() || slot != EquipmentSlot.CHEST) { return stack; } @@ -65,12 +63,11 @@ protected ArmorStandEntityMixin(EntityType entityType, W return stack; } - IGenderArmor armorConfig = WildfireHelper.getArmorConfig(stack); - if(armorConfig.armorStandsCopySettings()) { - BreastDataComponent component = BreastDataComponent.fromPlayer(player, playerConfig); - if(component != null) { - component.write(player.getWorld().getRegistryManager(), stack); - } + // Note that we always attach player data to the item stack as a server has no concept of resource packs, + // making it impossible to compare against any armor data that isn't registered through the mod API. + BreastDataComponent component = BreastDataComponent.fromPlayer(player, playerConfig); + if(component != null) { + component.write(player.getWorld().getRegistryManager(), stack); } return stack; diff --git a/src/main/java/com/wildfire/render/GenderArmorLayer.java b/src/main/java/com/wildfire/render/GenderArmorLayer.java index 8293098d..60378a4c 100644 --- a/src/main/java/com/wildfire/render/GenderArmorLayer.java +++ b/src/main/java/com/wildfire/render/GenderArmorLayer.java @@ -18,12 +18,13 @@ package com.wildfire.render; +import com.wildfire.api.IBreastArmorTexture; +import com.wildfire.api.impl.BreastArmorTexture; import com.wildfire.main.WildfireGender; import com.wildfire.main.entitydata.EntityConfig; import com.wildfire.render.WildfireModelRenderer.BreastModelBox; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; -import net.fabricmc.fabric.impl.client.rendering.ArmorRendererRegistryImpl; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.client.render.*; @@ -33,6 +34,7 @@ import net.minecraft.client.render.entity.state.BipedEntityRenderState; import net.minecraft.client.render.item.ItemRenderer; import net.minecraft.client.render.model.BakedModelManager; +import net.minecraft.client.texture.MissingSprite; import net.minecraft.client.texture.Sprite; import net.minecraft.client.texture.SpriteAtlasTexture; import net.minecraft.client.util.math.MatrixStack; @@ -48,6 +50,7 @@ import net.minecraft.registry.tag.ItemTags; import net.minecraft.util.Identifier; import net.minecraft.util.math.ColorHelper; +import org.jetbrains.annotations.NotNull; import java.util.Objects; @@ -56,13 +59,12 @@ public class GenderArmorLayer render, BakedModelManager b super(render); this.armorTrimsAtlas = bakery.getAtlas(TexturedRenderLayers.ARMOR_TRIMS_ATLAS_TEXTURE); this.equipmentModelLoader = equipmentModelLoader; + lBoobArmor = new BreastModelBox(64, 32, 16, 17, -4F, 0.0F, 0F, 4, 5, 3, 0.0F, false); + rBoobArmor = new BreastModelBox(64, 32, 20, 17, 0, 0.0F, 0F, 4, 5, 3, 0.0F, false); } @Override @@ -89,18 +93,13 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume // Check if the worn item in the chest slot is actually equippable in the chest slot, and has a model to render var component = chestplate.get(DataComponentTypes.EQUIPPABLE); if(component == null || component.slot() != EquipmentSlot.CHEST || component.model().isEmpty()) return; - // And similarly just entirely give up if the item has a renderer registered with Fabric API - // This will likely result in the player's breasts sticking out through the armor layer unless the mod in question - // implements an IGenderArmor to prevent them from rendering entirely, but oh well; at least we won't be - // rendering a pink box. - //noinspection UnstableApiUsage - if(ArmorRendererRegistryImpl.get(chestplate.getItem()) != null) return; try { entityConfig = EntityConfig.getEntity(ent); if(entityConfig == null) return; if(!setupRender(state, entityConfig)) return; + // TODO skip rendering if coversBreasts() is false, or maybe make a separate renderArmor() property instead? if(ent instanceof ArmorStandEntity && !genderArmor.armorStandsCopySettings()) return; int color = chestplate.isIn(ItemTags.DYEABLE) ? DyedColorComponent.getColor(chestplate, -1) : -1; @@ -108,6 +107,7 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume renderSides(state, getContextModel(), matrixStack, side -> { var modelId = component.model().orElseThrow(); + // TODO is there still a need to allow for overriding the armor texture identifier? equipmentModelLoader.get(modelId).getLayers(EquipmentModel.LayerType.HUMANOID).forEach(layer -> { // mojang what the Optional hell is this int layerColor = layer.dyeable().map(dye -> { @@ -130,7 +130,17 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume @Override protected void resizeBox(float breastSize) { - // this has no relevance to armor + if(genderArmor == null || Objects.equals(textureData, genderArmor.texture())) { + return; + } + + textureData = genderArmor.texture(); + var texSize = textureData.textureSize(); + var lUV = textureData.leftUv(); + var dim = textureData.dimensions(); + lBoobArmor = new BreastModelBox(texSize.x(), texSize.y(), lUV.x(), lUV.y(), -4F, 0.0F, 0F, dim.x(), dim.y(), 3, 0.0F, false); + var rUV = textureData.rightUv(); + rBoobArmor = new BreastModelBox(texSize.x(), texSize.y(), rUV.x(), rUV.y(), 0, 0.0F, 0F, dim.x(), dim.y(), 3, 0.0F, false); } @Override @@ -149,6 +159,10 @@ protected void setupTransformations(S state, M model, MatrixStack matrixStack, B // TODO eventually expose some way for mods to override this, maybe through a default impl in IGenderArmor or similar protected void renderBreastArmor(Identifier texture, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light, BreastSide side, int color, boolean glint) { + if(MinecraftClient.getInstance().getTextureManager().getTexture(texture) == MissingSprite.getMissingSpriteTexture()) { + return; + } + BreastModelBox armor = side.isLeft ? lBoobArmor : rBoobArmor; RenderLayer armorType = RenderLayer.getArmorCutoutNoCull(texture); VertexConsumer armorVertexConsumer = ItemRenderer.getArmorGlintConsumer(vertexConsumerProvider, armorType, glint); diff --git a/src/main/java/com/wildfire/render/armor/EmptyGenderArmor.java b/src/main/java/com/wildfire/render/armor/EmptyGenderArmor.java deleted file mode 100644 index b818693f..00000000 --- a/src/main/java/com/wildfire/render/armor/EmptyGenderArmor.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -package com.wildfire.render.armor; - -import com.wildfire.api.IGenderArmor; - -/** - * Implementation of {@link IGenderArmor} for when there is nothing being worn or the item being worn does not cover the breast area. - */ -public class EmptyGenderArmor implements IGenderArmor { - - public static final EmptyGenderArmor INSTANCE = new EmptyGenderArmor(); - - private EmptyGenderArmor() { - } - - @Override - public boolean coversBreasts() { - return false; - } - - @Override - public boolean armorStandsCopySettings() { - return false; - } -} diff --git a/src/main/java/com/wildfire/render/armor/SimpleGenderArmor.java b/src/main/java/com/wildfire/render/armor/SimpleGenderArmor.java deleted file mode 100644 index a861a8d8..00000000 --- a/src/main/java/com/wildfire/render/armor/SimpleGenderArmor.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -package com.wildfire.render.armor; - -import com.wildfire.api.IGenderArmor; - -/** - * Default implementations of {@link IGenderArmor} for vanilla armor types - */ -public record SimpleGenderArmor(float physicsResistance, float tightness, boolean armorStandsCopySettings) implements IGenderArmor { - - public static final SimpleGenderArmor FALLBACK = new SimpleGenderArmor(0.5F); - public static final SimpleGenderArmor LEATHER = new SimpleGenderArmor(0.3F, 0.5F); - public static final SimpleGenderArmor CHAIN_MAIL = new SimpleGenderArmor(0.5F, 0.2F); - public static final SimpleGenderArmor GOLD = new SimpleGenderArmor(0.85F, true); - public static final SimpleGenderArmor IRON = new SimpleGenderArmor(1, true); - public static final SimpleGenderArmor DIAMOND = new SimpleGenderArmor(1, true); - public static final SimpleGenderArmor NETHERITE = new SimpleGenderArmor(1, true); - - public SimpleGenderArmor(float physicsResistance) { - this(physicsResistance, 0, false); - } - - public SimpleGenderArmor(float physicsResistance, boolean armorStandsCopySettings) { - this(physicsResistance, 0f, armorStandsCopySettings); - } - - public SimpleGenderArmor(float physicsResistance, float tightness) { - this(physicsResistance, tightness, false); - } -} \ No newline at end of file diff --git a/src/main/java/com/wildfire/resources/GenderArmorResourceManager.java b/src/main/java/com/wildfire/resources/GenderArmorResourceManager.java new file mode 100644 index 00000000..264785f2 --- /dev/null +++ b/src/main/java/com/wildfire/resources/GenderArmorResourceManager.java @@ -0,0 +1,66 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.resources; + +import com.wildfire.api.IGenderArmor; +import com.wildfire.main.WildfireGender; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.EquippableComponent; +import net.minecraft.item.ItemStack; +import net.minecraft.resource.JsonDataLoader; +import net.minecraft.resource.ResourceManager; +import net.minecraft.util.Identifier; +import net.minecraft.util.profiler.Profiler; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.*; + +@Environment(EnvType.CLIENT) +public final class GenderArmorResourceManager extends JsonDataLoader implements IdentifiableResourceReloadListener { + private GenderArmorResourceManager() { + super(IGenderArmor.CODEC, "wildfire_gender_data"); + } + + public static final GenderArmorResourceManager INSTANCE = new GenderArmorResourceManager(); + private @Unmodifiable Map configs = Map.of(); + + public static @Nullable IGenderArmor get(Identifier model) { + return INSTANCE.configs.get(model); + } + + public static Optional get(ItemStack item) { + return Optional.ofNullable(item.get(DataComponentTypes.EQUIPPABLE)) + .flatMap(EquippableComponent::model) + .map(GenderArmorResourceManager::get); + } + + @Override + public Identifier getFabricId() { + return Identifier.of(WildfireGender.MODID, "armor_data"); + } + + @Override + protected void apply(Map prepared, ResourceManager manager, Profiler profiler) { + this.configs = Collections.unmodifiableMap(prepared); + } +} diff --git a/src/main/resources/assets/minecraft/wildfire_gender_data/chainmail.json b/src/main/resources/assets/minecraft/wildfire_gender_data/chainmail.json new file mode 100644 index 00000000..2c5df60f --- /dev/null +++ b/src/main/resources/assets/minecraft/wildfire_gender_data/chainmail.json @@ -0,0 +1,4 @@ +{ + "resistance": 0.5, + "tightness": 0.2 +} \ No newline at end of file diff --git a/src/main/resources/assets/minecraft/wildfire_gender_data/diamond.json b/src/main/resources/assets/minecraft/wildfire_gender_data/diamond.json new file mode 100644 index 00000000..8603d20d --- /dev/null +++ b/src/main/resources/assets/minecraft/wildfire_gender_data/diamond.json @@ -0,0 +1,3 @@ +{ + "resistance": 1.0 +} \ No newline at end of file diff --git a/src/main/resources/assets/minecraft/wildfire_gender_data/elytra.json b/src/main/resources/assets/minecraft/wildfire_gender_data/elytra.json new file mode 100644 index 00000000..c0cbede8 --- /dev/null +++ b/src/main/resources/assets/minecraft/wildfire_gender_data/elytra.json @@ -0,0 +1,3 @@ +{ + "covers_breasts": false +} \ No newline at end of file diff --git a/src/main/resources/assets/minecraft/wildfire_gender_data/gold.json b/src/main/resources/assets/minecraft/wildfire_gender_data/gold.json new file mode 100644 index 00000000..20387637 --- /dev/null +++ b/src/main/resources/assets/minecraft/wildfire_gender_data/gold.json @@ -0,0 +1,4 @@ +{ + "resistance": 0.85, + "render_on_armor_stands": true +} \ No newline at end of file diff --git a/src/main/resources/assets/minecraft/wildfire_gender_data/iron.json b/src/main/resources/assets/minecraft/wildfire_gender_data/iron.json new file mode 100644 index 00000000..8603d20d --- /dev/null +++ b/src/main/resources/assets/minecraft/wildfire_gender_data/iron.json @@ -0,0 +1,3 @@ +{ + "resistance": 1.0 +} \ No newline at end of file diff --git a/src/main/resources/assets/minecraft/wildfire_gender_data/leather.json b/src/main/resources/assets/minecraft/wildfire_gender_data/leather.json new file mode 100644 index 00000000..262f2334 --- /dev/null +++ b/src/main/resources/assets/minecraft/wildfire_gender_data/leather.json @@ -0,0 +1,4 @@ +{ + "resistance": 0.3, + "tightness": 0.5 +} \ No newline at end of file diff --git a/src/main/resources/assets/minecraft/wildfire_gender_data/netherite.json b/src/main/resources/assets/minecraft/wildfire_gender_data/netherite.json new file mode 100644 index 00000000..8603d20d --- /dev/null +++ b/src/main/resources/assets/minecraft/wildfire_gender_data/netherite.json @@ -0,0 +1,3 @@ +{ + "resistance": 1.0 +} \ No newline at end of file From 43bea6c6e770309970365545c61b868967c980bc Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 18 Oct 2024 21:14:02 -0600 Subject: [PATCH 113/238] fix circular reference oops --- .../java/com/wildfire/api/IBreastArmorTexture.java | 6 +++--- src/main/java/com/wildfire/api/IGenderArmor.java | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/wildfire/api/IBreastArmorTexture.java b/src/main/java/com/wildfire/api/IBreastArmorTexture.java index 5388e1c8..059fd2b0 100644 --- a/src/main/java/com/wildfire/api/IBreastArmorTexture.java +++ b/src/main/java/com/wildfire/api/IBreastArmorTexture.java @@ -35,16 +35,16 @@ public interface IBreastArmorTexture { Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( Vec2i.CODEC - .optionalFieldOf("texture_size", BreastArmorTexture.DEFAULT.textureSize()) + .optionalFieldOf("texture_size", DEFAULT_TEXTURE_SIZE) .forGetter(IBreastArmorTexture::textureSize), Vec2i.CODEC - .optionalFieldOf("left_uv", BreastArmorTexture.DEFAULT.leftUv()) + .optionalFieldOf("left_uv", DEFAULT_LEFT_UV) .forGetter(IBreastArmorTexture::leftUv), Vec2i.CODEC .optionalFieldOf("right_uv", new Vec2i(-1, -1)) .forGetter(IBreastArmorTexture::rightUv), Vec2i.CODEC - .optionalFieldOf("dimensions", BreastArmorTexture.DEFAULT.dimensions()) + .optionalFieldOf("dimensions", DEFAULT_DIMENSIONS) .forGetter(IBreastArmorTexture::dimensions) ).apply(instance, (size, leftUv, rightUv, dimensions) -> { var right = rightUv; diff --git a/src/main/java/com/wildfire/api/IGenderArmor.java b/src/main/java/com/wildfire/api/IGenderArmor.java index f76cf5bf..5b4605e3 100644 --- a/src/main/java/com/wildfire/api/IGenderArmor.java +++ b/src/main/java/com/wildfire/api/IGenderArmor.java @@ -33,22 +33,22 @@ public interface IGenderArmor { Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( WildfireHelper.boundedFloat(0f, 1f) - .optionalFieldOf("resistance", GenderArmor.DEFAULT.physicsResistance()) + .optionalFieldOf("resistance", 0.5f) .forGetter(IGenderArmor::physicsResistance), WildfireHelper.boundedFloat(0f, 1f) - .optionalFieldOf("tightness", GenderArmor.DEFAULT.tightness()) + .optionalFieldOf("tightness", 0f) .forGetter(IGenderArmor::tightness), Codec.BOOL - .optionalFieldOf("covers_breasts", GenderArmor.DEFAULT.coversBreasts()) + .optionalFieldOf("covers_breasts", true) .forGetter(IGenderArmor::coversBreasts), Codec.BOOL - .optionalFieldOf("hide_breasts", GenderArmor.DEFAULT.alwaysHidesBreasts()) + .optionalFieldOf("hide_breasts", false) .forGetter(IGenderArmor::alwaysHidesBreasts), WildfireHelper.TRISTATE .optionalFieldOf("render_on_armor_stands", TriState.DEFAULT) .forGetter(armor -> armor.armorStandsCopySettings() ? TriState.TRUE : TriState.FALSE), IBreastArmorTexture.CODEC - .optionalFieldOf("texture", GenderArmor.DEFAULT.texture()) + .optionalFieldOf("texture", BreastArmorTexture.DEFAULT) .forGetter(IGenderArmor::texture) ).apply(instance, (resistance, tightness, covers, hideBreasts, armorStands, texture) -> { if(!covers) { From baa403e32ba1fc1baecfc578bdf303b0667c9fc3 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Fri, 18 Oct 2024 23:31:17 -0400 Subject: [PATCH 114/238] Screen background no longer blurs, it's reverted back to the darkened background for consistency. --- .../java/com/wildfire/gui/screen/BaseWildfireScreen.java | 4 ++++ .../com/wildfire/gui/screen/WardrobeBrowserScreen.java | 4 +++- .../gui/screen/WildfireBreastCustomizationScreen.java | 9 +++++++-- .../gui/screen/WildfireCharacterSettingsScreen.java | 3 ++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java b/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java index a2ac1060..e0d84489 100644 --- a/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java +++ b/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java @@ -25,6 +25,9 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.CraftingScreen; +import net.minecraft.client.gui.screen.ingame.FurnaceScreen; +import net.minecraft.client.gui.screen.ingame.InventoryScreen; import net.minecraft.text.Text; import org.jetbrains.annotations.Nullable; @@ -38,6 +41,7 @@ protected BaseWildfireScreen(Text title, Screen parent, UUID uuid) { super(title); this.parent = parent; this.playerUUID = uuid; + } public @Nullable PlayerConfig getPlayer() { diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index 20711820..d531319b 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -33,6 +33,7 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.InventoryScreen; import net.minecraft.client.render.RenderLayer; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.text.Text; @@ -88,7 +89,8 @@ private Text getGenderLabel(Gender gender) { @Override public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delta) { - super.renderBackground(ctx, mouseX, mouseY, delta); + this.renderInGameBackground(ctx); + PlayerConfig plr = getPlayer(); if(plr == null) return; Identifier backgroundTexture = plr.getGender().canHaveBreasts() ? BACKGROUND_FEMALE : BACKGROUND; diff --git a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java index 11f67576..5d84f5d4 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java @@ -69,7 +69,7 @@ public void init() { button -> MinecraftClient.getInstance().setScreen(parent))); //Customization Tab - this.addDrawableChild(btnCustomization = new WildfireButton(this.width / 2 + 30, j - 60, 158 / 2 - 1, 10, + this.addDrawableChild(btnCustomization = new WildfireButton(this.width / 2 + 30, j - 60, 158, 10, Text.translatable("wildfire_gender.breast_customization.tab_customization"), button -> { currentTab = 0; btnCustomization.active = false; @@ -91,6 +91,10 @@ public void init() { btnDeletePreset.visible = true; PRESET_LIST.refreshList(); })); + + //Disable for now. + btnPresets.visible = false; + if(!FabricLoader.getInstance().isDevelopmentEnvironment()) { btnPresets.setTooltip(Tooltip.of(Text.translatable("wildfire_gender.coming_soon"))); } @@ -175,7 +179,8 @@ private void updatePresetTab() { @Override public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delta) { - super.renderBackground(ctx, mouseX, mouseY, delta); + this.renderInGameBackground(ctx); + int x = this.width / 2; int y = this.height / 2; ctx.fill(x + 28, y - 64 - 21, x + 190, y + 68, 0x55000000); diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java index 282a30d2..f000325f 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java @@ -128,7 +128,8 @@ public void init() { @Override public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delta) { - super.renderBackground(ctx, mouseX, mouseY, delta); + this.renderInGameBackground(ctx); + ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND, (this.width - 172) / 2, (this.height - 124) / 2, 0, 0, 172, 144, 256, 256); } From f036b2d13fe1ceda5a8483f608ec0b89a216135a Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Fri, 18 Oct 2024 23:41:45 -0400 Subject: [PATCH 115/238] Implemented simplified transform code PR #207. (I get scared of merging PRs that are from months ago) --- .../java/com/wildfire/render/GenderLayer.java | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index 7060d039..e078b6d6 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -57,6 +57,8 @@ @Environment(EnvType.CLIENT) public class GenderLayer> extends FeatureRenderer { + private static final float DEG_TO_RAD = (float) (Math.PI / 180); + private BreastModelBox lBreast, rBreast; private static final OverlayModelBox lBreastWear, rBreastWear; @@ -230,14 +232,8 @@ protected void setupTransformations(S state, M model, MatrixStack matrixStack, B ModelPart body = model.body; matrixStack.translate(body.pivotX * 0.0625f, body.pivotY * 0.0625f, body.pivotZ * 0.0625f); - if(body.roll != 0.0F) { - matrixStack.multiply(new Quaternionf().rotationXYZ(0f, 0f, body.roll)); - } - if(body.yaw != 0.0F) { - matrixStack.multiply(new Quaternionf().rotationXYZ(0f, body.yaw, 0f)); - } - if(body.pitch != 0.0F) { - matrixStack.multiply(new Quaternionf().rotationXYZ(body.pitch, 0f, 0f)); + if(body.roll != 0.0F || body.yaw != 0.0F || body.pitch != 0.0F) { + matrixStack.multiply(new Quaternionf().rotationZYX(body.roll, body.yaw, body.pitch)); } if(bounceEnabled) { @@ -257,32 +253,30 @@ protected void setupTransformations(S state, M model, MatrixStack matrixStack, B matrixStack.translate(0.0625f * 2 * (side.isLeft ? 1 : -1), 0, 0); } - float rotationMultiplier = 0; + float rotation = breastSize; if(bounceEnabled) { matrixStack.translate(0, -0.035f * breastSize, 0); //shift down to correct position - rotationMultiplier = -(side.isLeft ? lPhysPositionY : rPhysPositionY) / 12f; + rotation -= (side.isLeft ? lPhysPositionY : rPhysPositionY) / 12f; } - float totalRotation = breastSize + rotationMultiplier; - if(!bounceEnabled) { - totalRotation = breastSize; - } - if(totalRotation > breastSize + 0.2F) { - totalRotation = breastSize + 0.2F; - } - totalRotation = Math.min(totalRotation, 1); //hard limit for MAX + + rotation = Math.min(rotation, breastSize + 0.2f); + rotation = Math.min(rotation, 1); //hard limit for MAX if(isChestplateOccupied) { matrixStack.translate(0, 0, 0.01f); } - matrixStack.multiply(new Quaternionf().rotationXYZ(0, (float)((side.isLeft ? outwardAngle : -outwardAngle) * (Math.PI / 180f)), 0)); - matrixStack.multiply(new Quaternionf().rotationXYZ((float)(-35f * totalRotation * (Math.PI / 180f)), 0, 0)); + Quaternionf rotationTransform = new Quaternionf() + .rotationY((side.isLeft ? outwardAngle : -outwardAngle) * DEG_TO_RAD) + .rotateX(-35f * rotation * DEG_TO_RAD); if(breathingAnimation) { float f5 = -MathHelper.cos(state.age * 0.09F) * 0.45F + 0.45F; - matrixStack.multiply(new Quaternionf().rotationXYZ((float)(f5 * (Math.PI / 180f)), 0, 0)); + rotationTransform.rotateX(f5 * DEG_TO_RAD); + //matrixStack.multiply(new Quaternionf().rotationXYZ((float)(f5 * (Math.PI / 180f)), 0, 0)); } + matrixStack.multiply(rotationTransform); matrixStack.scale(0.9995f, 1f, 1f); //z-fighting FIXXX } From 9d05201536e6ffed8fa3aaa7e64a1ba3b7f64b2c Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Fri, 18 Oct 2024 23:43:10 -0400 Subject: [PATCH 116/238] Added RacoonDog as contributor. --- src/main/resources/fabric.mod.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index b77299ee..763fa60e 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -15,7 +15,8 @@ "IzzyBizzy45", "Kichura", "Powerless001", - "pupnewfster" + "pupnewfster", + "RacoonDog" ], "contact": { "homepage": "https://www.curseforge.com/minecraft/mc-mods/female-gender-fabric", From 550645dde16867ddde3cc38b61ba16750e5ff5d9 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Fri, 18 Oct 2024 23:48:57 -0400 Subject: [PATCH 117/238] Added Netherlands translation by PinguinLars. --- .../assets/wildfire_gender/lang/nl_nl.json | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/main/resources/assets/wildfire_gender/lang/nl_nl.json diff --git a/src/main/resources/assets/wildfire_gender/lang/nl_nl.json b/src/main/resources/assets/wildfire_gender/lang/nl_nl.json new file mode 100644 index 00000000..e8134383 --- /dev/null +++ b/src/main/resources/assets/wildfire_gender/lang/nl_nl.json @@ -0,0 +1,61 @@ +{ + "category.wildfire_gender.generic": "Wildfire's Vrouwelijke Geslachts Mod", + "key.wildfire_gender.gender_menu": "Vrouwelijke Geslachts Menu", + "toast.wildfire_gender.get_started": "Druk op '%s' om te starten!", + + "wildfire_gender.player_list.title": "Vrouwelijke Geslachts Mod", + "wildfire_gender.player_list.settings_button": "Instellingen", + "wildfire_gender.player_list.sync_status": "Status Synchroniseren", + "wildfire_gender.player_list.state.loading": "Data Laden...", + "wildfire_gender.player_list.state.synced": "Speler Gesynchroniseerd", + "wildfire_gender.player_list.bounce_multiplier": "Veerkracht Vermenigvuldiger: %sx", + "wildfire_gender.player_list.breast_momentum": "Borst Momentum: %s%%", + "wildfire_gender.player_list.female_sounds": "Vrouwelijk Geluiden: %s", + + "wildfire_gender.wardrobe.title": "Wildfire's Vrouwelijke Geslachts Mod", + "wildfire_gender.breast_customization.tab_customization": "Aanpassen", + "wildfire_gender.breast_customization.tab_presets": "Voorbeelden", + + "wildfire_gender.breast_customization.presets.add_new": "Iets nieuw toevoegen...", + "wildfire_gender.breast_customization.presets.delete": "Verwijderen", + + "wildfire_gender.wardrobe.slider.breast_size": "Borst Grote: %s%%", + "wildfire_gender.wardrobe.slider.separation": "Tussenruimte: %s", + "wildfire_gender.wardrobe.slider.height": "Hoogte: %s", + "wildfire_gender.wardrobe.slider.depth": "Diepte: %s", + "wildfire_gender.wardrobe.slider.rotation": "Rotatie: %s graden", + + "wildfire_gender.appearance_settings.title": "Uiterlijk instellingen", + "wildfire_gender.char_settings.title": "Karakter Instellingen", + "wildfire_gender.char_settings.physics": "Borst Zwaartekracht: %s", + + "wildfire_gender.char_settings.override_armor_physics": "Harnas Zwaartekracht: %s", + "wildfire_gender.tooltip.override_armor_physics.line1": "Borst zwaartekracht zal niet langer onderdrukt worden bij jouw aangetrokken harnas, als dit aan staat", + "wildfire_gender.tooltip.override_armor_physics.line2": "Dit is bedoeld voor het gebruik met bronpakketen die harnas verstoppen of iets in die geest", + + "wildfire_gender.char_settings.hide_in_armor": "Verstop In Harnas: %s", + "wildfire_gender.char_settings.hurt_sounds": "Vrouwelijke Pijn Geluiden: %s", + "wildfire_gender.tooltip.hurt_sounds": "Jou karakter zal een vrouwelijke pijn geluid spelen als je schade neemt en als je geslacht is ingesteld op Vrouwelijk of Anders", + + "wildfire_gender.breast_customization.dual_physics": "Dubbel-Zwaartekracht: %s", + + "wildfire_gender.label.gender": "Geslacht", + "wildfire_gender.label.female": "Vrouwelijk", + "wildfire_gender.label.male": "Mannelijk", + "wildfire_gender.label.other": "Anders", + + "wildfire_gender.label.enabled": "Aan", + "wildfire_gender.label.disabled": "Uit", + "wildfire_gender.label.yes": "Ja", + "wildfire_gender.label.no": "Nee", + "wildfire_gender.label.with_creator": "Je speelt op een server met de maker van deze mod!", + + "wildfire_gender.slider.bounce": "Verkracht Intensiteit: %s%%", + "wildfire_gender.slider.floppy": "Borst Momentum: %s%%", + "wildfire_gender.slider.min_bounce": "Waarom Is Zwaartekracht Aan?", + "wildfire_gender.slider.max_bounce": "Anime Borst Zwaartekracht!!!", + "wildfire_gender.tooltip.bounce_warning": "Instelling 'Verkracht Intensiteit' op een hoog nummer ziet er erg niet natuurlijk uit!", + + "wildfire_gender.cancer_awareness.title": "Hé, het is Borst Kanker Aandacht Maand", + "wildfire_gender.coming_soon": "Komt Binnenkort" +} From 20de8714ce90fedf720d0dd38d5e1a5042d8dcaa Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sat, 19 Oct 2024 10:53:58 -0400 Subject: [PATCH 118/238] Minor changes --- .../java/com/wildfire/render/GenderLayer.java | 19 ++++++++++++------- src/main/resources/fabric.mod.json | 1 + 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index e078b6d6..57080bbe 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -193,13 +193,19 @@ protected boolean setupRender(S state, EntityConfig entityConfig) { rPhysPositionX = MathHelper.lerp(partialTicks, rightBreastPhysics.getPrePositionX(), rightBreastPhysics.getPositionX()); rPhysBounceRotation = MathHelper.lerp(partialTicks, rightBreastPhysics.getPreBounceRotation(), rightBreastPhysics.getBounceRotation()); } - breastSize = bSize * 1.5f; - if(breastSize > 0.7f) breastSize = 0.7f; - if(bSize > 0.7f) breastSize = bSize; - if(breastSize < 0.02f) return false; - zOffset = 0.0625f - (bSize * 0.0625f); - breastSize = bSize + 0.5f * Math.abs(bSize - 0.7f) * 2f; + breastSize = Math.min(bSize * 1.5f, 0.7f); // Limit the max size to 0.7f + + if (bSize > 0.7f) { + breastSize = bSize; // If bSize exceeds 0.7f, use bSize + } + + if (breastSize < 0.02f) { + return false; // Return false if breastSize is too small + } + + zOffset = 0.0625f - (bSize * 0.0625f); // Calculate zOffset + breastSize += 0.5f * Math.abs(bSize - 0.7f) * 2f; // Adjust breastSize based on bSize float resistance = MathHelper.clamp(genderArmor.physicsResistance(), 0, 1); //Note: We only check if the breathing animation should be enabled if the chestplate's physics resistance @@ -273,7 +279,6 @@ protected void setupTransformations(S state, M model, MatrixStack matrixStack, B if(breathingAnimation) { float f5 = -MathHelper.cos(state.age * 0.09F) * 0.45F + 0.45F; rotationTransform.rotateX(f5 * DEG_TO_RAD); - //matrixStack.multiply(new Quaternionf().rotationXYZ((float)(f5 * (Math.PI / 180f)), 0, 0)); } matrixStack.multiply(rotationTransform); diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 763fa60e..4cd028ba 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -14,6 +14,7 @@ "diademiemi", "IzzyBizzy45", "Kichura", + "PinguinLars", "Powerless001", "pupnewfster", "RacoonDog" From 9d4cdbfbc86d04c74b382e2060279e404a94c12c Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 20 Oct 2024 14:13:22 -0600 Subject: [PATCH 119/238] mark essential as a conflict essential has caused many compatibility issues over the years, namely breaking our rendering on occasion and *still* completely breaking vanilla rendering features we rely on with certain cosmetics equipped, all while attempting to claim that its an "essential" mod. see also: https://notessential.blurry.gay/ --- src/main/resources/fabric.mod.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 4cd028ba..acee56b1 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -51,7 +51,8 @@ "java": ">=21" }, "conflicts": { - "skinlayers": "*" + "skinlayers": "*", + "essential": "*" }, "custom": { "modmenu": { From ba7efff8e7320f2c699e4ec4b852ca84c442d9e0 Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 21 Oct 2024 14:08:48 -0600 Subject: [PATCH 120/238] remove some redundant uses of render state entity references --- src/main/java/com/wildfire/render/GenderArmorLayer.java | 9 ++++----- src/main/java/com/wildfire/render/GenderLayer.java | 8 +++----- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/wildfire/render/GenderArmorLayer.java b/src/main/java/com/wildfire/render/GenderArmorLayer.java index 60378a4c..a5705e95 100644 --- a/src/main/java/com/wildfire/render/GenderArmorLayer.java +++ b/src/main/java/com/wildfire/render/GenderArmorLayer.java @@ -26,12 +26,13 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.client.render.*; import net.minecraft.client.render.entity.equipment.EquipmentModelLoader; import net.minecraft.client.render.entity.feature.FeatureRendererContext; import net.minecraft.client.render.entity.model.BipedEntityModel; +import net.minecraft.client.render.entity.state.ArmorStandEntityRenderState; import net.minecraft.client.render.entity.state.BipedEntityRenderState; +import net.minecraft.client.render.entity.state.PlayerEntityRenderState; import net.minecraft.client.render.item.ItemRenderer; import net.minecraft.client.render.model.BakedModelManager; import net.minecraft.client.texture.MissingSprite; @@ -43,7 +44,6 @@ import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.decoration.ArmorStandEntity; -import net.minecraft.entity.player.PlayerModelPart; import net.minecraft.item.ItemStack; import net.minecraft.item.equipment.EquipmentModel; import net.minecraft.item.equipment.trim.ArmorTrim; @@ -146,9 +146,8 @@ protected void resizeBox(float breastSize) { @Override protected void setupTransformations(S state, M model, MatrixStack matrixStack, BreastSide side) { super.setupTransformations(state, model, matrixStack, side); - LivingEntity entity = Objects.requireNonNull(getEntity(state), "getEntity()"); - if((entity instanceof AbstractClientPlayerEntity player && player.isPartVisible(PlayerModelPart.JACKET)) || - (entity instanceof ArmorStandEntity && entityConfig.hasJacketLayer())) { + if((state instanceof PlayerEntityRenderState playerState && playerState.jacketVisible) || + (state instanceof ArmorStandEntityRenderState && entityConfig.hasJacketLayer())) { matrixStack.translate(0, 0, -0.015f); matrixStack.scale(1.05f, 1.05f, 1.05f); } diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index 57080bbe..8ba89c83 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -37,17 +37,16 @@ import net.minecraft.block.Blocks; import net.minecraft.client.MinecraftClient; import net.minecraft.client.model.ModelPart; -import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.client.render.*; import net.minecraft.client.render.entity.LivingEntityRenderer; import net.minecraft.client.render.entity.feature.FeatureRenderer; import net.minecraft.client.render.entity.feature.FeatureRendererContext; import net.minecraft.client.render.entity.model.BipedEntityModel; import net.minecraft.client.render.entity.state.BipedEntityRenderState; +import net.minecraft.client.render.entity.state.PlayerEntityRenderState; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.effect.StatusEffectUtil; -import net.minecraft.entity.player.PlayerModelPart; import net.minecraft.item.ItemStack; import net.minecraft.util.Identifier; import net.minecraft.util.math.*; @@ -287,14 +286,13 @@ protected void setupTransformations(S state, M model, MatrixStack matrixStack, B private void renderBreast(S state, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light, int overlay, BreastSide side) { - LivingEntity entity = Objects.requireNonNull(getEntity(state), "getEntity()"); RenderLayer breastRenderType = getRenderLayer(state); if(breastRenderType == null) return; // only render if the player is visible in some capacity - int alpha = entity.isInvisible() ? ColorHelper.channelFromFloat(0.15f) : 255; + int alpha = state.invisible ? ColorHelper.channelFromFloat(0.15f) : 255; int color = ColorHelper.getArgb(alpha, 255, 255, 255); VertexConsumer vertexConsumer = vertexConsumerProvider.getBuffer(breastRenderType); renderBox(side.isLeft ? lBreast : rBreast, matrixStack, vertexConsumer, light, overlay, color); - if(entity instanceof AbstractClientPlayerEntity player && player.isPartVisible(PlayerModelPart.JACKET)) { + if(state instanceof PlayerEntityRenderState playerState && playerState.jacketVisible) { matrixStack.translate(0, 0, -0.015f); matrixStack.scale(1.05f, 1.05f, 1.05f); renderBox(side.isLeft ? lBreastWear : rBreastWear, matrixStack, vertexConsumer, light, overlay, color); From 79aa7629f39d6e33f99809fcac5049a3b23e8143 Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 21 Oct 2024 14:37:46 -0600 Subject: [PATCH 121/238] slightly improve how layer visibility is determined this also comes with the side-effect of making `covers_breasts` actually hide the armor layer; this was already the case to an extent (provided the wearer was invisible & wasn't glowing), but was more of an unintended interaction rather than intentional behavior. --- doc/armor_configs.md | 4 ++-- src/main/java/com/wildfire/api/IGenderArmor.java | 5 ++++- src/main/java/com/wildfire/render/GenderArmorLayer.java | 6 +++++- src/main/java/com/wildfire/render/GenderLayer.java | 9 ++++++--- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/doc/armor_configs.md b/doc/armor_configs.md index 59520e12..3adb4074 100644 --- a/doc/armor_configs.md +++ b/doc/armor_configs.md @@ -41,8 +41,8 @@ Defaults to `0.0` (0%) if not set. ### `covers_breasts` Boolean value determining if this armor piece covers the wearer's breasts; this is intended to be set to `false` for equippable -chest slot items with an open front, such as the Elytra. Note that this does *not* prevent the item from rendering over the wearer's -breasts. +chest slot items with an open front, such as the Elytra. +Note that if this is `false` the armor layer won't be rendered, as if the armor piece simply didn't exist. Defaults to `true` if not set. diff --git a/src/main/java/com/wildfire/api/IGenderArmor.java b/src/main/java/com/wildfire/api/IGenderArmor.java index 5b4605e3..4a8e7b9c 100644 --- a/src/main/java/com/wildfire/api/IGenderArmor.java +++ b/src/main/java/com/wildfire/api/IGenderArmor.java @@ -58,7 +58,10 @@ public interface IGenderArmor { })); /** - * Determines whether this {@link IGenderArmor} "covers" the breasts or if it has an open front ({@code false}) like the elytra. + *

Determines whether this {@link IGenderArmor} "covers" the breasts or if it has an open front ({@code false}) like the elytra.

+ * + *

If this returns {@code false} the breast armor layer will not be rendered while this item is worn, as if + * the item simply didn't exist.

* * @return {@code true} if the breasts are covered. * diff --git a/src/main/java/com/wildfire/render/GenderArmorLayer.java b/src/main/java/com/wildfire/render/GenderArmorLayer.java index a5705e95..5009819d 100644 --- a/src/main/java/com/wildfire/render/GenderArmorLayer.java +++ b/src/main/java/com/wildfire/render/GenderArmorLayer.java @@ -99,7 +99,6 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume if(entityConfig == null) return; if(!setupRender(state, entityConfig)) return; - // TODO skip rendering if coversBreasts() is false, or maybe make a separate renderArmor() property instead? if(ent instanceof ArmorStandEntity && !genderArmor.armorStandsCopySettings()) return; int color = chestplate.isIn(ItemTags.DYEABLE) ? DyedColorComponent.getColor(chestplate, -1) : -1; @@ -128,6 +127,11 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume } } + @Override + protected boolean isLayerVisible(S state) { + return genderArmor.coversBreasts(); + } + @Override protected void resizeBox(float breastSize) { if(genderArmor == null || Objects.equals(textureData, genderArmor.texture())) { diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index 8ba89c83..6948a08c 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -133,6 +133,7 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume if(!setupRender(state, entityConfig)) return; int overlay = LivingEntityRenderer.getOverlay(state, 0); + //noinspection CodeBlock2Expr renderSides(state, getContextModel(), matrixStack, side -> { renderBreast(state, matrixStack, vertexConsumerProvider, light, overlay, side); }); @@ -161,9 +162,7 @@ protected boolean setupRender(S state, EntityConfig entityConfig) { return false; } - RenderLayer type = getRenderLayer(state); - if(type == null && !isChestplateOccupied) { - // the entity is invisible and doesn't have a chestplate equipped + if(!isLayerVisible(state)) { return false; } @@ -216,6 +215,10 @@ protected boolean setupRender(S state, EntityConfig entityConfig) { return true; } + protected boolean isLayerVisible(S state) { + return !state.invisibleToPlayer || state.hasOutline; + } + protected void resizeBox(float breastSize) { float reducer = -1; if(breastSize < 0.84f) reducer++; From 95b4049fbae18e89657e1e712ec76b8ab6a98323 Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 21 Oct 2024 15:27:30 -0600 Subject: [PATCH 122/238] improve some armor config documentation --- doc/armor_configs.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/doc/armor_configs.md b/doc/armor_configs.md index 3adb4074..fdefc031 100644 --- a/doc/armor_configs.md +++ b/doc/armor_configs.md @@ -49,7 +49,7 @@ Defaults to `true` if not set. ### `hide_breasts` Boolean value determining if the wearer's breasts should be hidden entirely while this armor piece is worn; this is -intended for armor that use custom rendering which is largely incompatible with how this mod's breasts render. +intended for armor that use custom rendering which would just lead to clipping or other unintended behavior. Defaults to `false` if not set. @@ -65,15 +65,14 @@ Defaults to `true` *only* if `resistance` is `1.0` if unset. ### `texture` -Object containing various texture-related overrides; note that all values that specify `{"x": ..., "y": ...}` -*must* contain both `x` and `y` if specified. +Object containing various texture-related overrides; note that all values **must** contain both `x` *and* `y` if specified. #### `texture_size` Controls the armor sprite's texture size. -Note that if your sprite is simply an upscaled resolution of the vanilla sprite size (such as 128x64, 256x128, etc.) -then you do *not* need to modify this or any other texture values. +Note that if your sprite is simply an upscaled resolution of the vanilla sprite (such as 128x64, 256x128, etc.) +you do *not* need to modify this or any other texture values, as it'll already handle that automatically. Defaults to `{"x": 64, "y": 32}` if unset. From 078c84465dbeb963bc4a0b878e6ca920080b3b39 Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 21 Oct 2024 15:53:12 -0600 Subject: [PATCH 123/238] cleanup unused imports --- src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java | 4 ---- .../java/com/wildfire/gui/screen/WardrobeBrowserScreen.java | 1 - .../com/wildfire/resources/GenderArmorResourceManager.java | 4 +++- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java b/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java index e0d84489..a2ac1060 100644 --- a/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java +++ b/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java @@ -25,9 +25,6 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.screen.ingame.CraftingScreen; -import net.minecraft.client.gui.screen.ingame.FurnaceScreen; -import net.minecraft.client.gui.screen.ingame.InventoryScreen; import net.minecraft.text.Text; import org.jetbrains.annotations.Nullable; @@ -41,7 +38,6 @@ protected BaseWildfireScreen(Text title, Screen parent, UUID uuid) { super(title); this.parent = parent; this.playerUUID = uuid; - } public @Nullable PlayerConfig getPlayer() { diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index d531319b..e9a604dc 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -33,7 +33,6 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.screen.ingame.InventoryScreen; import net.minecraft.client.render.RenderLayer; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.text.Text; diff --git a/src/main/java/com/wildfire/resources/GenderArmorResourceManager.java b/src/main/java/com/wildfire/resources/GenderArmorResourceManager.java index 264785f2..bdf09ffb 100644 --- a/src/main/java/com/wildfire/resources/GenderArmorResourceManager.java +++ b/src/main/java/com/wildfire/resources/GenderArmorResourceManager.java @@ -33,7 +33,9 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; -import java.util.*; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; @Environment(EnvType.CLIENT) public final class GenderArmorResourceManager extends JsonDataLoader implements IdentifiableResourceReloadListener { From 588018626a98682be8990818fb8ec5250f07d543 Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 15 Nov 2024 19:51:11 -0700 Subject: [PATCH 124/238] implement cloud syncing --- .../java/com/wildfire/api/WildfireAPI.java | 6 +- .../java/com/wildfire/gui/WildfireButton.java | 16 +- .../gui/screen/BaseWildfireScreen.java | 5 + .../gui/screen/WardrobeBrowserScreen.java | 20 +- .../gui/screen/WildfireCloudSyncScreen.java | 129 +++++++++++ .../com/wildfire/main/WildfireGender.java | 3 + .../wildfire/main/WildfireGenderClient.java | 25 ++- .../com/wildfire/main/WildfireHelper.java | 6 + .../com/wildfire/main/cloud/CloudSync.java | 201 ++++++++++++++++++ .../cloud/SyncingTooFrequentlyException.java | 22 ++ .../main/config/AbstractConfiguration.java | 4 + .../wildfire/main/config/Configuration.java | 2 + .../wildfire/main/config/GlobalConfig.java | 20 ++ .../main/entitydata/PlayerConfig.java | 99 +++++---- .../assets/wildfire_gender/lang/en_us.json | 11 +- .../assets/wildfire_gender/textures/cloud.png | Bin 0 -> 697 bytes .../wildfire_gender/textures/gui/sync_bg.png | Bin 0 -> 6161 bytes 17 files changed, 505 insertions(+), 64 deletions(-) create mode 100644 src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java create mode 100644 src/main/java/com/wildfire/main/cloud/CloudSync.java create mode 100644 src/main/java/com/wildfire/main/cloud/SyncingTooFrequentlyException.java create mode 100644 src/main/java/com/wildfire/main/config/GlobalConfig.java create mode 100644 src/main/resources/assets/wildfire_gender/textures/cloud.png create mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/sync_bg.png diff --git a/src/main/java/com/wildfire/api/WildfireAPI.java b/src/main/java/com/wildfire/api/WildfireAPI.java index 6b20835b..667468db 100644 --- a/src/main/java/com/wildfire/api/WildfireAPI.java +++ b/src/main/java/com/wildfire/api/WildfireAPI.java @@ -81,20 +81,16 @@ public static void addGenderArmor(Item item, IGenderArmor genderArmor) { return cfg.getGender(); } - // FIXME this method currently has the limitation of only actually affecting players that are currently cached /** *

Load the cached Gender Settings file for the specified {@link UUID}

* *

You should avoid using this unless you need to, as the mod will do this for you when loading a player entity.

* - * @apiNote This method currently has the limitation of only affecting players that are {@link #getPlayerById in the mod's cache}, - * and won't load anything otherwise. - * * @param uuid the uuid of the target {@link PlayerEntity} * @param markForSync {@code true} if player data should be synced to the server upon being loaded; this only has an effect on the client player. */ @Environment(EnvType.CLIENT) - public static CompletableFuture<@Nullable PlayerConfig> loadGenderInfo(UUID uuid, boolean markForSync) { + public static CompletableFuture<@NotNull PlayerConfig> loadGenderInfo(UUID uuid, boolean markForSync) { return WildfireGenderClient.loadGenderInfo(uuid, markForSync); } diff --git a/src/main/java/com/wildfire/gui/WildfireButton.java b/src/main/java/com/wildfire/gui/WildfireButton.java index 369d31db..fe9d514d 100644 --- a/src/main/java/com/wildfire/gui/WildfireButton.java +++ b/src/main/java/com/wildfire/gui/WildfireButton.java @@ -45,19 +45,23 @@ public WildfireButton(int x, int y, int w, int h, Text text, ButtonWidget.PressA setTooltip(tooltip); } - @Override - protected void renderWidget(DrawContext ctx, int mouseX, int mouseY, float partialTicks) { + protected void drawInner(DrawContext ctx, int mouseX, int mouseY, float partialTicks) { MinecraftClient minecraft = MinecraftClient.getInstance(); TextRenderer font = minecraft.textRenderer; + int textColor = active ? 0xFFFFFF : 0x666666; + int i = this.getX() + 2; + int j = this.getX() + this.getWidth() - 2; + GuiUtils.drawScrollableTextWithoutShadow(ctx, font, this.getMessage(), i, this.getY(), j, this.getY() + this.getHeight(), textColor); + } + + @Override + protected void renderWidget(DrawContext ctx, int mouseX, int mouseY, float partialTicks) { int clr = 0x444444 + (84 << 24); if(this.isHovered()) clr = 0x666666 + (84 << 24); if(!this.active) clr = 0x222222 + (84 << 24); if(!transparent) ctx.fill(getX(), getY(), getX() + getWidth(), getY() + getHeight(), clr); - int textColor = active ? 0xFFFFFF : 0x666666; - int i = this.getX() + 2; - int j = this.getX() + this.getWidth() - 2; - GuiUtils.drawScrollableTextWithoutShadow(ctx, font, this.getMessage(), i, this.getY(), j, this.getY() + this.getHeight(), textColor); + drawInner(ctx, mouseX, mouseY, partialTicks); RenderSystem.setShaderColor(1f, 1f, 1f, 1f); } diff --git a/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java b/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java index a2ac1060..41337f42 100644 --- a/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java +++ b/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java @@ -48,4 +48,9 @@ protected BaseWildfireScreen(Text title, Screen parent, UUID uuid) { public boolean shouldPause() { return false; } + + @Override + public void close() { + client.setScreen(parent); + } } diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index e9a604dc..c2d64644 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -30,7 +30,6 @@ import com.wildfire.main.entitydata.PlayerConfig; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; -import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.render.RenderLayer; @@ -44,6 +43,7 @@ public class WardrobeBrowserScreen extends BaseWildfireScreen { private static final Identifier BACKGROUND_FEMALE = Identifier.of(WildfireGender.MODID, "textures/gui/wardrobe_bg2.png"); private static final Identifier BACKGROUND = Identifier.of(WildfireGender.MODID, "textures/gui/wardrobe_bg3.png"); private static final Identifier TXTR_RIBBON = Identifier.of(WildfireGender.MODID, "textures/bc_ribbon.png"); + private static final Identifier CLOUD_ICON = Identifier.of(WildfireGender.MODID, "textures/cloud.png"); private static final UUID CREATOR_UUID = UUID.fromString("23b6feed-2dfe-4f2e-9429-863fd4adb946"); private static final boolean isBreastCancerAwarenessMonth = Calendar.getInstance().get(Calendar.MONTH) == Calendar.OCTOBER; @@ -53,6 +53,7 @@ public WardrobeBrowserScreen(Screen parent, UUID uuid) { @Override public void init() { + final var client = Objects.requireNonNull(this.client); int y = this.height / 2; PlayerConfig plr = Objects.requireNonNull(getPlayer(), "getPlayer()"); @@ -71,13 +72,24 @@ public void init() { if (plr.getGender().canHaveBreasts()) { this.addDrawableChild(new WildfireButton(this.width / 2 - 42, y - 32, 158, 20, Text.translatable("wildfire_gender.appearance_settings.title").append("..."), - button -> MinecraftClient.getInstance().setScreen(new WildfireBreastCustomizationScreen(WardrobeBrowserScreen.this, this.playerUUID)))); + button -> client.setScreen(new WildfireBreastCustomizationScreen(WardrobeBrowserScreen.this, this.playerUUID)))); } this.addDrawableChild(new WildfireButton(this.width / 2 - 42, y - (plr.getGender().canHaveBreasts() ? 12 : 32), 158, 20, Text.translatable("wildfire_gender.char_settings.title").append("..."), - button -> MinecraftClient.getInstance().setScreen(new WildfireCharacterSettingsScreen(WardrobeBrowserScreen.this, this.playerUUID)))); + button -> client.setScreen(new WildfireCharacterSettingsScreen(WardrobeBrowserScreen.this, this.playerUUID)))); + + var cloud = new WildfireButton( + this.width / 2 + 97, y - 63, 12, 9, Text.translatable("wildfire_gender.cloud_settings"), + button -> client.setScreen(new WildfireCloudSyncScreen(this, this.playerUUID)) + ) { + @Override + protected void drawInner(DrawContext ctx, int mouseX, int mouseY, float partialTicks) { + ctx.drawTexture(RenderLayer::getGuiTextured, CLOUD_ICON, getX() + 1, getY() + 1, 0, 0, 10, 7, 17, 13, 16, 13); + } + }; + this.addDrawableChild(cloud); this.addDrawableChild(new WildfireButton(this.width / 2 + 111, y - 63, 9, 9, Text.literal("X"), - button -> MinecraftClient.getInstance().setScreen(parent))); + button -> client.setScreen(parent))); super.init(); } diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java new file mode 100644 index 00000000..8a0835fc --- /dev/null +++ b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java @@ -0,0 +1,129 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.gui.screen; + +import com.wildfire.gui.WildfireButton; +import com.wildfire.main.WildfireGender; +import com.wildfire.main.cloud.CloudSync; +import com.wildfire.main.config.GlobalConfig; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.TextFieldWidget; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; + +import java.net.MalformedURLException; +import java.net.URI; +import java.util.Objects; +import java.util.UUID; + +@Environment(EnvType.CLIENT) +public class WildfireCloudSyncScreen extends BaseWildfireScreen { + + private static final Text ENABLED = Text.translatable("wildfire_gender.label.enabled").formatted(Formatting.GREEN); + private static final Text DISABLED = Text.translatable("wildfire_gender.label.disabled").formatted(Formatting.RED); + private static final Identifier BACKGROUND = Identifier.of(WildfireGender.MODID, "textures/gui/sync_bg.png"); + + private TextFieldWidget cloudUrl; + + protected WildfireCloudSyncScreen(Screen parent, UUID uuid) { + super(Text.translatable("wildfire_gender.cloud_settings"), parent, uuid); + } + + @Override + public void init() { + final var client = Objects.requireNonNull(this.client); + int x = this.width / 2; + int y = this.height / 2; + int yPos = y - 44; + int xPos = x + 60 / 2 - 1; + + this.addDrawableChild(new WildfireButton(this.width / 2 + 85, yPos - 11, 9, 9, Text.literal("X"), button -> close())); + + this.addDrawableChild(new WildfireButton(xPos - 35, yPos, 100, 20, + CloudSync.isEnabled() ? ENABLED : DISABLED, + button -> { + var config = GlobalConfig.INSTANCE; + config.set(GlobalConfig.CLOUD_SYNC_ENABLED, !config.get(GlobalConfig.CLOUD_SYNC_ENABLED)); + button.setMessage(CloudSync.isEnabled() ? ENABLED : DISABLED); + })); + + this.cloudUrl = new TextFieldWidget(client.textRenderer, 100, 20, Text.translatable("wildfire_gender.cloud.url")); + cloudUrl.setX(xPos - 35); + cloudUrl.setY(yPos + 22); + cloudUrl.setText(GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SERVER)); + cloudUrl.setTooltip(Tooltip.of(Text.translatable("wildfire_gender.cloud.url.tooltip"))); + this.addDrawableChild(cloudUrl); + + var syncButton = new WildfireButton(xPos - 80, yPos + 80, 100, 15, Text.translatable("wildfire_gender.cloud.sync"), this::sync); + syncButton.setActive(GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SYNC_ENABLED)); + this.addDrawableChild(syncButton); + + super.init(); + } + + private void sync(ButtonWidget button) { + button.active = false; + button.setMessage(Text.translatable("wildfire_gender.cloud.syncing")); + CloudSync.sync(Objects.requireNonNull(getPlayer())) + .thenRun(() -> button.setMessage(Text.translatable("wildfire_gender.cloud.syncing.success"))) + .exceptionallyAsync(exc -> { + WildfireGender.LOGGER.error("Failed to sync settings", exc); + button.setMessage(Text.translatable("wildfire_gender.cloud.syncing.fail")); + return null; + }); + } + + @Override + public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delta) { + this.renderInGameBackground(ctx); + ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND, (this.width - 203) / 2, (this.height - 117) / 2, 0, 0, 203, 117, 256, 256); + } + + @Override + public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { + if(client == null || client.world == null) return; + super.render(ctx, mouseX, mouseY, delta); + int x = this.width / 2; + int y = this.height / 2; + + ctx.drawText(textRenderer, Text.translatable("wildfire_gender.cloud.status"), x - 95, y - 40, 0x000000, false); + ctx.drawText(textRenderer, Text.translatable("wildfire_gender.cloud.url"), x - 95, y - 16, 0x000000, false); + } + + @Override + public void close() { + try { + var url = cloudUrl.getText(); + //noinspection ResultOfMethodCallIgnored + URI.create(url).toURL(); + GlobalConfig.INSTANCE.set(GlobalConfig.CLOUD_SERVER, url); + } catch(MalformedURLException e) { + // invalid url, discard it + } + GlobalConfig.INSTANCE.save(); + super.close(); + } +} \ No newline at end of file diff --git a/src/main/java/com/wildfire/main/WildfireGender.java b/src/main/java/com/wildfire/main/WildfireGender.java index 8aba972d..3c484a07 100644 --- a/src/main/java/com/wildfire/main/WildfireGender.java +++ b/src/main/java/com/wildfire/main/WildfireGender.java @@ -23,6 +23,8 @@ import java.util.UUID; import com.mojang.logging.LogUtils; +import com.wildfire.main.cloud.CloudSync; +import com.wildfire.main.config.GlobalConfig; import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.networking.WildfireSync; import net.fabricmc.api.ModInitializer; @@ -39,6 +41,7 @@ public class WildfireGender implements ModInitializer { public void onInitialize() { WildfireSync.register(); WildfireEventHandler.registerCommonEvents(); + GlobalConfig.INSTANCE.load(); } public static @Nullable PlayerConfig getPlayerById(UUID id) { diff --git a/src/main/java/com/wildfire/main/WildfireGenderClient.java b/src/main/java/com/wildfire/main/WildfireGenderClient.java index d7375467..0a29e88d 100644 --- a/src/main/java/com/wildfire/main/WildfireGenderClient.java +++ b/src/main/java/com/wildfire/main/WildfireGenderClient.java @@ -18,6 +18,8 @@ package com.wildfire.main; +import com.google.gson.JsonObject; +import com.wildfire.main.cloud.CloudSync; import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.networking.WildfireSync; import com.wildfire.resources.GenderArmorResourceManager; @@ -27,7 +29,7 @@ import net.fabricmc.fabric.api.resource.ResourceManagerHelper; import net.minecraft.resource.ResourceType; import net.minecraft.util.Util; -import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -45,8 +47,25 @@ public void onInitializeClient() { ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(GenderArmorResourceManager.INSTANCE); } - public static CompletableFuture<@Nullable PlayerConfig> loadGenderInfo(UUID uuid, boolean markForSync) { - return CompletableFuture.supplyAsync(() -> PlayerConfig.loadCachedPlayer(uuid, markForSync), LOAD_EXECUTOR); + public static CompletableFuture<@NotNull PlayerConfig> loadGenderInfo(UUID uuid, boolean markForSync) { + return CompletableFuture.supplyAsync(() -> { + var player = WildfireGender.getOrAddPlayerById(uuid); + if(player.hasLocalConfig()) { + player.loadFromDisk(markForSync); + } else { + JsonObject data; + try { + data = CloudSync.getProfile(uuid).join(); + } catch(Exception e) { + WildfireGender.LOGGER.error("Failed to fetch profile from sync server", e); + throw e; + } + if(data != null) { + player.updateFromJson(data); + } + } + return player; + }, LOAD_EXECUTOR); } public static void loadPlayerIfMissing(UUID uuid, boolean markForSync) { diff --git a/src/main/java/com/wildfire/main/WildfireHelper.java b/src/main/java/com/wildfire/main/WildfireHelper.java index d453262f..42400642 100644 --- a/src/main/java/com/wildfire/main/WildfireHelper.java +++ b/src/main/java/com/wildfire/main/WildfireHelper.java @@ -25,6 +25,7 @@ import com.wildfire.api.IGenderArmor; import com.wildfire.api.WildfireAPI; import com.wildfire.main.config.FloatConfigKey; +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.component.DataComponentTypes; import net.minecraft.item.ItemStack; import com.wildfire.api.impl.GenderArmor; @@ -90,4 +91,9 @@ public static Codec boundedFloat(float minInclusive, float maxInclusive) public static Codec boundedFloat(FloatConfigKey configKey) { return boundedFloat(configKey.getMinInclusive(), configKey.getMaxInclusive()); } + + public static String getModVersion(String modId) { + var mod = FabricLoader.getInstance().getModContainer(modId).orElseThrow(); + return mod.getMetadata().getVersion().getFriendlyString(); + } } diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java new file mode 100644 index 00000000..fb97ed5a --- /dev/null +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -0,0 +1,201 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.main.cloud; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.mojang.authlib.HttpAuthenticationService; +import com.mojang.authlib.exceptions.AuthenticationException; +import com.wildfire.main.WildfireGender; +import com.wildfire.main.WildfireHelper; +import com.wildfire.main.config.GlobalConfig; +import com.wildfire.main.entitydata.PlayerConfig; +import net.minecraft.client.MinecraftClient; +import net.minecraft.util.Util; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.math.BigInteger; +import java.net.*; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +public final class CloudSync { + private CloudSync() { + throw new UnsupportedOperationException(); + } + + private static Instant lastSync = Instant.EPOCH; + + private static final Executor EXECUTOR = Util.getIoWorkerExecutor().named("wildfire_gender$cloudSync"); + private static final Gson GSON = new Gson(); + private static final String USER_AGENT = + "WildfireGender/" + StringUtils.split(WildfireHelper.getModVersion(WildfireGender.MODID), '+')[0] + + " Minecraft/" + WildfireHelper.getModVersion("minecraft"); + + private static final int CONNECT_TIMEOUT_MS = 5000; + private static final int READ_TIMEOUT_MS = 5000; + + private static final String DEFAULT_CLOUD_URL = "https://wfgm.celestialfault.dev"; + public static final Duration SYNC_COOLDOWN = Duration.of(15, ChronoUnit.SECONDS); + + public static boolean syncOnCooldown() { + return lastSync.plus(SYNC_COOLDOWN).isAfter(Instant.now()); + } + + public static boolean isEnabled() { + return GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SYNC_ENABLED); + } + + public static String getCloudServer() { + var url = GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SERVER); + return url.isBlank() ? DEFAULT_CLOUD_URL : url; + } + + private static HttpURLConnection createConnection(URL url) throws IOException { + WildfireGender.LOGGER.debug("Connecting to {}", url); + final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setConnectTimeout(CONNECT_TIMEOUT_MS); + connection.setReadTimeout(READ_TIMEOUT_MS); + connection.setUseCaches(false); + connection.setRequestProperty("User-Agent", USER_AGENT); + return connection; + } + + private static String generateServerId() { + // https://github.com/hibiii/Kappa/blob/main/src/main/java/hibiii/kappa/Provider.java#L40-L42 + BigInteger intA = new BigInteger(128, new Random()); + BigInteger intB = new BigInteger(128, new Random(System.identityHashCode(new Object()))); + return intA.xor(intB).toString(16); + } + + private static void beginAuth(String serverId) { + var client = MinecraftClient.getInstance(); + var session = client.getSession(); + try { + client.getSessionService().joinServer(Objects.requireNonNull(session.getUuidOrNull()), session.getAccessToken(), serverId); + } catch(AuthenticationException e) { + throw new RuntimeException(e); + } + } + + public static CompletableFuture sync(@NotNull PlayerConfig config) { + if(!isEnabled()) { + return CompletableFuture.completedFuture(null); + } + + // Force a 15s cooldown on syncing + if(syncOnCooldown()) { + var future = new CompletableFuture(); + future.completeExceptionally(new SyncingTooFrequentlyException()); + return future; + } + lastSync = Instant.now(); + + var client = MinecraftClient.getInstance(); + var username = client.getSession().getUsername(); + var json = config.toJson().toString().getBytes(StandardCharsets.UTF_8); + var serverId = generateServerId(); + + return CompletableFuture.runAsync(() -> { + var params = HttpAuthenticationService.buildQuery(Map.of( + "serverId", Objects.requireNonNull(serverId), + "username", username + )); + + URL url; + try { + url = URI.create(getCloudServer() + "/" + config.uuid + "?" + params).toURL(); + } catch(MalformedURLException e) { + throw new RuntimeException(e); + } + beginAuth(serverId); + + try { + var connection = createConnection(url); + connection.setRequestMethod("PUT"); + connection.setDoOutput(true); + connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); + connection.setFixedLengthStreamingMode(json.length); + connection.connect(); + try(var out = connection.getOutputStream()) { + out.write(json); + } + int code = connection.getResponseCode(); + if(code >= 400 || code == -1) { + String response; + try(var stream = connection.getErrorStream()) { + response = IOUtils.toString(stream, StandardCharsets.UTF_8); + } + throw new RuntimeException("Server returned " + code + " response code: " + response); + } + try(var stream = connection.getInputStream()) { + var response = IOUtils.toString(stream, StandardCharsets.UTF_8); + WildfireGender.LOGGER.debug("Server replied to update: {}", response); + } + } catch(IOException e) { + throw new RuntimeException(e); + } + }, EXECUTOR); + } + + public static CompletableFuture<@Nullable JsonObject> getProfile(UUID uuid) { + if(!isEnabled()) { + return CompletableFuture.completedFuture(null); + } + + return CompletableFuture.supplyAsync(() -> { + URL url; + try { + url = URI.create(getCloudServer() + "/" + uuid).toURL(); + } catch(MalformedURLException e) { + throw new RuntimeException(e); + } + + try { + var connection = createConnection(url); + connection.connect(); + int code = connection.getResponseCode(); + if(code == 404) { + return null; + } else if(code >= 400 || code == -1) { + throw new RuntimeException("Server responded with " + code + " response code"); + } + + String response; + try(var stream = connection.getInputStream()) { + response = IOUtils.toString(stream, StandardCharsets.UTF_8); + WildfireGender.LOGGER.debug("Server response for {}: {}", uuid, response); + } + + return GSON.fromJson(response, JsonObject.class); + } catch(IOException e) { + throw new RuntimeException(e); + } + }, EXECUTOR); + } +} diff --git a/src/main/java/com/wildfire/main/cloud/SyncingTooFrequentlyException.java b/src/main/java/com/wildfire/main/cloud/SyncingTooFrequentlyException.java new file mode 100644 index 00000000..a23237e4 --- /dev/null +++ b/src/main/java/com/wildfire/main/cloud/SyncingTooFrequentlyException.java @@ -0,0 +1,22 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.main.cloud; + +public class SyncingTooFrequentlyException extends RuntimeException { +} diff --git a/src/main/java/com/wildfire/main/config/AbstractConfiguration.java b/src/main/java/com/wildfire/main/config/AbstractConfiguration.java index 3fe4f31c..e3a54a8f 100644 --- a/src/main/java/com/wildfire/main/config/AbstractConfiguration.java +++ b/src/main/java/com/wildfire/main/config/AbstractConfiguration.java @@ -80,6 +80,10 @@ public void removeParameter(String key) { SAVE_VALUES.remove(key); } + public boolean exists() { + return CFG_FILE.exists(); + } + public void save() { if(!supportsSaving()) return; try(FileWriter writer = new FileWriter(CFG_FILE); JsonWriter jsonWriter = new JsonWriter(writer)) { diff --git a/src/main/java/com/wildfire/main/config/Configuration.java b/src/main/java/com/wildfire/main/config/Configuration.java index 5eb6ecf3..d2c05157 100644 --- a/src/main/java/com/wildfire/main/config/Configuration.java +++ b/src/main/java/com/wildfire/main/config/Configuration.java @@ -18,6 +18,8 @@ package com.wildfire.main.config; +import com.google.gson.JsonObject; + import java.nio.charset.StandardCharsets; import java.util.UUID; diff --git a/src/main/java/com/wildfire/main/config/GlobalConfig.java b/src/main/java/com/wildfire/main/config/GlobalConfig.java new file mode 100644 index 00000000..3cafaa35 --- /dev/null +++ b/src/main/java/com/wildfire/main/config/GlobalConfig.java @@ -0,0 +1,20 @@ +package com.wildfire.main.config; + +public class GlobalConfig extends AbstractConfiguration { + public static final GlobalConfig INSTANCE = new GlobalConfig(); + + private GlobalConfig() { + super(".", "wildfire_gender"); + } + + public static final BooleanConfigKey CLOUD_SYNC_ENABLED = new BooleanConfigKey("cloud_sync", false); + public static final StringConfigKey CLOUD_SERVER = new StringConfigKey("cloud_server", ""); + + static { + INSTANCE.setDefault(CLOUD_SYNC_ENABLED); + INSTANCE.setDefault(CLOUD_SERVER); + if(!INSTANCE.exists()) { + INSTANCE.save(); + } + } +} diff --git a/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java b/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java index 766b9c65..ce4b1afd 100644 --- a/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java +++ b/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java @@ -136,57 +136,60 @@ public SyncStatus getSyncStatus() { return this.syncStatus; } + /** + * @deprecated Use {@link #toJson()} instead + */ + @Deprecated public static JsonObject toJsonObject(PlayerConfig plr) { - JsonObject obj = new JsonObject(); - Configuration.USERNAME.save(obj, plr.uuid); - Configuration.GENDER.save(obj, plr.getGender()); - Configuration.BUST_SIZE.save(obj, plr.getBustSize()); - Configuration.HURT_SOUNDS.save(obj, plr.hasHurtSounds()); - - Configuration.BREAST_PHYSICS.save(obj, plr.hasBreastPhysics()); - Configuration.SHOW_IN_ARMOR.save(obj, plr.showBreastsInArmor()); - Configuration.ARMOR_PHYSICS_OVERRIDE.save(obj, plr.getArmorPhysicsOverride()); - Configuration.BOUNCE_MULTIPLIER.save(obj, plr.getBounceMultiplier()); - Configuration.FLOPPY_MULTIPLIER.save(obj, plr.getFloppiness()); - - Breasts breasts = plr.getBreasts(); - Configuration.BREASTS_OFFSET_X.save(obj, breasts.getXOffset()); - Configuration.BREASTS_OFFSET_Y.save(obj, breasts.getYOffset()); - Configuration.BREASTS_OFFSET_Z.save(obj, breasts.getZOffset()); - Configuration.BREASTS_UNIBOOB.save(obj, breasts.isUniboob()); - Configuration.BREASTS_CLEAVAGE.save(obj, breasts.getCleavage()); - return obj; + return plr.toJson(); + } + + public JsonObject toJson() { + return cfg.SAVE_VALUES.deepCopy(); } + public boolean hasLocalConfig() { + return cfg.exists(); + } + + public void loadFromDisk(boolean markForSync) { + this.syncStatus = SyncStatus.CACHED; + cfg.load(); + loadFromConfig(markForSync); + } + + public void loadFromConfig(boolean markForSync) { + updateGender(cfg.get(Configuration.GENDER)); + updateBustSize(cfg.get(Configuration.BUST_SIZE)); + updateHurtSounds(cfg.get(Configuration.HURT_SOUNDS)); + + //physics + updateBreastPhysics(cfg.get(Configuration.BREAST_PHYSICS)); + updateShowBreastsInArmor(cfg.get(Configuration.SHOW_IN_ARMOR)); + updateArmorPhysicsOverride(cfg.get(Configuration.ARMOR_PHYSICS_OVERRIDE)); + updateBounceMultiplier(cfg.get(Configuration.BOUNCE_MULTIPLIER)); + updateFloppiness(cfg.get(Configuration.FLOPPY_MULTIPLIER)); + + breasts.updateXOffset(cfg.get(Configuration.BREASTS_OFFSET_X)); + breasts.updateYOffset(cfg.get(Configuration.BREASTS_OFFSET_Y)); + breasts.updateZOffset(cfg.get(Configuration.BREASTS_OFFSET_Z)); + breasts.updateUniboob(cfg.get(Configuration.BREASTS_UNIBOOB)); + breasts.updateCleavage(cfg.get(Configuration.BREASTS_CLEAVAGE)); + if(markForSync) { + this.needsSync = true; + } + } + + /** + * @deprecated Use {@link #loadFromDisk(boolean)} instead + */ + @Deprecated public static PlayerConfig loadCachedPlayer(UUID uuid, boolean markForSync) { PlayerConfig plr = WildfireGender.getPlayerById(uuid); - if (plr != null) { - plr.syncStatus = SyncStatus.CACHED; - Configuration config = plr.getConfig(); - config.load(); - plr.updateGender(config.get(Configuration.GENDER)); - plr.updateBustSize(config.get(Configuration.BUST_SIZE)); - plr.updateHurtSounds(config.get(Configuration.HURT_SOUNDS)); - - //physics - plr.updateBreastPhysics(config.get(Configuration.BREAST_PHYSICS)); - plr.updateShowBreastsInArmor(config.get(Configuration.SHOW_IN_ARMOR)); - plr.updateArmorPhysicsOverride(config.get(Configuration.ARMOR_PHYSICS_OVERRIDE)); - plr.updateBounceMultiplier(config.get(Configuration.BOUNCE_MULTIPLIER)); - plr.updateFloppiness(config.get(Configuration.FLOPPY_MULTIPLIER)); - - Breasts breasts = plr.getBreasts(); - breasts.updateXOffset(config.get(Configuration.BREASTS_OFFSET_X)); - breasts.updateYOffset(config.get(Configuration.BREASTS_OFFSET_Y)); - breasts.updateZOffset(config.get(Configuration.BREASTS_OFFSET_Z)); - breasts.updateUniboob(config.get(Configuration.BREASTS_UNIBOOB)); - breasts.updateCleavage(config.get(Configuration.BREASTS_CLEAVAGE)); - if (markForSync) { - plr.needsSync = true; - } - return plr; + if (plr != null && plr.hasLocalConfig()) { + plr.loadFromDisk(markForSync); } - return null; + return plr; } public static void saveGenderInfo(PlayerConfig plr) { @@ -218,6 +221,12 @@ public boolean hasJacketLayer() { throw new UnsupportedOperationException("PlayerConfig does not support #hasJacketLayer(); use PlayerEntity#isPartVisible instead"); } + public void updateFromJson(JsonObject json) { + json.asMap().forEach(this.cfg.SAVE_VALUES::add); + loadFromConfig(false); + this.syncStatus = SyncStatus.SYNCED; + } + public enum SyncStatus { CACHED, SYNCED, UNKNOWN } diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index 5fe95629..765a230b 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -57,5 +57,14 @@ "wildfire_gender.tooltip.bounce_warning": "Setting 'Bounce Intensity' to a high value will look very unnatural!", "wildfire_gender.cancer_awareness.title": "Hey, it's Breast Cancer Awareness Month!", - "wildfire_gender.coming_soon": "Coming soon" + "wildfire_gender.coming_soon": "Coming soon", + + "wildfire_gender.cloud_settings": "Cloud Sync Settings", + "wildfire_gender.cloud.status": "Syncing:", + "wildfire_gender.cloud.url": "Sync Server:", + "wildfire_gender.cloud.url.tooltip": "Leave this empty for the default sync server", + "wildfire_gender.cloud.sync": "Sync", + "wildfire_gender.cloud.syncing": "Syncing...", + "wildfire_gender.cloud.syncing.success": "Synced", + "wildfire_gender.cloud.syncing.fail": "Sync failed" } diff --git a/src/main/resources/assets/wildfire_gender/textures/cloud.png b/src/main/resources/assets/wildfire_gender/textures/cloud.png new file mode 100644 index 0000000000000000000000000000000000000000..660fb0b6bed4ec0d1be22d01a3a136803dfc1dab GIT binary patch literal 697 zcmV;q0!ICbP)EX>4Tx04R}tkv&MmP!xqvQ>CI61v`j1WT;LSL`5963Pq?8YK2xEOkVm2O&XFE z7e~Rh;NZ_<)xpJCR|i)?5c~mgc5qU3krMAq3N2#1@OU5R-E(;FK0v6KnPzp21DbA| zsYG1NWLL$|D|*n60HO%W%rfRADGA^Dx~ER6yBN>%@B6cQ)ttqEfJi*c4AUmwAfDc| z4bJ<-5muB{;&b9LlP*a7$aTfzH_io@1)do()2Vsl2(egbW2KE*(bR~ih@+~eQ@)V# zSmnIMSu0mr^Pc>L;hes*%ynABNMI35kRU=q6(y8mBSyPUiiH%N$9?=mu3sXTLaq`R zITlcX2HEw4|H1FsTKUNdFDV=cI$s>;V+0880*#vEd>=bb;{*sk16O*>U#SB#pQP7X zTJ#9$+XgPKTbi;5Tk8{ps& z7%foty3f12+UNFfPjh}h8}@R9d%b%n00006VoOIv08Ri!07CEsGQa=;010qNS#tmY z4c7nw4c7reD4Tcy000McNliru=nD`5ClWcd{M!Hk0Jcd)K~yNurII0T0s#<3-)yp` zkU-!H)!YD$uOb*Y1vjXuBH;q%3MecZhpR$>LXZ#w5?)11vNT)5E>F+@GxKL42Q^je z6h(beN9xHRw(3w#{?kN89Ka0d4m5#z5@@ zy#jGnv!iz{$6RfOx&&I3V-o5SsFXC=CBPTuIVQmS69%8zAXfzfJg6UP8h@Ut`FrPk foPkY5TmZfSpFQG!jtZE400000NkvXXu0mjf^{*)- literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/sync_bg.png b/src/main/resources/assets/wildfire_gender/textures/gui/sync_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..362e70eedeb36a3b14cda51b64df9696ea132d2a GIT binary patch literal 6161 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 z Date: Fri, 15 Nov 2024 23:15:07 -0700 Subject: [PATCH 125/238] fix server url validation --- .../com/wildfire/gui/screen/WildfireCloudSyncScreen.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java index 8a0835fc..6a6dee9c 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java @@ -36,6 +36,7 @@ import java.net.MalformedURLException; import java.net.URI; +import java.net.URISyntaxException; import java.util.Objects; import java.util.UUID; @@ -117,10 +118,12 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { public void close() { try { var url = cloudUrl.getText(); - //noinspection ResultOfMethodCallIgnored - URI.create(url).toURL(); + if(!url.isBlank()) { + //noinspection ResultOfMethodCallIgnored + new URI(url).toURL(); + } GlobalConfig.INSTANCE.set(GlobalConfig.CLOUD_SERVER, url); - } catch(MalformedURLException e) { + } catch(MalformedURLException | URISyntaxException | IllegalArgumentException e) { // invalid url, discard it } GlobalConfig.INSTANCE.save(); From 262214d961732af825a2d74ea1a116b0670a224d Mon Sep 17 00:00:00 2001 From: celeste Date: Sat, 16 Nov 2024 10:56:40 -0700 Subject: [PATCH 126/238] disable player data fetching if too many errors occur --- .../java/com/wildfire/main/cloud/CloudSync.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index fb97ed5a..2ed8c01a 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -50,6 +50,8 @@ private CloudSync() { } private static Instant lastSync = Instant.EPOCH; + private static final List fetchErrors = new ArrayList<>(); + private static @Nullable Instant disableUntil; private static final Executor EXECUTOR = Util.getIoWorkerExecutor().named("wildfire_gender$cloudSync"); private static final Gson GSON = new Gson(); @@ -76,6 +78,15 @@ public static String getCloudServer() { return url.isBlank() ? DEFAULT_CLOUD_URL : url; } + private static void markError() { + fetchErrors.add(Instant.now()); + fetchErrors.removeIf(e -> e.plus(10, ChronoUnit.SECONDS).isBefore(Instant.now())); + if(fetchErrors.size() >= 10) { + WildfireGender.LOGGER.error("Too many recent sync errors, disabling future lookups for 5 minutes"); + disableUntil = Instant.now().plus(5, ChronoUnit.MINUTES); + } + } + private static HttpURLConnection createConnection(URL url) throws IOException { WildfireGender.LOGGER.debug("Connecting to {}", url); final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); @@ -164,7 +175,7 @@ public static CompletableFuture sync(@NotNull PlayerConfig config) { } public static CompletableFuture<@Nullable JsonObject> getProfile(UUID uuid) { - if(!isEnabled()) { + if(!isEnabled() || disableUntil != null && disableUntil.isAfter(Instant.now())) { return CompletableFuture.completedFuture(null); } @@ -181,8 +192,10 @@ public static CompletableFuture sync(@NotNull PlayerConfig config) { connection.connect(); int code = connection.getResponseCode(); if(code == 404) { + WildfireGender.LOGGER.debug("Server replied no data for {}", uuid); return null; } else if(code >= 400 || code == -1) { + markError(); throw new RuntimeException("Server responded with " + code + " response code"); } @@ -194,6 +207,7 @@ public static CompletableFuture sync(@NotNull PlayerConfig config) { return GSON.fromJson(response, JsonObject.class); } catch(IOException e) { + markError(); throw new RuntimeException(e); } }, EXECUTOR); From b4167e5f07367857d9142a2605d53ea26bc47a16 Mon Sep 17 00:00:00 2001 From: celeste Date: Sat, 16 Nov 2024 11:14:23 -0700 Subject: [PATCH 127/238] add opt-in automatic sync make cloud syncing enabled by default also remove the server url option from the ui, and keep it as a config file only option --- .../gui/screen/WildfireCloudSyncScreen.java | 39 +++++++------------ .../wildfire/main/WildfireEventHandler.java | 24 ++++++++++-- .../wildfire/main/WildfireGenderClient.java | 2 +- .../wildfire/main/config/GlobalConfig.java | 5 ++- .../main/entitydata/PlayerConfig.java | 3 ++ .../main/networking/WildfireSync.java | 1 + .../assets/wildfire_gender/lang/en_us.json | 5 ++- 7 files changed, 47 insertions(+), 32 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java index 6a6dee9c..acbc0022 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java @@ -28,15 +28,11 @@ import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.tooltip.Tooltip; import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.gui.widget.TextFieldWidget; import net.minecraft.client.render.RenderLayer; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; import java.util.Objects; import java.util.UUID; @@ -47,15 +43,12 @@ public class WildfireCloudSyncScreen extends BaseWildfireScreen { private static final Text DISABLED = Text.translatable("wildfire_gender.label.disabled").formatted(Formatting.RED); private static final Identifier BACKGROUND = Identifier.of(WildfireGender.MODID, "textures/gui/sync_bg.png"); - private TextFieldWidget cloudUrl; - protected WildfireCloudSyncScreen(Screen parent, UUID uuid) { super(Text.translatable("wildfire_gender.cloud_settings"), parent, uuid); } @Override public void init() { - final var client = Objects.requireNonNull(this.client); int x = this.width / 2; int y = this.height / 2; int yPos = y - 44; @@ -63,7 +56,7 @@ public void init() { this.addDrawableChild(new WildfireButton(this.width / 2 + 85, yPos - 11, 9, 9, Text.literal("X"), button -> close())); - this.addDrawableChild(new WildfireButton(xPos - 35, yPos, 100, 20, + this.addDrawableChild(new WildfireButton(xPos - 15, yPos, 80, 20, CloudSync.isEnabled() ? ENABLED : DISABLED, button -> { var config = GlobalConfig.INSTANCE; @@ -71,12 +64,18 @@ public void init() { button.setMessage(CloudSync.isEnabled() ? ENABLED : DISABLED); })); - this.cloudUrl = new TextFieldWidget(client.textRenderer, 100, 20, Text.translatable("wildfire_gender.cloud.url")); - cloudUrl.setX(xPos - 35); - cloudUrl.setY(yPos + 22); - cloudUrl.setText(GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SERVER)); - cloudUrl.setTooltip(Tooltip.of(Text.translatable("wildfire_gender.cloud.url.tooltip"))); - this.addDrawableChild(cloudUrl); + var automaticTooltip = Tooltip.of(Text.empty() + .append(Text.translatable("wildfire_gender.cloud.automatic.tooltip.l1")) + .append("\n\n") + .append(Text.translatable("wildfire_gender.cloud.automatic.tooltip.l2"))); + this.addDrawableChild(new WildfireButton(xPos - 15, yPos + 22, 80, 20, + GlobalConfig.INSTANCE.get(GlobalConfig.AUTOMATIC_CLOUD_SYNC) ? ENABLED : DISABLED, + button -> { + var config = GlobalConfig.INSTANCE; + var newVal = !config.get(GlobalConfig.AUTOMATIC_CLOUD_SYNC); + config.set(GlobalConfig.AUTOMATIC_CLOUD_SYNC, newVal); + button.setMessage(newVal ? ENABLED : DISABLED); + }, automaticTooltip)); var syncButton = new WildfireButton(xPos - 80, yPos + 80, 100, 15, Text.translatable("wildfire_gender.cloud.sync"), this::sync); syncButton.setActive(GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SYNC_ENABLED)); @@ -111,21 +110,11 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { int y = this.height / 2; ctx.drawText(textRenderer, Text.translatable("wildfire_gender.cloud.status"), x - 95, y - 40, 0x000000, false); - ctx.drawText(textRenderer, Text.translatable("wildfire_gender.cloud.url"), x - 95, y - 16, 0x000000, false); + ctx.drawText(textRenderer, Text.translatable("wildfire_gender.cloud.automatic"), x - 95, y - 16, 0x000000, false); } @Override public void close() { - try { - var url = cloudUrl.getText(); - if(!url.isBlank()) { - //noinspection ResultOfMethodCallIgnored - new URI(url).toURL(); - } - GlobalConfig.INSTANCE.set(GlobalConfig.CLOUD_SERVER, url); - } catch(MalformedURLException | URISyntaxException | IllegalArgumentException e) { - // invalid url, discard it - } GlobalConfig.INSTANCE.save(); super.close(); } diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index 76419a7a..de1723a7 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -18,7 +18,10 @@ package com.wildfire.main; +import com.wildfire.gui.screen.BaseWildfireScreen; import com.wildfire.gui.screen.WardrobeBrowserScreen; +import com.wildfire.main.cloud.CloudSync; +import com.wildfire.main.config.GlobalConfig; import com.wildfire.main.entitydata.EntityConfig; import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.networking.ServerboundSyncPacket; @@ -144,12 +147,27 @@ private static void onEntityUnload(Entity entity, World world) { private static void onClientTick(MinecraftClient client) { if(client.world == null || client.player == null) return; + PlayerConfig clientConfig = WildfireGender.getPlayerById(client.player.getUuid()); + timer++; + // Only attempt to sync if the server will accept the packet, and only once every 5 ticks, or around 4 times a second - if(ServerboundSyncPacket.canSend() && timer++ % 5 == 0) { - PlayerConfig aPlr = WildfireGender.getPlayerById(client.player.getUuid()); + if(ServerboundSyncPacket.canSend() && timer % 5 == 0) { // sendToServer will only actually send a packet if any changes have been made that need to be synced, // or if we haven't synced before. - if(aPlr != null) WildfireSync.sendToServer(aPlr); + if(clientConfig != null) WildfireSync.sendToServer(clientConfig); + } + + // Only attempt once every 15.5 seconds to ensure that the cloud sync cooldown isn't hit + if(timer % (15.5 * 20) == 0 && clientConfig != null && clientConfig.needsCloudSync && !(client.currentScreen instanceof BaseWildfireScreen)) { + if(GlobalConfig.INSTANCE.get(GlobalConfig.AUTOMATIC_CLOUD_SYNC)) { + CloudSync.sync(clientConfig) + .thenRun(() -> WildfireGender.LOGGER.info("Synced player data to the cloud")) + .exceptionallyAsync(exc -> { + WildfireGender.LOGGER.error("Failed to sync player data", exc); + return null; + }); + clientConfig.needsCloudSync = false; + } } if(CONFIG_KEYBIND.wasPressed() && client.currentScreen == null) { diff --git a/src/main/java/com/wildfire/main/WildfireGenderClient.java b/src/main/java/com/wildfire/main/WildfireGenderClient.java index 0a29e88d..cc318770 100644 --- a/src/main/java/com/wildfire/main/WildfireGenderClient.java +++ b/src/main/java/com/wildfire/main/WildfireGenderClient.java @@ -60,7 +60,7 @@ public void onInitializeClient() { WildfireGender.LOGGER.error("Failed to fetch profile from sync server", e); throw e; } - if(data != null) { + if(data != null && player.syncStatus == PlayerConfig.SyncStatus.UNKNOWN) { player.updateFromJson(data); } } diff --git a/src/main/java/com/wildfire/main/config/GlobalConfig.java b/src/main/java/com/wildfire/main/config/GlobalConfig.java index 3cafaa35..b30ba054 100644 --- a/src/main/java/com/wildfire/main/config/GlobalConfig.java +++ b/src/main/java/com/wildfire/main/config/GlobalConfig.java @@ -7,11 +7,14 @@ private GlobalConfig() { super(".", "wildfire_gender"); } - public static final BooleanConfigKey CLOUD_SYNC_ENABLED = new BooleanConfigKey("cloud_sync", false); + public static final BooleanConfigKey CLOUD_SYNC_ENABLED = new BooleanConfigKey("cloud_sync", true); + public static final BooleanConfigKey AUTOMATIC_CLOUD_SYNC = new BooleanConfigKey("sync_player_data", false); + // see CloudSync#DEFAULT_CLOUD_URL for the actual default public static final StringConfigKey CLOUD_SERVER = new StringConfigKey("cloud_server", ""); static { INSTANCE.setDefault(CLOUD_SYNC_ENABLED); + INSTANCE.setDefault(AUTOMATIC_CLOUD_SYNC); INSTANCE.setDefault(CLOUD_SERVER); if(!INSTANCE.exists()) { INSTANCE.save(); diff --git a/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java b/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java index ce4b1afd..2a16adc0 100644 --- a/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java +++ b/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java @@ -35,6 +35,7 @@ public class PlayerConfig extends EntityConfig { public boolean needsSync; + public boolean needsCloudSync; public SyncStatus syncStatus = SyncStatus.UNKNOWN; private final Configuration cfg; @@ -177,6 +178,7 @@ public void loadFromConfig(boolean markForSync) { breasts.updateCleavage(cfg.get(Configuration.BREASTS_CLEAVAGE)); if(markForSync) { this.needsSync = true; + this.needsCloudSync = true; } } @@ -214,6 +216,7 @@ public static void saveGenderInfo(PlayerConfig plr) { config.save(); plr.needsSync = true; + plr.needsCloudSync = true; } @Override diff --git a/src/main/java/com/wildfire/main/networking/WildfireSync.java b/src/main/java/com/wildfire/main/networking/WildfireSync.java index 93fcf988..1720646b 100644 --- a/src/main/java/com/wildfire/main/networking/WildfireSync.java +++ b/src/main/java/com/wildfire/main/networking/WildfireSync.java @@ -97,5 +97,6 @@ public static void sendToServer(@NotNull PlayerConfig plr) { ClientPlayNetworking.send(new ServerboundSyncPacket(plr)); plr.needsSync = false; + plr.needsCloudSync = true; } } diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index 765a230b..e08c008d 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -61,8 +61,9 @@ "wildfire_gender.cloud_settings": "Cloud Sync Settings", "wildfire_gender.cloud.status": "Syncing:", - "wildfire_gender.cloud.url": "Sync Server:", - "wildfire_gender.cloud.url.tooltip": "Leave this empty for the default sync server", + "wildfire_gender.cloud.automatic": "Automatic Sync:", + "wildfire_gender.cloud.automatic.tooltip.l1": "While enabled, your config will automatically be synced to the cloud after making any changes.", + "wildfire_gender.cloud.automatic.tooltip.l2": "You can still sync manually with the button below if this is disabled.", "wildfire_gender.cloud.sync": "Sync", "wildfire_gender.cloud.syncing": "Syncing...", "wildfire_gender.cloud.syncing.success": "Synced", From 632a5516aad52f7248924f2325752974abf32aaf Mon Sep 17 00:00:00 2001 From: celeste Date: Sat, 16 Nov 2024 11:36:07 -0700 Subject: [PATCH 128/238] avoid asking the sync server for data if we have it already also rewrite the javadoc for WildfireAPI#loadGenderInfo to account for the sync server existing --- src/main/java/com/wildfire/api/WildfireAPI.java | 9 +++++++-- .../java/com/wildfire/main/WildfireGenderClient.java | 4 +++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/wildfire/api/WildfireAPI.java b/src/main/java/com/wildfire/api/WildfireAPI.java index 667468db..1ab684dd 100644 --- a/src/main/java/com/wildfire/api/WildfireAPI.java +++ b/src/main/java/com/wildfire/api/WildfireAPI.java @@ -82,9 +82,14 @@ public static void addGenderArmor(Item item, IGenderArmor genderArmor) { } /** - *

Load the cached Gender Settings file for the specified {@link UUID}

+ *

Load data for the provided player UUID

* - *

You should avoid using this unless you need to, as the mod will do this for you when loading a player entity.

+ *

This attempts to load a local config file for the provided UUID, before falling back to making a + * request to the {@link com.wildfire.main.cloud.CloudSync cloud sync} server for it + * (if cloud syncing is enabled).

+ * + *

Note that you should generally avoid using this unless you know that you need to, as the mod already runs + * this load process when the relevant player entity is spawned in the world.

* * @param uuid the uuid of the target {@link PlayerEntity} * @param markForSync {@code true} if player data should be synced to the server upon being loaded; this only has an effect on the client player. diff --git a/src/main/java/com/wildfire/main/WildfireGenderClient.java b/src/main/java/com/wildfire/main/WildfireGenderClient.java index cc318770..3359f2bf 100644 --- a/src/main/java/com/wildfire/main/WildfireGenderClient.java +++ b/src/main/java/com/wildfire/main/WildfireGenderClient.java @@ -52,7 +52,7 @@ public void onInitializeClient() { var player = WildfireGender.getOrAddPlayerById(uuid); if(player.hasLocalConfig()) { player.loadFromDisk(markForSync); - } else { + } else if(player.syncStatus == PlayerConfig.SyncStatus.UNKNOWN) { JsonObject data; try { data = CloudSync.getProfile(uuid).join(); @@ -60,6 +60,8 @@ public void onInitializeClient() { WildfireGender.LOGGER.error("Failed to fetch profile from sync server", e); throw e; } + // make sure the server we're connected to hasn't provided player data while we were fetching data from + // the sync server if(data != null && player.syncStatus == PlayerConfig.SyncStatus.UNKNOWN) { player.updateFromJson(data); } From 07514a5c08e4f940e1c05fde2292149631514e34 Mon Sep 17 00:00:00 2001 From: celeste Date: Sat, 16 Nov 2024 11:51:00 -0700 Subject: [PATCH 129/238] include request body in fetch failures --- src/main/java/com/wildfire/main/cloud/CloudSync.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index 2ed8c01a..63b7f44a 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -196,7 +196,11 @@ public static CompletableFuture sync(@NotNull PlayerConfig config) { return null; } else if(code >= 400 || code == -1) { markError(); - throw new RuntimeException("Server responded with " + code + " response code"); + String response; + try(var stream = connection.getErrorStream()) { + response = IOUtils.toString(stream, StandardCharsets.UTF_8); + } + throw new RuntimeException("Server responded " + connection.getResponseMessage() + ": " + response); } String response; From 8920e981a911b9eaea40c644682c8a6bb57afdae Mon Sep 17 00:00:00 2001 From: celeste Date: Sat, 16 Nov 2024 12:20:32 -0700 Subject: [PATCH 130/238] make automatic SyncingTooFrequentlyException failures quieter --- src/main/java/com/wildfire/main/WildfireEventHandler.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index de1723a7..f6f9e5a0 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -21,6 +21,7 @@ import com.wildfire.gui.screen.BaseWildfireScreen; import com.wildfire.gui.screen.WardrobeBrowserScreen; import com.wildfire.main.cloud.CloudSync; +import com.wildfire.main.cloud.SyncingTooFrequentlyException; import com.wildfire.main.config.GlobalConfig; import com.wildfire.main.entitydata.EntityConfig; import com.wildfire.main.entitydata.PlayerConfig; @@ -163,7 +164,11 @@ private static void onClientTick(MinecraftClient client) { CloudSync.sync(clientConfig) .thenRun(() -> WildfireGender.LOGGER.info("Synced player data to the cloud")) .exceptionallyAsync(exc -> { - WildfireGender.LOGGER.error("Failed to sync player data", exc); + if(exc instanceof SyncingTooFrequentlyException) { + WildfireGender.LOGGER.warn("Couldn't sync player data as we've already synced too recently"); + } else { + WildfireGender.LOGGER.error("Failed to sync player data", exc); + } return null; }); clientConfig.needsCloudSync = false; From b8375c91fac8d5357097c5d99a602280ca77fc14 Mon Sep 17 00:00:00 2001 From: celeste Date: Sat, 16 Nov 2024 14:23:23 -0700 Subject: [PATCH 131/238] add some javadocs, fix some incorrect cloud syncs --- .../gui/screen/WildfireCloudSyncScreen.java | 4 +-- .../com/wildfire/main/cloud/CloudSync.java | 27 ++++++++++++++++++- .../wildfire/main/config/Configuration.java | 2 -- .../main/entitydata/PlayerConfig.java | 1 - .../main/networking/WildfireSync.java | 1 - .../assets/wildfire_gender/lang/en_us.json | 4 +-- 6 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java index acbc0022..0753057e 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java @@ -65,9 +65,9 @@ public void init() { })); var automaticTooltip = Tooltip.of(Text.empty() - .append(Text.translatable("wildfire_gender.cloud.automatic.tooltip.l1")) + .append(Text.translatable("wildfire_gender.cloud.automatic.tooltip.line1")) .append("\n\n") - .append(Text.translatable("wildfire_gender.cloud.automatic.tooltip.l2"))); + .append(Text.translatable("wildfire_gender.cloud.automatic.tooltip.line2"))); this.addDrawableChild(new WildfireButton(xPos - 15, yPos + 22, 80, 20, GlobalConfig.INSTANCE.get(GlobalConfig.AUTOMATIC_CLOUD_SYNC) ? ENABLED : DISABLED, button -> { diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index 63b7f44a..57bbbe85 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -65,14 +65,23 @@ private CloudSync() { private static final String DEFAULT_CLOUD_URL = "https://wfgm.celestialfault.dev"; public static final Duration SYNC_COOLDOWN = Duration.of(15, ChronoUnit.SECONDS); + /** + * @return {@code true} if the last sync was within the last {@link #SYNC_COOLDOWN 15 seconds} + */ public static boolean syncOnCooldown() { return lastSync.plus(SYNC_COOLDOWN).isAfter(Instant.now()); } + /** + * Convenience shorthand for {@code GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SYNC_ENABLED)} + */ public static boolean isEnabled() { return GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SYNC_ENABLED); } + /** + * @return The URL of the sync server currently being used + */ public static String getCloudServer() { var url = GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SERVER); return url.isBlank() ? DEFAULT_CLOUD_URL : url; @@ -114,7 +123,15 @@ private static void beginAuth(String serverId) { } } - public static CompletableFuture sync(@NotNull PlayerConfig config) { + /** + * Send the client player config to the cloud sync server for syncing to other players + * + * @param config The config of the client player + * + * @return A {@link CompletableFuture} indicating when the process has finished, or with an exception if + * syncing failed. + */ + public static synchronized CompletableFuture sync(@NotNull PlayerConfig config) { if(!isEnabled()) { return CompletableFuture.completedFuture(null); } @@ -174,6 +191,14 @@ public static CompletableFuture sync(@NotNull PlayerConfig config) { }, EXECUTOR); } + /** + * Fetch player data from the sync server + * + * @param uuid The UUID of the player to get data for + * + * @return A {@link CompletableFuture} containing a {@link JsonObject} of the player's data if they have any data + * stored in the sync server, or {@code null} otherwise. + */ public static CompletableFuture<@Nullable JsonObject> getProfile(UUID uuid) { if(!isEnabled() || disableUntil != null && disableUntil.isAfter(Instant.now())) { return CompletableFuture.completedFuture(null); diff --git a/src/main/java/com/wildfire/main/config/Configuration.java b/src/main/java/com/wildfire/main/config/Configuration.java index d2c05157..5eb6ecf3 100644 --- a/src/main/java/com/wildfire/main/config/Configuration.java +++ b/src/main/java/com/wildfire/main/config/Configuration.java @@ -18,8 +18,6 @@ package com.wildfire.main.config; -import com.google.gson.JsonObject; - import java.nio.charset.StandardCharsets; import java.util.UUID; diff --git a/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java b/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java index 2a16adc0..a860d275 100644 --- a/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java +++ b/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java @@ -178,7 +178,6 @@ public void loadFromConfig(boolean markForSync) { breasts.updateCleavage(cfg.get(Configuration.BREASTS_CLEAVAGE)); if(markForSync) { this.needsSync = true; - this.needsCloudSync = true; } } diff --git a/src/main/java/com/wildfire/main/networking/WildfireSync.java b/src/main/java/com/wildfire/main/networking/WildfireSync.java index 1720646b..93fcf988 100644 --- a/src/main/java/com/wildfire/main/networking/WildfireSync.java +++ b/src/main/java/com/wildfire/main/networking/WildfireSync.java @@ -97,6 +97,5 @@ public static void sendToServer(@NotNull PlayerConfig plr) { ClientPlayNetworking.send(new ServerboundSyncPacket(plr)); plr.needsSync = false; - plr.needsCloudSync = true; } } diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index e08c008d..2bb77807 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -62,8 +62,8 @@ "wildfire_gender.cloud_settings": "Cloud Sync Settings", "wildfire_gender.cloud.status": "Syncing:", "wildfire_gender.cloud.automatic": "Automatic Sync:", - "wildfire_gender.cloud.automatic.tooltip.l1": "While enabled, your config will automatically be synced to the cloud after making any changes.", - "wildfire_gender.cloud.automatic.tooltip.l2": "You can still sync manually with the button below if this is disabled.", + "wildfire_gender.cloud.automatic.tooltip.line1": "While enabled, your config will automatically be synced to the cloud after making any changes.", + "wildfire_gender.cloud.automatic.tooltip.line2": "You can still sync manually with the button below if this is disabled.", "wildfire_gender.cloud.sync": "Sync", "wildfire_gender.cloud.syncing": "Syncing...", "wildfire_gender.cloud.syncing.success": "Synced", From 7544007e7e287eedda667642af942092f67ea912 Mon Sep 17 00:00:00 2001 From: celeste Date: Sat, 16 Nov 2024 15:12:22 -0700 Subject: [PATCH 132/238] use try-join-catch over exceptionallyAsync --- .../gui/screen/WildfireCloudSyncScreen.java | 24 ++++++++++++------ .../wildfire/main/WildfireEventHandler.java | 25 ++++++++----------- .../com/wildfire/main/cloud/CloudSync.java | 14 +++++------ 3 files changed, 35 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java index 0753057e..5053df0f 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java @@ -21,6 +21,7 @@ import com.wildfire.gui.WildfireButton; import com.wildfire.main.WildfireGender; import com.wildfire.main.cloud.CloudSync; +import com.wildfire.main.cloud.SyncingTooFrequentlyException; import com.wildfire.main.config.GlobalConfig; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -35,6 +36,8 @@ import java.util.Objects; import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; @Environment(EnvType.CLIENT) public class WildfireCloudSyncScreen extends BaseWildfireScreen { @@ -87,13 +90,20 @@ public void init() { private void sync(ButtonWidget button) { button.active = false; button.setMessage(Text.translatable("wildfire_gender.cloud.syncing")); - CloudSync.sync(Objects.requireNonNull(getPlayer())) - .thenRun(() -> button.setMessage(Text.translatable("wildfire_gender.cloud.syncing.success"))) - .exceptionallyAsync(exc -> { - WildfireGender.LOGGER.error("Failed to sync settings", exc); - button.setMessage(Text.translatable("wildfire_gender.cloud.syncing.fail")); - return null; - }); + CompletableFuture.runAsync(() -> { + try { + CloudSync.sync(Objects.requireNonNull(getPlayer())).join(); + button.setMessage(Text.translatable("wildfire_gender.cloud.syncing.success")); + } catch(Exception e) { + var actualException = e instanceof CompletionException ce ? ce.getCause() : e; + if(actualException instanceof SyncingTooFrequentlyException) { + WildfireGender.LOGGER.warn("Failed to sync settings as we've already synced too recently"); + } else { + WildfireGender.LOGGER.error("Failed to sync settings", actualException); + } + button.setMessage(Text.translatable("wildfire_gender.cloud.syncing.fail")); + } + }); } @Override diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index f6f9e5a0..4609d8ef 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -21,7 +21,6 @@ import com.wildfire.gui.screen.BaseWildfireScreen; import com.wildfire.gui.screen.WardrobeBrowserScreen; import com.wildfire.main.cloud.CloudSync; -import com.wildfire.main.cloud.SyncingTooFrequentlyException; import com.wildfire.main.config.GlobalConfig; import com.wildfire.main.entitydata.EntityConfig; import com.wildfire.main.entitydata.PlayerConfig; @@ -59,6 +58,7 @@ import org.lwjgl.glfw.GLFW; import java.util.UUID; +import java.util.concurrent.CompletableFuture; public final class WildfireEventHandler { private WildfireEventHandler() { @@ -158,19 +158,16 @@ private static void onClientTick(MinecraftClient client) { if(clientConfig != null) WildfireSync.sendToServer(clientConfig); } - // Only attempt once every 15.5 seconds to ensure that the cloud sync cooldown isn't hit - if(timer % (15.5 * 20) == 0 && clientConfig != null && clientConfig.needsCloudSync && !(client.currentScreen instanceof BaseWildfireScreen)) { - if(GlobalConfig.INSTANCE.get(GlobalConfig.AUTOMATIC_CLOUD_SYNC)) { - CloudSync.sync(clientConfig) - .thenRun(() -> WildfireGender.LOGGER.info("Synced player data to the cloud")) - .exceptionallyAsync(exc -> { - if(exc instanceof SyncingTooFrequentlyException) { - WildfireGender.LOGGER.warn("Couldn't sync player data as we've already synced too recently"); - } else { - WildfireGender.LOGGER.error("Failed to sync player data", exc); - } - return null; - }); + if(timer % 40 == 0 && clientConfig != null && clientConfig.needsCloudSync && !(client.currentScreen instanceof BaseWildfireScreen)) { + if(GlobalConfig.INSTANCE.get(GlobalConfig.AUTOMATIC_CLOUD_SYNC) && !CloudSync.syncOnCooldown()) { + CompletableFuture.runAsync(() -> { + try { + CloudSync.sync(clientConfig).join(); + WildfireGender.LOGGER.info("Synced player data to the cloud"); + } catch(Exception e) { + WildfireGender.LOGGER.error("Failed to sync player data", e); + } + }); clientConfig.needsCloudSync = false; } } diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index 57bbbe85..5a809990 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -51,7 +51,7 @@ private CloudSync() { private static Instant lastSync = Instant.EPOCH; private static final List fetchErrors = new ArrayList<>(); - private static @Nullable Instant disableUntil; + private static @Nullable Instant disableFetchingUntil; private static final Executor EXECUTOR = Util.getIoWorkerExecutor().named("wildfire_gender$cloudSync"); private static final Gson GSON = new Gson(); @@ -87,12 +87,12 @@ public static String getCloudServer() { return url.isBlank() ? DEFAULT_CLOUD_URL : url; } - private static void markError() { + private static void markFetchError() { fetchErrors.add(Instant.now()); - fetchErrors.removeIf(e -> e.plus(10, ChronoUnit.SECONDS).isBefore(Instant.now())); + fetchErrors.removeIf(e -> e.plus(30, ChronoUnit.SECONDS).isBefore(Instant.now())); if(fetchErrors.size() >= 10) { WildfireGender.LOGGER.error("Too many recent sync errors, disabling future lookups for 5 minutes"); - disableUntil = Instant.now().plus(5, ChronoUnit.MINUTES); + disableFetchingUntil = Instant.now().plus(5, ChronoUnit.MINUTES); } } @@ -200,7 +200,7 @@ public static synchronized CompletableFuture sync(@NotNull PlayerConfig co * stored in the sync server, or {@code null} otherwise. */ public static CompletableFuture<@Nullable JsonObject> getProfile(UUID uuid) { - if(!isEnabled() || disableUntil != null && disableUntil.isAfter(Instant.now())) { + if(!isEnabled() || disableFetchingUntil != null && disableFetchingUntil.isAfter(Instant.now())) { return CompletableFuture.completedFuture(null); } @@ -220,7 +220,7 @@ public static synchronized CompletableFuture sync(@NotNull PlayerConfig co WildfireGender.LOGGER.debug("Server replied no data for {}", uuid); return null; } else if(code >= 400 || code == -1) { - markError(); + markFetchError(); String response; try(var stream = connection.getErrorStream()) { response = IOUtils.toString(stream, StandardCharsets.UTF_8); @@ -236,7 +236,7 @@ public static synchronized CompletableFuture sync(@NotNull PlayerConfig co return GSON.fromJson(response, JsonObject.class); } catch(IOException e) { - markError(); + markFetchError(); throw new RuntimeException(e); } }, EXECUTOR); From d3cd3e9f7e7873d7e29bfc12375aef627c4b0ae8 Mon Sep 17 00:00:00 2001 From: celeste Date: Sat, 16 Nov 2024 22:51:52 -0700 Subject: [PATCH 133/238] use HttpClient instead of manually building requests --- .../com/wildfire/main/cloud/CloudSync.java | 91 ++++++------------- 1 file changed, 27 insertions(+), 64 deletions(-) diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index 5a809990..489aa4b2 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -28,15 +28,16 @@ import com.wildfire.main.entitydata.PlayerConfig; import net.minecraft.client.MinecraftClient; import net.minecraft.util.Util; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.math.BigInteger; -import java.net.*; -import java.nio.charset.StandardCharsets; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.time.Duration; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -55,13 +56,11 @@ private CloudSync() { private static final Executor EXECUTOR = Util.getIoWorkerExecutor().named("wildfire_gender$cloudSync"); private static final Gson GSON = new Gson(); + private static final HttpClient CLIENT = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build(); private static final String USER_AGENT = "WildfireGender/" + StringUtils.split(WildfireHelper.getModVersion(WildfireGender.MODID), '+')[0] + " Minecraft/" + WildfireHelper.getModVersion("minecraft"); - private static final int CONNECT_TIMEOUT_MS = 5000; - private static final int READ_TIMEOUT_MS = 5000; - private static final String DEFAULT_CLOUD_URL = "https://wfgm.celestialfault.dev"; public static final Duration SYNC_COOLDOWN = Duration.of(15, ChronoUnit.SECONDS); @@ -96,14 +95,11 @@ private static void markFetchError() { } } - private static HttpURLConnection createConnection(URL url) throws IOException { - WildfireGender.LOGGER.debug("Connecting to {}", url); - final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setConnectTimeout(CONNECT_TIMEOUT_MS); - connection.setReadTimeout(READ_TIMEOUT_MS); - connection.setUseCaches(false); - connection.setRequestProperty("User-Agent", USER_AGENT); - return connection; + private static HttpRequest.Builder createConnection(URI uri) throws IOException { + WildfireGender.LOGGER.debug("Connecting to {}", uri); + return HttpRequest.newBuilder(uri) + .header("User-Agent", USER_AGENT) + .timeout(Duration.ofSeconds(5)); } private static String generateServerId() { @@ -146,7 +142,7 @@ public static synchronized CompletableFuture sync(@NotNull PlayerConfig co var client = MinecraftClient.getInstance(); var username = client.getSession().getUsername(); - var json = config.toJson().toString().getBytes(StandardCharsets.UTF_8); + var json = config.toJson().toString(); var serverId = generateServerId(); return CompletableFuture.runAsync(() -> { @@ -155,36 +151,19 @@ public static synchronized CompletableFuture sync(@NotNull PlayerConfig co "username", username )); - URL url; - try { - url = URI.create(getCloudServer() + "/" + config.uuid + "?" + params).toURL(); - } catch(MalformedURLException e) { - throw new RuntimeException(e); - } + URI url = URI.create(getCloudServer() + "/" + config.uuid + "?" + params); beginAuth(serverId); try { - var connection = createConnection(url); - connection.setRequestMethod("PUT"); - connection.setDoOutput(true); - connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); - connection.setFixedLengthStreamingMode(json.length); - connection.connect(); - try(var out = connection.getOutputStream()) { - out.write(json); - } - int code = connection.getResponseCode(); - if(code >= 400 || code == -1) { - String response; - try(var stream = connection.getErrorStream()) { - response = IOUtils.toString(stream, StandardCharsets.UTF_8); - } - throw new RuntimeException("Server returned " + code + " response code: " + response); - } - try(var stream = connection.getInputStream()) { - var response = IOUtils.toString(stream, StandardCharsets.UTF_8); - WildfireGender.LOGGER.debug("Server replied to update: {}", response); + var request = createConnection(url) + .PUT(HttpRequest.BodyPublishers.ofString(json)) + .header("Content-Type", "application/json; charset=UTF-8") + .build(); + var response = CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join(); + if(response.statusCode() >= 400) { + throw new RuntimeException("Server responded " + response.statusCode() + ": " + response.body()); } + WildfireGender.LOGGER.debug("Server responded to update: {}", response.body()); } catch(IOException e) { throw new RuntimeException(e); } @@ -205,36 +184,20 @@ public static synchronized CompletableFuture sync(@NotNull PlayerConfig co } return CompletableFuture.supplyAsync(() -> { - URL url; - try { - url = URI.create(getCloudServer() + "/" + uuid).toURL(); - } catch(MalformedURLException e) { - throw new RuntimeException(e); - } + URI url = URI.create(getCloudServer() + "/" + uuid); try { - var connection = createConnection(url); - connection.connect(); - int code = connection.getResponseCode(); - if(code == 404) { + var request = createConnection(url).GET().build(); + var response = CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join(); + if(response.statusCode() == 404) { WildfireGender.LOGGER.debug("Server replied no data for {}", uuid); return null; - } else if(code >= 400 || code == -1) { + } else if(response.statusCode() >= 400) { markFetchError(); - String response; - try(var stream = connection.getErrorStream()) { - response = IOUtils.toString(stream, StandardCharsets.UTF_8); - } - throw new RuntimeException("Server responded " + connection.getResponseMessage() + ": " + response); - } - - String response; - try(var stream = connection.getInputStream()) { - response = IOUtils.toString(stream, StandardCharsets.UTF_8); - WildfireGender.LOGGER.debug("Server response for {}: {}", uuid, response); + throw new RuntimeException("Server responded " + response.statusCode() + ": " + response.body()); } - return GSON.fromJson(response, JsonObject.class); + return GSON.fromJson(response.body(), JsonObject.class); } catch(IOException e) { markFetchError(); throw new RuntimeException(e); From 330c68bff7b34ea465a6f2c74b902d9e915f132a Mon Sep 17 00:00:00 2001 From: celeste Date: Sat, 16 Nov 2024 23:13:11 -0700 Subject: [PATCH 134/238] only enable cloud sync when logged into a valid account --- src/main/java/com/wildfire/main/cloud/CloudSync.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index 489aa4b2..d3928fe1 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -27,6 +27,7 @@ import com.wildfire.main.config.GlobalConfig; import com.wildfire.main.entitydata.PlayerConfig; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.session.Session; import net.minecraft.util.Util; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; @@ -72,9 +73,12 @@ public static boolean syncOnCooldown() { } /** - * Convenience shorthand for {@code GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SYNC_ENABLED)} + * @return {@code true} if syncing should be enabled */ public static boolean isEnabled() { + if(MinecraftClient.getInstance().getSession().getAccountType() != Session.AccountType.MSA) { + return false; + } return GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SYNC_ENABLED); } From acbb85d87fe60d98491f6923be6d2f2588c41cfc Mon Sep 17 00:00:00 2001 From: celeste Date: Sat, 16 Nov 2024 23:29:18 -0700 Subject: [PATCH 135/238] also disable the sync settings button when syncing is unavailable --- .../wildfire/gui/screen/WardrobeBrowserScreen.java | 10 +++++++++- .../java/com/wildfire/main/cloud/CloudSync.java | 14 +++++++++----- .../assets/wildfire_gender/lang/en_us.json | 1 + 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index c2d64644..aa562c79 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -27,11 +27,13 @@ import java.util.UUID; import com.wildfire.gui.WildfireButton; +import com.wildfire.main.cloud.CloudSync; import com.wildfire.main.entitydata.PlayerConfig; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.tooltip.Tooltip; import net.minecraft.client.render.RenderLayer; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.text.Text; @@ -77,6 +79,7 @@ public void init() { this.addDrawableChild(new WildfireButton(this.width / 2 - 42, y - (plr.getGender().canHaveBreasts() ? 12 : 32), 158, 20, Text.translatable("wildfire_gender.char_settings.title").append("..."), button -> client.setScreen(new WildfireCharacterSettingsScreen(WardrobeBrowserScreen.this, this.playerUUID)))); + //noinspection ExtractMethodRecommender var cloud = new WildfireButton( this.width / 2 + 97, y - 63, 12, 9, Text.translatable("wildfire_gender.cloud_settings"), button -> client.setScreen(new WildfireCloudSyncScreen(this, this.playerUUID)) @@ -87,7 +90,12 @@ protected void drawInner(DrawContext ctx, int mouseX, int mouseY, float partialT } }; - this.addDrawableChild(cloud); + if(!CloudSync.isAvailable()) { + cloud.setTooltip(Tooltip.of(Text.translatable("wildfire_gender.cloud.unavailable_offline"))); + cloud.setActive(false); + } + + this.addDrawableChild(cloud); this.addDrawableChild(new WildfireButton(this.width / 2 + 111, y - 63, 9, 9, Text.literal("X"), button -> client.setScreen(parent))); diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index d3928fe1..cd9caab5 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -73,13 +73,17 @@ public static boolean syncOnCooldown() { } /** - * @return {@code true} if syncing should be enabled + * @return {@code true} if syncing is available; currently, this only checks for a valid Minecraft session. + */ + public static boolean isAvailable() { + return MinecraftClient.getInstance().getSession().getAccountType() == Session.AccountType.MSA; + } + + /** + * @return {@code true} if syncing is enabled; this will always return {@code false} if {@link #isAvailable() syncing is unavailable}. */ public static boolean isEnabled() { - if(MinecraftClient.getInstance().getSession().getAccountType() != Session.AccountType.MSA) { - return false; - } - return GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SYNC_ENABLED); + return isAvailable() && GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SYNC_ENABLED); } /** diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index 2bb77807..13346471 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -60,6 +60,7 @@ "wildfire_gender.coming_soon": "Coming soon", "wildfire_gender.cloud_settings": "Cloud Sync Settings", + "wildfire_gender.cloud.unavailable_offline": "Cloud syncing is unavailable as you aren't currently logged into a valid Minecraft account", "wildfire_gender.cloud.status": "Syncing:", "wildfire_gender.cloud.automatic": "Automatic Sync:", "wildfire_gender.cloud.automatic.tooltip.line1": "While enabled, your config will automatically be synced to the cloud after making any changes.", From ccd4e8d33cc8025e57bd67c67a52b0111ad67e90 Mon Sep 17 00:00:00 2001 From: celeste Date: Sat, 16 Nov 2024 23:33:06 -0700 Subject: [PATCH 136/238] remove checked IOException thats never actually thrown anymore --- .../com/wildfire/main/cloud/CloudSync.java | 46 ++++++++----------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index cd9caab5..bcdd4bc4 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -33,7 +33,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.IOException; import java.math.BigInteger; import java.net.URI; import java.net.http.HttpClient; @@ -103,7 +102,7 @@ private static void markFetchError() { } } - private static HttpRequest.Builder createConnection(URI uri) throws IOException { + private static HttpRequest.Builder createConnection(URI uri) { WildfireGender.LOGGER.debug("Connecting to {}", uri); return HttpRequest.newBuilder(uri) .header("User-Agent", USER_AGENT) @@ -162,19 +161,15 @@ public static synchronized CompletableFuture sync(@NotNull PlayerConfig co URI url = URI.create(getCloudServer() + "/" + config.uuid + "?" + params); beginAuth(serverId); - try { - var request = createConnection(url) - .PUT(HttpRequest.BodyPublishers.ofString(json)) - .header("Content-Type", "application/json; charset=UTF-8") - .build(); - var response = CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join(); - if(response.statusCode() >= 400) { - throw new RuntimeException("Server responded " + response.statusCode() + ": " + response.body()); - } - WildfireGender.LOGGER.debug("Server responded to update: {}", response.body()); - } catch(IOException e) { - throw new RuntimeException(e); + var request = createConnection(url) + .PUT(HttpRequest.BodyPublishers.ofString(json)) + .header("Content-Type", "application/json; charset=UTF-8") + .build(); + var response = CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join(); + if(response.statusCode() >= 400) { + throw new RuntimeException("Server responded " + response.statusCode() + ": " + response.body()); } + WildfireGender.LOGGER.debug("Server responded to update: {}", response.body()); }, EXECUTOR); } @@ -194,22 +189,17 @@ public static synchronized CompletableFuture sync(@NotNull PlayerConfig co return CompletableFuture.supplyAsync(() -> { URI url = URI.create(getCloudServer() + "/" + uuid); - try { - var request = createConnection(url).GET().build(); - var response = CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join(); - if(response.statusCode() == 404) { - WildfireGender.LOGGER.debug("Server replied no data for {}", uuid); - return null; - } else if(response.statusCode() >= 400) { - markFetchError(); - throw new RuntimeException("Server responded " + response.statusCode() + ": " + response.body()); - } - - return GSON.fromJson(response.body(), JsonObject.class); - } catch(IOException e) { + var request = createConnection(url).GET().build(); + var response = CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join(); + if(response.statusCode() == 404) { + WildfireGender.LOGGER.debug("Server replied no data for {}", uuid); + return null; + } else if(response.statusCode() >= 400) { markFetchError(); - throw new RuntimeException(e); + throw new RuntimeException("Server responded " + response.statusCode() + ": " + response.body()); } + + return GSON.fromJson(response.body(), JsonObject.class); }, EXECUTOR); } } From ac4e2b10c51cbda910dcca5696598c6c446d49be Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sun, 17 Nov 2024 01:34:08 -0500 Subject: [PATCH 137/238] Initial implementation of first-time setup screen. - Still needs proper translation keys. - Needs to set first time setup to false in cfg. --- src/main/java/com/wildfire/gui/GuiUtils.java | 22 ++- .../screen/WildfireFirstTimeSetupScreen.java | 133 ++++++++++++++++++ .../wildfire/main/WildfireEventHandler.java | 7 +- .../wildfire/main/config/GlobalConfig.java | 2 + .../textures/gui/first_time_bg.png | Bin 0 -> 30975 bytes 5 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java create mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/first_time_bg.png diff --git a/src/main/java/com/wildfire/gui/GuiUtils.java b/src/main/java/com/wildfire/gui/GuiUtils.java index db819ff6..17b70847 100644 --- a/src/main/java/com/wildfire/gui/GuiUtils.java +++ b/src/main/java/com/wildfire/gui/GuiUtils.java @@ -24,12 +24,15 @@ import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.ingame.InventoryScreen; import net.minecraft.entity.LivingEntity; +import net.minecraft.text.OrderedText; +import net.minecraft.text.StringVisitable; import net.minecraft.text.Text; import net.minecraft.util.Util; import net.minecraft.util.math.MathHelper; import org.joml.Quaternionf; import org.joml.Vector3f; +import java.util.Iterator; import java.util.Objects; @Environment(EnvType.CLIENT) @@ -40,8 +43,23 @@ private GuiUtils() { // Reimplementation of DrawContext#drawCenteredTextWithShadow but with the text shadow removed public static void drawCenteredText(DrawContext ctx, TextRenderer textRenderer, Text text, int x, int y, int color) { - int centeredX = x - textRenderer.getWidth(text) / 2; - ctx.drawText(textRenderer, text, centeredX, y, color, false); + int centeredX = x - textRenderer.getWidth(text) / 2; + ctx.drawText(textRenderer, text, centeredX, y, color, false); + } + + + public static void drawCenteredText(DrawContext ctx, TextRenderer textRenderer, OrderedText text, int x, int y, int color) { + int centeredX = x - textRenderer.getWidth(text) / 2; + ctx.drawText(textRenderer, text, centeredX, y, color, false); + } + + public static void drawCenteredTextWrapped(DrawContext ctx, TextRenderer textRenderer, StringVisitable text, int x, int y, int width, int color) { + for(Iterator var7 = textRenderer.wrapLines(text, width).iterator(); var7.hasNext(); y += 9) { + OrderedText orderedText = (OrderedText)var7.next(); + GuiUtils.drawCenteredText(ctx, textRenderer, orderedText, x, y, color); + Objects.requireNonNull(textRenderer); + } + } // Reimplementation of ClickableWidget#drawScrollableText but with the text shadow removed diff --git a/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java new file mode 100644 index 00000000..773aa4c5 --- /dev/null +++ b/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java @@ -0,0 +1,133 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.gui.screen; + +import com.wildfire.gui.GuiUtils; +import com.wildfire.gui.WildfireButton; +import com.wildfire.main.WildfireGender; +import com.wildfire.main.cloud.CloudSync; +import com.wildfire.main.cloud.SyncingTooFrequentlyException; +import com.wildfire.main.config.GlobalConfig; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.CraftingScreen; +import net.minecraft.client.gui.screen.ingame.InventoryScreen; +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.OrderedText; +import net.minecraft.text.StringVisitable; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; + +import java.util.Iterator; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +@Environment(EnvType.CLIENT) +public class WildfireFirstTimeSetupScreen extends BaseWildfireScreen { + + //TODO: PROPER TRANSLATIONS + + private static final Text ENABLED = Text.translatable("wildfire_gender.label.enabled").formatted(Formatting.GREEN); + private static final Text DISABLED = Text.translatable("wildfire_gender.label.disabled").formatted(Formatting.RED); + private static final Identifier BACKGROUND = Identifier.of(WildfireGender.MODID, "textures/gui/first_time_bg.png"); + + public WildfireFirstTimeSetupScreen(Screen parent, UUID uuid) { + super(Text.translatable("wildfire_gender.cloud_settings"), parent, uuid); + } + + @Override + public void init() { + + //var config = GlobalConfig.INSTANCE; + // config.set(GlobalConfig.CLOUD_SYNC_ENABLED, !config.get(GlobalConfig.CLOUD_SYNC_ENABLED)); + + int x = this.width / 2; + int y = this.height / 2; + + this.addDrawableChild(new WildfireButton(x + 1, y + 75, 128 - 5 - 1, 20, + Text.of("Yes"), + button -> { + var config = GlobalConfig.INSTANCE; + config.set(GlobalConfig.CLOUD_SYNC_ENABLED, true); + //also set first time setup false + })); + + + this.addDrawableChild(new WildfireButton(x - 128 + 5, y + 75, 128 - 5 - 1, 20, + Text.of("No"), + button -> { + var config = GlobalConfig.INSTANCE; + config.set(GlobalConfig.CLOUD_SYNC_ENABLED, false); + //also set first time setup false + })); + + super.init(); + } + + @Override + public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delta) { + this.renderInGameBackground(ctx); + ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND, (this.width - 256) / 2, (this.height - 200) / 2, 0, 0, 256, 200, 256, 256); + } + + @Override + public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { + if(client == null || client.world == null) return; + super.render(ctx, mouseX, mouseY, delta); + + MatrixStack mStack = ctx.getMatrices(); + + int x = this.width / 2; + int y = this.height / 2; + + GuiUtils.drawCenteredText(ctx, textRenderer, Text.translatable("Welcome to Wildfire's Female Gender Mod!").formatted(Formatting.UNDERLINE), x, y - 20, 0x000000); + + mStack.push(); + mStack.translate(x, y - 5, 0); + mStack.scale(0.8f, 0.8f, 1); + mStack.translate(-x, -y + 5, 0); + GuiUtils.drawCenteredTextWrapped(ctx, textRenderer, Text.translatable("Would you like to enable cloud server syncing for your gender settings? This feature allows other players to view your customized gender appearance, even if the server doesn't have the mod installed."), x, y - 5, (int) ((256-10) * 1.2f), 4210752); + mStack.pop(); + + + mStack.push(); + mStack.translate(x, y + 47, 0); + mStack.scale(0.6f, 0.6f, 1); + mStack.translate(-x, -y - 47, 0); + GuiUtils.drawCenteredTextWrapped(ctx, textRenderer, Text.translatable("You can always change this setting later in the mod menu."), x, y + 77, (int) ((256-10) * 1.2f), 4210752); + mStack.pop(); + + + } + + @Override + public void close() { + GlobalConfig.INSTANCE.save(); + super.close(); + } +} \ No newline at end of file diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index 4609d8ef..b6042813 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -20,6 +20,7 @@ import com.wildfire.gui.screen.BaseWildfireScreen; import com.wildfire.gui.screen.WardrobeBrowserScreen; +import com.wildfire.gui.screen.WildfireFirstTimeSetupScreen; import com.wildfire.main.cloud.CloudSync; import com.wildfire.main.config.GlobalConfig; import com.wildfire.main.entitydata.EntityConfig; @@ -173,7 +174,11 @@ private static void onClientTick(MinecraftClient client) { } if(CONFIG_KEYBIND.wasPressed() && client.currentScreen == null) { - client.setScreen(new WardrobeBrowserScreen(null, client.player.getUuid())); + if(GlobalConfig.INSTANCE.get(GlobalConfig.FIRST_TIME_LOAD)) { + client.setScreen(new WildfireFirstTimeSetupScreen(null, client.player.getUuid())); + } else { + client.setScreen(new WardrobeBrowserScreen(null, client.player.getUuid())); + } } } diff --git a/src/main/java/com/wildfire/main/config/GlobalConfig.java b/src/main/java/com/wildfire/main/config/GlobalConfig.java index b30ba054..bb1cf711 100644 --- a/src/main/java/com/wildfire/main/config/GlobalConfig.java +++ b/src/main/java/com/wildfire/main/config/GlobalConfig.java @@ -7,12 +7,14 @@ private GlobalConfig() { super(".", "wildfire_gender"); } + public static final BooleanConfigKey FIRST_TIME_LOAD = new BooleanConfigKey("firstTimeLoad", true); public static final BooleanConfigKey CLOUD_SYNC_ENABLED = new BooleanConfigKey("cloud_sync", true); public static final BooleanConfigKey AUTOMATIC_CLOUD_SYNC = new BooleanConfigKey("sync_player_data", false); // see CloudSync#DEFAULT_CLOUD_URL for the actual default public static final StringConfigKey CLOUD_SERVER = new StringConfigKey("cloud_server", ""); static { + INSTANCE.setDefault(FIRST_TIME_LOAD); INSTANCE.setDefault(CLOUD_SYNC_ENABLED); INSTANCE.setDefault(AUTOMATIC_CLOUD_SYNC); INSTANCE.setDefault(CLOUD_SERVER); diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/first_time_bg.png b/src/main/resources/assets/wildfire_gender/textures/gui/first_time_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..30dcd5aff7cf0659686159e249f99de932b5f796 GIT binary patch literal 30975 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 Date: Sun, 17 Nov 2024 01:39:24 -0500 Subject: [PATCH 138/238] Made it possible to get out of the first-time load screen. My bad. --- .../gui/screen/WildfireFirstTimeSetupScreen.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java index 773aa4c5..25258cb4 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java @@ -26,6 +26,7 @@ import com.wildfire.main.config.GlobalConfig; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; @@ -73,8 +74,13 @@ public void init() { Text.of("Yes"), button -> { var config = GlobalConfig.INSTANCE; + //Enable both settings, they can always disable automatic later? TBD config.set(GlobalConfig.CLOUD_SYNC_ENABLED, true); - //also set first time setup false + config.set(GlobalConfig.AUTOMATIC_CLOUD_SYNC, true); + config.set(GlobalConfig.FIRST_TIME_LOAD, false); + config.save(); + + client.setScreen(new WardrobeBrowserScreen(null, client.player.getUuid())); })); @@ -83,7 +89,11 @@ public void init() { button -> { var config = GlobalConfig.INSTANCE; config.set(GlobalConfig.CLOUD_SYNC_ENABLED, false); - //also set first time setup false + config.set(GlobalConfig.AUTOMATIC_CLOUD_SYNC, false); + config.set(GlobalConfig.FIRST_TIME_LOAD, false); + config.save(); + + client.setScreen(new WardrobeBrowserScreen(null, client.player.getUuid())); })); super.init(); From 8c4deb712548a25f2179d5ffce6ddf8d594a9637 Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 17 Nov 2024 13:04:28 -0700 Subject: [PATCH 139/238] improve narrator accessibility in cloud settings screen --- .../gui/screen/WildfireCloudSyncScreen.java | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java index 5053df0f..7607427c 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java @@ -57,33 +57,44 @@ public void init() { int yPos = y - 44; int xPos = x + 60 / 2 - 1; - this.addDrawableChild(new WildfireButton(this.width / 2 + 85, yPos - 11, 9, 9, Text.literal("X"), button -> close())); - this.addDrawableChild(new WildfireButton(xPos - 15, yPos, 80, 20, CloudSync.isEnabled() ? ENABLED : DISABLED, button -> { var config = GlobalConfig.INSTANCE; config.set(GlobalConfig.CLOUD_SYNC_ENABLED, !config.get(GlobalConfig.CLOUD_SYNC_ENABLED)); button.setMessage(CloudSync.isEnabled() ? ENABLED : DISABLED); - })); - - var automaticTooltip = Tooltip.of(Text.empty() - .append(Text.translatable("wildfire_gender.cloud.automatic.tooltip.line1")) - .append("\n\n") - .append(Text.translatable("wildfire_gender.cloud.automatic.tooltip.line2"))); - this.addDrawableChild(new WildfireButton(xPos - 15, yPos + 22, 80, 20, + }, + text -> Text.empty() + .append(Text.translatable("wildfire_gender.cloud.status")) + .append(" ") + .append(text.get()))); + + WildfireButton automatic; + this.addDrawableChild(automatic = new WildfireButton(xPos - 15, yPos + 22, 80, 20, GlobalConfig.INSTANCE.get(GlobalConfig.AUTOMATIC_CLOUD_SYNC) ? ENABLED : DISABLED, button -> { var config = GlobalConfig.INSTANCE; var newVal = !config.get(GlobalConfig.AUTOMATIC_CLOUD_SYNC); config.set(GlobalConfig.AUTOMATIC_CLOUD_SYNC, newVal); button.setMessage(newVal ? ENABLED : DISABLED); - }, automaticTooltip)); + }, + text -> Text.empty() + .append(Text.translatable("wildfire_gender.cloud.automatic")) + .append(" ") + .append(text.get()))); + automatic.setTooltip(Tooltip.of(Text.empty() + .append(Text.translatable("wildfire_gender.cloud.automatic.tooltip.line1")) + .append("\n\n") + .append(Text.translatable("wildfire_gender.cloud.automatic.tooltip.line2")))); var syncButton = new WildfireButton(xPos - 80, yPos + 80, 100, 15, Text.translatable("wildfire_gender.cloud.sync"), this::sync); syncButton.setActive(GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SYNC_ENABLED)); this.addDrawableChild(syncButton); + this.addDrawableChild(new WildfireButton(this.width / 2 + 85, yPos - 11, 9, 9, Text.literal("X"), + button -> close(), + text -> Text.translatable("gui.narrate.button", Text.translatable("gui.done")))); + super.init(); } From 7f17e3fce429c402de7ef8e3d722b59e02b12e45 Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 17 Nov 2024 13:27:07 -0700 Subject: [PATCH 140/238] add basic rate limiting to sync queries --- .../java/com/wildfire/main/cloud/CloudSync.java | 14 ++++++++++++++ .../main/cloud/SyncingTooFrequentlyException.java | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index bcdd4bc4..f89fb37e 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -18,6 +18,7 @@ package com.wildfire.main.cloud; +import com.google.common.util.concurrent.RateLimiter; import com.google.gson.Gson; import com.google.gson.JsonObject; import com.mojang.authlib.HttpAuthenticationService; @@ -26,6 +27,8 @@ import com.wildfire.main.WildfireHelper; import com.wildfire.main.config.GlobalConfig; import com.wildfire.main.entitydata.PlayerConfig; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; import net.minecraft.client.MinecraftClient; import net.minecraft.client.session.Session; import net.minecraft.util.Util; @@ -45,11 +48,15 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; +@Environment(EnvType.CLIENT) public final class CloudSync { private CloudSync() { throw new UnsupportedOperationException(); } + @SuppressWarnings("UnstableApiUsage") + private static final RateLimiter rateLimiter = RateLimiter.create(5); + private static Instant lastSync = Instant.EPOCH; private static final List fetchErrors = new ArrayList<>(); private static @Nullable Instant disableFetchingUntil; @@ -185,8 +192,15 @@ public static synchronized CompletableFuture sync(@NotNull PlayerConfig co if(!isEnabled() || disableFetchingUntil != null && disableFetchingUntil.isAfter(Instant.now())) { return CompletableFuture.completedFuture(null); } + if(uuid.version() != 4) { + // some servers (namely hypixel) use non-v4 uuids for their npcs + return CompletableFuture.completedFuture(null); + } return CompletableFuture.supplyAsync(() -> { + //noinspection UnstableApiUsage + rateLimiter.acquire(); + URI url = URI.create(getCloudServer() + "/" + uuid); var request = createConnection(url).GET().build(); diff --git a/src/main/java/com/wildfire/main/cloud/SyncingTooFrequentlyException.java b/src/main/java/com/wildfire/main/cloud/SyncingTooFrequentlyException.java index a23237e4..34989524 100644 --- a/src/main/java/com/wildfire/main/cloud/SyncingTooFrequentlyException.java +++ b/src/main/java/com/wildfire/main/cloud/SyncingTooFrequentlyException.java @@ -18,5 +18,9 @@ package com.wildfire.main.cloud; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +@Environment(EnvType.CLIENT) public class SyncingTooFrequentlyException extends RuntimeException { } From 6b6c24999f1cc57957c96bcd394c27b57adad08b Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 17 Nov 2024 15:05:55 -0700 Subject: [PATCH 141/238] add useful narration text to all close buttons --- src/main/java/com/wildfire/gui/GuiUtils.java | 5 +++++ src/main/java/com/wildfire/gui/WildfireButton.java | 2 +- .../com/wildfire/gui/screen/WardrobeBrowserScreen.java | 2 +- .../gui/screen/WildfireBreastCustomizationScreen.java | 7 +++---- .../gui/screen/WildfireCharacterSettingsScreen.java | 7 +++---- .../com/wildfire/gui/screen/WildfireCloudSyncScreen.java | 4 ++-- 6 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/wildfire/gui/GuiUtils.java b/src/main/java/com/wildfire/gui/GuiUtils.java index 17b70847..e2f394c9 100644 --- a/src/main/java/com/wildfire/gui/GuiUtils.java +++ b/src/main/java/com/wildfire/gui/GuiUtils.java @@ -24,6 +24,7 @@ import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.ingame.InventoryScreen; import net.minecraft.entity.LivingEntity; +import net.minecraft.text.MutableText; import net.minecraft.text.OrderedText; import net.minecraft.text.StringVisitable; import net.minecraft.text.Text; @@ -41,6 +42,10 @@ private GuiUtils() { throw new UnsupportedOperationException(); } + public static MutableText doneNarrationText() { + return Text.translatable("gui.narrate.button", Text.translatable("gui.done")); + } + // Reimplementation of DrawContext#drawCenteredTextWithShadow but with the text shadow removed public static void drawCenteredText(DrawContext ctx, TextRenderer textRenderer, Text text, int x, int y, int color) { int centeredX = x - textRenderer.getWidth(text) / 2; diff --git a/src/main/java/com/wildfire/gui/WildfireButton.java b/src/main/java/com/wildfire/gui/WildfireButton.java index fe9d514d..283c72cb 100644 --- a/src/main/java/com/wildfire/gui/WildfireButton.java +++ b/src/main/java/com/wildfire/gui/WildfireButton.java @@ -58,7 +58,7 @@ protected void drawInner(DrawContext ctx, int mouseX, int mouseY, float partialT protected void renderWidget(DrawContext ctx, int mouseX, int mouseY, float partialTicks) { int clr = 0x444444 + (84 << 24); if(this.isHovered()) clr = 0x666666 + (84 << 24); - if(!this.active) clr = 0x222222 + (84 << 24); + if(!isSelected()) clr = 0x222222 + (84 << 24); if(!transparent) ctx.fill(getX(), getY(), getX() + getWidth(), getY() + getHeight(), clr); drawInner(ctx, mouseX, mouseY, partialTicks); diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index aa562c79..17e5c936 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -97,7 +97,7 @@ protected void drawInner(DrawContext ctx, int mouseX, int mouseY, float partialT this.addDrawableChild(cloud); this.addDrawableChild(new WildfireButton(this.width / 2 + 111, y - 63, 9, 9, Text.literal("X"), - button -> client.setScreen(parent))); + button -> close(), text -> GuiUtils.doneNarrationText())); super.init(); } diff --git a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java index 5d84f5d4..0aa92c83 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java @@ -30,7 +30,6 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.tooltip.Tooltip; @@ -65,9 +64,6 @@ public void init() { PlayerConfig.saveGenderInfo(plr); }; - this.addDrawableChild(new WildfireButton(this.width / 2 + 178, j - 72, 9, 9, Text.literal("X"), - button -> MinecraftClient.getInstance().setScreen(parent))); - //Customization Tab this.addDrawableChild(btnCustomization = new WildfireButton(this.width / 2 + 30, j - 60, 158, 10, Text.translatable("wildfire_gender.breast_customization.tab_customization"), button -> { @@ -144,6 +140,9 @@ public void init() { this.addSelectableChild(this.PRESET_LIST); + this.addDrawableChild(new WildfireButton(this.width / 2 + 178, j - 72, 9, 9, Text.literal("X"), + button -> close(), text -> GuiUtils.doneNarrationText())); + this.currentTab = 0; super.init(); diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java index f000325f..70fb9d46 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java @@ -30,7 +30,6 @@ import com.wildfire.main.entitydata.PlayerConfig; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; -import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.tooltip.Tooltip; @@ -62,9 +61,6 @@ public void init() { int yPos = y - 47; int xPos = x - 156 / 2 - 1; - this.addDrawableChild(new WildfireButton(this.width / 2 + 73, yPos - 11, 9, 9, Text.literal("X"), - button -> MinecraftClient.getInstance().setScreen(parent))); - this.addDrawableChild(new WildfireButton(xPos, yPos, 157, 20, Text.translatable("wildfire_gender.char_settings.physics", aPlr.hasBreastPhysics() ? ENABLED : DISABLED), button -> { boolean enablePhysics = !aPlr.hasBreastPhysics(); @@ -123,6 +119,9 @@ public void init() { } }, Tooltip.of(Text.translatable("wildfire_gender.tooltip.hurt_sounds")))); + this.addDrawableChild(new WildfireButton(this.width / 2 + 73, yPos - 11, 9, 9, Text.literal("X"), + button -> close(), text -> GuiUtils.doneNarrationText())); + super.init(); } diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java index 7607427c..d6adf3c1 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java @@ -18,6 +18,7 @@ package com.wildfire.gui.screen; +import com.wildfire.gui.GuiUtils; import com.wildfire.gui.WildfireButton; import com.wildfire.main.WildfireGender; import com.wildfire.main.cloud.CloudSync; @@ -92,8 +93,7 @@ public void init() { this.addDrawableChild(syncButton); this.addDrawableChild(new WildfireButton(this.width / 2 + 85, yPos - 11, 9, 9, Text.literal("X"), - button -> close(), - text -> Text.translatable("gui.narrate.button", Text.translatable("gui.done")))); + button -> close(), text -> GuiUtils.doneNarrationText())); super.init(); } From 8bf3e3a12f3c06c5c3716a58ee934e6af93af1b0 Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 17 Nov 2024 16:00:21 -0700 Subject: [PATCH 142/238] improve slider accessibility --- .../java/com/wildfire/gui/WildfireButton.java | 4 +- .../java/com/wildfire/gui/WildfireSlider.java | 48 +++++++++++++------ .../WildfireBreastCustomizationScreen.java | 3 ++ .../WildfireCharacterSettingsScreen.java | 2 + 4 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/wildfire/gui/WildfireButton.java b/src/main/java/com/wildfire/gui/WildfireButton.java index 283c72cb..baf53a9d 100644 --- a/src/main/java/com/wildfire/gui/WildfireButton.java +++ b/src/main/java/com/wildfire/gui/WildfireButton.java @@ -57,8 +57,8 @@ protected void drawInner(DrawContext ctx, int mouseX, int mouseY, float partialT @Override protected void renderWidget(DrawContext ctx, int mouseX, int mouseY, float partialTicks) { int clr = 0x444444 + (84 << 24); - if(this.isHovered()) clr = 0x666666 + (84 << 24); - if(!isSelected()) clr = 0x222222 + (84 << 24); + if(this.isSelected()) clr = 0x666666 + (84 << 24); + if(!active) clr = 0x222222 + (84 << 24); if(!transparent) ctx.fill(getX(), getY(), getX() + getWidth(), getY() + getHeight(), clr); drawInner(ctx, mouseX, mouseY, partialTicks); diff --git a/src/main/java/com/wildfire/gui/WildfireSlider.java b/src/main/java/com/wildfire/gui/WildfireSlider.java index 507ae57e..e28b36dd 100644 --- a/src/main/java/com/wildfire/gui/WildfireSlider.java +++ b/src/main/java/com/wildfire/gui/WildfireSlider.java @@ -28,6 +28,7 @@ import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.gui.screen.narration.NarrationPart; import net.minecraft.client.gui.widget.ClickableWidget; import net.minecraft.text.MutableText; import net.minecraft.text.Text; @@ -46,6 +47,8 @@ public class WildfireSlider extends ClickableWidget { private float lastValue; private boolean changed; + private double arrowKeyStep = 0.05; + public WildfireSlider(int xPos, int yPos, int width, int height, FloatConfigKey config, double currentVal, FloatConsumer valueUpdate, Float2ObjectFunction messageUpdate, FloatConsumer onSave) { this(xPos, yPos, width, height, config.getMinInclusive(), config.getMaxInclusive(), currentVal, valueUpdate, messageUpdate, onSave); @@ -62,6 +65,10 @@ public WildfireSlider(int xPos, int yPos, int width, int height, double minVal, setValueInternal(currentVal); } + public void setArrowKeyStep(double arrowKeyStep) { + this.arrowKeyStep = arrowKeyStep; + } + protected void updateMessage() { setMessage(messageUpdate.get(lastValue)); } @@ -94,11 +101,23 @@ public void onClick(double mouseX, double mouseY) { @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - boolean result = super.keyPressed(keyCode, scanCode, modifiers); - if (keyCode == GLFW.GLFW_KEY_LEFT || keyCode == GLFW.GLFW_KEY_RIGHT) { + if(keyCode == GLFW.GLFW_KEY_LEFT || keyCode == GLFW.GLFW_KEY_RIGHT) { + value += (keyCode == GLFW.GLFW_KEY_LEFT ? -arrowKeyStep : arrowKeyStep); + value = MathHelper.clamp(value, 0, 1); + applyValue(); + updateMessage(); + return true; + } + return super.keyPressed(keyCode, scanCode, modifiers); + } + + @Override + public boolean keyReleased(int keyCode, int scanCode, int modifiers) { + if(keyCode == GLFW.GLFW_KEY_LEFT || keyCode == GLFW.GLFW_KEY_RIGHT) { save(); + return true; } - return result; + return super.keyReleased(keyCode, scanCode, modifiers); } protected MutableText getNarrationMessage() { @@ -109,7 +128,6 @@ protected MutableText getNarrationMessage() { protected void renderWidget(DrawContext ctx, int mouseX, int mouseY, float delta) { if (this.visible) { RenderSystem.disableDepthTest(); - this.hovered = mouseX >= this.getX() && mouseY >= this.getY() && mouseX < this.getX() + this.width && mouseY < this.getY() + this.height; int xP = getX() + 2; ctx.fill(xP - 2, getY(), getX() + this.width - 1, getY() + this.height, 0x222222 + (128 << 24)); @@ -122,7 +140,7 @@ protected void renderWidget(DrawContext ctx, int mouseX, int mouseY, float delta TextRenderer font = MinecraftClient.getInstance().textRenderer; int i = this.getX() + 2; int j = this.getX() + this.getWidth() - 2; - GuiUtils.drawScrollableTextWithoutShadow(ctx, font, this.getMessage(), i, this.getY(), j, this.getY() + this.getHeight(), this.hovered || changed ? 0xFFFF55 : 0xFFFFFF); + GuiUtils.drawScrollableTextWithoutShadow(ctx, font, this.getMessage(), i, this.getY(), j, this.getY() + this.getHeight(), isSelected() || changed ? 0xFFFF55 : 0xFFFFFF); } } @@ -148,21 +166,23 @@ private void setValueInternal(double value) { protected void onDrag(double mouseX, double mouseY, double deltaX, double deltaY) { this.setValueFromMouse(mouseX); - super.onDrag(mouseX, mouseY, deltaX, deltaY); } @Override - public void appendClickableNarrations(NarrationMessageBuilder builder) {} + public void appendClickableNarrations(NarrationMessageBuilder builder) { + builder.put(NarrationPart.TITLE, Text.translatable("gui.narrate.slider", this.getMessage())); + if(active) { + if(isFocused()) { + builder.put(NarrationPart.USAGE, Text.translatable("narration.slider.usage.focused")); + } else { + builder.put(NarrationPart.USAGE, Text.translatable("narration.slider.usage.hovered")); + } + } + } private void setValueFromMouse(double mouseX) { this.value = ((mouseX - (double)(this.getX() + 4)) / (double)(this.getWidth() - 8)); - if (this.value < 0.0F) { - this.value = 0.0F; - } - - if (this.value > 1.0F) { - this.value = 1.0F; - } + this.value = MathHelper.clamp(this.value, 0, 1); applyValue(); updateMessage(); } diff --git a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java index 0aa92c83..5577f3c6 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java @@ -111,6 +111,7 @@ public void init() { this.addDrawableChild(this.breastSlider = new WildfireSlider(this.width / 2 + 30, j - 48, 158, 20, Configuration.BUST_SIZE, plr.getBustSize(), plr::updateBustSize, value -> Text.translatable("wildfire_gender.wardrobe.slider.breast_size", Math.round(value * 1.25f * 100)), onSave)); + this.breastSlider.setArrowKeyStep(0.01); //Customization this.addDrawableChild(this.xOffsetBoobSlider = new WildfireSlider(this.width / 2 + 30, j - 27, 158, 20, Configuration.BREASTS_OFFSET_X, breasts.getXOffset(), @@ -119,9 +120,11 @@ public void init() { breasts::updateYOffset, value -> Text.translatable("wildfire_gender.wardrobe.slider.height", Math.round((Math.round(value * 100f) / 100f) * 10)), onSave)); this.addDrawableChild(this.zOffsetBoobSlider = new WildfireSlider(this.width / 2 + 30, j + 15, 158, 20, Configuration.BREASTS_OFFSET_Z, breasts.getZOffset(), breasts::updateZOffset, value -> Text.translatable("wildfire_gender.wardrobe.slider.depth", Math.round((Math.round(value * 100f) / 100f) * 10)), onSave)); + this.zOffsetBoobSlider.setArrowKeyStep(0.1); this.addDrawableChild(this.cleavageSlider = new WildfireSlider(this.width / 2 + 30, j + 36, 158, 20, Configuration.BREASTS_CLEAVAGE, breasts.getCleavage(), breasts::updateCleavage, value -> Text.translatable("wildfire_gender.wardrobe.slider.rotation", Math.round((Math.round(value * 100f) / 100f) * 100)), onSave)); + this.cleavageSlider.setArrowKeyStep(0.1); this.addDrawableChild(this.btnDualPhysics =new WildfireButton(this.width / 2 + 30, j + 57, 158, 20, Text.translatable("wildfire_gender.breast_customization.dual_physics", Text.translatable(breasts.isUniboob() ? "wildfire_gender.label.no" : "wildfire_gender.label.yes")), button -> { diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java index 70fb9d46..dfe12cde 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java @@ -102,6 +102,7 @@ public void init() { PlayerConfig.saveGenderInfo(aPlr); } })); + this.bounceSlider.setArrowKeyStep(0.005); this.addDrawableChild(this.floppySlider = new WildfireSlider(xPos, yPos + 80, 158, 20, Configuration.FLOPPY_MULTIPLIER, aPlr.getFloppiness(), value -> { }, value -> Text.translatable("wildfire_gender.slider.floppy", Math.round(value * 100)), value -> { @@ -109,6 +110,7 @@ public void init() { PlayerConfig.saveGenderInfo(aPlr); } })); + this.floppySlider.setArrowKeyStep(0.01); this.addDrawableChild(new WildfireButton(xPos, yPos + 100, 157, 20, Text.translatable("wildfire_gender.char_settings.hurt_sounds", aPlr.hasHurtSounds() ? ENABLED : DISABLED), button -> { From 7217f739dd7e04de92d58d1c68efec95198e1f7f Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sun, 17 Nov 2024 21:47:45 -0500 Subject: [PATCH 143/238] - Remove saving on button click as it saves upon closing the GUI. - Added translation keys - Redesigned menu a bit. --- .../screen/WildfireFirstTimeSetupScreen.java | 32 ++++++++++-------- .../assets/wildfire_gender/lang/en_us.json | 7 ++++ .../textures/gui/first_time_bg.png | Bin 30975 -> 31054 bytes 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java index 25258cb4..346b9c17 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java @@ -27,6 +27,7 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.FontManager; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; @@ -53,8 +54,16 @@ public class WildfireFirstTimeSetupScreen extends BaseWildfireScreen { //TODO: PROPER TRANSLATIONS + private static final Text TITLE = Text.translatable("wildfire_gender.first_time_setup.title").formatted(Formatting.UNDERLINE); + private static final Text DESCRIPTION = Text.translatable("wildfire_gender.first_time_setup.description"); + private static final Text NOTICE = Text.translatable("wildfire_gender.first_time_setup.notice"); + private static final Text ENABLED = Text.translatable("wildfire_gender.label.enabled").formatted(Formatting.GREEN); private static final Text DISABLED = Text.translatable("wildfire_gender.label.disabled").formatted(Formatting.RED); + + private static final Text ENABLE_CLOUD_SYNCING = Text.translatable("wildfire_gender.first_time_setup.enable").formatted(Formatting.GREEN); + private static final Text DISABLE_CLOUD_SYNCING = Text.translatable("wildfire_gender.first_time_setup.disable").formatted(Formatting.RED); + private static final Identifier BACKGROUND = Identifier.of(WildfireGender.MODID, "textures/gui/first_time_bg.png"); public WildfireFirstTimeSetupScreen(Screen parent, UUID uuid) { @@ -70,28 +79,26 @@ public void init() { int x = this.width / 2; int y = this.height / 2; - this.addDrawableChild(new WildfireButton(x + 1, y + 75, 128 - 5 - 1, 20, - Text.of("Yes"), + this.addDrawableChild(new WildfireButton(x, y + 74, 128 - 5 - 1, 20, + ENABLE_CLOUD_SYNCING, button -> { var config = GlobalConfig.INSTANCE; //Enable both settings, they can always disable automatic later? TBD config.set(GlobalConfig.CLOUD_SYNC_ENABLED, true); config.set(GlobalConfig.AUTOMATIC_CLOUD_SYNC, true); config.set(GlobalConfig.FIRST_TIME_LOAD, false); - config.save(); client.setScreen(new WardrobeBrowserScreen(null, client.player.getUuid())); })); - this.addDrawableChild(new WildfireButton(x - 128 + 5, y + 75, 128 - 5 - 1, 20, - Text.of("No"), + this.addDrawableChild(new WildfireButton(x - 128 + 6, y + 74, 128 - 5 - 1, 20, + DISABLE_CLOUD_SYNCING, button -> { var config = GlobalConfig.INSTANCE; config.set(GlobalConfig.CLOUD_SYNC_ENABLED, false); config.set(GlobalConfig.AUTOMATIC_CLOUD_SYNC, false); config.set(GlobalConfig.FIRST_TIME_LOAD, false); - config.save(); client.setScreen(new WardrobeBrowserScreen(null, client.player.getUuid())); })); @@ -115,21 +122,16 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { int x = this.width / 2; int y = this.height / 2; - GuiUtils.drawCenteredText(ctx, textRenderer, Text.translatable("Welcome to Wildfire's Female Gender Mod!").formatted(Formatting.UNDERLINE), x, y - 20, 0x000000); + GuiUtils.drawCenteredText(ctx, textRenderer, TITLE, x, y - 20, 0x000000); - mStack.push(); - mStack.translate(x, y - 5, 0); - mStack.scale(0.8f, 0.8f, 1); - mStack.translate(-x, -y + 5, 0); - GuiUtils.drawCenteredTextWrapped(ctx, textRenderer, Text.translatable("Would you like to enable cloud server syncing for your gender settings? This feature allows other players to view your customized gender appearance, even if the server doesn't have the mod installed."), x, y - 5, (int) ((256-10) * 1.2f), 4210752); - mStack.pop(); + GuiUtils.drawCenteredTextWrapped(ctx, textRenderer, DESCRIPTION, x, y - 5, (int) ((256-10)), 4210752); mStack.push(); mStack.translate(x, y + 47, 0); - mStack.scale(0.6f, 0.6f, 1); + mStack.scale(0.8f, 0.8f, 1); mStack.translate(-x, -y - 47, 0); - GuiUtils.drawCenteredTextWrapped(ctx, textRenderer, Text.translatable("You can always change this setting later in the mod menu."), x, y + 77, (int) ((256-10) * 1.2f), 4210752); + GuiUtils.drawCenteredTextWrapped(ctx, textRenderer, NOTICE, x, y + 65, (int) ((256-10) * 1.2f), 4210752); mStack.pop(); diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index 13346471..bf1b51dc 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -59,6 +59,13 @@ "wildfire_gender.cancer_awareness.title": "Hey, it's Breast Cancer Awareness Month!", "wildfire_gender.coming_soon": "Coming soon", + "wildfire_gender.first_time_setup.title": "Welcome to Wildfire's Female Gender Mod!", + "wildfire_gender.first_time_setup.description": "Would you like to enable cloud server syncing for your gender settings? This feature allows other players to view your customized gender appearance, even if the server doesn't have the mod installed.", + "wildfire_gender.first_time_setup.notice": "You can always change this setting later in the mod menu.", + "wildfire_gender.first_time_setup.enable": "Enable Cloud Syncing", + "wildfire_gender.first_time_setup.disable": "Disable Cloud Syncing", + + "wildfire_gender.cloud_settings": "Cloud Sync Settings", "wildfire_gender.cloud.unavailable_offline": "Cloud syncing is unavailable as you aren't currently logged into a valid Minecraft account", "wildfire_gender.cloud.status": "Syncing:", diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/first_time_bg.png b/src/main/resources/assets/wildfire_gender/textures/gui/first_time_bg.png index 30dcd5aff7cf0659686159e249f99de932b5f796..a4c79f9d8b2d570a211a64abdaa6581c00bf22fc 100644 GIT binary patch delta 536 zcmV+z0_Xkz@d3{A0kFvd0dTX*0rzu%$GDADaU940V`S{uVI8p&C80rk0ap>a4}!aym@|a zP=n2crp=74YOB`2wbp-xs=I#8#ID-)ip)*@)csiBtiG+MzkXLgeENNT{rIbYdb0bZ zhC9Rlx%-c+8c)a7wOx&NM|E?3TX%o;17_`da@n96)MVPP6#U}Z%Np;FYy8m%)!xpk zYnz&Wd{@7}__MArFKf6nshvmT8c+MZ7}~C_<@05&maD3!A7C5|26eN#sSj^QwKv@M`($p8(8edo|geRJ-0*itgDj&ueF=pRRoM`nra*rminn)s6aC57z7aZQkuq z_9oRdP2H_-`*Dk=t*e*Iy8QXKntt|4HEmm?N0a*E^DqBf0Os>~0o&uf0|0EV_ud)| z*lv?ycpsCWcj1$tcP~Afs_MF~tE#H=^Yf~zIypJHcN*}J?eFi`@$qq0Rh^!mR#nwv zv3Pd?E-o(K_&V^A9UUFj;o)J`y#ROwldyL>M7?uO!1;WB?^NI+d)u0TZw&yjy(|g} zp*9!)Mzj9x&d$#Mbr9HIZ{7hk2L}iD4g=e2u~<}9HJdwtjealy*mn1?eUq_xHW>i; a-~A0 delta 436 zcmV;l0ZabQ@&W(x0kFvd0U@)>0rzu%|F}(qaU6$#4hs2KNCgrjVnJM>Q-#C@m^ca> z2Y|H)$&9!`ufT|e1QmsWKnX2vZ3ZVulm!-se4l5DEKeEyk^TDh`wnWbnb5SEu~lu= z`oGrtL#Vp!*G%lHU9ZU8)SK>gJzYJk=O13wtGh4j$?eDWVCO*%C&T`^`|D|cjc4QP z+O9@BqdGsksLSvDfLXhqTsCM1HJ$Y<1s}ivSmT{>jc;75_F`UL+tlpVWqmpRR%a(C zHJnUqa&uhcS-%%U+qJbkTGnd0s%rWH#=&4v=d1I&ele=u`ECt|{TqkR%g?_8FrV+% zbZ1)adMmp3?~ZCR>8C57{y3{g;k>D{lT~%2KGuWvI)9sY`_tWNHBD2OtBZczqG{{& z Date: Sun, 17 Nov 2024 22:36:59 -0500 Subject: [PATCH 144/238] I really want to just completely redesign the WardrobeBrowserScreen eventually, but I've just made a few quality of life changes for the time being. - Added simple player list on the left of the WardrobeBrowserScreen to show who is using the mod (improvements needed, also needs testing, but might work for initial release) - Added "playing with contributor" notice when connected to a server. --- .../gui/screen/WardrobeBrowserScreen.java | 57 ++++++++++++++++--- .../com/wildfire/main/WildfireGender.java | 17 +++++- .../assets/wildfire_gender/lang/en_us.json | 3 + 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index 17e5c936..4f0e28d7 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -22,23 +22,29 @@ import com.wildfire.main.Gender; import com.wildfire.main.WildfireGender; -import java.util.Calendar; -import java.util.Objects; -import java.util.UUID; +import java.util.*; import com.wildfire.gui.WildfireButton; import com.wildfire.main.cloud.CloudSync; import com.wildfire.main.entitydata.PlayerConfig; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.hud.PlayerListHud; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.network.PlayerListEntry; +import net.minecraft.client.realms.dto.PlayerInfo; import net.minecraft.client.render.RenderLayer; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.scoreboard.Team; +import net.minecraft.server.dedicated.gui.PlayerListGui; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; +import net.minecraft.util.Nullables; +import net.minecraft.world.GameMode; @Environment(EnvType.CLIENT) public class WardrobeBrowserScreen extends BaseWildfireScreen { @@ -46,7 +52,7 @@ public class WardrobeBrowserScreen extends BaseWildfireScreen { private static final Identifier BACKGROUND = Identifier.of(WildfireGender.MODID, "textures/gui/wardrobe_bg3.png"); private static final Identifier TXTR_RIBBON = Identifier.of(WildfireGender.MODID, "textures/bc_ribbon.png"); private static final Identifier CLOUD_ICON = Identifier.of(WildfireGender.MODID, "textures/cloud.png"); - private static final UUID CREATOR_UUID = UUID.fromString("23b6feed-2dfe-4f2e-9429-863fd4adb946"); + private static final boolean isBreastCancerAwarenessMonth = Calendar.getInstance().get(Calendar.MONTH) == Calendar.OCTOBER; public WardrobeBrowserScreen(Screen parent, UUID uuid) { @@ -93,6 +99,8 @@ protected void drawInner(DrawContext ctx, int mouseX, int mouseY, float partialT if(!CloudSync.isAvailable()) { cloud.setTooltip(Tooltip.of(Text.translatable("wildfire_gender.cloud.unavailable_offline"))); cloud.setActive(false); + } else { + cloud.setTooltip(Tooltip.of(Text.translatable("wildfire_gender.cloud.available_online"))); } this.addDrawableChild(cloud); @@ -136,12 +144,38 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { if(client != null && client.player != null) { boolean withCreator = client.player.networkHandler.getPlayerList().stream() - .anyMatch((player) -> player.getProfile().getId().equals(CREATOR_UUID)); - if(withCreator) { - int creatorY = y + 65; - // move down so we don't overlap with the breast cancer awareness month banner - if(isBreastCancerAwarenessMonth) creatorY += 30; + .anyMatch((player) -> player.getProfile().getId().equals(WildfireGender.CREATOR_UUID)); + boolean withContributor = client.player.networkHandler.getPlayerList().stream() + .anyMatch(player -> WildfireGender.CONTRIBUTOR_UUIDS.contains(player.getProfile().getId())); + + int creatorY = y + 65; + + // move down so we don't overlap with the breast cancer awareness month banner + if(isBreastCancerAwarenessMonth) creatorY += 30; + + if(withCreator && withContributor) { //with both the creator and a contributor + GuiUtils.drawCenteredTextWrapped(ctx, this.textRenderer, Text.translatable("wildfire_gender.label.with_both"), this.width / 2, creatorY, 250, 0xFF00FF); + } else if(withCreator) { //only with the creator GuiUtils.drawCenteredText(ctx, this.textRenderer, Text.translatable("wildfire_gender.label.with_creator"), this.width / 2, creatorY, 0xFF00FF); + } else if(withContributor) { //only with a contributor + GuiUtils.drawCenteredText(ctx, this.textRenderer, Text.translatable("wildfire_gender.label.with_contributor"), this.width / 2, creatorY, 0xFF00FF); + } + + + + + List playerListEntry = collectPlayerEntries(); + + ctx.drawText(textRenderer, Text.literal("Players Using the Mod").formatted(Formatting.AQUA), 5,5, 0xFFFFFF, false); + + + int yPos = 18; + for(PlayerListEntry entry : playerListEntry) { + PlayerConfig cfg = WildfireGender.getPlayerById(entry.getProfile().getId()); + if(cfg != null) { //if it is null, they aren't using the mod, so just skip them. + ctx.drawText(textRenderer, Text.literal(entry.getProfile().getName() + " - ").append(cfg.getGender().getDisplayName()), 10, yPos, 0xFFFFFF, false); + yPos += 10; + } } } @@ -152,4 +186,9 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { ctx.drawTexture(RenderLayer::getGuiTextured, TXTR_RIBBON, x + 130, bcaY + 109, 0, 0, 26, 26, 20, 20, 20, 20); } } + + + private List collectPlayerEntries() { + return this.client.player.networkHandler.getListedPlayerListEntries().stream().limit(40L).toList(); + } } \ No newline at end of file diff --git a/src/main/java/com/wildfire/main/WildfireGender.java b/src/main/java/com/wildfire/main/WildfireGender.java index 3c484a07..89bc1f4a 100644 --- a/src/main/java/com/wildfire/main/WildfireGender.java +++ b/src/main/java/com/wildfire/main/WildfireGender.java @@ -18,9 +18,7 @@ package com.wildfire.main; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; +import java.util.*; import com.mojang.logging.LogUtils; import com.wildfire.main.cloud.CloudSync; @@ -37,6 +35,19 @@ public class WildfireGender implements ModInitializer { public static final Logger LOGGER = LogUtils.getLogger(); public static final Map PLAYER_CACHE = new HashMap<>(); + public static final UUID CREATOR_UUID = UUID.fromString("23b6feed-2dfe-4f2e-9429-863fd4adb946"); + public static final List CONTRIBUTOR_UUIDS = Arrays.asList( + UUID.fromString("70336328-0de7-430e-8cba-2779e2a05ab5"), //celeste + UUID.fromString("64e57307-72e5-4f43-be9c-181e8e35cc9b"), //pupnewfster + UUID.fromString("618a8390-51b1-43b2-a53a-ab72c1bbd8bd"), //Kichura + UUID.fromString("33feda66-c706-4725-8983-f62e5e6cbee7"), //BlueLight + UUID.fromString("ad8ee68c-0aa1-47f9-b29f-f92fa1ef66dc"), //Diademiemi + UUID.fromString("8fb5e95d-7f41-4b4c-b8c5-4f15ea3fa2c1"), //Arcti.cc + UUID.fromString("3f36f7e9-7459-43fe-87ce-4e8a5d47da80"), //IzzyBizzy45 + UUID.fromString("525b0455-15e9-49b7-b61d-f291e8ee6c5b") //Powerless001 + //UUID.fromString("23b6feed-2dfe-4f2e-9429-863fd4adb946") //WildfireFGM (I'm not a contributor, silly!) + ); + @Override public void onInitialize() { WildfireSync.register(); diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index bf1b51dc..7529b8dd 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -49,6 +49,8 @@ "wildfire_gender.label.yes": "Yes", "wildfire_gender.label.no": "No", "wildfire_gender.label.with_creator": "You are playing on a server with the creator of this mod!", + "wildfire_gender.label.with_contributor": "You are playing on a server with a contributor of this mod!", + "wildfire_gender.label.with_both": "You are playing on a server with the creator and a contributor of this mod!", "wildfire_gender.slider.bounce": "Bounce Intensity: %s%%", "wildfire_gender.slider.floppy": "Breast Momentum: %s%%", @@ -67,6 +69,7 @@ "wildfire_gender.cloud_settings": "Cloud Sync Settings", + "wildfire_gender.cloud.available_online": "Cloud Sync", "wildfire_gender.cloud.unavailable_offline": "Cloud syncing is unavailable as you aren't currently logged into a valid Minecraft account", "wildfire_gender.cloud.status": "Syncing:", "wildfire_gender.cloud.automatic": "Automatic Sync:", From d62aeac655ff014c83bbabcd5b5539e2190f879b Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 17 Nov 2024 21:31:32 -0700 Subject: [PATCH 145/238] use a longer lived auth token instead of reauthenticating every update --- .../wildfire/main/WildfireEventHandler.java | 2 +- .../com/wildfire/main/cloud/CloudSync.java | 132 +++++++++++++----- 2 files changed, 96 insertions(+), 38 deletions(-) diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index b6042813..a059c88e 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -174,7 +174,7 @@ private static void onClientTick(MinecraftClient client) { } if(CONFIG_KEYBIND.wasPressed() && client.currentScreen == null) { - if(GlobalConfig.INSTANCE.get(GlobalConfig.FIRST_TIME_LOAD)) { + if(GlobalConfig.INSTANCE.get(GlobalConfig.FIRST_TIME_LOAD) && CloudSync.isAvailable()) { client.setScreen(new WildfireFirstTimeSetupScreen(null, client.player.getUuid())); } else { client.setScreen(new WardrobeBrowserScreen(null, client.player.getUuid())); diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index f89fb37e..5a0399c1 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -20,19 +20,23 @@ import com.google.common.util.concurrent.RateLimiter; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.google.gson.JsonObject; import com.mojang.authlib.HttpAuthenticationService; import com.mojang.authlib.exceptions.AuthenticationException; +import com.mojang.util.InstantTypeAdapter; import com.wildfire.main.WildfireGender; import com.wildfire.main.WildfireHelper; import com.wildfire.main.config.GlobalConfig; import com.wildfire.main.entitydata.PlayerConfig; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.MinecraftClient; import net.minecraft.client.session.Session; import net.minecraft.util.Util; import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.Blocking; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -55,24 +59,45 @@ private CloudSync() { } @SuppressWarnings("UnstableApiUsage") - private static final RateLimiter rateLimiter = RateLimiter.create(5); + private static final RateLimiter RATE_LIMITER = RateLimiter.create(5); private static Instant lastSync = Instant.EPOCH; private static final List fetchErrors = new ArrayList<>(); private static @Nullable Instant disableFetchingUntil; + private static CloudAuth auth; + private static final Object AUTH_LOCK = new Object(); + private static final Object SYNC_LOCK = new Object(); private static final Executor EXECUTOR = Util.getIoWorkerExecutor().named("wildfire_gender$cloudSync"); - private static final Gson GSON = new Gson(); - private static final HttpClient CLIENT = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build(); + private static final Gson GSON = new GsonBuilder().registerTypeAdapter(Instant.class, new InstantTypeAdapter()).create(); + + private static final HttpClient CLIENT = HttpClient.newBuilder() + .version(useHttp1_1() ? HttpClient.Version.HTTP_1_1 : HttpClient.Version.HTTP_2) + .connectTimeout(Duration.ofSeconds(5)) + .followRedirects(HttpClient.Redirect.NORMAL) + .build(); + private static final String USER_AGENT = "WildfireGender/" + StringUtils.split(WildfireHelper.getModVersion(WildfireGender.MODID), '+')[0] + " Minecraft/" + WildfireHelper.getModVersion("minecraft"); private static final String DEFAULT_CLOUD_URL = "https://wfgm.celestialfault.dev"; - public static final Duration SYNC_COOLDOWN = Duration.of(15, ChronoUnit.SECONDS); + public static final Duration SYNC_COOLDOWN = Duration.of(10, ChronoUnit.SECONDS); + // Assume that authentication tokens have already expired if they're due to expire within 30 seconds to account + // for potential clock drift and network latency + public static final Duration AUTH_INVALIDATION_ADJUSTMENT = Duration.of(30, ChronoUnit.SECONDS); + + private static boolean useHttp1_1() { + // FIXME this is a terrible workaround to a really dumb issue. + // HttpClient will seemingly _completely_ break PUT requests if allowed to use its default of HTTP/2 with + // a sync server not running over https; this should realistically only ever be an issue you'd encounter + // when running the sync server locally to develop on it, which is why this enforces that you're in a + // dev env to allow using HTTP/1.1. + return FabricLoader.getInstance().isDevelopmentEnvironment() && getCloudServer().startsWith("http://"); + } /** - * @return {@code true} if the last sync was within the last {@link #SYNC_COOLDOWN 15 seconds} + * @return {@code true} if the last sync was within the last {@link #SYNC_COOLDOWN 10 seconds} */ public static boolean syncOnCooldown() { return lastSync.plus(SYNC_COOLDOWN).isAfter(Instant.now()); @@ -109,10 +134,15 @@ private static void markFetchError() { } } - private static HttpRequest.Builder createConnection(URI uri) { + private static boolean isFetchingDisabled() { + return !isEnabled() || disableFetchingUntil != null && disableFetchingUntil.isAfter(Instant.now()); + } + + private static HttpRequest.Builder createRequest(URI uri) { WildfireGender.LOGGER.debug("Connecting to {}", uri); return HttpRequest.newBuilder(uri) .header("User-Agent", USER_AGENT) + .header("Accept", "application/json") .timeout(Duration.ofSeconds(5)); } @@ -123,14 +153,39 @@ private static String generateServerId() { return intA.xor(intB).toString(16); } - private static void beginAuth(String serverId) { - var client = MinecraftClient.getInstance(); - var session = client.getSession(); - try { - client.getSessionService().joinServer(Objects.requireNonNull(session.getUuidOrNull()), session.getAccessToken(), serverId); - } catch(AuthenticationException e) { - throw new RuntimeException(e); + @Blocking + private static String getAuthToken() { + synchronized(AUTH_LOCK) { + var client = MinecraftClient.getInstance(); + if(auth == null || auth.isExpired() || (client.player != null && !auth.account().equals(client.player.getUuid()))) { + WildfireGender.LOGGER.info("Obtaining new authentication token from the cloud sync server"); + + var serverId = generateServerId(); + var session = client.getSession(); + + try { + client.getSessionService().joinServer(Objects.requireNonNull(session.getUuidOrNull()), session.getAccessToken(), serverId); + } catch(AuthenticationException e) { + throw new RuntimeException(e); + } + + var query = HttpAuthenticationService.buildQuery(Map.of("serverId", serverId, "username", session.getUsername())); + var uri = URI.create(getCloudServer() + "/auth?" + query); + var request = createRequest(uri).GET().build(); + var response = CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join(); + if(response.statusCode() >= 400) { + throw new RuntimeException("Failed to authenticate with sync server: " + response.body()); + } + + auth = GSON.fromJson(response.body(), CloudAuth.class); + if(client.player != null && !auth.account().equals(client.player.getUuid())) { + WildfireGender.LOGGER.warn("Authenticated account {} does not match the current player ({}); you likely have a misbehaving account switcher mod installed!", auth.account(), client.player.getUuid()); + } else { + WildfireGender.LOGGER.info("Obtained authentication token for {}, expiry {}", auth.account, auth.expires); + } + } } + return auth.token(); } /** @@ -141,36 +196,30 @@ private static void beginAuth(String serverId) { * @return A {@link CompletableFuture} indicating when the process has finished, or with an exception if * syncing failed. */ - public static synchronized CompletableFuture sync(@NotNull PlayerConfig config) { + public static CompletableFuture sync(@NotNull PlayerConfig config) { if(!isEnabled()) { return CompletableFuture.completedFuture(null); } - // Force a 15s cooldown on syncing - if(syncOnCooldown()) { - var future = new CompletableFuture(); - future.completeExceptionally(new SyncingTooFrequentlyException()); - return future; + synchronized(SYNC_LOCK) { + // Force a 10s cooldown on syncing + if(syncOnCooldown()) { + var future = new CompletableFuture(); + future.completeExceptionally(new SyncingTooFrequentlyException()); + return future; + } + lastSync = Instant.now(); } - lastSync = Instant.now(); - - var client = MinecraftClient.getInstance(); - var username = client.getSession().getUsername(); - var json = config.toJson().toString(); - var serverId = generateServerId(); return CompletableFuture.runAsync(() -> { - var params = HttpAuthenticationService.buildQuery(Map.of( - "serverId", Objects.requireNonNull(serverId), - "username", username - )); + var token = getAuthToken(); + var url = URI.create(getCloudServer() + "/" + config.uuid); + var json = config.toJson().toString(); - URI url = URI.create(getCloudServer() + "/" + config.uuid + "?" + params); - beginAuth(serverId); - - var request = createConnection(url) + var request = createRequest(url) .PUT(HttpRequest.BodyPublishers.ofString(json)) .header("Content-Type", "application/json; charset=UTF-8") + .header("Auth-Token", token) .build(); var response = CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join(); if(response.statusCode() >= 400) { @@ -189,21 +238,24 @@ public static synchronized CompletableFuture sync(@NotNull PlayerConfig co * stored in the sync server, or {@code null} otherwise. */ public static CompletableFuture<@Nullable JsonObject> getProfile(UUID uuid) { - if(!isEnabled() || disableFetchingUntil != null && disableFetchingUntil.isAfter(Instant.now())) { + if(isFetchingDisabled()) { return CompletableFuture.completedFuture(null); } if(uuid.version() != 4) { - // some servers (namely hypixel) use non-v4 uuids for their npcs + // some servers (namely hypixel) use non-v4 uuids for their npcs; the sync server will immediately reject + // such uuids with a 422 response, so don't bother trying to fetch them. return CompletableFuture.completedFuture(null); } return CompletableFuture.supplyAsync(() -> { //noinspection UnstableApiUsage - rateLimiter.acquire(); + RATE_LIMITER.acquire(); + // check again to be sure, as we might've had to wait for the above rate limit + if(isFetchingDisabled()) return null; URI url = URI.create(getCloudServer() + "/" + uuid); - var request = createConnection(url).GET().build(); + var request = createRequest(url).GET().build(); var response = CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join(); if(response.statusCode() == 404) { WildfireGender.LOGGER.debug("Server replied no data for {}", uuid); @@ -216,4 +268,10 @@ public static synchronized CompletableFuture sync(@NotNull PlayerConfig co return GSON.fromJson(response.body(), JsonObject.class); }, EXECUTOR); } + + record CloudAuth(boolean success, String token, UUID account, Instant expires) { + boolean isExpired() { + return expires.minus(AUTH_INVALIDATION_ADJUSTMENT).isBefore(Instant.now()); + } + } } From 46da38c690e72bcfd2f6fad3393db5f56285e206 Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 17 Nov 2024 21:38:17 -0700 Subject: [PATCH 146/238] correctly ignore players not using the mod the client attempts to cache *every* player entity it sees, making the null check not actually accomplish the intended outcome --- .../gui/screen/WardrobeBrowserScreen.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index 4f0e28d7..c3500d9f 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -166,16 +166,16 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { List playerListEntry = collectPlayerEntries(); - ctx.drawText(textRenderer, Text.literal("Players Using the Mod").formatted(Formatting.AQUA), 5,5, 0xFFFFFF, false); + if(!playerListEntry.isEmpty()) { + ctx.drawText(textRenderer, Text.literal("Players Using the Mod").formatted(Formatting.AQUA), 5, 5, 0xFFFFFF, false); + } int yPos = 18; for(PlayerListEntry entry : playerListEntry) { PlayerConfig cfg = WildfireGender.getPlayerById(entry.getProfile().getId()); - if(cfg != null) { //if it is null, they aren't using the mod, so just skip them. - ctx.drawText(textRenderer, Text.literal(entry.getProfile().getName() + " - ").append(cfg.getGender().getDisplayName()), 10, yPos, 0xFFFFFF, false); - yPos += 10; - } + ctx.drawText(textRenderer, Text.literal(entry.getProfile().getName() + " - ").append(cfg.getGender().getDisplayName()), 10, yPos, 0xFFFFFF, false); + yPos += 10; } } @@ -189,6 +189,12 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { private List collectPlayerEntries() { - return this.client.player.networkHandler.getListedPlayerListEntries().stream().limit(40L).toList(); + return this.client.player.networkHandler.getListedPlayerListEntries().stream() + .filter(entry -> { + var cfg = WildfireGender.getPlayerById(entry.getProfile().getId()); + return cfg != null && cfg.getSyncStatus() != PlayerConfig.SyncStatus.UNKNOWN; + }) + .limit(40L) + .toList(); } } \ No newline at end of file From ceec237c8eb64642d4d8cf94a7fe0f88d55de0f9 Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 17 Nov 2024 21:47:41 -0700 Subject: [PATCH 147/238] dont list the client player, as we already know we're using the mod --- src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index c3500d9f..d82a2086 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -190,6 +190,7 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { private List collectPlayerEntries() { return this.client.player.networkHandler.getListedPlayerListEntries().stream() + .filter(entry -> !entry.getProfile().getId().equals(client.player.getUuid())) .filter(entry -> { var cfg = WildfireGender.getPlayerById(entry.getProfile().getId()); return cfg != null && cfg.getSyncStatus() != PlayerConfig.SyncStatus.UNKNOWN; From 1b3bc1c2264a905cdc72a0af8925ed5ce1669393 Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 17 Nov 2024 21:59:45 -0700 Subject: [PATCH 148/238] atempt to reauthenticate if the current token is invalid --- src/main/java/com/wildfire/main/cloud/CloudSync.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index 5a0399c1..168a8a45 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -211,6 +211,10 @@ public static CompletableFuture sync(@NotNull PlayerConfig config) { lastSync = Instant.now(); } + return syncInternal(config, false); + } + + private static CompletableFuture syncInternal(PlayerConfig config, boolean resyncing) { return CompletableFuture.runAsync(() -> { var token = getAuthToken(); var url = URI.create(getCloudServer() + "/" + config.uuid); @@ -222,7 +226,12 @@ public static CompletableFuture sync(@NotNull PlayerConfig config) { .header("Auth-Token", token) .build(); var response = CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join(); - if(response.statusCode() >= 400) { + if(response.statusCode() == 401 && !resyncing) { + WildfireGender.LOGGER.warn("Auth token is invalid, attempting to reauth..."); + auth = null; + syncInternal(config, true).join(); + return; + } else if(response.statusCode() >= 400) { throw new RuntimeException("Server responded " + response.statusCode() + ": " + response.body()); } WildfireGender.LOGGER.debug("Server responded to update: {}", response.body()); From a023c76a4bcd847d620208797e5cae649e64d80f Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 18 Nov 2024 00:42:34 -0700 Subject: [PATCH 149/238] cache sync query responses --- .../com/wildfire/main/cloud/CloudSync.java | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index 168a8a45..a0b1a170 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -18,6 +18,8 @@ package com.wildfire.main.cloud; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; import com.google.common.util.concurrent.RateLimiter; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -58,9 +60,6 @@ private CloudSync() { throw new UnsupportedOperationException(); } - @SuppressWarnings("UnstableApiUsage") - private static final RateLimiter RATE_LIMITER = RateLimiter.create(5); - private static Instant lastSync = Instant.EPOCH; private static final List fetchErrors = new ArrayList<>(); private static @Nullable Instant disableFetchingUntil; @@ -81,11 +80,19 @@ private CloudSync() { "WildfireGender/" + StringUtils.split(WildfireHelper.getModVersion(WildfireGender.MODID), '+')[0] + " Minecraft/" + WildfireHelper.getModVersion("minecraft"); + @SuppressWarnings("UnstableApiUsage") + private static final RateLimiter RATE_LIMITER = RateLimiter.create(5); + private static final Cache> FETCH_CACHE = CacheBuilder.newBuilder() + .expireAfterAccess(Duration.ofMinutes(5)) + .maximumSize(256) + .concurrencyLevel(6) // rate limit + 1 + .build(); + private static final String DEFAULT_CLOUD_URL = "https://wfgm.celestialfault.dev"; - public static final Duration SYNC_COOLDOWN = Duration.of(10, ChronoUnit.SECONDS); + private static final Duration SYNC_COOLDOWN = Duration.ofSeconds(10); // Assume that authentication tokens have already expired if they're due to expire within 30 seconds to account // for potential clock drift and network latency - public static final Duration AUTH_INVALIDATION_ADJUSTMENT = Duration.of(30, ChronoUnit.SECONDS); + private static final Duration AUTH_INVALIDATION_ADJUSTMENT = Duration.ofSeconds(30); private static boolean useHttp1_1() { // FIXME this is a terrible workaround to a really dumb issue. @@ -97,7 +104,7 @@ private static boolean useHttp1_1() { } /** - * @return {@code true} if the last sync was within the last {@link #SYNC_COOLDOWN 10 seconds} + * @return {@code true} if the last {@link #sync(PlayerConfig) sync} was within the last 10 seconds */ public static boolean syncOnCooldown() { return lastSync.plus(SYNC_COOLDOWN).isAfter(Instant.now()); @@ -180,9 +187,8 @@ private static String getAuthToken() { auth = GSON.fromJson(response.body(), CloudAuth.class); if(client.player != null && !auth.account().equals(client.player.getUuid())) { WildfireGender.LOGGER.warn("Authenticated account {} does not match the current player ({}); you likely have a misbehaving account switcher mod installed!", auth.account(), client.player.getUuid()); - } else { - WildfireGender.LOGGER.info("Obtained authentication token for {}, expiry {}", auth.account, auth.expires); } + WildfireGender.LOGGER.info("Obtained authentication token for {}, expiry {}", auth.account, auth.expires); } } return auth.token(); @@ -255,6 +261,11 @@ private static CompletableFuture syncInternal(PlayerConfig config, boolean // such uuids with a 422 response, so don't bother trying to fetch them. return CompletableFuture.completedFuture(null); } + var cached = FETCH_CACHE.getIfPresent(uuid); + //noinspection OptionalAssignedToNull + if(cached != null) { + return CompletableFuture.completedFuture(cached.orElse(null)); + } return CompletableFuture.supplyAsync(() -> { //noinspection UnstableApiUsage @@ -268,13 +279,16 @@ private static CompletableFuture syncInternal(PlayerConfig config, boolean var response = CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join(); if(response.statusCode() == 404) { WildfireGender.LOGGER.debug("Server replied no data for {}", uuid); + FETCH_CACHE.put(uuid, Optional.empty()); return null; } else if(response.statusCode() >= 400) { markFetchError(); throw new RuntimeException("Server responded " + response.statusCode() + ": " + response.body()); } - return GSON.fromJson(response.body(), JsonObject.class); + var data = GSON.fromJson(response.body(), JsonObject.class); + FETCH_CACHE.put(uuid, Optional.of(data)); + return data; }, EXECUTOR); } From d0d655ca152c528970dadca0f218ee6a9adf9f76 Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 18 Nov 2024 16:16:33 -0700 Subject: [PATCH 150/238] switch to queueing & bulk queries the code for the queued futures kinda sucks - i wish there was a more reasonable way to copy the result of one future to another --- .../wildfire/main/WildfireEventHandler.java | 25 ++-- .../com/wildfire/main/WildfireGender.java | 1 - .../wildfire/main/WildfireGenderClient.java | 2 +- .../com/wildfire/main/cloud/BulkFetch.java | 9 ++ .../com/wildfire/main/cloud/CloudAuth.java | 15 +++ .../com/wildfire/main/cloud/CloudSync.java | 123 ++++++++++++++---- .../com/wildfire/main/cloud/QueuedFetch.java | 10 ++ 7 files changed, 149 insertions(+), 36 deletions(-) create mode 100644 src/main/java/com/wildfire/main/cloud/BulkFetch.java create mode 100644 src/main/java/com/wildfire/main/cloud/CloudAuth.java create mode 100644 src/main/java/com/wildfire/main/cloud/QueuedFetch.java diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index a059c88e..8b1aa4c2 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -159,17 +159,20 @@ private static void onClientTick(MinecraftClient client) { if(clientConfig != null) WildfireSync.sendToServer(clientConfig); } - if(timer % 40 == 0 && clientConfig != null && clientConfig.needsCloudSync && !(client.currentScreen instanceof BaseWildfireScreen)) { - if(GlobalConfig.INSTANCE.get(GlobalConfig.AUTOMATIC_CLOUD_SYNC) && !CloudSync.syncOnCooldown()) { - CompletableFuture.runAsync(() -> { - try { - CloudSync.sync(clientConfig).join(); - WildfireGender.LOGGER.info("Synced player data to the cloud"); - } catch(Exception e) { - WildfireGender.LOGGER.error("Failed to sync player data", e); - } - }); - clientConfig.needsCloudSync = false; + if(timer % 40 == 0) { + CloudSync.sendNextQueueBatch(); + if(clientConfig != null && clientConfig.needsCloudSync && !(client.currentScreen instanceof BaseWildfireScreen)) { + if(GlobalConfig.INSTANCE.get(GlobalConfig.AUTOMATIC_CLOUD_SYNC) && !CloudSync.syncOnCooldown()) { + CompletableFuture.runAsync(() -> { + try { + CloudSync.sync(clientConfig).join(); + WildfireGender.LOGGER.info("Synced player data to the cloud"); + } catch(Exception e) { + WildfireGender.LOGGER.error("Failed to sync player data", e); + } + }); + clientConfig.needsCloudSync = false; + } } } diff --git a/src/main/java/com/wildfire/main/WildfireGender.java b/src/main/java/com/wildfire/main/WildfireGender.java index 89bc1f4a..3cb88e1f 100644 --- a/src/main/java/com/wildfire/main/WildfireGender.java +++ b/src/main/java/com/wildfire/main/WildfireGender.java @@ -21,7 +21,6 @@ import java.util.*; import com.mojang.logging.LogUtils; -import com.wildfire.main.cloud.CloudSync; import com.wildfire.main.config.GlobalConfig; import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.networking.WildfireSync; diff --git a/src/main/java/com/wildfire/main/WildfireGenderClient.java b/src/main/java/com/wildfire/main/WildfireGenderClient.java index 3359f2bf..c0e009dd 100644 --- a/src/main/java/com/wildfire/main/WildfireGenderClient.java +++ b/src/main/java/com/wildfire/main/WildfireGenderClient.java @@ -55,7 +55,7 @@ public void onInitializeClient() { } else if(player.syncStatus == PlayerConfig.SyncStatus.UNKNOWN) { JsonObject data; try { - data = CloudSync.getProfile(uuid).join(); + data = CloudSync.queueFetch(uuid).join(); } catch(Exception e) { WildfireGender.LOGGER.error("Failed to fetch profile from sync server", e); throw e; diff --git a/src/main/java/com/wildfire/main/cloud/BulkFetch.java b/src/main/java/com/wildfire/main/cloud/BulkFetch.java new file mode 100644 index 00000000..b7439892 --- /dev/null +++ b/src/main/java/com/wildfire/main/cloud/BulkFetch.java @@ -0,0 +1,9 @@ +package com.wildfire.main.cloud; + +import com.google.gson.JsonObject; + +import java.util.Map; +import java.util.UUID; + +record BulkFetch(boolean success, Map users) { +} diff --git a/src/main/java/com/wildfire/main/cloud/CloudAuth.java b/src/main/java/com/wildfire/main/cloud/CloudAuth.java new file mode 100644 index 00000000..f64ea48f --- /dev/null +++ b/src/main/java/com/wildfire/main/cloud/CloudAuth.java @@ -0,0 +1,15 @@ +package com.wildfire.main.cloud; + +import java.time.Duration; +import java.time.Instant; +import java.util.UUID; + +record CloudAuth(boolean success, String token, UUID account, Instant expires) { + // Assume that authentication tokens have already expired if they're due to expire within 30 seconds to account + // for potential clock drift and network latency + static final Duration AUTH_INVALIDATION_ADJUSTMENT = Duration.ofSeconds(30); + + boolean isExpired() { + return expires.minus(AUTH_INVALIDATION_ADJUSTMENT).isBefore(Instant.now()); + } +} diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index a0b1a170..703e1922 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -20,9 +20,9 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -import com.google.common.util.concurrent.RateLimiter; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.mojang.authlib.HttpAuthenticationService; import com.mojang.authlib.exceptions.AuthenticationException; @@ -38,6 +38,7 @@ import net.minecraft.client.session.Session; import net.minecraft.util.Util; import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Blocking; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -51,8 +52,7 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; +import java.util.concurrent.*; @Environment(EnvType.CLIENT) public final class CloudSync { @@ -80,19 +80,12 @@ private CloudSync() { "WildfireGender/" + StringUtils.split(WildfireHelper.getModVersion(WildfireGender.MODID), '+')[0] + " Minecraft/" + WildfireHelper.getModVersion("minecraft"); - @SuppressWarnings("UnstableApiUsage") - private static final RateLimiter RATE_LIMITER = RateLimiter.create(5); + private static final Queue QUEUED = new ConcurrentLinkedDeque<>(); private static final Cache> FETCH_CACHE = CacheBuilder.newBuilder() - .expireAfterAccess(Duration.ofMinutes(5)) - .maximumSize(256) - .concurrencyLevel(6) // rate limit + 1 - .build(); + .expireAfterAccess(Duration.ofMinutes(10)).maximumSize(512).concurrencyLevel(6).build(); private static final String DEFAULT_CLOUD_URL = "https://wfgm.celestialfault.dev"; private static final Duration SYNC_COOLDOWN = Duration.ofSeconds(10); - // Assume that authentication tokens have already expired if they're due to expire within 30 seconds to account - // for potential clock drift and network latency - private static final Duration AUTH_INVALIDATION_ADJUSTMENT = Duration.ofSeconds(30); private static boolean useHttp1_1() { // FIXME this is a terrible workaround to a really dumb issue. @@ -188,7 +181,7 @@ private static String getAuthToken() { if(client.player != null && !auth.account().equals(client.player.getUuid())) { WildfireGender.LOGGER.warn("Authenticated account {} does not match the current player ({}); you likely have a misbehaving account switcher mod installed!", auth.account(), client.player.getUuid()); } - WildfireGender.LOGGER.info("Obtained authentication token for {}, expiry {}", auth.account, auth.expires); + WildfireGender.LOGGER.info("Obtained authentication token for {}, expiry {}", auth.account(), auth.expires()); } } return auth.token(); @@ -268,13 +261,7 @@ private static CompletableFuture syncInternal(PlayerConfig config, boolean } return CompletableFuture.supplyAsync(() -> { - //noinspection UnstableApiUsage - RATE_LIMITER.acquire(); - // check again to be sure, as we might've had to wait for the above rate limit - if(isFetchingDisabled()) return null; - - URI url = URI.create(getCloudServer() + "/" + uuid); - + var url = URI.create(getCloudServer() + "/" + uuid); var request = createRequest(url).GET().build(); var response = CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join(); if(response.statusCode() == 404) { @@ -292,9 +279,99 @@ private static CompletableFuture syncInternal(PlayerConfig config, boolean }, EXECUTOR); } - record CloudAuth(boolean success, String token, UUID account, Instant expires) { - boolean isExpired() { - return expires.minus(AUTH_INVALIDATION_ADJUSTMENT).isBefore(Instant.now()); + /** + * Fetch data for multiple players from the sync server + * + * @param uuids A collection of between 1 and 20 UUIDs to fetch player data for + * + * @return A {@link CompletableFuture} containing a map of player UUIDs to their synced data; any provided + * player UUIDs without any sync data will not be included in the returned map. + */ + public static CompletableFuture> getMultiple(Collection uuids) { + return CompletableFuture.supplyAsync(() -> { + if(isFetchingDisabled()) { + return Collections.emptyMap(); + } + + var url = URI.create(getCloudServer() + "/"); + var json = new JsonArray(); + uuids.forEach(uuid -> json.add(uuid.toString())); + var request = createRequest(url) + .POST(HttpRequest.BodyPublishers.ofString(json.toString())) + .header("Content-Type", "application/json; charset=UTF-8") + .build(); + var response = CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join(); + if(response.statusCode() >= 400) { + throw new RuntimeException("Server responded " + response.statusCode() + ": " + response.body()); + } + + var data = GSON.fromJson(response.body(), BulkFetch.class).users(); + uuids.forEach(uuid -> FETCH_CACHE.put(uuid, Optional.ofNullable(data.get(uuid)))); + return data; + }, EXECUTOR); + } + + /** + * Add a UUID to the fetch queue; this may be requested individually or in bulk, depending on how many other + * users are queued to be fetched. + * + * @param uuid The UUID of the user to fetch + * + * @return A {@link CompletableFuture} containing a {@link JsonObject} of the relevant player data, + * which will be completed once the next queued batch is finished. + */ + public static CompletableFuture<@Nullable JsonObject> queueFetch(UUID uuid) { + var cached = FETCH_CACHE.getIfPresent(uuid); + //noinspection OptionalAssignedToNull + if(cached != null) { + return CompletableFuture.completedFuture(cached.orElse(null)); + } + + var future = new CompletableFuture<@Nullable JsonObject>(); + QUEUED.add(new QueuedFetch(uuid, future)); + return future; + } + + @ApiStatus.Internal + public static void sendNextQueueBatch() { + if(QUEUED.isEmpty()) { + return; } + + final var toFetch = new ArrayList(); + for(int i = 0; i < 20; i++) { + var next = QUEUED.poll(); + if(next == null) break; + toFetch.add(next); + } + + // If there's 3 or fewer players in the queue, just send them all individually so that the requests + // can be cached easier + if(toFetch.size() < 4) { + WildfireGender.LOGGER.debug("Sending {} queued sync queries", toFetch.size()); + toFetch.forEach(queued -> CompletableFuture.runAsync(() -> { + try { + queued.future().complete(getProfile(queued.uuid()).join()); + } catch(Exception e) { + var actualException = e instanceof CompletionException ce ? ce.getCause() : e; + queued.future().completeExceptionally(actualException); + } + })); + return; + } + + WildfireGender.LOGGER.debug("Fetching sync data for {} players in bulk", toFetch.size()); + CompletableFuture.runAsync(() -> { + Map result; + try { + result = getMultiple(toFetch.stream().map(QueuedFetch::uuid).toList()).join(); + } catch(Exception e) { + var actualException = e instanceof CompletionException ce ? ce.getCause() : e; + toFetch.forEach(queued -> queued.future().completeExceptionally(actualException)); + return; + } + + toFetch.forEach(queued -> queued.future().complete(result.get(queued.uuid()))); + }); } } diff --git a/src/main/java/com/wildfire/main/cloud/QueuedFetch.java b/src/main/java/com/wildfire/main/cloud/QueuedFetch.java new file mode 100644 index 00000000..7cb05964 --- /dev/null +++ b/src/main/java/com/wildfire/main/cloud/QueuedFetch.java @@ -0,0 +1,10 @@ +package com.wildfire.main.cloud; + +import com.google.gson.JsonObject; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +record QueuedFetch(UUID uuid, CompletableFuture<@Nullable JsonObject> future) { +} From de352fcbfac12db3bf6478b5e7cdf325c25411b7 Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 18 Nov 2024 17:41:38 -0700 Subject: [PATCH 151/238] re-ignore invalid uuids this is the second time now ive forgotten to do this. also add/improve some javadocs --- .../com/wildfire/main/cloud/CloudAuth.java | 7 ++++ .../com/wildfire/main/cloud/CloudSync.java | 37 ++++++++++++++++--- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/wildfire/main/cloud/CloudAuth.java b/src/main/java/com/wildfire/main/cloud/CloudAuth.java index f64ea48f..9f448287 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudAuth.java +++ b/src/main/java/com/wildfire/main/cloud/CloudAuth.java @@ -1,5 +1,7 @@ package com.wildfire.main.cloud; +import net.minecraft.client.MinecraftClient; + import java.time.Duration; import java.time.Instant; import java.util.UUID; @@ -12,4 +14,9 @@ record CloudAuth(boolean success, String token, UUID account, Instant expires) { boolean isExpired() { return expires.minus(AUTH_INVALIDATION_ADJUSTMENT).isBefore(Instant.now()); } + + boolean isInvalidForClientPlayer() { + var client = MinecraftClient.getInstance(); + return client.player == null || !account.equals(client.player.getUuid()); + } } diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index 703e1922..603fe4fa 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -54,6 +54,10 @@ import java.util.*; import java.util.concurrent.*; +/** + *

Utility class for managing syncing player data to/from the cloud, even if the current connected server doesn't + * have the mod installed.

+ */ @Environment(EnvType.CLIENT) public final class CloudSync { private CloudSync() { @@ -157,7 +161,10 @@ private static String generateServerId() { private static String getAuthToken() { synchronized(AUTH_LOCK) { var client = MinecraftClient.getInstance(); - if(auth == null || auth.isExpired() || (client.player != null && !auth.account().equals(client.player.getUuid()))) { + if(client.player == null) { + throw new IllegalStateException("Cannot get a new auth token while the client player is unset"); + } + if(auth == null || auth.isExpired() || auth.isInvalidForClientPlayer()) { WildfireGender.LOGGER.info("Obtaining new authentication token from the cloud sync server"); var serverId = generateServerId(); @@ -178,7 +185,7 @@ private static String getAuthToken() { } auth = GSON.fromJson(response.body(), CloudAuth.class); - if(client.player != null && !auth.account().equals(client.player.getUuid())) { + if(auth.isInvalidForClientPlayer()) { WildfireGender.LOGGER.warn("Authenticated account {} does not match the current player ({}); you likely have a misbehaving account switcher mod installed!", auth.account(), client.player.getUuid()); } WildfireGender.LOGGER.info("Obtained authentication token for {}, expiry {}", auth.account(), auth.expires()); @@ -244,6 +251,10 @@ private static CompletableFuture syncInternal(PlayerConfig config, boolean * * @return A {@link CompletableFuture} containing a {@link JsonObject} of the player's data if they have any data * stored in the sync server, or {@code null} otherwise. + * + * @apiNote The provided UUID must be {@link UUID#version() version 4}, otherwise the request will fail. + * + * @see #queueFetch(UUID) */ public static CompletableFuture<@Nullable JsonObject> getProfile(UUID uuid) { if(isFetchingDisabled()) { @@ -280,12 +291,16 @@ private static CompletableFuture syncInternal(PlayerConfig config, boolean } /** - * Fetch data for multiple players from the sync server + * Fetch data for multiple players from the sync server. * * @param uuids A collection of between 1 and 20 UUIDs to fetch player data for * * @return A {@link CompletableFuture} containing a map of player UUIDs to their synced data; any provided * player UUIDs without any sync data will not be included in the returned map. + * + * @apiNote All UUIDs must be {@link UUID#version() version 4}, otherwise the request will fail. + * + * @see #queueFetch(UUID) */ public static CompletableFuture> getMultiple(Collection uuids) { return CompletableFuture.supplyAsync(() -> { @@ -312,8 +327,10 @@ public static CompletableFuture> getMultiple(CollectionAdd a UUID to the fetch queue; this may be requested individually or in bulk, depending on how many other + * users are queued to be fetched.

+ * + *

Queued queries are currently sent once every 2 seconds (or every 40th tick) in batches of up to 20 at once.

* * @param uuid The UUID of the user to fetch * @@ -326,12 +343,22 @@ public static CompletableFuture> getMultiple(Collection(); QUEUED.add(new QueuedFetch(uuid, future)); return future; } + /** + * Sends up to 20 {@link #queueFetch(UUID) queued sync queries} + * + * @apiNote This method is not intended to be used by other mods. + */ @ApiStatus.Internal public static void sendNextQueueBatch() { if(QUEUED.isEmpty()) { From d963950895ea2660a2081a9db8762d0ea210e80e Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 18 Nov 2024 19:32:20 -0700 Subject: [PATCH 152/238] count failed bulk queries toward the failed request limit also lower the fail threshold to 5 requests to account for the reduced request rate --- src/main/java/com/wildfire/main/cloud/CloudSync.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index 603fe4fa..fd8c7785 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -132,7 +132,7 @@ public static String getCloudServer() { private static void markFetchError() { fetchErrors.add(Instant.now()); fetchErrors.removeIf(e -> e.plus(30, ChronoUnit.SECONDS).isBefore(Instant.now())); - if(fetchErrors.size() >= 10) { + if(fetchErrors.size() >= 5) { WildfireGender.LOGGER.error("Too many recent sync errors, disabling future lookups for 5 minutes"); disableFetchingUntil = Instant.now().plus(5, ChronoUnit.MINUTES); } @@ -293,7 +293,7 @@ private static CompletableFuture syncInternal(PlayerConfig config, boolean /** * Fetch data for multiple players from the sync server. * - * @param uuids A collection of between 1 and 20 UUIDs to fetch player data for + * @param uuids A collection of between 2 and 20 UUIDs to fetch player data for * * @return A {@link CompletableFuture} containing a map of player UUIDs to their synced data; any provided * player UUIDs without any sync data will not be included in the returned map. @@ -317,6 +317,7 @@ public static CompletableFuture> getMultiple(Collection= 400) { + markFetchError(); throw new RuntimeException("Server responded " + response.statusCode() + ": " + response.body()); } From 5f32a41f37fccd36c146b38efc9332bba6d5e954 Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 18 Nov 2024 22:33:57 -0700 Subject: [PATCH 153/238] turn off all cloud sync features by default with the first time setup screen, it feels more reasonable to wait for the user to opt-in to having this feature enabled than it did with this only being in a somewhat tucked away menu. it would be nice to fetch player data from the cloud if they don't have any data locally when opting in, but that's something that could probably be done at a later point. --- src/main/java/com/wildfire/main/cloud/CloudSync.java | 4 ++++ src/main/java/com/wildfire/main/config/GlobalConfig.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index fd8c7785..cd3cb0f3 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -339,6 +339,10 @@ public static CompletableFuture> getMultiple(Collection queueFetch(UUID uuid) { + if(!isEnabled()) { + return CompletableFuture.completedFuture(null); + } + var cached = FETCH_CACHE.getIfPresent(uuid); //noinspection OptionalAssignedToNull if(cached != null) { diff --git a/src/main/java/com/wildfire/main/config/GlobalConfig.java b/src/main/java/com/wildfire/main/config/GlobalConfig.java index bb1cf711..6bae4395 100644 --- a/src/main/java/com/wildfire/main/config/GlobalConfig.java +++ b/src/main/java/com/wildfire/main/config/GlobalConfig.java @@ -8,7 +8,7 @@ private GlobalConfig() { } public static final BooleanConfigKey FIRST_TIME_LOAD = new BooleanConfigKey("firstTimeLoad", true); - public static final BooleanConfigKey CLOUD_SYNC_ENABLED = new BooleanConfigKey("cloud_sync", true); + public static final BooleanConfigKey CLOUD_SYNC_ENABLED = new BooleanConfigKey("cloud_sync", false); public static final BooleanConfigKey AUTOMATIC_CLOUD_SYNC = new BooleanConfigKey("sync_player_data", false); // see CloudSync#DEFAULT_CLOUD_URL for the actual default public static final StringConfigKey CLOUD_SERVER = new StringConfigKey("cloud_server", ""); From f0c1cc3c3bc49a5bca48d0e26109907c0590ccc4 Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 18 Nov 2024 22:37:45 -0700 Subject: [PATCH 154/238] actually save first time setup state the close method doesn't actually get called unless the screen itself calls it or the user presses escape, meaning that the config was only being saved if you happened to also open the cloud sync menu afterwards --- .../wildfire/gui/screen/WildfireFirstTimeSetupScreen.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java index 346b9c17..36c7c6c7 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java @@ -87,6 +87,9 @@ public void init() { config.set(GlobalConfig.CLOUD_SYNC_ENABLED, true); config.set(GlobalConfig.AUTOMATIC_CLOUD_SYNC, true); config.set(GlobalConfig.FIRST_TIME_LOAD, false); + config.save(); + + // TODO fetch user data if they don't have anything saved locally / sync existing data? client.setScreen(new WardrobeBrowserScreen(null, client.player.getUuid())); })); @@ -99,6 +102,7 @@ public void init() { config.set(GlobalConfig.CLOUD_SYNC_ENABLED, false); config.set(GlobalConfig.AUTOMATIC_CLOUD_SYNC, false); config.set(GlobalConfig.FIRST_TIME_LOAD, false); + config.save(); client.setScreen(new WardrobeBrowserScreen(null, client.player.getUuid())); })); @@ -139,7 +143,6 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { @Override public void close() { - GlobalConfig.INSTANCE.save(); super.close(); } } \ No newline at end of file From de8aac2c798b884bfd63be44b2555fcc40984397 Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 18 Nov 2024 23:12:01 -0700 Subject: [PATCH 155/238] comply with the font awesome CC-BY license --- src/main/resources/assets/wildfire_gender/CREDITS.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/main/resources/assets/wildfire_gender/CREDITS.txt diff --git a/src/main/resources/assets/wildfire_gender/CREDITS.txt b/src/main/resources/assets/wildfire_gender/CREDITS.txt new file mode 100644 index 00000000..7b3801f3 --- /dev/null +++ b/src/main/resources/assets/wildfire_gender/CREDITS.txt @@ -0,0 +1,2 @@ +textures/cloud.png is sourced from Font Awesome Free 6.6.0 (https://fontawesome.com/), which is licensed under the CC-BY-4.0 license (https://fontawesome.com/license/free). + From 91623fdf926e4a476e5c4a91b56ca286639abc0f Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 19 Nov 2024 17:45:13 -0700 Subject: [PATCH 156/238] add initial sync from the cloud when opting in also fix an untranslated string --- .../java/com/wildfire/api/WildfireAPI.java | 2 +- .../gui/screen/WardrobeBrowserScreen.java | 24 +++---- .../screen/WildfireFirstTimeSetupScreen.java | 68 ++++++++++++------- .../wildfire/main/WildfireGenderClient.java | 7 +- .../assets/wildfire_gender/lang/en_us.json | 1 + 5 files changed, 59 insertions(+), 43 deletions(-) diff --git a/src/main/java/com/wildfire/api/WildfireAPI.java b/src/main/java/com/wildfire/api/WildfireAPI.java index 1ab684dd..91012ae5 100644 --- a/src/main/java/com/wildfire/api/WildfireAPI.java +++ b/src/main/java/com/wildfire/api/WildfireAPI.java @@ -96,7 +96,7 @@ public static void addGenderArmor(Item item, IGenderArmor genderArmor) { */ @Environment(EnvType.CLIENT) public static CompletableFuture<@NotNull PlayerConfig> loadGenderInfo(UUID uuid, boolean markForSync) { - return WildfireGenderClient.loadGenderInfo(uuid, markForSync); + return WildfireGenderClient.loadGenderInfo(uuid, markForSync, false); } /** diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index d82a2086..bffb9e5d 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -161,21 +161,15 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { GuiUtils.drawCenteredText(ctx, this.textRenderer, Text.translatable("wildfire_gender.label.with_contributor"), this.width / 2, creatorY, 0xFF00FF); } - - - - List playerListEntry = collectPlayerEntries(); - - if(!playerListEntry.isEmpty()) { - ctx.drawText(textRenderer, Text.literal("Players Using the Mod").formatted(Formatting.AQUA), 5, 5, 0xFFFFFF, false); - } - - - int yPos = 18; - for(PlayerListEntry entry : playerListEntry) { - PlayerConfig cfg = WildfireGender.getPlayerById(entry.getProfile().getId()); - ctx.drawText(textRenderer, Text.literal(entry.getProfile().getName() + " - ").append(cfg.getGender().getDisplayName()), 10, yPos, 0xFFFFFF, false); - yPos += 10; + List syncedPlayers = collectPlayerEntries(); + if(!syncedPlayers.isEmpty()) { + ctx.drawText(textRenderer, Text.translatable("wildfire_gender.wardrobe.players_using_mod").formatted(Formatting.AQUA), 5, 5, 0xFFFFFF, false); + int yPos = 18; + for(PlayerListEntry entry : syncedPlayers) { + PlayerConfig cfg = WildfireGender.getPlayerById(entry.getProfile().getId()); + ctx.drawText(textRenderer, Text.literal(entry.getProfile().getName() + " - ").append(cfg.getGender().getDisplayName()), 10, yPos, 0xFFFFFF, false); + yPos += 10; + } } } diff --git a/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java index 36c7c6c7..6ce7593c 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java @@ -21,29 +21,19 @@ import com.wildfire.gui.GuiUtils; import com.wildfire.gui.WildfireButton; import com.wildfire.main.WildfireGender; -import com.wildfire.main.cloud.CloudSync; -import com.wildfire.main.cloud.SyncingTooFrequentlyException; +import com.wildfire.main.WildfireGenderClient; import com.wildfire.main.config.GlobalConfig; +import com.wildfire.main.entitydata.PlayerConfig; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.font.FontManager; -import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.screen.ingame.CraftingScreen; -import net.minecraft.client.gui.screen.ingame.InventoryScreen; -import net.minecraft.client.gui.tooltip.Tooltip; -import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.render.RenderLayer; import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.text.OrderedText; -import net.minecraft.text.StringVisitable; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; -import java.util.Iterator; import java.util.Objects; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -58,9 +48,6 @@ public class WildfireFirstTimeSetupScreen extends BaseWildfireScreen { private static final Text DESCRIPTION = Text.translatable("wildfire_gender.first_time_setup.description"); private static final Text NOTICE = Text.translatable("wildfire_gender.first_time_setup.notice"); - private static final Text ENABLED = Text.translatable("wildfire_gender.label.enabled").formatted(Formatting.GREEN); - private static final Text DISABLED = Text.translatable("wildfire_gender.label.disabled").formatted(Formatting.RED); - private static final Text ENABLE_CLOUD_SYNCING = Text.translatable("wildfire_gender.first_time_setup.enable").formatted(Formatting.GREEN); private static final Text DISABLE_CLOUD_SYNCING = Text.translatable("wildfire_gender.first_time_setup.disable").formatted(Formatting.RED); @@ -72,13 +59,14 @@ public WildfireFirstTimeSetupScreen(Screen parent, UUID uuid) { @Override public void init() { - - //var config = GlobalConfig.INSTANCE; - // config.set(GlobalConfig.CLOUD_SYNC_ENABLED, !config.get(GlobalConfig.CLOUD_SYNC_ENABLED)); - int x = this.width / 2; int y = this.height / 2; + // why must Java be? + final var ref = new Object() { + WildfireButton no = null; + }; + this.addDrawableChild(new WildfireButton(x, y + 74, 128 - 5 - 1, 20, ENABLE_CLOUD_SYNCING, button -> { @@ -87,22 +75,23 @@ public void init() { config.set(GlobalConfig.CLOUD_SYNC_ENABLED, true); config.set(GlobalConfig.AUTOMATIC_CLOUD_SYNC, true); config.set(GlobalConfig.FIRST_TIME_LOAD, false); - config.save(); - // TODO fetch user data if they don't have anything saved locally / sync existing data? + button.active = false; + button.setMessage(Text.literal("...")); + ref.no.setActive(false); - client.setScreen(new WardrobeBrowserScreen(null, client.player.getUuid())); + final var nextScreen = new WardrobeBrowserScreen(null, client.player.getUuid()); + doInitialSync().thenRun(() -> client.execute(() -> client.setScreen(nextScreen))); })); - this.addDrawableChild(new WildfireButton(x - 128 + 6, y + 74, 128 - 5 - 1, 20, + this.addDrawableChild(ref.no = new WildfireButton(x - 128 + 6, y + 74, 128 - 5 - 1, 20, DISABLE_CLOUD_SYNCING, button -> { var config = GlobalConfig.INSTANCE; config.set(GlobalConfig.CLOUD_SYNC_ENABLED, false); config.set(GlobalConfig.AUTOMATIC_CLOUD_SYNC, false); config.set(GlobalConfig.FIRST_TIME_LOAD, false); - config.save(); client.setScreen(new WardrobeBrowserScreen(null, client.player.getUuid())); })); @@ -110,6 +99,32 @@ public void init() { super.init(); } + private CompletableFuture doInitialSync() { + var client = Objects.requireNonNull(this.client); + var clientUUID = client.player.getUuid(); + return CompletableFuture.runAsync(() -> { + var clientConfig = WildfireGender.getOrAddPlayerById(clientUUID); + // if the player has a local config, assume that needsCloudSync is already set to true, and as such + // their config will be synced on the next attempt + if(!clientConfig.hasLocalConfig()) { + try { + // note that we wait for this to ensure that we don't have any inconsistencies with the synced + // data once we open the main menu + WildfireGenderClient.loadGenderInfo(clientUUID, false, true).join(); + } catch(CompletionException ignored) { + // loadGenderInfo should log any errors for us + return; + } catch(Exception e) { + WildfireGender.LOGGER.error("Failed to perform initial sync from the cloud", e); + return; + } + PlayerConfig.saveGenderInfo(clientConfig); + // don't immediately re-sync the data we just got back to the cloud + clientConfig.needsCloudSync = false; + } + }); + } + @Override public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delta) { this.renderInGameBackground(ctx); @@ -145,4 +160,9 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { public void close() { super.close(); } + + @Override + public void removed() { + GlobalConfig.INSTANCE.save(); + } } \ No newline at end of file diff --git a/src/main/java/com/wildfire/main/WildfireGenderClient.java b/src/main/java/com/wildfire/main/WildfireGenderClient.java index c0e009dd..cccaf0fc 100644 --- a/src/main/java/com/wildfire/main/WildfireGenderClient.java +++ b/src/main/java/com/wildfire/main/WildfireGenderClient.java @@ -47,7 +47,7 @@ public void onInitializeClient() { ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(GenderArmorResourceManager.INSTANCE); } - public static CompletableFuture<@NotNull PlayerConfig> loadGenderInfo(UUID uuid, boolean markForSync) { + public static CompletableFuture<@NotNull PlayerConfig> loadGenderInfo(UUID uuid, boolean markForSync, boolean bypassQueue) { return CompletableFuture.supplyAsync(() -> { var player = WildfireGender.getOrAddPlayerById(uuid); if(player.hasLocalConfig()) { @@ -55,7 +55,8 @@ public void onInitializeClient() { } else if(player.syncStatus == PlayerConfig.SyncStatus.UNKNOWN) { JsonObject data; try { - data = CloudSync.queueFetch(uuid).join(); + var future = bypassQueue ? CloudSync.getProfile(uuid) : CloudSync.queueFetch(uuid); + data = future.join(); } catch(Exception e) { WildfireGender.LOGGER.error("Failed to fetch profile from sync server", e); throw e; @@ -73,6 +74,6 @@ public void onInitializeClient() { public static void loadPlayerIfMissing(UUID uuid, boolean markForSync) { if(WildfireGender.PLAYER_CACHE.containsKey(uuid)) return; WildfireGender.getOrAddPlayerById(uuid); - loadGenderInfo(uuid, markForSync); + loadGenderInfo(uuid, markForSync, false); } } diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index 7529b8dd..4d141852 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -11,6 +11,7 @@ "wildfire_gender.player_list.bounce_multiplier": "Bounce Multiplier: %sx", "wildfire_gender.player_list.breast_momentum": "Breast Momentum: %s%%", "wildfire_gender.player_list.female_sounds": "Female Sounds: %s", + "wildfire_gender.wardrobe.players_using_mod": "Players using the mod:", "wildfire_gender.wardrobe.title": "Wildfire's Female Gender Mod", "wildfire_gender.breast_customization.tab_customization": "Customization", From 09dcb966de2d293e69bb50237914636db67e8d8f Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 19 Nov 2024 17:54:05 -0700 Subject: [PATCH 157/238] my assumption about this being set appears to have been incorrect --- .../com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java index 6ce7593c..76148a60 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java @@ -104,8 +104,6 @@ private CompletableFuture doInitialSync() { var clientUUID = client.player.getUuid(); return CompletableFuture.runAsync(() -> { var clientConfig = WildfireGender.getOrAddPlayerById(clientUUID); - // if the player has a local config, assume that needsCloudSync is already set to true, and as such - // their config will be synced on the next attempt if(!clientConfig.hasLocalConfig()) { try { // note that we wait for this to ensure that we don't have any inconsistencies with the synced @@ -121,6 +119,8 @@ private CompletableFuture doInitialSync() { PlayerConfig.saveGenderInfo(clientConfig); // don't immediately re-sync the data we just got back to the cloud clientConfig.needsCloudSync = false; + } else { + clientConfig.needsCloudSync = true; } }); } From 4be24dfb7e8fe3d3547b59738fc8c6718dbe7791 Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 19 Nov 2024 17:58:07 -0700 Subject: [PATCH 158/238] remove max size on the fetch cache --- src/main/java/com/wildfire/main/cloud/CloudSync.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index cd3cb0f3..ca500b5b 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -86,7 +86,7 @@ private CloudSync() { private static final Queue QUEUED = new ConcurrentLinkedDeque<>(); private static final Cache> FETCH_CACHE = CacheBuilder.newBuilder() - .expireAfterAccess(Duration.ofMinutes(10)).maximumSize(512).concurrencyLevel(6).build(); + .expireAfterAccess(Duration.ofMinutes(10)).concurrencyLevel(6).build(); private static final String DEFAULT_CLOUD_URL = "https://wfgm.celestialfault.dev"; private static final Duration SYNC_COOLDOWN = Duration.ofSeconds(10); From eefd404598c535c54492c56e220c38033c52b04a Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Tue, 19 Nov 2024 20:10:02 -0500 Subject: [PATCH 159/238] Updated Dutch Translation --- .../assets/wildfire_gender/lang/nl_nl.json | 40 ++++++++++++++----- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/src/main/resources/assets/wildfire_gender/lang/nl_nl.json b/src/main/resources/assets/wildfire_gender/lang/nl_nl.json index e8134383..fb52f56f 100644 --- a/src/main/resources/assets/wildfire_gender/lang/nl_nl.json +++ b/src/main/resources/assets/wildfire_gender/lang/nl_nl.json @@ -27,17 +27,17 @@ "wildfire_gender.appearance_settings.title": "Uiterlijk instellingen", "wildfire_gender.char_settings.title": "Karakter Instellingen", - "wildfire_gender.char_settings.physics": "Borst Zwaartekracht: %s", + "wildfire_gender.char_settings.physics": "Borst Bewegingen: %s", - "wildfire_gender.char_settings.override_armor_physics": "Harnas Zwaartekracht: %s", - "wildfire_gender.tooltip.override_armor_physics.line1": "Borst zwaartekracht zal niet langer onderdrukt worden bij jouw aangetrokken harnas, als dit aan staat", + "wildfire_gender.char_settings.override_armor_physics": "Beweegt in Harnas: %s", + "wildfire_gender.tooltip.override_armor_physics.line1": "De borst bewegingen zullen niet langer onderdrukt worden bij jouw aangetrokken harnas, als dit aan staat", "wildfire_gender.tooltip.override_armor_physics.line2": "Dit is bedoeld voor het gebruik met bronpakketen die harnas verstoppen of iets in die geest", "wildfire_gender.char_settings.hide_in_armor": "Verstop In Harnas: %s", "wildfire_gender.char_settings.hurt_sounds": "Vrouwelijke Pijn Geluiden: %s", "wildfire_gender.tooltip.hurt_sounds": "Jou karakter zal een vrouwelijke pijn geluid spelen als je schade neemt en als je geslacht is ingesteld op Vrouwelijk of Anders", - "wildfire_gender.breast_customization.dual_physics": "Dubbel-Zwaartekracht: %s", + "wildfire_gender.breast_customization.dual_physics": "Dubbele Bewegingen: %s", "wildfire_gender.label.gender": "Geslacht", "wildfire_gender.label.female": "Vrouwelijk", @@ -49,13 +49,33 @@ "wildfire_gender.label.yes": "Ja", "wildfire_gender.label.no": "Nee", "wildfire_gender.label.with_creator": "Je speelt op een server met de maker van deze mod!", + "wildfire_gender.label.with_contributor": "Je speelt op een server met een bijdrager van deze mod!", + "wildfire_gender.label.with_both": "Je speelt op een server met de maker en een bijdrager van deze mod!", - "wildfire_gender.slider.bounce": "Verkracht Intensiteit: %s%%", + "wildfire_gender.slider.bounce": "Veerkracht Intensiteit: %s%%", "wildfire_gender.slider.floppy": "Borst Momentum: %s%%", - "wildfire_gender.slider.min_bounce": "Waarom Is Zwaartekracht Aan?", - "wildfire_gender.slider.max_bounce": "Anime Borst Zwaartekracht!!!", - "wildfire_gender.tooltip.bounce_warning": "Instelling 'Verkracht Intensiteit' op een hoog nummer ziet er erg niet natuurlijk uit!", + "wildfire_gender.slider.min_bounce": "Waarom Staan De Bewegingen Aan?", + "wildfire_gender.slider.max_bounce": "Anime Borst Bewegingen!!!", + "wildfire_gender.tooltip.bounce_warning": "Instelling 'Veerkracht Intensiteit' op een hoog nummer ziet er erg niet natuurlijk uit!", "wildfire_gender.cancer_awareness.title": "Hé, het is Borst Kanker Aandacht Maand", - "wildfire_gender.coming_soon": "Komt Binnenkort" -} + "wildfire_gender.coming_soon": "Komt Binnenkort", + + "wildfire_gender.first_time_setup.title": "Welkom bij Wildfire's Vrouwelijke Geslachts Mod!", + "wildfire_gender.first_time_setup.description": "Wil je de optie aanzetten om je geslachts instellingen online te Synchroniseren? Hier mee kunnen anderen jouw aangepaste geslacht uiterlijk, zelfs al heeft de server deze mod niet geïnstalleerd.", + "wildfire_gender.first_time_setup.notice": "Je kunt dit later altijd aanpassen in het mod menu.", + "wildfire_gender.first_time_setup.enable": "Zet Online Synchronisatie Aan", + "wildfire_gender.first_time_setup.disable": "Zet Online Synchronisatie Uit", + + "wildfire_gender.cloud_settings": "Online Synchronisatie Instellingen", + "wildfire_gender.cloud.available_online": "Online Synchronisatie", + "wildfire_gender.cloud.unavailable_offline": "Online synchronisatie is niet beschikbaar, omdat je niet bent ingelogd in een geldig Minecraft account", + "wildfire_gender.cloud.status": "Synchroniseren:", + "wildfire_gender.cloud.automatic": "Automatische Synchronisatie:", + "wildfire_gender.cloud.automatic.tooltip.line1": "Wanner dit aanstaat, jouw configuratie wordt automatisch online gesynchroniseerd na dat je aanpassingen maakt.", + "wildfire_gender.cloud.sync": "Synchroniseer", + "wildfire_gender.cloud.automatic.tooltip.line2": "Je kan nog steeds manueel synchroniseren met de knop hier onder, als dit uitstaat.", + "wildfire_gender.cloud.syncing": "Synchroniseren...", + "wildfire_gender.cloud.syncing.success": "Gesynchroniseerd", + "wildfire_gender.cloud.syncing.fail": "Synchronisatie Niet Gelukt" +} \ No newline at end of file From 6967d8120ca86715099f4e2e1374492436ab2777 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Tue, 19 Nov 2024 21:18:04 -0500 Subject: [PATCH 160/238] Cloud Sync Screen UI Redesign - Redesigned Cloud Sync UI to follow the rest of the Wildfire UI design. - Added Sync Log Also removed player_list.png as it is no longer used. --- .../gui/screen/WildfireCloudSyncScreen.java | 70 +++++++++++++----- .../gui/screen/WildfireLocalization.java | 40 ++++++++++ .../com/wildfire/main/cloud/CloudSync.java | 12 +++ .../assets/wildfire_gender/lang/en_us.json | 22 ++++-- .../textures/gui/player_list.png | Bin 746 -> 0 bytes .../textures/gui/sync_bg_v2.png | Bin 0 -> 677 bytes 6 files changed, 119 insertions(+), 25 deletions(-) create mode 100644 src/main/java/com/wildfire/gui/screen/WildfireLocalization.java delete mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/player_list.png create mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/sync_bg_v2.png diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java index d6adf3c1..eac9c223 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java @@ -35,6 +35,9 @@ import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; +import java.text.Normalizer; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -43,9 +46,13 @@ @Environment(EnvType.CLIENT) public class WildfireCloudSyncScreen extends BaseWildfireScreen { - private static final Text ENABLED = Text.translatable("wildfire_gender.label.enabled").formatted(Formatting.GREEN); - private static final Text DISABLED = Text.translatable("wildfire_gender.label.disabled").formatted(Formatting.RED); - private static final Identifier BACKGROUND = Identifier.of(WildfireGender.MODID, "textures/gui/sync_bg.png"); + //There should only ever be one instance of this file, so I'm putting this list here. Inform if should be moved. + private static final List STATUS_LOG = new ArrayList<>(); + + private static final Identifier BACKGROUND = Identifier.of(WildfireGender.MODID, "textures/gui/sync_bg_v2.png"); + + private WildfireButton btnAutomaticSync = null; + private WildfireButton btnSyncNow = null; protected WildfireCloudSyncScreen(Screen parent, UUID uuid) { super(Text.translatable("wildfire_gender.cloud_settings"), parent, uuid); @@ -55,44 +62,49 @@ protected WildfireCloudSyncScreen(Screen parent, UUID uuid) { public void init() { int x = this.width / 2; int y = this.height / 2; - int yPos = y - 44; - int xPos = x + 60 / 2 - 1; + int yPos = y - 47; + int xPos = x - 156 / 2 - 1; + - this.addDrawableChild(new WildfireButton(xPos - 15, yPos, 80, 20, - CloudSync.isEnabled() ? ENABLED : DISABLED, + this.addDrawableChild(new WildfireButton(xPos, yPos, 157, 20, + Text.translatable("wildfire_gender.cloud.status", CloudSync.isEnabled() ? WildfireLocalization.ENABLED : WildfireLocalization.DISABLED), button -> { var config = GlobalConfig.INSTANCE; config.set(GlobalConfig.CLOUD_SYNC_ENABLED, !config.get(GlobalConfig.CLOUD_SYNC_ENABLED)); - button.setMessage(CloudSync.isEnabled() ? ENABLED : DISABLED); + button.setMessage(Text.translatable("wildfire_gender.cloud.status", CloudSync.isEnabled() ? WildfireLocalization.ENABLED : WildfireLocalization.DISABLED)); + btnAutomaticSync.setActive(CloudSync.isEnabled()); + btnAutomaticSync.setMessage(Text.translatable("wildfire_gender.cloud.automatic", CloudSync.isEnabled() ? (GlobalConfig.INSTANCE.get(GlobalConfig.AUTOMATIC_CLOUD_SYNC) ? WildfireLocalization.ENABLED : WildfireLocalization.DISABLED) : WildfireLocalization.OFF)); + btnSyncNow.visible = GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SYNC_ENABLED); }, text -> Text.empty() .append(Text.translatable("wildfire_gender.cloud.status")) .append(" ") .append(text.get()))); - WildfireButton automatic; - this.addDrawableChild(automatic = new WildfireButton(xPos - 15, yPos + 22, 80, 20, - GlobalConfig.INSTANCE.get(GlobalConfig.AUTOMATIC_CLOUD_SYNC) ? ENABLED : DISABLED, + this.addDrawableChild(btnAutomaticSync = new WildfireButton(xPos, yPos + 20, 157, 20, + Text.translatable("wildfire_gender.cloud.automatic", CloudSync.isEnabled() ? (GlobalConfig.INSTANCE.get(GlobalConfig.AUTOMATIC_CLOUD_SYNC) ? WildfireLocalization.ENABLED : WildfireLocalization.DISABLED) : WildfireLocalization.OFF), button -> { var config = GlobalConfig.INSTANCE; var newVal = !config.get(GlobalConfig.AUTOMATIC_CLOUD_SYNC); config.set(GlobalConfig.AUTOMATIC_CLOUD_SYNC, newVal); - button.setMessage(newVal ? ENABLED : DISABLED); + button.setMessage(Text.translatable("wildfire_gender.cloud.automatic", newVal ? WildfireLocalization.ENABLED : WildfireLocalization.DISABLED)); }, text -> Text.empty() .append(Text.translatable("wildfire_gender.cloud.automatic")) .append(" ") .append(text.get()))); - automatic.setTooltip(Tooltip.of(Text.empty() + btnAutomaticSync.setTooltip(Tooltip.of(Text.empty() .append(Text.translatable("wildfire_gender.cloud.automatic.tooltip.line1")) .append("\n\n") .append(Text.translatable("wildfire_gender.cloud.automatic.tooltip.line2")))); + btnAutomaticSync.setActive(CloudSync.isEnabled()); - var syncButton = new WildfireButton(xPos - 80, yPos + 80, 100, 15, Text.translatable("wildfire_gender.cloud.sync"), this::sync); - syncButton.setActive(GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SYNC_ENABLED)); - this.addDrawableChild(syncButton); + btnSyncNow = new WildfireButton(xPos + 98, yPos + 42, 60, 15, Text.translatable("wildfire_gender.cloud.sync"), this::sync); + //btnSyncNow.setActive(GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SYNC_ENABLED)); + btnSyncNow.visible = GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SYNC_ENABLED); + this.addDrawableChild(btnSyncNow); - this.addDrawableChild(new WildfireButton(this.width / 2 + 85, yPos - 11, 9, 9, Text.literal("X"), + this.addDrawableChild(new WildfireButton(this.width / 2 + 73, yPos - 11, 9, 9, Text.literal("X"), button -> close(), text -> GuiUtils.doneNarrationText())); super.init(); @@ -109,6 +121,7 @@ private void sync(ButtonWidget button) { var actualException = e instanceof CompletionException ce ? ce.getCause() : e; if(actualException instanceof SyncingTooFrequentlyException) { WildfireGender.LOGGER.warn("Failed to sync settings as we've already synced too recently"); + WildfireCloudSyncScreen.log(WildfireLocalization.SYNC_LOG_SYNC_TOO_FREQUENTLY); } else { WildfireGender.LOGGER.error("Failed to sync settings", actualException); } @@ -120,18 +133,28 @@ private void sync(ButtonWidget button) { @Override public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delta) { this.renderInGameBackground(ctx); - ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND, (this.width - 203) / 2, (this.height - 117) / 2, 0, 0, 203, 117, 256, 256); + ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND, (this.width - 172) / 2, (this.height - 124) / 2, 0, 0, 172, 144, 256, 256); } @Override public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { if(client == null || client.world == null) return; super.render(ctx, mouseX, mouseY, delta); + int x = this.width / 2; int y = this.height / 2; + int yPos = y - 47; + + ctx.drawText(textRenderer, getTitle(), x - 79, yPos - 10, 4473924, false); + ctx.drawText(textRenderer, Text.translatable("wildfire_gender.cloud.status_log"), x - 79, yPos + 49, 4473924, false); - ctx.drawText(textRenderer, Text.translatable("wildfire_gender.cloud.status"), x - 95, y - 40, 0x000000, false); - ctx.drawText(textRenderer, Text.translatable("wildfire_gender.cloud.automatic"), x - 95, y - 16, 0x000000, false); + for(int i = STATUS_LOG.size()-1; i >= 0; i--) { + int reverseIndex = STATUS_LOG.size() - 1 - i; + + if (reverseIndex < 6) { + ctx.drawText(textRenderer, STATUS_LOG.get(i), x - 78, yPos + 111 - (reverseIndex * 10), 0x00FF00, false); + } + } } @Override @@ -139,4 +162,11 @@ public void close() { GlobalConfig.INSTANCE.save(); super.close(); } + + public static void log(Text text) { + STATUS_LOG.add(text); + if(STATUS_LOG.size() > 6) { + STATUS_LOG.removeFirst(); //remove first entry since it's never seen again + } + } } \ No newline at end of file diff --git a/src/main/java/com/wildfire/gui/screen/WildfireLocalization.java b/src/main/java/com/wildfire/gui/screen/WildfireLocalization.java new file mode 100644 index 00000000..4a2a3e7a --- /dev/null +++ b/src/main/java/com/wildfire/gui/screen/WildfireLocalization.java @@ -0,0 +1,40 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.gui.screen; + +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +//I mostly made this file for the "status log" text in the cloud sync screen. +public class WildfireLocalization { + + public static final Text ENABLED = Text.translatable("wildfire_gender.label.enabled").formatted(Formatting.GREEN); + public static final Text DISABLED = Text.translatable("wildfire_gender.label.disabled").formatted(Formatting.RED); + public static final Text OFF = Text.translatable("wildfire_gender.label.off"); + + public static final Text SYNC_LOG_AUTHENTICATING = Text.translatable("wildfire_gender.sync_log.authenticating"); + public static final Text SYNC_LOG_AUTHENTICATION_FAILED = Text.translatable("wildfire_gender.sync_log.authentication_failed"); + public static final Text SYNC_LOG_AUTHENTICATION_SUCCESS = Text.translatable("wildfire_gender.sync_log.authentication_success"); + public static final Text SYNC_LOG_REAUTHENTICATING = Text.translatable("wildfire_gender.sync_log.reauthenticating"); + public static final Text SYNC_LOG_ATTMEPTING_SYNC = Text.translatable("wildfire_gender.sync_log.attempting_sync"); + public static final Text SYNC_LOG_SYNC_SUCCESS = Text.translatable("wildfire_gender.sync_log.sync_success"); + public static final Text SYNC_LOG_SYNC_TOO_FREQUENTLY = Text.translatable("wildfire_gender.sync_log.sync_too_frequently"); + + +} diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index ca500b5b..212e6c13 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -27,6 +27,8 @@ import com.mojang.authlib.HttpAuthenticationService; import com.mojang.authlib.exceptions.AuthenticationException; import com.mojang.util.InstantTypeAdapter; +import com.wildfire.gui.screen.WildfireCloudSyncScreen; +import com.wildfire.gui.screen.WildfireLocalization; import com.wildfire.main.WildfireGender; import com.wildfire.main.WildfireHelper; import com.wildfire.main.config.GlobalConfig; @@ -162,10 +164,12 @@ private static String getAuthToken() { synchronized(AUTH_LOCK) { var client = MinecraftClient.getInstance(); if(client.player == null) { + WildfireCloudSyncScreen.log(WildfireLocalization.SYNC_LOG_AUTHENTICATION_FAILED); throw new IllegalStateException("Cannot get a new auth token while the client player is unset"); } if(auth == null || auth.isExpired() || auth.isInvalidForClientPlayer()) { WildfireGender.LOGGER.info("Obtaining new authentication token from the cloud sync server"); + WildfireCloudSyncScreen.log(WildfireLocalization.SYNC_LOG_AUTHENTICATING); var serverId = generateServerId(); var session = client.getSession(); @@ -181,6 +185,7 @@ private static String getAuthToken() { var request = createRequest(uri).GET().build(); var response = CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join(); if(response.statusCode() >= 400) { + WildfireCloudSyncScreen.log(WildfireLocalization.SYNC_LOG_AUTHENTICATION_FAILED); throw new RuntimeException("Failed to authenticate with sync server: " + response.body()); } @@ -189,6 +194,9 @@ private static String getAuthToken() { WildfireGender.LOGGER.warn("Authenticated account {} does not match the current player ({}); you likely have a misbehaving account switcher mod installed!", auth.account(), client.player.getUuid()); } WildfireGender.LOGGER.info("Obtained authentication token for {}, expiry {}", auth.account(), auth.expires()); + if(!auth.isInvalidForClientPlayer()) { //TODO: This might not need to be here. + WildfireCloudSyncScreen.log(WildfireLocalization.SYNC_LOG_AUTHENTICATION_SUCCESS); + } } } return auth.token(); @@ -226,6 +234,8 @@ private static CompletableFuture syncInternal(PlayerConfig config, boolean var url = URI.create(getCloudServer() + "/" + config.uuid); var json = config.toJson().toString(); + WildfireCloudSyncScreen.log(WildfireLocalization.SYNC_LOG_ATTMEPTING_SYNC); + var request = createRequest(url) .PUT(HttpRequest.BodyPublishers.ofString(json)) .header("Content-Type", "application/json; charset=UTF-8") @@ -233,6 +243,7 @@ private static CompletableFuture syncInternal(PlayerConfig config, boolean .build(); var response = CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join(); if(response.statusCode() == 401 && !resyncing) { + WildfireCloudSyncScreen.log(WildfireLocalization.SYNC_LOG_REAUTHENTICATING); WildfireGender.LOGGER.warn("Auth token is invalid, attempting to reauth..."); auth = null; syncInternal(config, true).join(); @@ -241,6 +252,7 @@ private static CompletableFuture syncInternal(PlayerConfig config, boolean throw new RuntimeException("Server responded " + response.statusCode() + ": " + response.body()); } WildfireGender.LOGGER.debug("Server responded to update: {}", response.body()); + WildfireCloudSyncScreen.log(WildfireLocalization.SYNC_LOG_SYNC_SUCCESS); }, EXECUTOR); } diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index 4d141852..5b99ee86 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -47,6 +47,7 @@ "wildfire_gender.label.enabled": "Enabled", "wildfire_gender.label.disabled": "Disabled", + "wildfire_gender.label.off": "Off", "wildfire_gender.label.yes": "Yes", "wildfire_gender.label.no": "No", "wildfire_gender.label.with_creator": "You are playing on a server with the creator of this mod!", @@ -69,15 +70,26 @@ "wildfire_gender.first_time_setup.disable": "Disable Cloud Syncing", - "wildfire_gender.cloud_settings": "Cloud Sync Settings", + "wildfire_gender.cloud_settings": "Cloud Sync Server Settings", "wildfire_gender.cloud.available_online": "Cloud Sync", "wildfire_gender.cloud.unavailable_offline": "Cloud syncing is unavailable as you aren't currently logged into a valid Minecraft account", - "wildfire_gender.cloud.status": "Syncing:", - "wildfire_gender.cloud.automatic": "Automatic Sync:", + "wildfire_gender.cloud.status": "Cloud Sync: %s", + "wildfire_gender.cloud.automatic": "Automatic Sync: %s", "wildfire_gender.cloud.automatic.tooltip.line1": "While enabled, your config will automatically be synced to the cloud after making any changes.", "wildfire_gender.cloud.automatic.tooltip.line2": "You can still sync manually with the button below if this is disabled.", - "wildfire_gender.cloud.sync": "Sync", + "wildfire_gender.cloud.sync": "Sync Now", "wildfire_gender.cloud.syncing": "Syncing...", + + "wildfire_gender.cloud.status_log": "Status Log", + "wildfire_gender.cloud.syncing.success": "Synced", - "wildfire_gender.cloud.syncing.fail": "Sync failed" + "wildfire_gender.cloud.syncing.fail": "Sync failed", + + "wildfire_gender.sync_log.authenticating": "Authenticating Account...", + "wildfire_gender.sync_log.authentication_success": "Authentication Successful.", + "wildfire_gender.sync_log.authentication_failed": "Failed Authentication.", + "wildfire_gender.sync_log.reauthenticating": "Re-Authenticating Account...", + "wildfire_gender.sync_log.attempting_sync": "Syncing Profile...", + "wildfire_gender.sync_log.sync_success": "Sync Successful.", + "wildfire_gender.sync_log.sync_too_frequently": "Sync Rate Limited." } diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/player_list.png b/src/main/resources/assets/wildfire_gender/textures/gui/player_list.png deleted file mode 100644 index d23c31b9d5fae45ba981f991e9f712676e77555f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 746 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911MRQ8&P5D>38$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&eBzC?geh%1o(|NsB7W5+^6L(R?2!@|O{v$KJMfiGC!04a`=AirRs0uW%h z*St3pD8^af5n0T@z_%ZS87)|YRTvnU+&x_!Ln`9lUU$rsaTH)aI5DO2-~Z(4cWRuc zGq}Z7+ocC%{&MTTdDs1cQYUn^K^{F2!>CXm%YC5oFAGrlolQbO zF@`gu3`N(sGRzq<3WFH-{lE8aPu(wBhHgd;_8+&{D(26Z4vPj-cQ#&q^}C+oS^kDw zmMN>B&cDy^u+5C&<}HSlY~~4Tc?TV5TT#RJzwFFqza|Ip`ig*J)6><@Wt~$(695JY B3-38$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&eBzI1?3h%1o(|NsB7W5+^6L(R?2!@|P2xVW;jvw`B%3a{P)Qk*40e!)OB zV89UUUm*k%EbxddW?(~IEGZjy}jcYQ+e%5Z*dG=G5u zkbB~6Feq>u_H3v`1`rVuSXBsgJw2FLWJR_s;2=+bEh3M@}oKbLh*2~7aCE8gS) literal 0 HcmV?d00001 From 801758d8742db44b831d5e5a7391813675612be0 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Tue, 19 Nov 2024 21:21:14 -0500 Subject: [PATCH 161/238] Removed unused imports in WildfireCloudSyncScreen.java --- .../java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java index eac9c223..e6dab0ed 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java @@ -32,10 +32,7 @@ import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.render.RenderLayer; import net.minecraft.text.Text; -import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; - -import java.text.Normalizer; import java.util.ArrayList; import java.util.List; import java.util.Objects; From 5be3a626c92e92fb8eed439ceb321340144b45e7 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Tue, 19 Nov 2024 21:29:12 -0500 Subject: [PATCH 162/238] Language File Changes - Removed unused translation keys - Changed a few translation keys --- src/main/resources/assets/wildfire_gender/lang/en_us.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index 5b99ee86..387ce3b3 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -56,12 +56,9 @@ "wildfire_gender.slider.bounce": "Bounce Intensity: %s%%", "wildfire_gender.slider.floppy": "Breast Momentum: %s%%", - "wildfire_gender.slider.min_bounce": "Why Are Physics Even On?", - "wildfire_gender.slider.max_bounce": "Anime Breast Physics!!!", "wildfire_gender.tooltip.bounce_warning": "Setting 'Bounce Intensity' to a high value will look very unnatural!", "wildfire_gender.cancer_awareness.title": "Hey, it's Breast Cancer Awareness Month!", - "wildfire_gender.coming_soon": "Coming soon", "wildfire_gender.first_time_setup.title": "Welcome to Wildfire's Female Gender Mod!", "wildfire_gender.first_time_setup.description": "Would you like to enable cloud server syncing for your gender settings? This feature allows other players to view your customized gender appearance, even if the server doesn't have the mod installed.", @@ -83,7 +80,7 @@ "wildfire_gender.cloud.status_log": "Status Log", "wildfire_gender.cloud.syncing.success": "Synced", - "wildfire_gender.cloud.syncing.fail": "Sync failed", + "wildfire_gender.cloud.syncing.fail": "Sync Failed", "wildfire_gender.sync_log.authenticating": "Authenticating Account...", "wildfire_gender.sync_log.authentication_success": "Authentication Successful.", From afcba7c358688e859011c11e4428d0b2648e7706 Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 19 Nov 2024 19:33:37 -0700 Subject: [PATCH 163/238] remove now redundant narrator messages --- .../wildfire/gui/screen/WildfireCloudSyncScreen.java | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java index e6dab0ed..47b73cec 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java @@ -72,11 +72,7 @@ public void init() { btnAutomaticSync.setActive(CloudSync.isEnabled()); btnAutomaticSync.setMessage(Text.translatable("wildfire_gender.cloud.automatic", CloudSync.isEnabled() ? (GlobalConfig.INSTANCE.get(GlobalConfig.AUTOMATIC_CLOUD_SYNC) ? WildfireLocalization.ENABLED : WildfireLocalization.DISABLED) : WildfireLocalization.OFF)); btnSyncNow.visible = GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SYNC_ENABLED); - }, - text -> Text.empty() - .append(Text.translatable("wildfire_gender.cloud.status")) - .append(" ") - .append(text.get()))); + })); this.addDrawableChild(btnAutomaticSync = new WildfireButton(xPos, yPos + 20, 157, 20, Text.translatable("wildfire_gender.cloud.automatic", CloudSync.isEnabled() ? (GlobalConfig.INSTANCE.get(GlobalConfig.AUTOMATIC_CLOUD_SYNC) ? WildfireLocalization.ENABLED : WildfireLocalization.DISABLED) : WildfireLocalization.OFF), @@ -85,11 +81,7 @@ public void init() { var newVal = !config.get(GlobalConfig.AUTOMATIC_CLOUD_SYNC); config.set(GlobalConfig.AUTOMATIC_CLOUD_SYNC, newVal); button.setMessage(Text.translatable("wildfire_gender.cloud.automatic", newVal ? WildfireLocalization.ENABLED : WildfireLocalization.DISABLED)); - }, - text -> Text.empty() - .append(Text.translatable("wildfire_gender.cloud.automatic")) - .append(" ") - .append(text.get()))); + })); btnAutomaticSync.setTooltip(Tooltip.of(Text.empty() .append(Text.translatable("wildfire_gender.cloud.automatic.tooltip.line1")) .append("\n\n") From b51470507799879a566443812f76816d70b7673d Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Tue, 19 Nov 2024 21:56:52 -0500 Subject: [PATCH 164/238] Minecraft Version Changed - Version changed from pre-releases to official release - Updated fabric loader version - Updated mod version to 3.3-PRE --- gradle.properties | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index 78d58cd4..fc00825c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,12 +4,12 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://fabricmc.net/develop # Note that this variable does *not* define the minimum required version in fabric.mod.json! -minecraft_version=1.21.2-pre3 -yarn_build=4 -loader_version=0.16.7 +minecraft_version=1.21.2 +yarn_build=1 +loader_version=0.16.9 # Mod Properties -mod_version = 3.2.1 +mod_version = 3.3-PRE maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod From 20fb66ba329ec5837e04e652cd98b3b2323448b5 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Wed, 20 Nov 2024 00:27:31 -0500 Subject: [PATCH 165/238] Sync Log text now scrolls if overflowing Added more logging Moved WildfireLocalization.java to main package. --- src/main/java/com/wildfire/gui/GuiUtils.java | 44 +++++++++++-------- .../java/com/wildfire/gui/WildfireButton.java | 2 +- .../java/com/wildfire/gui/WildfireSlider.java | 2 +- .../gui/screen/WildfireCloudSyncScreen.java | 3 +- .../wildfire/main/WildfireEventHandler.java | 6 +-- .../screen => main}/WildfireLocalization.java | 5 ++- .../com/wildfire/main/cloud/CloudSync.java | 2 +- .../assets/wildfire_gender/lang/en_us.json | 3 ++ 8 files changed, 41 insertions(+), 26 deletions(-) rename src/main/java/com/wildfire/{gui/screen => main}/WildfireLocalization.java (88%) diff --git a/src/main/java/com/wildfire/gui/GuiUtils.java b/src/main/java/com/wildfire/gui/GuiUtils.java index e2f394c9..32a1d17d 100644 --- a/src/main/java/com/wildfire/gui/GuiUtils.java +++ b/src/main/java/com/wildfire/gui/GuiUtils.java @@ -38,6 +38,10 @@ @Environment(EnvType.CLIENT) public final class GuiUtils { + public enum Justify { + LEFT, CENTER + } + private GuiUtils() { throw new UnsupportedOperationException(); } @@ -68,24 +72,28 @@ public static void drawCenteredTextWrapped(DrawContext ctx, TextRenderer textRen } // Reimplementation of ClickableWidget#drawScrollableText but with the text shadow removed - public static void drawScrollableTextWithoutShadow(DrawContext context, TextRenderer textRenderer, Text text, int left, int top, int right, int bottom, int color) { - int i = textRenderer.getWidth(text); - int var10000 = top + bottom; - Objects.requireNonNull(textRenderer); - int j = (var10000 - 9) / 2 + 1; - int k = right - left; - if (i > k) { - int l = i - k; - double d = (double) Util.getMeasuringTimeMs() / 1000.0; - double e = Math.max((double)l * 0.5, 3.0); - double f = Math.sin(1.5707963267948966 * Math.cos(6.283185307179586 * d / e)) / 2.0 + 0.5; - double g = MathHelper.lerp(f, 0.0, l); - context.enableScissor(left, top, right, bottom); - context.drawText(textRenderer, text, left - (int)g, j, color, false); - context.disableScissor(); - } else { - drawCenteredText(context, textRenderer, text, (left + right) / 2, j, color); - } + public static void drawScrollableTextWithoutShadow(Justify justify, DrawContext context, TextRenderer textRenderer, Text text, int left, int top, int right, int bottom, int color) { + int i = textRenderer.getWidth(text); + int var10000 = top + bottom; + Objects.requireNonNull(textRenderer); + int j = (var10000 - 9) / 2 + 1; + int k = right - left; + if (i > k) { + int l = i - k; + double d = (double) Util.getMeasuringTimeMs() / 1000.0; + double e = Math.max((double)l * 0.5, 3.0); + double f = Math.sin(1.5707963267948966 * Math.cos(6.283185307179586 * d / e)) / 2.0 + 0.5; + double g = MathHelper.lerp(f, 0.0, l); + context.enableScissor(left, top, right, bottom); + context.drawText(textRenderer, text, left - (int)g, j, color, false); + context.disableScissor(); + } else { + if(justify == Justify.CENTER) { + drawCenteredText(context, textRenderer, text, (left + right) / 2, j, color); + } else if(justify == Justify.LEFT) { + context.drawText(textRenderer, text, left, j, color, false); + } + } } // Reimplementation of InventoryScreen#drawEntity, intended to allow for applying our own scissor calls, and diff --git a/src/main/java/com/wildfire/gui/WildfireButton.java b/src/main/java/com/wildfire/gui/WildfireButton.java index baf53a9d..baef2688 100644 --- a/src/main/java/com/wildfire/gui/WildfireButton.java +++ b/src/main/java/com/wildfire/gui/WildfireButton.java @@ -51,7 +51,7 @@ protected void drawInner(DrawContext ctx, int mouseX, int mouseY, float partialT int textColor = active ? 0xFFFFFF : 0x666666; int i = this.getX() + 2; int j = this.getX() + this.getWidth() - 2; - GuiUtils.drawScrollableTextWithoutShadow(ctx, font, this.getMessage(), i, this.getY(), j, this.getY() + this.getHeight(), textColor); + GuiUtils.drawScrollableTextWithoutShadow(GuiUtils.Justify.CENTER, ctx, font, this.getMessage(), i, this.getY(), j, this.getY() + this.getHeight(), textColor); } @Override diff --git a/src/main/java/com/wildfire/gui/WildfireSlider.java b/src/main/java/com/wildfire/gui/WildfireSlider.java index e28b36dd..a850835d 100644 --- a/src/main/java/com/wildfire/gui/WildfireSlider.java +++ b/src/main/java/com/wildfire/gui/WildfireSlider.java @@ -140,7 +140,7 @@ protected void renderWidget(DrawContext ctx, int mouseX, int mouseY, float delta TextRenderer font = MinecraftClient.getInstance().textRenderer; int i = this.getX() + 2; int j = this.getX() + this.getWidth() - 2; - GuiUtils.drawScrollableTextWithoutShadow(ctx, font, this.getMessage(), i, this.getY(), j, this.getY() + this.getHeight(), isSelected() || changed ? 0xFFFF55 : 0xFFFFFF); + GuiUtils.drawScrollableTextWithoutShadow(GuiUtils.Justify.CENTER, ctx, font, this.getMessage(), i, this.getY(), j, this.getY() + this.getHeight(), isSelected() || changed ? 0xFFFF55 : 0xFFFFFF); } } diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java index 47b73cec..40d248a4 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java @@ -21,6 +21,7 @@ import com.wildfire.gui.GuiUtils; import com.wildfire.gui.WildfireButton; import com.wildfire.main.WildfireGender; +import com.wildfire.main.WildfireLocalization; import com.wildfire.main.cloud.CloudSync; import com.wildfire.main.cloud.SyncingTooFrequentlyException; import com.wildfire.main.config.GlobalConfig; @@ -141,7 +142,7 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { int reverseIndex = STATUS_LOG.size() - 1 - i; if (reverseIndex < 6) { - ctx.drawText(textRenderer, STATUS_LOG.get(i), x - 78, yPos + 111 - (reverseIndex * 10), 0x00FF00, false); + GuiUtils.drawScrollableTextWithoutShadow(GuiUtils.Justify.LEFT, ctx, textRenderer, STATUS_LOG.get(i), x - 78, yPos + 111 - (reverseIndex * 10), (x - 78) + 156, (yPos + 111 - (reverseIndex * 10)) + 10, 0x00FF00); } } } diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index 8b1aa4c2..679d01d5 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -18,9 +18,7 @@ package com.wildfire.main; -import com.wildfire.gui.screen.BaseWildfireScreen; -import com.wildfire.gui.screen.WardrobeBrowserScreen; -import com.wildfire.gui.screen.WildfireFirstTimeSetupScreen; +import com.wildfire.gui.screen.*; import com.wildfire.main.cloud.CloudSync; import com.wildfire.main.config.GlobalConfig; import com.wildfire.main.entitydata.EntityConfig; @@ -167,8 +165,10 @@ private static void onClientTick(MinecraftClient client) { try { CloudSync.sync(clientConfig).join(); WildfireGender.LOGGER.info("Synced player data to the cloud"); + WildfireCloudSyncScreen.log(WildfireLocalization.SYNC_LOG_SYNC_TO_CLOUD); } catch(Exception e) { WildfireGender.LOGGER.error("Failed to sync player data", e); + WildfireCloudSyncScreen.log(WildfireLocalization.SYNC_LOG_FAILED_TO_SYNC_DATA); } }); clientConfig.needsCloudSync = false; diff --git a/src/main/java/com/wildfire/gui/screen/WildfireLocalization.java b/src/main/java/com/wildfire/main/WildfireLocalization.java similarity index 88% rename from src/main/java/com/wildfire/gui/screen/WildfireLocalization.java rename to src/main/java/com/wildfire/main/WildfireLocalization.java index 4a2a3e7a..96cd59a7 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireLocalization.java +++ b/src/main/java/com/wildfire/main/WildfireLocalization.java @@ -16,7 +16,7 @@ along with this program. If not, see . */ -package com.wildfire.gui.screen; +package com.wildfire.main; import net.minecraft.text.Text; import net.minecraft.util.Formatting; @@ -35,6 +35,9 @@ public class WildfireLocalization { public static final Text SYNC_LOG_ATTMEPTING_SYNC = Text.translatable("wildfire_gender.sync_log.attempting_sync"); public static final Text SYNC_LOG_SYNC_SUCCESS = Text.translatable("wildfire_gender.sync_log.sync_success"); public static final Text SYNC_LOG_SYNC_TOO_FREQUENTLY = Text.translatable("wildfire_gender.sync_log.sync_too_frequently"); + public static final Text SYNC_LOG_FAILED_TO_SYNC_DATA = Text.translatable("wildfire_gender.sync_log.failed_to_sync_data"); + public static final Text SYNC_LOG_SYNC_TO_CLOUD = Text.translatable("wildfire_gender.sync_log.sync_to_cloud"); + } diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index 212e6c13..67d9f30b 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -28,7 +28,7 @@ import com.mojang.authlib.exceptions.AuthenticationException; import com.mojang.util.InstantTypeAdapter; import com.wildfire.gui.screen.WildfireCloudSyncScreen; -import com.wildfire.gui.screen.WildfireLocalization; +import com.wildfire.main.WildfireLocalization; import com.wildfire.main.WildfireGender; import com.wildfire.main.WildfireHelper; import com.wildfire.main.config.GlobalConfig; diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index 387ce3b3..922168e1 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -86,6 +86,9 @@ "wildfire_gender.sync_log.authentication_success": "Authentication Successful.", "wildfire_gender.sync_log.authentication_failed": "Failed Authentication.", "wildfire_gender.sync_log.reauthenticating": "Re-Authenticating Account...", + + "wildfire_gender.sync_log.failed_to_sync_data": "Failed to Sync Data.", + "wildfire_gender.sync_log.sync_to_cloud": "Syncing Data to Cloud...", "wildfire_gender.sync_log.attempting_sync": "Syncing Profile...", "wildfire_gender.sync_log.sync_success": "Sync Successful.", "wildfire_gender.sync_log.sync_too_frequently": "Sync Rate Limited." From a36ea5ac5d66e113ade74b6835cc3f99ec93b7a2 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Wed, 20 Nov 2024 00:31:59 -0500 Subject: [PATCH 166/238] Delete old sync_bg.png Up version to 3.3-PRE2 --- gradle.properties | 2 +- .../wildfire_gender/textures/gui/sync_bg.png | Bin 6161 -> 0 bytes 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/sync_bg.png diff --git a/gradle.properties b/gradle.properties index fc00825c..e33b5b15 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ yarn_build=1 loader_version=0.16.9 # Mod Properties -mod_version = 3.3-PRE +mod_version = 3.3-PRE2 maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/sync_bg.png b/src/main/resources/assets/wildfire_gender/textures/gui/sync_bg.png deleted file mode 100644 index 362e70eedeb36a3b14cda51b64df9696ea132d2a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6161 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 z Date: Tue, 19 Nov 2024 22:49:06 -0700 Subject: [PATCH 167/238] Various sync UI changes - Moved sync log to its own dedicated class - Sync log messages will now slowly darken the older they are - Made more of the UI text scrollable --- src/main/java/com/wildfire/gui/GuiUtils.java | 5 ++- .../gui/screen/WildfireCloudSyncScreen.java | 35 ++++++++----------- .../wildfire/main/WildfireEventHandler.java | 9 +++-- .../com/wildfire/main/cloud/CloudSync.java | 15 ++++---- .../java/com/wildfire/main/cloud/SyncLog.java | 31 ++++++++++++++++ 5 files changed, 60 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/wildfire/main/cloud/SyncLog.java diff --git a/src/main/java/com/wildfire/gui/GuiUtils.java b/src/main/java/com/wildfire/gui/GuiUtils.java index 32a1d17d..db9bd118 100644 --- a/src/main/java/com/wildfire/gui/GuiUtils.java +++ b/src/main/java/com/wildfire/gui/GuiUtils.java @@ -33,7 +33,6 @@ import org.joml.Quaternionf; import org.joml.Vector3f; -import java.util.Iterator; import java.util.Objects; @Environment(EnvType.CLIENT) @@ -63,8 +62,8 @@ public static void drawCenteredText(DrawContext ctx, TextRenderer textRenderer, } public static void drawCenteredTextWrapped(DrawContext ctx, TextRenderer textRenderer, StringVisitable text, int x, int y, int width, int color) { - for(Iterator var7 = textRenderer.wrapLines(text, width).iterator(); var7.hasNext(); y += 9) { - OrderedText orderedText = (OrderedText)var7.next(); + for(var var7 = textRenderer.wrapLines(text, width).iterator(); var7.hasNext(); y += 9) { + OrderedText orderedText = var7.next(); GuiUtils.drawCenteredText(ctx, textRenderer, orderedText, x, y, color); Objects.requireNonNull(textRenderer); } diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java index 40d248a4..106547c1 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java @@ -23,6 +23,7 @@ import com.wildfire.main.WildfireGender; import com.wildfire.main.WildfireLocalization; import com.wildfire.main.cloud.CloudSync; +import com.wildfire.main.cloud.SyncLog; import com.wildfire.main.cloud.SyncingTooFrequentlyException; import com.wildfire.main.config.GlobalConfig; import net.fabricmc.api.EnvType; @@ -34,8 +35,6 @@ import net.minecraft.client.render.RenderLayer; import net.minecraft.text.Text; import net.minecraft.util.Identifier; -import java.util.ArrayList; -import java.util.List; import java.util.Objects; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -44,9 +43,6 @@ @Environment(EnvType.CLIENT) public class WildfireCloudSyncScreen extends BaseWildfireScreen { - //There should only ever be one instance of this file, so I'm putting this list here. Inform if should be moved. - private static final List STATUS_LOG = new ArrayList<>(); - private static final Identifier BACKGROUND = Identifier.of(WildfireGender.MODID, "textures/gui/sync_bg_v2.png"); private WildfireButton btnAutomaticSync = null; @@ -90,7 +86,6 @@ public void init() { btnAutomaticSync.setActive(CloudSync.isEnabled()); btnSyncNow = new WildfireButton(xPos + 98, yPos + 42, 60, 15, Text.translatable("wildfire_gender.cloud.sync"), this::sync); - //btnSyncNow.setActive(GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SYNC_ENABLED)); btnSyncNow.visible = GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SYNC_ENABLED); this.addDrawableChild(btnSyncNow); @@ -111,7 +106,7 @@ private void sync(ButtonWidget button) { var actualException = e instanceof CompletionException ce ? ce.getCause() : e; if(actualException instanceof SyncingTooFrequentlyException) { WildfireGender.LOGGER.warn("Failed to sync settings as we've already synced too recently"); - WildfireCloudSyncScreen.log(WildfireLocalization.SYNC_LOG_SYNC_TOO_FREQUENTLY); + SyncLog.add(WildfireLocalization.SYNC_LOG_SYNC_TOO_FREQUENTLY); } else { WildfireGender.LOGGER.error("Failed to sync settings", actualException); } @@ -133,16 +128,21 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { int x = this.width / 2; int y = this.height / 2; - int yPos = y - 47; + y -= 47; - ctx.drawText(textRenderer, getTitle(), x - 79, yPos - 10, 4473924, false); - ctx.drawText(textRenderer, Text.translatable("wildfire_gender.cloud.status_log"), x - 79, yPos + 49, 4473924, false); + GuiUtils.drawScrollableTextWithoutShadow(GuiUtils.Justify.LEFT, ctx, textRenderer, getTitle(), + x - 79, y - 12, x - 79 + 150, y - 11 + 10, 4473924); + GuiUtils.drawScrollableTextWithoutShadow(GuiUtils.Justify.LEFT, ctx, textRenderer, Text.translatable("wildfire_gender.cloud.status_log"), + x - 79, y + 47, x - 79 + 95, y + 48 + 10, 4473924); - for(int i = STATUS_LOG.size()-1; i >= 0; i--) { - int reverseIndex = STATUS_LOG.size() - 1 - i; + for(int i = SyncLog.SYNC_LOG.size() - 1; i >= 0; i--) { + int reverseIndex = SyncLog.SYNC_LOG.size() - 1 - i; + var entry = SyncLog.SYNC_LOG.get(i); - if (reverseIndex < 6) { - GuiUtils.drawScrollableTextWithoutShadow(GuiUtils.Justify.LEFT, ctx, textRenderer, STATUS_LOG.get(i), x - 78, yPos + 111 - (reverseIndex * 10), (x - 78) + 156, (yPos + 111 - (reverseIndex * 10)) + 10, 0x00FF00); + if(reverseIndex < 6) { + int ey = y + 110 - (reverseIndex * 10); + GuiUtils.drawScrollableTextWithoutShadow(GuiUtils.Justify.LEFT, ctx, textRenderer, entry.text(), + x - 78, ey, x - 78 + 156, ey + 10, entry.color()); } } } @@ -152,11 +152,4 @@ public void close() { GlobalConfig.INSTANCE.save(); super.close(); } - - public static void log(Text text) { - STATUS_LOG.add(text); - if(STATUS_LOG.size() > 6) { - STATUS_LOG.removeFirst(); //remove first entry since it's never seen again - } - } } \ No newline at end of file diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index 679d01d5..45e5ffe9 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -18,8 +18,11 @@ package com.wildfire.main; -import com.wildfire.gui.screen.*; +import com.wildfire.gui.screen.BaseWildfireScreen; +import com.wildfire.gui.screen.WardrobeBrowserScreen; +import com.wildfire.gui.screen.WildfireFirstTimeSetupScreen; import com.wildfire.main.cloud.CloudSync; +import com.wildfire.main.cloud.SyncLog; import com.wildfire.main.config.GlobalConfig; import com.wildfire.main.entitydata.EntityConfig; import com.wildfire.main.entitydata.PlayerConfig; @@ -165,10 +168,10 @@ private static void onClientTick(MinecraftClient client) { try { CloudSync.sync(clientConfig).join(); WildfireGender.LOGGER.info("Synced player data to the cloud"); - WildfireCloudSyncScreen.log(WildfireLocalization.SYNC_LOG_SYNC_TO_CLOUD); + SyncLog.add(WildfireLocalization.SYNC_LOG_SYNC_TO_CLOUD); } catch(Exception e) { WildfireGender.LOGGER.error("Failed to sync player data", e); - WildfireCloudSyncScreen.log(WildfireLocalization.SYNC_LOG_FAILED_TO_SYNC_DATA); + SyncLog.add(WildfireLocalization.SYNC_LOG_FAILED_TO_SYNC_DATA); } }); clientConfig.needsCloudSync = false; diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index 67d9f30b..97046a9b 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -27,7 +27,6 @@ import com.mojang.authlib.HttpAuthenticationService; import com.mojang.authlib.exceptions.AuthenticationException; import com.mojang.util.InstantTypeAdapter; -import com.wildfire.gui.screen.WildfireCloudSyncScreen; import com.wildfire.main.WildfireLocalization; import com.wildfire.main.WildfireGender; import com.wildfire.main.WildfireHelper; @@ -164,12 +163,12 @@ private static String getAuthToken() { synchronized(AUTH_LOCK) { var client = MinecraftClient.getInstance(); if(client.player == null) { - WildfireCloudSyncScreen.log(WildfireLocalization.SYNC_LOG_AUTHENTICATION_FAILED); + SyncLog.add(WildfireLocalization.SYNC_LOG_AUTHENTICATION_FAILED); throw new IllegalStateException("Cannot get a new auth token while the client player is unset"); } if(auth == null || auth.isExpired() || auth.isInvalidForClientPlayer()) { WildfireGender.LOGGER.info("Obtaining new authentication token from the cloud sync server"); - WildfireCloudSyncScreen.log(WildfireLocalization.SYNC_LOG_AUTHENTICATING); + SyncLog.add(WildfireLocalization.SYNC_LOG_AUTHENTICATING); var serverId = generateServerId(); var session = client.getSession(); @@ -185,7 +184,7 @@ private static String getAuthToken() { var request = createRequest(uri).GET().build(); var response = CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join(); if(response.statusCode() >= 400) { - WildfireCloudSyncScreen.log(WildfireLocalization.SYNC_LOG_AUTHENTICATION_FAILED); + SyncLog.add(WildfireLocalization.SYNC_LOG_AUTHENTICATION_FAILED); throw new RuntimeException("Failed to authenticate with sync server: " + response.body()); } @@ -195,7 +194,7 @@ private static String getAuthToken() { } WildfireGender.LOGGER.info("Obtained authentication token for {}, expiry {}", auth.account(), auth.expires()); if(!auth.isInvalidForClientPlayer()) { //TODO: This might not need to be here. - WildfireCloudSyncScreen.log(WildfireLocalization.SYNC_LOG_AUTHENTICATION_SUCCESS); + SyncLog.add(WildfireLocalization.SYNC_LOG_AUTHENTICATION_SUCCESS); } } } @@ -234,7 +233,7 @@ private static CompletableFuture syncInternal(PlayerConfig config, boolean var url = URI.create(getCloudServer() + "/" + config.uuid); var json = config.toJson().toString(); - WildfireCloudSyncScreen.log(WildfireLocalization.SYNC_LOG_ATTMEPTING_SYNC); + SyncLog.add(WildfireLocalization.SYNC_LOG_ATTMEPTING_SYNC); var request = createRequest(url) .PUT(HttpRequest.BodyPublishers.ofString(json)) @@ -243,7 +242,7 @@ private static CompletableFuture syncInternal(PlayerConfig config, boolean .build(); var response = CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join(); if(response.statusCode() == 401 && !resyncing) { - WildfireCloudSyncScreen.log(WildfireLocalization.SYNC_LOG_REAUTHENTICATING); + SyncLog.add(WildfireLocalization.SYNC_LOG_REAUTHENTICATING); WildfireGender.LOGGER.warn("Auth token is invalid, attempting to reauth..."); auth = null; syncInternal(config, true).join(); @@ -252,7 +251,7 @@ private static CompletableFuture syncInternal(PlayerConfig config, boolean throw new RuntimeException("Server responded " + response.statusCode() + ": " + response.body()); } WildfireGender.LOGGER.debug("Server responded to update: {}", response.body()); - WildfireCloudSyncScreen.log(WildfireLocalization.SYNC_LOG_SYNC_SUCCESS); + SyncLog.add(WildfireLocalization.SYNC_LOG_SYNC_SUCCESS); }, EXECUTOR); } diff --git a/src/main/java/com/wildfire/main/cloud/SyncLog.java b/src/main/java/com/wildfire/main/cloud/SyncLog.java new file mode 100644 index 00000000..6afbbf83 --- /dev/null +++ b/src/main/java/com/wildfire/main/cloud/SyncLog.java @@ -0,0 +1,31 @@ +package com.wildfire.main.cloud; + +import net.minecraft.text.Text; +import net.minecraft.util.math.ColorHelper; +import net.minecraft.util.math.MathHelper; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +public final class SyncLog { + public static final List SYNC_LOG = new ArrayList<>(); + + public static void add(Text text) { + SYNC_LOG.add(new Entry(text, Instant.now())); + if(SYNC_LOG.size() > 6) { + SYNC_LOG.removeFirst(); + } + } + + public record Entry(Text text, Instant timestamp) { + public static final int NEW_COLOR = 0x00FF00; + public static final int OLD_COLOR = 0x34A100; + + public int color() { + long secondsPassed = Instant.now().getEpochSecond() - timestamp.getEpochSecond(); + float delta = MathHelper.clamp(secondsPassed / 60f, 0f, 1f); + return ColorHelper.lerp(delta, NEW_COLOR, OLD_COLOR); + } + } +} From 5f518fccf19b965c9ecc7ac4b86d41c38414ca96 Mon Sep 17 00:00:00 2001 From: PinguinLars1105 Date: Wed, 20 Nov 2024 16:31:32 +0100 Subject: [PATCH 168/238] Update nl_nl.json --- src/main/resources/assets/wildfire_gender/lang/nl_nl.json | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/resources/assets/wildfire_gender/lang/nl_nl.json b/src/main/resources/assets/wildfire_gender/lang/nl_nl.json index fb52f56f..65e68365 100644 --- a/src/main/resources/assets/wildfire_gender/lang/nl_nl.json +++ b/src/main/resources/assets/wildfire_gender/lang/nl_nl.json @@ -54,12 +54,9 @@ "wildfire_gender.slider.bounce": "Veerkracht Intensiteit: %s%%", "wildfire_gender.slider.floppy": "Borst Momentum: %s%%", - "wildfire_gender.slider.min_bounce": "Waarom Staan De Bewegingen Aan?", - "wildfire_gender.slider.max_bounce": "Anime Borst Bewegingen!!!", "wildfire_gender.tooltip.bounce_warning": "Instelling 'Veerkracht Intensiteit' op een hoog nummer ziet er erg niet natuurlijk uit!", "wildfire_gender.cancer_awareness.title": "Hé, het is Borst Kanker Aandacht Maand", - "wildfire_gender.coming_soon": "Komt Binnenkort", "wildfire_gender.first_time_setup.title": "Welkom bij Wildfire's Vrouwelijke Geslachts Mod!", "wildfire_gender.first_time_setup.description": "Wil je de optie aanzetten om je geslachts instellingen online te Synchroniseren? Hier mee kunnen anderen jouw aangepaste geslacht uiterlijk, zelfs al heeft de server deze mod niet geïnstalleerd.", @@ -70,8 +67,8 @@ "wildfire_gender.cloud_settings": "Online Synchronisatie Instellingen", "wildfire_gender.cloud.available_online": "Online Synchronisatie", "wildfire_gender.cloud.unavailable_offline": "Online synchronisatie is niet beschikbaar, omdat je niet bent ingelogd in een geldig Minecraft account", - "wildfire_gender.cloud.status": "Synchroniseren:", - "wildfire_gender.cloud.automatic": "Automatische Synchronisatie:", + "wildfire_gender.cloud.status": "Synchroniseren: %s", + "wildfire_gender.cloud.automatic": "Automatische Synchronisatie: %s", "wildfire_gender.cloud.automatic.tooltip.line1": "Wanner dit aanstaat, jouw configuratie wordt automatisch online gesynchroniseerd na dat je aanpassingen maakt.", "wildfire_gender.cloud.sync": "Synchroniseer", "wildfire_gender.cloud.automatic.tooltip.line2": "Je kan nog steeds manueel synchroniseren met de knop hier onder, als dit uitstaat.", From 44ea5b23e744fd87cfde6d09148719f3eeae4e5f Mon Sep 17 00:00:00 2001 From: PinguinLars1105 Date: Wed, 20 Nov 2024 16:56:43 +0100 Subject: [PATCH 169/238] Translated missed language lines --- .../assets/wildfire_gender/lang/nl_nl.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/resources/assets/wildfire_gender/lang/nl_nl.json b/src/main/resources/assets/wildfire_gender/lang/nl_nl.json index 65e68365..77395319 100644 --- a/src/main/resources/assets/wildfire_gender/lang/nl_nl.json +++ b/src/main/resources/assets/wildfire_gender/lang/nl_nl.json @@ -74,5 +74,17 @@ "wildfire_gender.cloud.automatic.tooltip.line2": "Je kan nog steeds manueel synchroniseren met de knop hier onder, als dit uitstaat.", "wildfire_gender.cloud.syncing": "Synchroniseren...", "wildfire_gender.cloud.syncing.success": "Gesynchroniseerd", - "wildfire_gender.cloud.syncing.fail": "Synchronisatie Niet Gelukt" + "wildfire_gender.cloud.syncing.fail": "Synchronisatie Niet Gelukt", + "wildfire_gender.wardrobe.players_using_mod": "Spelers die de mod gebruiken:", + "wildfire_gender.label.off": "Uit", + "wildfire_gender.cloud.status_log": "Status Log", + "wildfire_gender.sync_log.authenticating": "Account Verifiëren...", + "wildfire_gender.sync_log.authentication_success": "Verificatie Gelukt.", + "wildfire_gender.sync_log.authentication_failed": "Verificatie Gefaald.", + "wildfire_gender.sync_log.reauthenticating": "Account Opnieuw Verifiëren...", + "wildfire_gender.sync_log.failed_to_sync_data": "Gefaald om Data te Sychroniseren.", + "wildfire_gender.sync_log.sync_to_cloud": "Data Online Sychroniseren...", + "wildfire_gender.sync_log.attempting_sync": "Profiel Sychroniseren...", + "wildfire_gender.sync_log.sync_success": "Sychronisatie Gelukt.", + "wildfire_gender.sync_log.sync_too_frequently": "Sychronisatie Tarief is Beperkt." } \ No newline at end of file From 20ae373b68e21619f925371deb7ff19a743ba46d Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Thu, 21 Nov 2024 00:20:18 -0500 Subject: [PATCH 170/238] Cloud Sync UI Changes - Added disclaimer about Cloud Sync cache. --- .../com/wildfire/gui/screen/WildfireCloudSyncScreen.java | 8 ++++++++ src/main/resources/assets/wildfire_gender/lang/en_us.json | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java index 106547c1..017728a3 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java @@ -86,6 +86,8 @@ public void init() { btnAutomaticSync.setActive(CloudSync.isEnabled()); btnSyncNow = new WildfireButton(xPos + 98, yPos + 42, 60, 15, Text.translatable("wildfire_gender.cloud.sync"), this::sync); + //btnSyncNow.setTooltip(Tooltip.of(Text.empty() + // .append(Text.literal("Sync Server data is cached for a minimum time of 30 minutes. If you do not see any changes please try to re-sync later.")))); btnSyncNow.visible = GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SYNC_ENABLED); this.addDrawableChild(btnSyncNow); @@ -135,6 +137,12 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { GuiUtils.drawScrollableTextWithoutShadow(GuiUtils.Justify.LEFT, ctx, textRenderer, Text.translatable("wildfire_gender.cloud.status_log"), x - 79, y + 47, x - 79 + 95, y + 48 + 10, 4473924); + + GuiUtils.drawCenteredTextWrapped(ctx, textRenderer, Text.translatable("wildfire_gender.cloud.disclaimer.line1") + .append("\n\n") + .append(Text.translatable("wildfire_gender.cloud.disclaimer.line2")), + x, y + 142, 240, 0xFFFFFF); + for(int i = SyncLog.SYNC_LOG.size() - 1; i >= 0; i--) { int reverseIndex = SyncLog.SYNC_LOG.size() - 1 - i; var entry = SyncLog.SYNC_LOG.get(i); diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index 922168e1..45692f35 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -81,6 +81,8 @@ "wildfire_gender.cloud.syncing.success": "Synced", "wildfire_gender.cloud.syncing.fail": "Sync Failed", + "wildfire_gender.cloud.disclaimer.line1": "Sync Server data is cached for at least 30 minutes.", + "wildfire_gender.cloud.disclaimer.line2": "If changes are not visible, please try re-syncing later.", "wildfire_gender.sync_log.authenticating": "Authenticating Account...", "wildfire_gender.sync_log.authentication_success": "Authentication Successful.", From bc270ea0c0d54c22cd83c3c272be2b4a3ab6ca25 Mon Sep 17 00:00:00 2001 From: celeste Date: Wed, 20 Nov 2024 21:54:17 -0700 Subject: [PATCH 171/238] use proper caches for player & entity config caches --- .../java/com/wildfire/api/WildfireAPI.java | 7 ++-- .../wildfire/main/WildfireEventHandler.java | 24 ++---------- .../com/wildfire/main/WildfireGender.java | 37 +++++++++++++++++-- .../wildfire/main/WildfireGenderClient.java | 22 +++++++---- .../com/wildfire/main/WildfireHelper.java | 4 ++ .../main/entitydata/EntityConfig.java | 25 ++++++++++--- .../mixins/BreastPhysicsTickMixin.java | 1 - .../com/wildfire/render/GenderArmorLayer.java | 1 - .../java/com/wildfire/render/GenderLayer.java | 1 - 9 files changed, 78 insertions(+), 44 deletions(-) diff --git a/src/main/java/com/wildfire/api/WildfireAPI.java b/src/main/java/com/wildfire/api/WildfireAPI.java index 91012ae5..2af5ecf2 100644 --- a/src/main/java/com/wildfire/api/WildfireAPI.java +++ b/src/main/java/com/wildfire/api/WildfireAPI.java @@ -88,14 +88,15 @@ public static void addGenderArmor(Item item, IGenderArmor genderArmor) { * request to the {@link com.wildfire.main.cloud.CloudSync cloud sync} server for it * (if cloud syncing is enabled).

* - *

Note that you should generally avoid using this unless you know that you need to, as the mod already runs - * this load process when the relevant player entity is spawned in the world.

+ *

Use of this method is heavily discouraged, as the mod will already perform this load process upon + * first loading a player's config; the exact return type of this method may also change between versions.

* * @param uuid the uuid of the target {@link PlayerEntity} * @param markForSync {@code true} if player data should be synced to the server upon being loaded; this only has an effect on the client player. */ + @ApiStatus.Obsolete // further discourage use of this @Environment(EnvType.CLIENT) - public static CompletableFuture<@NotNull PlayerConfig> loadGenderInfo(UUID uuid, boolean markForSync) { + public static CompletableFuture<@Nullable PlayerConfig> loadGenderInfo(UUID uuid, boolean markForSync) { return WildfireGenderClient.loadGenderInfo(uuid, markForSync, false); } diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index 45e5ffe9..a85663e4 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -41,7 +41,6 @@ import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.option.KeyBinding; import net.minecraft.client.render.entity.ArmorStandEntityRenderer; @@ -59,7 +58,6 @@ import net.minecraft.world.World; import org.lwjgl.glfw.GLFW; -import java.util.UUID; import java.util.concurrent.CompletableFuture; public final class WildfireEventHandler { @@ -97,7 +95,6 @@ public static void registerCommonEvents() { */ @Environment(EnvType.CLIENT) public static void registerClientEvents() { - ClientEntityEvents.ENTITY_LOAD.register(WildfireEventHandler::onEntityLoad); ClientEntityEvents.ENTITY_UNLOAD.register(WildfireEventHandler::onEntityUnload); ClientTickEvents.END_CLIENT_TICK.register(WildfireEventHandler::onClientTick); ClientPlayConnectionEvents.DISCONNECT.register(WildfireEventHandler::clientDisconnect); @@ -119,19 +116,6 @@ private static void registerRenderLayers(EntityType enti } } - /** - * Load data for a loaded player if applicable - */ - @Environment(EnvType.CLIENT) - private static void onEntityLoad(Entity entity, World world) { - if(!world.isClient() || MinecraftClient.getInstance().player == null) return; - if(entity instanceof AbstractClientPlayerEntity plr) { - UUID uuid = plr.getUuid(); - boolean isClientPlayer = uuid.equals(MinecraftClient.getInstance().player.getUuid()); - WildfireGenderClient.loadPlayerIfMissing(uuid, isClientPlayer); - } - } - /** * Remove (non-player) entities from the client cache when they're unloaded */ @@ -139,7 +123,7 @@ private static void onEntityLoad(Entity entity, World world) { private static void onEntityUnload(Entity entity, World world) { // note that we don't attempt to unload players; they're instead only ever unloaded once we leave a world, // or once they disconnect - EntityConfig.ENTITY_CACHE.remove(entity.getUuid()); + EntityConfig.CACHE.invalidate(entity.getUuid()); } /** @@ -193,15 +177,15 @@ private static void onClientTick(MinecraftClient client) { */ @Environment(EnvType.CLIENT) private static void clientDisconnect(ClientPlayNetworkHandler networkHandler, MinecraftClient client) { - WildfireGender.PLAYER_CACHE.clear(); - EntityConfig.ENTITY_CACHE.clear(); + WildfireGender.CACHE.invalidateAll(); + EntityConfig.CACHE.invalidateAll(); } /** * Removes a disconnecting player from the cache on a server */ private static void playerDisconnected(ServerPlayNetworkHandler handler, MinecraftServer server) { - WildfireGender.PLAYER_CACHE.remove(handler.getPlayer().getUuid()); + WildfireGender.CACHE.invalidate(handler.getPlayer().getUuid()); } /** diff --git a/src/main/java/com/wildfire/main/WildfireGender.java b/src/main/java/com/wildfire/main/WildfireGender.java index 3cb88e1f..77e05583 100644 --- a/src/main/java/com/wildfire/main/WildfireGender.java +++ b/src/main/java/com/wildfire/main/WildfireGender.java @@ -18,8 +18,12 @@ package com.wildfire.main; +import java.time.Duration; import java.util.*; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import com.mojang.logging.LogUtils; import com.wildfire.main.config.GlobalConfig; import com.wildfire.main.entitydata.PlayerConfig; @@ -32,10 +36,35 @@ public class WildfireGender implements ModInitializer { public static final String MODID = "wildfire_gender"; public static final Logger LOGGER = LogUtils.getLogger(); - public static final Map PLAYER_CACHE = new HashMap<>(); + public static final LoadingCache CACHE; + + static { + var builder = CacheBuilder.newBuilder(); + // Only automatically expire cache entries on the client; a server may go a decent while without accessing + // the player cache, and we can't easily re-cache a player's settings on a server, while a client + // will typically either receive settings from the server in a sync, or simply re-fetch from + // a local config file or from the cloud. + // Note that servers will manually invalidate cache entries upon a player disconnecting + // (see WildfireEventHandler#playerDisconnected). + if(WildfireHelper.onClient()) { + builder.expireAfterAccess(Duration.ofMinutes(15)); + } + CACHE = builder.build(new CacheLoader<>() { + @Override + public @NotNull PlayerConfig load(@NotNull UUID key) { + var config = new PlayerConfig(key); + // only attempt to load player data on the client, and if the provided uuid is valid + if(WildfireHelper.onClient() && key.version() == 4) { + // markForSync being true will only ever do anything for the client player + WildfireGenderClient.loadGenderInfo(config, true, false); + } + return config; + } + }); + } public static final UUID CREATOR_UUID = UUID.fromString("23b6feed-2dfe-4f2e-9429-863fd4adb946"); - public static final List CONTRIBUTOR_UUIDS = Arrays.asList( + public static final List CONTRIBUTOR_UUIDS = List.of( UUID.fromString("70336328-0de7-430e-8cba-2779e2a05ab5"), //celeste UUID.fromString("64e57307-72e5-4f43-be9c-181e8e35cc9b"), //pupnewfster UUID.fromString("618a8390-51b1-43b2-a53a-ab72c1bbd8bd"), //Kichura @@ -55,10 +84,10 @@ public void onInitialize() { } public static @Nullable PlayerConfig getPlayerById(UUID id) { - return PLAYER_CACHE.get(id); + return CACHE.getIfPresent(id); } public static @NotNull PlayerConfig getOrAddPlayerById(UUID id) { - return PLAYER_CACHE.computeIfAbsent(id, PlayerConfig::new); + return CACHE.getUnchecked(id); } } diff --git a/src/main/java/com/wildfire/main/WildfireGenderClient.java b/src/main/java/com/wildfire/main/WildfireGenderClient.java index cccaf0fc..9619a41d 100644 --- a/src/main/java/com/wildfire/main/WildfireGenderClient.java +++ b/src/main/java/com/wildfire/main/WildfireGenderClient.java @@ -30,6 +30,7 @@ import net.minecraft.resource.ResourceType; import net.minecraft.util.Util; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -47,9 +48,17 @@ public void onInitializeClient() { ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(GenderArmorResourceManager.INSTANCE); } - public static CompletableFuture<@NotNull PlayerConfig> loadGenderInfo(UUID uuid, boolean markForSync, boolean bypassQueue) { + public static CompletableFuture<@Nullable PlayerConfig> loadGenderInfo(UUID uuid, boolean markForSync, boolean bypassQueue) { + var cache = WildfireGender.getPlayerById(uuid); + if(cache == null) { + return CompletableFuture.completedFuture(null); + } + return loadGenderInfo(cache, markForSync, bypassQueue); + } + + public static CompletableFuture<@NotNull PlayerConfig> loadGenderInfo(PlayerConfig player, boolean markForSync, boolean bypassQueue) { return CompletableFuture.supplyAsync(() -> { - var player = WildfireGender.getOrAddPlayerById(uuid); + var uuid = player.uuid; if(player.hasLocalConfig()) { player.loadFromDisk(markForSync); } else if(player.syncStatus == PlayerConfig.SyncStatus.UNKNOWN) { @@ -65,15 +74,12 @@ public void onInitializeClient() { // the sync server if(data != null && player.syncStatus == PlayerConfig.SyncStatus.UNKNOWN) { player.updateFromJson(data); + if(markForSync) { + player.needsSync = true; + } } } return player; }, LOAD_EXECUTOR); } - - public static void loadPlayerIfMissing(UUID uuid, boolean markForSync) { - if(WildfireGender.PLAYER_CACHE.containsKey(uuid)) return; - WildfireGender.getOrAddPlayerById(uuid); - loadGenderInfo(uuid, markForSync, false); - } } diff --git a/src/main/java/com/wildfire/main/WildfireHelper.java b/src/main/java/com/wildfire/main/WildfireHelper.java index 42400642..cbe5d628 100644 --- a/src/main/java/com/wildfire/main/WildfireHelper.java +++ b/src/main/java/com/wildfire/main/WildfireHelper.java @@ -96,4 +96,8 @@ public static String getModVersion(String modId) { var mod = FabricLoader.getInstance().getModContainer(modId).orElseThrow(); return mod.getMetadata().getVersion().getFriendlyString(); } + + public static boolean onClient() { + return FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT; + } } diff --git a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java index c591745b..87c21aeb 100644 --- a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java +++ b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java @@ -18,6 +18,9 @@ package com.wildfire.main.entitydata; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import com.wildfire.api.IGenderArmor; import com.wildfire.main.WildfireGender; import com.wildfire.main.WildfireHelper; @@ -36,9 +39,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.HashMap; +import java.time.Duration; import java.util.Objects; -import java.util.Map; import java.util.UUID; /** @@ -50,7 +52,14 @@ */ public class EntityConfig { - public static final Map ENTITY_CACHE = new HashMap<>(); + public static final LoadingCache CACHE = CacheBuilder.newBuilder() + .expireAfterAccess(Duration.ofMinutes(5)) + .build(new CacheLoader<>() { + @Override + public @NotNull EntityConfig load(@NotNull UUID key) { + return new EntityConfig(key); + } + }); public final UUID uuid; protected Gender gender = Configuration.GENDER.getDefault(); @@ -111,13 +120,17 @@ public void readFromStack(@NotNull ItemStack chestplate) { /** * Get the configuration for a given entity * + * @apiNote Configuration settings for {@link PlayerConfig}s may not be immediately available upon being + * returned, and may take several seconds to be populated if loaded from the + * {@link com.wildfire.main.cloud.CloudSync cloud sync server}. + * * @return The relevant {@link EntityConfig}, or {@link PlayerConfig} if given a {@link PlayerEntity player} */ - public static @Nullable EntityConfig getEntity(@NotNull LivingEntity entity) { + public static @NotNull EntityConfig getEntity(@NotNull LivingEntity entity) { if(entity instanceof PlayerEntity) { - return WildfireGender.getPlayerById(entity.getUuid()); + return WildfireGender.getOrAddPlayerById(entity.getUuid()); } - return ENTITY_CACHE.computeIfAbsent(entity.getUuid(), EntityConfig::new); + return CACHE.getUnchecked(entity.getUuid()); } public @NotNull Gender getGender() { diff --git a/src/main/java/com/wildfire/mixins/BreastPhysicsTickMixin.java b/src/main/java/com/wildfire/mixins/BreastPhysicsTickMixin.java index 84d1e0a2..fb4e0230 100644 --- a/src/main/java/com/wildfire/mixins/BreastPhysicsTickMixin.java +++ b/src/main/java/com/wildfire/mixins/BreastPhysicsTickMixin.java @@ -40,7 +40,6 @@ abstract class BreastPhysicsTickMixin { if(!entity.getWorld().isClient()) return; EntityConfig cfg = EntityConfig.getEntity(entity); - if(cfg == null) return; if(entity instanceof ArmorStandEntity) { cfg.readFromStack(entity.getEquippedStack(EquipmentSlot.CHEST)); } diff --git a/src/main/java/com/wildfire/render/GenderArmorLayer.java b/src/main/java/com/wildfire/render/GenderArmorLayer.java index 5009819d..ede23925 100644 --- a/src/main/java/com/wildfire/render/GenderArmorLayer.java +++ b/src/main/java/com/wildfire/render/GenderArmorLayer.java @@ -96,7 +96,6 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume try { entityConfig = EntityConfig.getEntity(ent); - if(entityConfig == null) return; if(!setupRender(state, entityConfig)) return; if(ent instanceof ArmorStandEntity && !genderArmor.armorStandsCopySettings()) return; diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index 6948a08c..21ac1b78 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -127,7 +127,6 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume if(ent == null) return; EntityConfig entityConfig = EntityConfig.getEntity(ent); - if(entityConfig == null) return; try { if(!setupRender(state, entityConfig)) return; From 27b2290c80de2f880d232cbae258a52c449450fe Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Thu, 21 Nov 2024 00:31:00 -0500 Subject: [PATCH 172/238] Cloud Sync UI Changes - Moved disclaimer about Cloud Sync cache to a "help" (?) button tooltip. - Reworded to say "may be cached", since the cache might not be permanent. --- .../gui/screen/WildfireCloudSyncScreen.java | 15 ++++++++------- .../assets/wildfire_gender/lang/en_us.json | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java index 017728a3..8159b505 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java @@ -47,6 +47,7 @@ public class WildfireCloudSyncScreen extends BaseWildfireScreen { private WildfireButton btnAutomaticSync = null; private WildfireButton btnSyncNow = null; + private WildfireButton btnHelp = null; protected WildfireCloudSyncScreen(Screen parent, UUID uuid) { super(Text.translatable("wildfire_gender.cloud_settings"), parent, uuid); @@ -94,6 +95,12 @@ public void init() { this.addDrawableChild(new WildfireButton(this.width / 2 + 73, yPos - 11, 9, 9, Text.literal("X"), button -> close(), text -> GuiUtils.doneNarrationText())); + this.addDrawableChild(btnHelp = new WildfireButton(this.width / 2 + 73 - 10, yPos - 11, 9, 9, Text.literal("?"), + button -> { /* TODO: Maybe make a new page with instructions / detail on how the sync feature works */ }, text -> GuiUtils.doneNarrationText())); + btnHelp.setTooltip(Tooltip.of(Text.translatable("wildfire_gender.cloud.disclaimer.line1") + .append("\n\n") + .append(Text.translatable("wildfire_gender.cloud.disclaimer.line2")))); + super.init(); } @@ -133,16 +140,10 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { y -= 47; GuiUtils.drawScrollableTextWithoutShadow(GuiUtils.Justify.LEFT, ctx, textRenderer, getTitle(), - x - 79, y - 12, x - 79 + 150, y - 11 + 10, 4473924); + x - 79, y - 12, x - 79 + 141, y - 11 + 10, 4473924); GuiUtils.drawScrollableTextWithoutShadow(GuiUtils.Justify.LEFT, ctx, textRenderer, Text.translatable("wildfire_gender.cloud.status_log"), x - 79, y + 47, x - 79 + 95, y + 48 + 10, 4473924); - - GuiUtils.drawCenteredTextWrapped(ctx, textRenderer, Text.translatable("wildfire_gender.cloud.disclaimer.line1") - .append("\n\n") - .append(Text.translatable("wildfire_gender.cloud.disclaimer.line2")), - x, y + 142, 240, 0xFFFFFF); - for(int i = SyncLog.SYNC_LOG.size() - 1; i >= 0; i--) { int reverseIndex = SyncLog.SYNC_LOG.size() - 1 - i; var entry = SyncLog.SYNC_LOG.get(i); diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index 45692f35..998bd5c5 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -81,7 +81,7 @@ "wildfire_gender.cloud.syncing.success": "Synced", "wildfire_gender.cloud.syncing.fail": "Sync Failed", - "wildfire_gender.cloud.disclaimer.line1": "Sync Server data is cached for at least 30 minutes.", + "wildfire_gender.cloud.disclaimer.line1": "Sync Server data may be cached for at least 30 minutes.", "wildfire_gender.cloud.disclaimer.line2": "If changes are not visible, please try re-syncing later.", "wildfire_gender.sync_log.authenticating": "Authenticating Account...", From 276d781a309f73f1d4523e4a2f10816c4e6c63e7 Mon Sep 17 00:00:00 2001 From: PinguinLars1105 <68437296+PinguinLars@users.noreply.github.com> Date: Thu, 21 Nov 2024 10:25:15 +0100 Subject: [PATCH 173/238] Updated the Dutch Translation (again) Translated the new lines added in commit 20ae373 --- src/main/resources/assets/wildfire_gender/lang/nl_nl.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/resources/assets/wildfire_gender/lang/nl_nl.json b/src/main/resources/assets/wildfire_gender/lang/nl_nl.json index 77395319..46e1ab1b 100644 --- a/src/main/resources/assets/wildfire_gender/lang/nl_nl.json +++ b/src/main/resources/assets/wildfire_gender/lang/nl_nl.json @@ -86,5 +86,7 @@ "wildfire_gender.sync_log.sync_to_cloud": "Data Online Sychroniseren...", "wildfire_gender.sync_log.attempting_sync": "Profiel Sychroniseren...", "wildfire_gender.sync_log.sync_success": "Sychronisatie Gelukt.", - "wildfire_gender.sync_log.sync_too_frequently": "Sychronisatie Tarief is Beperkt." -} \ No newline at end of file + "wildfire_gender.sync_log.sync_too_frequently": "Sychronisatie Tarief is Beperkt.", + "wildfire_gender.cloud.disclaimer.line1": "Server Sychronisatie date wordt mogelijk gecachd voor minimaal 30 minuten.", + "wildfire_gender.cloud.disclaimer.line2": "Als de aanpassen niet zichtbaar zijn, synchroniseer later opnieuw aub." +} From c632669f66582a5824881e71cfd4e81173857d3f Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Thu, 21 Nov 2024 21:46:06 -0500 Subject: [PATCH 174/238] Internal Stuff --- .../screen/WildfireCloudDetailsScreen.java | 121 ++++++++++++++++++ .../gui/screen/WildfireCloudSyncScreen.java | 5 +- .../assets/wildfire_gender/lang/en_us.json | 7 +- .../textures/gui/details_page.png | Bin 0 -> 1017 bytes 4 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/wildfire/gui/screen/WildfireCloudDetailsScreen.java create mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/details_page.png diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCloudDetailsScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCloudDetailsScreen.java new file mode 100644 index 00000000..721ce8d1 --- /dev/null +++ b/src/main/java/com/wildfire/gui/screen/WildfireCloudDetailsScreen.java @@ -0,0 +1,121 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.gui.screen; + +import com.wildfire.gui.GuiUtils; +import com.wildfire.gui.WildfireButton; +import com.wildfire.main.WildfireGender; +import com.wildfire.main.WildfireGenderClient; +import com.wildfire.main.config.GlobalConfig; +import com.wildfire.main.entitydata.PlayerConfig; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; + +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +@Environment(EnvType.CLIENT) +public class WildfireCloudDetailsScreen extends BaseWildfireScreen { + + //TODO: PROPER TRANSLATIONS + + private static final Text TITLE = Text.translatable("wildfire_gender.cloud_details.title"); + + private static final Text PAGE_1 = Text.translatable("wildfire_gender.cloud_details.title").formatted(Formatting.UNDERLINE); + + private static final Text NEXT_PAGE = Text.translatable("wildfire_gender.details.next_page"); + private static final Text PREV_PAGE = Text.translatable("wildfire_gender.details.prev_page"); + private static final Identifier BACKGROUND = Identifier.of(WildfireGender.MODID, "textures/gui/details_page.png"); + + private int currentPage = 0; + + public WildfireCloudDetailsScreen(Screen parent, UUID uuid) { + super(Text.translatable("wildfire_gender.cloud_settings"), parent, uuid); + } + + @Override + public void init() { + int x = this.width / 2; + int y = this.height / 2; + + currentPage = 0; + + // why must Java be? + final var ref = new Object() { + WildfireButton no = null; + }; + + this.addDrawableChild(new WildfireButton(x + 46, y + 74, 76, 20, + NEXT_PAGE, + button -> { + if(currentPage < 1) { + currentPage++; + } + })); + + + this.addDrawableChild(ref.no = new WildfireButton(x - 128 + 6, y + 74, 76, 20, + PREV_PAGE, + button -> { + if(currentPage > 0) { + currentPage--; + } + })); + + super.init(); + } + + + @Override + public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delta) { + this.renderInGameBackground(ctx); + ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND, (this.width - 256) / 2, (this.height - 200) / 2, 0, 0, 256, 200, 256, 256); + } + + @Override + public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { + if (client == null || client.world == null) return; + super.render(ctx, mouseX, mouseY, delta); + + MatrixStack mStack = ctx.getMatrices(); + + int x = this.width / 2; + int y = this.height / 2; + + GuiUtils.drawCenteredText(ctx, textRenderer, TITLE, x, y - 94, 4473924); + + if (currentPage == 0) { + GuiUtils.drawCenteredTextWrapped(ctx, textRenderer, Text.translatable("wildfire_gender.cloud_details.page1"), x, y - 75, 256 - 10, 0x00FF00); + } + } + + @Override + public void close() { + super.close(); + } +} \ No newline at end of file diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java index 8159b505..23397e8a 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java @@ -96,7 +96,10 @@ public void init() { button -> close(), text -> GuiUtils.doneNarrationText())); this.addDrawableChild(btnHelp = new WildfireButton(this.width / 2 + 73 - 10, yPos - 11, 9, 9, Text.literal("?"), - button -> { /* TODO: Maybe make a new page with instructions / detail on how the sync feature works */ }, text -> GuiUtils.doneNarrationText())); + button -> { + //client.setScreen(new WildfireCloudDetailsScreen(this, client.player.getUuid())); // Disabled for now. Not complete + // BUTTON IS SUPPOSED TO DO NOTHING AT THE MOMENT + }, text -> GuiUtils.doneNarrationText())); btnHelp.setTooltip(Tooltip.of(Text.translatable("wildfire_gender.cloud.disclaimer.line1") .append("\n\n") .append(Text.translatable("wildfire_gender.cloud.disclaimer.line2")))); diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index 998bd5c5..bfb9ff50 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -93,5 +93,10 @@ "wildfire_gender.sync_log.sync_to_cloud": "Syncing Data to Cloud...", "wildfire_gender.sync_log.attempting_sync": "Syncing Profile...", "wildfire_gender.sync_log.sync_success": "Sync Successful.", - "wildfire_gender.sync_log.sync_too_frequently": "Sync Rate Limited." + "wildfire_gender.sync_log.sync_too_frequently": "Sync Rate Limited.", + + "wildfire_gender.details.next_page": "Next Page", + "wildfire_gender.details.prev_page": "Prev Page", + + "wildfire_gender.cloud_details.title": "Cloud Sync Server Information" } diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/details_page.png b/src/main/resources/assets/wildfire_gender/textures/gui/details_page.png new file mode 100644 index 0000000000000000000000000000000000000000..95d68f480d2f43f99ddb308a341942bd246fd247 GIT binary patch literal 1017 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911MRQ8&P5Fn=|)7d$|)7e=epeR2rGbfdS zLF4??iMAex14LT=gO@H6Wi{@wNLk@0=He=|mCJR7Kvrv*tWVr8c3-JUBI51`w?24q zKI!Vgqmkk4;f?}-7z@h^f=VU|z4zwQbz;@Gxp%zsz0AAa`~q33%Wlg)ZpgC;Gm@Kp zX=bSAmoB+4$_*S%T$|5YM*1ztt-X8Mchzo<^N0Sg3O{{|hr{E@<}+Jl9`QbU>3jUi z9)F!pO^XEQ3-?D}QTV8QJ#_wki|4jt70WV~PWpUAY2%dq#I3E72QPIl?@*qV7qD-_ zx5&qPjz;Q-MlAOKcc|c6MC3E8wTVYIcBr11uwYS>l2?Ji(z^;r-6ow&&i&&&FJ5iB z*E%H$OR<19zN4R;{~vsR^VZIjoc5YB%vSL+-;-H5-ZFh%^}0@ecivwX4m;*>x6ASC z7>(yI37;MHgyY3+HobMTXYFE-dB@P~zbr{}p2!l_Cq)N-OV$RnN$gYARuuz_*>QN^75_c-k!Ja{rQ(c`dd+p%7&%3z-W;U@Ck7R(*OVeKX&X` zXlSUpxp`Pv7#9~;c6K&Ud|KhvJ3xxFB*-rqs0Iueg8eImK!ODxk;M!QeEUI|(SkKt z1sK8iJzX3_D&pSWJecL=DByDOWN`BT|Kg{Og_dmVTWwV?yn@Ymj>D!Cvm*DNJu~a; z<=a;Ee=N;P*Vs2K-g-K9E`#NF7NE%>aAK!Ah^k0(R`Hxr`JBSMd4K`*lg;1eS zHw0jk5c)*2Gpc|>KQAajA^=?sIbOKe{2SW^DWJ7+`&r7%PUml4|0hho^!K&-2dnL> z<9FZZgu3X)|AY6lzFWiG_}l(R^xHa6AjC2J^q<^+D*b)-ckBO*QQ7P}<}hqNw|?td z)6GRXS!ZX>yn5S)aTW6f=V*qa3~mR$T*ea%br~$Tuqw1~V|a2wtU=}$6E!5SGTSgr XFga>>cq7jtkefYS{an^LB{Ts5=Fer{ literal 0 HcmV?d00001 From 25861c6c98d5f158cb4501240b2d639afd5e50c3 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Thu, 21 Nov 2024 21:47:14 -0500 Subject: [PATCH 175/238] Push version to 4.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index e33b5b15..531a241d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ yarn_build=1 loader_version=0.16.9 # Mod Properties -mod_version = 3.3-PRE2 +mod_version = 4.0 maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod From bb4822dce377c759d45572333e5a5eb6c47d323b Mon Sep 17 00:00:00 2001 From: PinguinLars1105 <68437296+PinguinLars@users.noreply.github.com> Date: Fri, 22 Nov 2024 07:35:25 +0100 Subject: [PATCH 176/238] Update nl_nl.json Added new lines, that I think don't do any thing. --- src/main/resources/assets/wildfire_gender/lang/nl_nl.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/resources/assets/wildfire_gender/lang/nl_nl.json b/src/main/resources/assets/wildfire_gender/lang/nl_nl.json index 46e1ab1b..07662a58 100644 --- a/src/main/resources/assets/wildfire_gender/lang/nl_nl.json +++ b/src/main/resources/assets/wildfire_gender/lang/nl_nl.json @@ -88,5 +88,9 @@ "wildfire_gender.sync_log.sync_success": "Sychronisatie Gelukt.", "wildfire_gender.sync_log.sync_too_frequently": "Sychronisatie Tarief is Beperkt.", "wildfire_gender.cloud.disclaimer.line1": "Server Sychronisatie date wordt mogelijk gecachd voor minimaal 30 minuten.", - "wildfire_gender.cloud.disclaimer.line2": "Als de aanpassen niet zichtbaar zijn, synchroniseer later opnieuw aub." + "wildfire_gender.cloud.disclaimer.line2": "Als de aanpassen niet zichtbaar zijn, synchroniseer later opnieuw aub.", + "wildfire_gender.details.next_page": "Volgende Pagina", + "wildfire_gender.details.prev_page": "Vorige Pagina", + "wildfire_gender.cloud_details.title": "Online Synchronisatie Server Informatie" } + From a092d458721bd83f7143c8769a2c9f98fb0e76c7 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Fri, 22 Nov 2024 17:43:25 -0500 Subject: [PATCH 177/238] Initial Voice Pitch Implementation --- gradle.properties | 2 +- .../gui/screen/WildfireCharacterSettingsScreen.java | 10 +++++++++- .../java/com/wildfire/main/config/Configuration.java | 2 ++ .../com/wildfire/main/entitydata/EntityConfig.java | 7 +++++++ .../com/wildfire/main/entitydata/PlayerConfig.java | 8 ++++++++ .../wildfire/main/networking/AbstractSyncPacket.java | 11 ++++++++--- .../main/networking/ClientboundSyncPacket.java | 4 ++-- .../main/networking/ServerboundSyncPacket.java | 4 ++-- .../java/com/wildfire/mixins/LivingEntityMixin.java | 4 ++-- .../resources/assets/wildfire_gender/lang/en_us.json | 1 + 10 files changed, 42 insertions(+), 11 deletions(-) diff --git a/gradle.properties b/gradle.properties index 531a241d..addb0c75 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ yarn_build=1 loader_version=0.16.9 # Mod Properties -mod_version = 4.0 +mod_version = 4.0.1 maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java index dfe12cde..8a271e9d 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java @@ -46,7 +46,7 @@ public class WildfireCharacterSettingsScreen extends BaseWildfireScreen { private static final Text DISABLED = Text.translatable("wildfire_gender.label.disabled").formatted(Formatting.RED); private static final Identifier BACKGROUND = Identifier.of(WildfireGender.MODID, "textures/gui/settings_bg.png"); - private WildfireSlider bounceSlider, floppySlider; + private WildfireSlider bounceSlider, floppySlider, voicePitchSlider; private boolean bounceWarning; protected WildfireCharacterSettingsScreen(Screen parent, UUID uuid) { @@ -121,6 +121,14 @@ public void init() { } }, Tooltip.of(Text.translatable("wildfire_gender.tooltip.hurt_sounds")))); + this.addDrawableChild(this.voicePitchSlider = new WildfireSlider(xPos, yPos + 120, 158, 20, Configuration.VOICE_PITCH, aPlr.getVoicePitch(), value -> { + }, value -> Text.translatable("wildfire_gender.slider.voice_pitch", Math.round(value * 100)), value -> { + if (aPlr.updateVoicePitch(value)) { + PlayerConfig.saveGenderInfo(aPlr); + } + })); + this.voicePitchSlider.setArrowKeyStep(0.01); + this.addDrawableChild(new WildfireButton(this.width / 2 + 73, yPos - 11, 9, 9, Text.literal("X"), button -> close(), text -> GuiUtils.doneNarrationText())); diff --git a/src/main/java/com/wildfire/main/config/Configuration.java b/src/main/java/com/wildfire/main/config/Configuration.java index 5eb6ecf3..d5d88578 100644 --- a/src/main/java/com/wildfire/main/config/Configuration.java +++ b/src/main/java/com/wildfire/main/config/Configuration.java @@ -42,6 +42,8 @@ public class Configuration extends AbstractConfiguration { public static final FloatConfigKey BOUNCE_MULTIPLIER = new FloatConfigKey("bounce_multiplier", 0.333F, 0, 0.5f); public static final FloatConfigKey FLOPPY_MULTIPLIER = new FloatConfigKey("floppy_multiplier", 0.75F, 0.25f, 1); + public static final FloatConfigKey VOICE_PITCH = new FloatConfigKey("voice_pitch", 1F, 0.8f, 1.2f); + public Configuration(String cfgName) { super(CONFIG_DIR, cfgName); } diff --git a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java index 87c21aeb..c9b846af 100644 --- a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java +++ b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java @@ -67,6 +67,9 @@ public class EntityConfig { protected boolean breastPhysics = Configuration.BREAST_PHYSICS.getDefault(); protected float bounceMultiplier = Configuration.BOUNCE_MULTIPLIER.getDefault(); protected float floppyMultiplier = Configuration.FLOPPY_MULTIPLIER.getDefault(); + + protected float voicePitch = Configuration.VOICE_PITCH.getDefault(); + // note: hurt sounds, armor physics override, and show in armor are not defined here, as they have no relevance // to entities, and are instead entirely in PlayerConfig @@ -165,6 +168,10 @@ public float getFloppiness() { return this.floppyMultiplier; } + public float getVoicePitch() { + return this.voicePitch; + } + public @NotNull BreastPhysics getLeftBreastPhysics() { return lBreastPhysics; } diff --git a/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java b/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java index a860d275..48a33c21 100644 --- a/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java +++ b/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java @@ -71,6 +71,7 @@ public PlayerConfig(UUID uuid) { this.cfg.setDefault(Configuration.SHOW_IN_ARMOR); this.cfg.setDefault(Configuration.BOUNCE_MULTIPLIER); this.cfg.setDefault(Configuration.FLOPPY_MULTIPLIER); + this.cfg.setDefault(Configuration.VOICE_PITCH); } // this shouldn't ever be called on players, but just to be safe, override with a noop. @@ -101,6 +102,10 @@ public boolean hasHurtSounds() { return hurtSounds; } + public boolean updateVoicePitch(float value) { + return updateValue(Configuration.VOICE_PITCH, value, v -> this.voicePitch = v); + } + public boolean updateHurtSounds(boolean value) { return updateValue(Configuration.HURT_SOUNDS, value, v -> this.hurtSounds = v); } @@ -163,6 +168,7 @@ public void loadFromConfig(boolean markForSync) { updateGender(cfg.get(Configuration.GENDER)); updateBustSize(cfg.get(Configuration.BUST_SIZE)); updateHurtSounds(cfg.get(Configuration.HURT_SOUNDS)); + updateVoicePitch(cfg.get(Configuration.VOICE_PITCH)); //physics updateBreastPhysics(cfg.get(Configuration.BREAST_PHYSICS)); @@ -176,6 +182,7 @@ public void loadFromConfig(boolean markForSync) { breasts.updateZOffset(cfg.get(Configuration.BREASTS_OFFSET_Z)); breasts.updateUniboob(cfg.get(Configuration.BREASTS_UNIBOOB)); breasts.updateCleavage(cfg.get(Configuration.BREASTS_CLEAVAGE)); + if(markForSync) { this.needsSync = true; } @@ -199,6 +206,7 @@ public static void saveGenderInfo(PlayerConfig plr) { config.set(Configuration.GENDER, plr.getGender()); config.set(Configuration.BUST_SIZE, plr.getBustSize()); config.set(Configuration.HURT_SOUNDS, plr.hasHurtSounds()); + config.set(Configuration.VOICE_PITCH, plr.getVoicePitch()); //physics config.set(Configuration.BREAST_PHYSICS, plr.hasBreastPhysics()); diff --git a/src/main/java/com/wildfire/main/networking/AbstractSyncPacket.java b/src/main/java/com/wildfire/main/networking/AbstractSyncPacket.java index 07dcdad8..d0287e3f 100644 --- a/src/main/java/com/wildfire/main/networking/AbstractSyncPacket.java +++ b/src/main/java/com/wildfire/main/networking/AbstractSyncPacket.java @@ -19,6 +19,7 @@ package com.wildfire.main.networking; import com.mojang.datafixers.util.Function6; +import com.mojang.datafixers.util.Function7; import com.wildfire.main.entitydata.Breasts; import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.Gender; @@ -37,6 +38,7 @@ protected static PacketCodec codec(Sy Gender.CODEC, p -> p.gender, PacketCodecs.FLOAT, p -> p.bustSize, PacketCodecs.BOOL, p -> p.hurtSounds, + PacketCodecs.FLOAT, p -> p.voicePitch, BreastPhysics.CODEC, p -> p.physics, Breasts.CODEC, p -> p.breasts, constructor @@ -47,26 +49,29 @@ protected static PacketCodec codec(Sy protected final Gender gender; protected final float bustSize; protected final boolean hurtSounds; + protected final float voicePitch; protected final BreastPhysics physics; protected final Breasts breasts; - protected AbstractSyncPacket(UUID uuid, Gender gender, float bustSize, boolean hurtSounds, BreastPhysics physics, Breasts breasts) { + protected AbstractSyncPacket(UUID uuid, Gender gender, float bustSize, boolean hurtSounds, float voicePitch, BreastPhysics physics, Breasts breasts) { this.uuid = uuid; this.gender = gender; this.bustSize = bustSize; this.hurtSounds = hurtSounds; + this.voicePitch = voicePitch; this.physics = physics; this.breasts = breasts; } protected AbstractSyncPacket(PlayerConfig plr) { - this(plr.uuid, plr.getGender(), plr.getBustSize(), plr.hasHurtSounds(), new BreastPhysics(plr), plr.getBreasts()); + this(plr.uuid, plr.getGender(), plr.getBustSize(), plr.hasHurtSounds(), plr.getVoicePitch(), new BreastPhysics(plr), plr.getBreasts()); } protected void updatePlayerFromPacket(PlayerConfig plr) { plr.updateGender(gender); plr.updateBustSize(bustSize); plr.updateHurtSounds(hurtSounds); + plr.updateVoicePitch(voicePitch); physics.applyTo(plr); plr.getBreasts().copyFrom(breasts); } @@ -94,6 +99,6 @@ private void applyTo(PlayerConfig plr) { } @FunctionalInterface - protected interface SyncPacketConstructor extends Function6 { + protected interface SyncPacketConstructor extends Function7 { } } diff --git a/src/main/java/com/wildfire/main/networking/ClientboundSyncPacket.java b/src/main/java/com/wildfire/main/networking/ClientboundSyncPacket.java index 623bd57d..8a137d4a 100644 --- a/src/main/java/com/wildfire/main/networking/ClientboundSyncPacket.java +++ b/src/main/java/com/wildfire/main/networking/ClientboundSyncPacket.java @@ -43,8 +43,8 @@ public ClientboundSyncPacket(PlayerConfig plr) { super(plr); } - private ClientboundSyncPacket(UUID uuid, Gender gender, float bustSize, boolean hurtSounds, BreastPhysics physics, Breasts breasts) { - super(uuid, gender, bustSize, hurtSounds, physics, breasts); + private ClientboundSyncPacket(UUID uuid, Gender gender, float bustSize, boolean hurtSounds, float voicePitch, BreastPhysics physics, Breasts breasts) { + super(uuid, gender, bustSize, hurtSounds, voicePitch, physics, breasts); } @Override diff --git a/src/main/java/com/wildfire/main/networking/ServerboundSyncPacket.java b/src/main/java/com/wildfire/main/networking/ServerboundSyncPacket.java index c874896f..cc58841f 100644 --- a/src/main/java/com/wildfire/main/networking/ServerboundSyncPacket.java +++ b/src/main/java/com/wildfire/main/networking/ServerboundSyncPacket.java @@ -43,8 +43,8 @@ public ServerboundSyncPacket(PlayerConfig plr) { super(plr); } - private ServerboundSyncPacket(UUID uuid, Gender gender, float bustSize, boolean hurtSounds, BreastPhysics physics, Breasts breasts) { - super(uuid, gender, bustSize, hurtSounds, physics, breasts); + private ServerboundSyncPacket(UUID uuid, Gender gender, float bustSize, boolean hurtSounds, float voicePitch, BreastPhysics physics, Breasts breasts) { + super(uuid, gender, bustSize, hurtSounds, voicePitch, physics, breasts); } @Override diff --git a/src/main/java/com/wildfire/mixins/LivingEntityMixin.java b/src/main/java/com/wildfire/mixins/LivingEntityMixin.java index 2bdf07ae..d9edc6b2 100644 --- a/src/main/java/com/wildfire/mixins/LivingEntityMixin.java +++ b/src/main/java/com/wildfire/mixins/LivingEntityMixin.java @@ -52,8 +52,8 @@ abstract class LivingEntityMixin { SoundEvent hurtSound = genderPlayer.getGender().getHurtSound(); if(hurtSound != null) { - float pitch = (player.getRandom().nextFloat() - player.getRandom().nextFloat()) * 0.2F + 1.0F; - player.playSound(hurtSound, 1f, pitch); + float pitch = (player.getRandom().nextFloat() - player.getRandom().nextFloat()) * 0.2F /*+ 1.0F*/; // +1 is from getVoicePitch() + player.playSound(hurtSound, 1f, pitch + genderPlayer.getVoicePitch()); } } } diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index bfb9ff50..6c19ada5 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -25,6 +25,7 @@ "wildfire_gender.wardrobe.slider.height": "Height: %s", "wildfire_gender.wardrobe.slider.depth": "Depth: %s", "wildfire_gender.wardrobe.slider.rotation": "Rotation: %s degrees", + "wildfire_gender.slider.voice_pitch": "Voice Pitch: %s%%", "wildfire_gender.appearance_settings.title": "Appearance Settings", "wildfire_gender.char_settings.title": "Character Settings", From 355238002a0dbc5efdaa8c75d3bc743a24a848df Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Fri, 22 Nov 2024 19:58:25 -0500 Subject: [PATCH 178/238] Voice Pitch Improvements - UI doesn't overflow anymore - Letting go of voice pitch slider plays hurt sound. --- .../screen/WildfireCharacterSettingsScreen.java | 12 +++++++++++- .../textures/gui/settings_bg.png | Bin 662 -> 670 bytes 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java index 8a271e9d..fd793014 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java @@ -21,6 +21,7 @@ import com.wildfire.gui.GuiUtils; import com.wildfire.gui.WildfireSlider; import com.wildfire.main.WildfireGender; +import com.wildfire.main.WildfireSounds; import com.wildfire.main.config.Configuration; import java.util.Objects; @@ -30,11 +31,13 @@ import com.wildfire.main.entitydata.PlayerConfig; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.tooltip.Tooltip; import net.minecraft.client.render.RenderLayer; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.sound.SoundEvent; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; @@ -125,6 +128,13 @@ public void init() { }, value -> Text.translatable("wildfire_gender.slider.voice_pitch", Math.round(value * 100)), value -> { if (aPlr.updateVoicePitch(value)) { PlayerConfig.saveGenderInfo(aPlr); + if(client.player != null) { + SoundEvent hurtSound = aPlr.getGender().getHurtSound(); + if(hurtSound != null) { + float pitch = (client.player.getRandom().nextFloat() - client.player.getRandom().nextFloat()) * 0.2F /*+ 1.0F*/; // +1 is from getVoicePitch() + client.player.playSound(hurtSound, 1f, pitch + aPlr.getVoicePitch()); + } + } } })); this.voicePitchSlider.setArrowKeyStep(0.01); @@ -139,7 +149,7 @@ public void init() { public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delta) { this.renderInGameBackground(ctx); - ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND, (this.width - 172) / 2, (this.height - 124) / 2, 0, 0, 172, 144, 256, 256); + ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND, (this.width - 172) / 2, (this.height - 124) / 2, 0, 0, 172, 164, 256, 256); } @Override diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/settings_bg.png b/src/main/resources/assets/wildfire_gender/textures/gui/settings_bg.png index 1ef3afa669c77d5f1a378da589c3f05423fc4885..ca83893c90e53c91348f549f55a512f7f2b68412 100644 GIT binary patch literal 670 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911MRQ8&P5D>38$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&eBzI1?3h%1o(|NsB7W5+^6L(R?2!@|P2xVW;jvw`B%3a{P)Qk*40e!)OB zV89UUUm*k%EbxddW?{?7RI&Z$5Q4 z@he}r)o+~tHlz5?#XFa0JT7#f&2mil|Nn}2f)(c^ii7$ z#yK(D9prN`n9v_D2xB|kv(bXF8*2V^!PpF+o&|y&0|rkXhqJ)g6U_TT0S^WW^81xw z?1nv+pm2u(s87J0CubLfT+wis;r-&<`~|>x0QuyFRKs<>21~p9`xf~!6hB$$eEZXX z`3B2(8uRxp^PRs}oxySot3vxWh9?)q8f0!UO>mB8D9Ye=;LBw^u~2t#NPcD5KQro> UoU>IWC|xjky85}Sb4q9e0MyIJUjP6A literal 662 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911MRQ8&P5D>38$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&eBzI1?3h%1o(|NsB7W5+^6L(R?2!@|P2xVW;jvw`B%3a{P)Qk*40e!)OB zV89UUUm*k%EbxddW?V|b_jr9wxrg8517S)k(`^5#ha ztzZE0Hi$gPe!b70;lt))e~=guz@)+KdAE$gf8U#%9=AnFVK8W}Sz# zi#}h0vp-##2MRqfc=Bu|g1yof6r5mi;%qR29bC-@6Q3}9IfA|X8_xlDhI!H3 Date: Fri, 22 Nov 2024 20:08:19 -0500 Subject: [PATCH 179/238] WildfireSlider.java now has a proper "disabled" state --- .../java/com/wildfire/gui/WildfireSlider.java | 16 ++++++++++++---- .../screen/WildfireCharacterSettingsScreen.java | 2 ++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/wildfire/gui/WildfireSlider.java b/src/main/java/com/wildfire/gui/WildfireSlider.java index a850835d..77cf393f 100644 --- a/src/main/java/com/wildfire/gui/WildfireSlider.java +++ b/src/main/java/com/wildfire/gui/WildfireSlider.java @@ -132,15 +132,23 @@ protected void renderWidget(DrawContext ctx, int mouseX, int mouseY, float delta int xP = getX() + 2; ctx.fill(xP - 2, getY(), getX() + this.width - 1, getY() + this.height, 0x222222 + (128 << 24)); int xPos = getX() + 2 + (int) (this.value * (float)(this.width - 3)); - ctx.fill(getX() + 1, getY() + 1, xPos - 1, getY() + this.height - 1, 0x222266 + (180 << 24)); - int xPos2 = this.getX() + 3 + (int) (this.value * (float)(this.width - 5)); - ctx.fill(xPos2 - 2, getY() + 1, xPos2, getY() + this.height - 1, 0xFFFFFF + (120 << 24)); + ctx.fill(getX() + 1, getY() + 1, xPos - 1, getY() + this.height - 1, active?(0x222266 + (180 << 24)):(0x111133 + (180 << 24))); + + if(active) { + int xPos2 = this.getX() + 3 + (int) (this.value * (float) (this.width - 5)); + ctx.fill(xPos2 - 2, getY() + 1, xPos2, getY() + this.height - 1, 0xFFFFFF + (120 << 24)); + } RenderSystem.enableDepthTest(); TextRenderer font = MinecraftClient.getInstance().textRenderer; int i = this.getX() + 2; int j = this.getX() + this.getWidth() - 2; - GuiUtils.drawScrollableTextWithoutShadow(GuiUtils.Justify.CENTER, ctx, font, this.getMessage(), i, this.getY(), j, this.getY() + this.getHeight(), isSelected() || changed ? 0xFFFF55 : 0xFFFFFF); + + int textColor = (isSelected()&&active) || changed ? 0xFFFF55 : 0xFFFFFF; + if(!active) { + textColor = 0x666666; + } + GuiUtils.drawScrollableTextWithoutShadow(GuiUtils.Justify.CENTER, ctx, font, this.getMessage(), i, this.getY(), j, this.getY() + this.getHeight(), textColor); } } diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java index fd793014..1a8a7742 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java @@ -119,6 +119,7 @@ public void init() { Text.translatable("wildfire_gender.char_settings.hurt_sounds", aPlr.hasHurtSounds() ? ENABLED : DISABLED), button -> { boolean enableHurtSounds = !aPlr.hasHurtSounds(); if (aPlr.updateHurtSounds(enableHurtSounds)) { + voicePitchSlider.active = aPlr.hasHurtSounds(); button.setMessage(Text.translatable("wildfire_gender.char_settings.hurt_sounds", enableHurtSounds ? ENABLED : DISABLED)); PlayerConfig.saveGenderInfo(aPlr); } @@ -137,6 +138,7 @@ public void init() { } } })); + voicePitchSlider.active = aPlr.hasHurtSounds(); this.voicePitchSlider.setArrowKeyStep(0.01); this.addDrawableChild(new WildfireButton(this.width / 2 + 73, yPos - 11, 9, 9, Text.literal("X"), From 5710c62247ae56b230a242680c286d58cfcc1402 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Fri, 22 Nov 2024 20:45:27 -0500 Subject: [PATCH 180/238] Disable breast physics buttons/sliders when physics are off. --- .../screen/WildfireCharacterSettingsScreen.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java index 1a8a7742..456d6ebe 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java @@ -50,6 +50,7 @@ public class WildfireCharacterSettingsScreen extends BaseWildfireScreen { private static final Identifier BACKGROUND = Identifier.of(WildfireGender.MODID, "textures/gui/settings_bg.png"); private WildfireSlider bounceSlider, floppySlider, voicePitchSlider; + private WildfireButton btnHideInArmor, btnOverrideArmorPhys; private boolean bounceWarning; protected WildfireCharacterSettingsScreen(Screen parent, UUID uuid) { @@ -68,12 +69,18 @@ public void init() { Text.translatable("wildfire_gender.char_settings.physics", aPlr.hasBreastPhysics() ? ENABLED : DISABLED), button -> { boolean enablePhysics = !aPlr.hasBreastPhysics(); if (aPlr.updateBreastPhysics(enablePhysics)) { + + this.bounceSlider.active = aPlr.hasBreastPhysics(); + this.floppySlider.active = aPlr.hasBreastPhysics(); + //this.btnHideInArmor.active = aPlr.hasBreastPhysics(); + this.btnOverrideArmorPhys.active = aPlr.hasBreastPhysics(); + button.setMessage(Text.translatable("wildfire_gender.char_settings.physics", enablePhysics ? ENABLED : DISABLED)); PlayerConfig.saveGenderInfo(aPlr); } })); - this.addDrawableChild(new WildfireButton(xPos, yPos + 20, 157, 20, + this.addDrawableChild(btnHideInArmor = new WildfireButton(xPos, yPos + 20, 157, 20, Text.translatable("wildfire_gender.char_settings.hide_in_armor", aPlr.showBreastsInArmor() ? DISABLED : ENABLED), button -> { boolean enableShowInArmor = !aPlr.showBreastsInArmor(); if (aPlr.updateShowBreastsInArmor(enableShowInArmor)) { @@ -81,8 +88,10 @@ public void init() { PlayerConfig.saveGenderInfo(aPlr); } })); + //this.btnHideInArmor.active = aPlr.hasBreastPhysics(); + - this.addDrawableChild(new WildfireButton(xPos, yPos + 40, 157, 20, + this.addDrawableChild(btnOverrideArmorPhys = new WildfireButton(xPos, yPos + 40, 157, 20, Text.translatable("wildfire_gender.char_settings.override_armor_physics", aPlr.getArmorPhysicsOverride() ? ENABLED : DISABLED), button -> { boolean enableArmorPhysicsOverride = !aPlr.getArmorPhysicsOverride(); if (aPlr.updateArmorPhysicsOverride(enableArmorPhysicsOverride )) { @@ -93,6 +102,7 @@ public void init() { .append("\n\n") .append(Text.translatable("wildfire_gender.tooltip.override_armor_physics.line2"))) )); + this.btnOverrideArmorPhys.active = aPlr.hasBreastPhysics(); this.addDrawableChild(this.bounceSlider = new WildfireSlider(xPos, yPos + 60, 158, 20, Configuration.BOUNCE_MULTIPLIER, aPlr.getBounceMultiplier(), value -> { }, value -> { @@ -105,6 +115,7 @@ public void init() { PlayerConfig.saveGenderInfo(aPlr); } })); + this.bounceSlider.active = aPlr.hasBreastPhysics(); this.bounceSlider.setArrowKeyStep(0.005); this.addDrawableChild(this.floppySlider = new WildfireSlider(xPos, yPos + 80, 158, 20, Configuration.FLOPPY_MULTIPLIER, aPlr.getFloppiness(), value -> { @@ -113,6 +124,7 @@ public void init() { PlayerConfig.saveGenderInfo(aPlr); } })); + this.floppySlider.active = aPlr.hasBreastPhysics(); this.floppySlider.setArrowKeyStep(0.01); this.addDrawableChild(new WildfireButton(xPos, yPos + 100, 157, 20, From b94b94b655a2b7c30e1979cd1eeb3c1da321ead6 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Fri, 22 Nov 2024 20:52:52 -0500 Subject: [PATCH 181/238] Update version 4.1 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index addb0c75..97906b1b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ yarn_build=1 loader_version=0.16.9 # Mod Properties -mod_version = 4.0.1 +mod_version = 4.1 maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod From fad34624fd8142f8ab6f8a0ff112242a12b67efe Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sat, 23 Nov 2024 18:39:20 -0500 Subject: [PATCH 182/238] Major UI Rework In Progress --- src/main/java/com/wildfire/gui/GuiUtils.java | 6 + .../java/com/wildfire/gui/WildfireSlider.java | 4 +- .../gui/screen/WardrobeBrowserScreen.java | 49 +++--- .../WildfireBreastCustomizationScreen.java | 144 ++++++++++-------- .../wildfire/main/WildfireLocalization.java | 5 +- .../com/wildfire/main/cloud/CloudSync.java | 9 +- .../java/com/wildfire/main/cloud/SyncLog.java | 4 + .../assets/wildfire_gender/lang/en_us.json | 12 +- .../assets/wildfire_gender/textures/cloud.png | Bin 697 -> 877 bytes .../textures/gui/wardrobe_bg.png | Bin 720 -> 0 bytes .../textures/gui/wardrobe_bg3.png | Bin 717 -> 0 bytes 11 files changed, 141 insertions(+), 92 deletions(-) delete mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/wardrobe_bg.png delete mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/wardrobe_bg3.png diff --git a/src/main/java/com/wildfire/gui/GuiUtils.java b/src/main/java/com/wildfire/gui/GuiUtils.java index db9bd118..b227118d 100644 --- a/src/main/java/com/wildfire/gui/GuiUtils.java +++ b/src/main/java/com/wildfire/gui/GuiUtils.java @@ -108,6 +108,11 @@ public static void drawEntityOnScreen(DrawContext ctx, int x, int y, int size, f float m = entity.getPitch(); float n = entity.prevHeadYaw; float o = entity.headYaw; + + ctx.getMatrices().push(); + + ctx.getMatrices().translate(0, 0, 50.0); //prevent rear model clipping + entity.bodyYaw = 180.0F + i * 20.0F; entity.setYaw(180.0F + i * 40.0F); entity.setPitch(-j * 20.0F); @@ -121,5 +126,6 @@ public static void drawEntityOnScreen(DrawContext ctx, int x, int y, int size, f entity.setPitch(m); entity.prevHeadYaw = n; entity.headYaw = o; + ctx.getMatrices().pop(); } } diff --git a/src/main/java/com/wildfire/gui/WildfireSlider.java b/src/main/java/com/wildfire/gui/WildfireSlider.java index 77cf393f..6381d1f5 100644 --- a/src/main/java/com/wildfire/gui/WildfireSlider.java +++ b/src/main/java/com/wildfire/gui/WildfireSlider.java @@ -130,13 +130,13 @@ protected void renderWidget(DrawContext ctx, int mouseX, int mouseY, float delta RenderSystem.disableDepthTest(); int xP = getX() + 2; - ctx.fill(xP - 2, getY(), getX() + this.width - 1, getY() + this.height, 0x222222 + (128 << 24)); + ctx.fill(xP - 2, getY(), getX() + this.width, getY() + this.height, 0x222222 + (128 << 24)); int xPos = getX() + 2 + (int) (this.value * (float)(this.width - 3)); ctx.fill(getX() + 1, getY() + 1, xPos - 1, getY() + this.height - 1, active?(0x222266 + (180 << 24)):(0x111133 + (180 << 24))); if(active) { - int xPos2 = this.getX() + 3 + (int) (this.value * (float) (this.width - 5)); + int xPos2 = this.getX() + 3 + (int) (this.value * (float) (this.width - 4)); ctx.fill(xPos2 - 2, getY() + 1, xPos2, getY() + this.height - 1, 0xFFFFFF + (120 << 24)); } RenderSystem.enableDepthTest(); diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index bffb9e5d..d1a1e15c 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -48,13 +48,16 @@ @Environment(EnvType.CLIENT) public class WardrobeBrowserScreen extends BaseWildfireScreen { - private static final Identifier BACKGROUND_FEMALE = Identifier.of(WildfireGender.MODID, "textures/gui/wardrobe_bg2.png"); - private static final Identifier BACKGROUND = Identifier.of(WildfireGender.MODID, "textures/gui/wardrobe_bg3.png"); + private static final Identifier BACKGROUND_MALE = Identifier.of(WildfireGender.MODID, "textures/gui/wardrobe_bg_male.png"); + private static final Identifier BACKGROUND_FEMALE = Identifier.of(WildfireGender.MODID, "textures/gui/wardrobe_bg_female.png"); + private static final Identifier BACKGROUND_OTHER = Identifier.of(WildfireGender.MODID, "textures/gui/wardrobe_bg_other.png"); + private static final Identifier TXTR_RIBBON = Identifier.of(WildfireGender.MODID, "textures/bc_ribbon.png"); private static final Identifier CLOUD_ICON = Identifier.of(WildfireGender.MODID, "textures/cloud.png"); private static final boolean isBreastCancerAwarenessMonth = Calendar.getInstance().get(Calendar.MONTH) == Calendar.OCTOBER; + private WildfireButton btnMale, btnFemale, btnOther, btnCharacterPersonalization; public WardrobeBrowserScreen(Screen parent, UUID uuid) { super(Text.translatable("wildfire_gender.wardrobe.title"), parent, uuid); } @@ -65,7 +68,7 @@ public void init() { int y = this.height / 2; PlayerConfig plr = Objects.requireNonNull(getPlayer(), "getPlayer()"); - this.addDrawableChild(new WildfireButton(this.width / 2 - 42, y - 52, 158, 20, getGenderLabel(plr.getGender()), button -> { + this.addDrawableChild(btnFemale = new WildfireButton(this.width / 2 - 128, this.height / 2 + 35, 80, 15, plr.getGender().getDisplayName(), button -> { Gender gender = switch (plr.getGender()) { case MALE -> Gender.FEMALE; case FEMALE -> Gender.OTHER; @@ -78,21 +81,21 @@ public void init() { } })); - if (plr.getGender().canHaveBreasts()) { - this.addDrawableChild(new WildfireButton(this.width / 2 - 42, y - 32, 158, 20, Text.translatable("wildfire_gender.appearance_settings.title").append("..."), - button -> client.setScreen(new WildfireBreastCustomizationScreen(WardrobeBrowserScreen.this, this.playerUUID)))); - } - this.addDrawableChild(new WildfireButton(this.width / 2 - 42, y - (plr.getGender().canHaveBreasts() ? 12 : 32), 158, 20, Text.translatable("wildfire_gender.char_settings.title").append("..."), - button -> client.setScreen(new WildfireCharacterSettingsScreen(WardrobeBrowserScreen.this, this.playerUUID)))); + this.addDrawableChild(this.btnCharacterPersonalization = new WildfireButton(this.width / 2 - 34, this.height / 2 - 51, 158, 20, Text.translatable("wildfire_gender.appearance_settings.title").append("..."), + button -> client.setScreen(new WildfireBreastCustomizationScreen(WardrobeBrowserScreen.this, this.playerUUID)))); + + this.btnCharacterPersonalization.active = plr.getGender().canHaveBreasts(); + /*this.addDrawableChild(new WildfireButton(this.width / 2 - 42, y - (plr.getGender().canHaveBreasts() ? 12 : 32), 158, 20, Text.translatable("wildfire_gender.char_settings.title").append("..."), + button -> client.setScreen(new WildfireCharacterSettingsScreen(WardrobeBrowserScreen.this, this.playerUUID))));*/ //noinspection ExtractMethodRecommender var cloud = new WildfireButton( - this.width / 2 + 97, y - 63, 12, 9, Text.translatable("wildfire_gender.cloud_settings"), + this.width / 2 - 34, y + 32, 24, 18, Text.translatable("wildfire_gender.cloud_settings"), button -> client.setScreen(new WildfireCloudSyncScreen(this, this.playerUUID)) ) { @Override protected void drawInner(DrawContext ctx, int mouseX, int mouseY, float partialTicks) { - ctx.drawTexture(RenderLayer::getGuiTextured, CLOUD_ICON, getX() + 1, getY() + 1, 0, 0, 10, 7, 17, 13, 16, 13); + ctx.drawTexture(RenderLayer::getGuiTextured, CLOUD_ICON, getX() + 2, getY() + 2, 0, 0, 20, 14, 32, 26, 32, 26); } }; @@ -104,8 +107,9 @@ protected void drawInner(DrawContext ctx, int mouseX, int mouseY, float partialT } this.addDrawableChild(cloud); - this.addDrawableChild(new WildfireButton(this.width / 2 + 111, y - 63, 9, 9, Text.literal("X"), - button -> close(), text -> GuiUtils.doneNarrationText())); + + /*this.addDrawableChild(new WildfireButton(this.width / 2 + 111, y - 63, 9, 9, Text.literal("X"), + button -> close(), text -> GuiUtils.doneNarrationText()));*/ super.init(); } @@ -120,16 +124,21 @@ public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delt PlayerConfig plr = getPlayer(); if(plr == null) return; - Identifier backgroundTexture = plr.getGender().canHaveBreasts() ? BACKGROUND_FEMALE : BACKGROUND; - ctx.drawTexture(RenderLayer::getGuiTextured, backgroundTexture, (this.width - 248) / 2, (this.height - 134) / 2, 0, 0, 248, 156, 256, 256); + Identifier backgroundTexture = switch(plr.getGender()) { + case Gender.MALE -> BACKGROUND_MALE; + case Gender.FEMALE -> BACKGROUND_FEMALE; + case Gender.OTHER -> BACKGROUND_OTHER; + }; + + ctx.drawTexture(RenderLayer::getGuiTextured, backgroundTexture, (this.width - 268) / 2, (this.height - 114) / 2, 0, 0, 268, 114, 512, 512); if(client != null && client.world != null) { - int xP = this.width / 2 - 82; - int yP = this.height / 2 + 40; + int xP = this.width / 2 - 88; + int yP = this.height / 2 + 20; PlayerEntity ent = client.world.getPlayerByUuid(this.playerUUID); if(ent != null) { - ctx.enableScissor(xP - 35, yP - 93, xP + 35, yP + 6); - GuiUtils.drawEntityOnScreen(ctx, xP, yP, 45, (xP - mouseX), (yP - 76 - mouseY), ent); + ctx.enableScissor(xP - 34, yP - 97, xP + 35, yP + 9); + GuiUtils.drawEntityOnScreen(ctx, xP, yP + 60, 65, (xP - mouseX), (yP - 46 - mouseY), ent); ctx.disableScissor(); } } @@ -140,7 +149,7 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { super.render(ctx, mouseX, mouseY, delta); int x = this.width / 2; int y = this.height / 2; - ctx.drawText(textRenderer, title, x - 118, y - 62, 4473924, false); + ctx.drawText(textRenderer, getTitle(), x - textRenderer.getWidth(getTitle()) / 2, y - 68, 0xFFFFFF, false); if(client != null && client.player != null) { boolean withCreator = client.player.networkHandler.getPlayerList().stream() diff --git a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java index 5577f3c6..9f4bb556 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java @@ -22,6 +22,8 @@ import com.wildfire.gui.WildfireBreastPresetList; import com.wildfire.gui.WildfireButton; import com.wildfire.gui.WildfireSlider; +import com.wildfire.main.Gender; +import com.wildfire.main.WildfireGender; import com.wildfire.main.entitydata.Breasts; import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.config.Configuration; @@ -33,8 +35,10 @@ import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.render.RenderLayer; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.text.Text; +import net.minecraft.util.Identifier; import java.util.Objects; import java.util.UUID; @@ -42,9 +46,14 @@ @Environment(EnvType.CLIENT) public class WildfireBreastCustomizationScreen extends BaseWildfireScreen { + private static final Identifier BACKGROUND_FEMALE = Identifier.of(WildfireGender.MODID, "textures/gui/breast_customization.png"); + private static final Identifier BACKGROUND_OTHER = Identifier.of(WildfireGender.MODID, "textures/gui/breast_customization_other.png"); + private static final Identifier BACKGROUND_CUSTOMIZATION = Identifier.of(WildfireGender.MODID, "textures/gui/breast_customization_tab.png"); + private static final Identifier BACKGROUND_PHYSICS = Identifier.of(WildfireGender.MODID, "textures/gui/breast_physics_tab.png"); + private WildfireSlider breastSlider, xOffsetBoobSlider, yOffsetBoobSlider, zOffsetBoobSlider, cleavageSlider; - private WildfireButton btnDualPhysics, btnPresets, btnCustomization; - private WildfireButton btnAddPreset, btnDeletePreset; + private WildfireButton btnDualPhysics, btnPhysics, btnCustomization, btnMiscellaneous; + //private WildfireButton btnAddPreset, btnDeletePreset; private WildfireBreastPresetList PRESET_LIST; private int currentTab = 0; // 0 = customization, 1 = presets @@ -65,75 +74,59 @@ public void init() { }; //Customization Tab - this.addDrawableChild(btnCustomization = new WildfireButton(this.width / 2 + 30, j - 60, 158, 10, + this.addDrawableChild(btnCustomization = new WildfireButton(this.width / 2 - 130, j + 54, 172/2 - 2, 10, Text.translatable("wildfire_gender.breast_customization.tab_customization"), button -> { currentTab = 0; - btnCustomization.active = false; - btnPresets.active = true; - btnAddPreset.visible = false; - btnDeletePreset.visible = false; + updateTabs(); })).setActive(false); - //Presets Tab - this.addDrawableChild(btnPresets = new WildfireButton(this.width / 2 + 31 + 158/2, j - 60, 158 / 2 - 1, 10, - Text.translatable("wildfire_gender.breast_customization.tab_presets"), button -> { - // TODO temporary release readiness fix: lock presets tab behind a development environment (-Dfabric.development=true) - if(!FabricLoader.getInstance().isDevelopmentEnvironment()) return; + + //Breast Physics Tab + this.addDrawableChild(btnPhysics = new WildfireButton(this.width / 2 - 42, j + 54, 172 / 2 - 2, 10, + Text.translatable("wildfire_gender.breast_customization.tab_physics"), button -> { currentTab = 1; - btnCustomization.active = true; - btnPresets.active = false; - btnAddPreset.visible = true; - btnDeletePreset.visible = true; - PRESET_LIST.refreshList(); + updateTabs(); + //PRESET_LIST.refreshList(); })); - //Disable for now. - btnPresets.visible = false; + //Miscellaneous + this.addDrawableChild(btnMiscellaneous = new WildfireButton(this.width / 2 + 46, j + 54, 172 / 2 - 2, 10, + Text.translatable("wildfire_gender.breast_customization.tab_miscellaneous"), button -> { - if(!FabricLoader.getInstance().isDevelopmentEnvironment()) { - btnPresets.setTooltip(Tooltip.of(Text.translatable("wildfire_gender.coming_soon"))); - } - this.addDrawableChild(btnAddPreset = new WildfireButton(this.width / 2 + 31 + 158/2, j + 80, 158 / 2 - 1, 12, - Text.translatable("wildfire_gender.breast_customization.presets.add_new"), button -> { - createNewPreset("Test Preset"); + currentTab = 2; + updateTabs(); + //PRESET_LIST.refreshList(); })); - btnAddPreset.visible = false; - - this.addDrawableChild(btnDeletePreset = new WildfireButton(this.width / 2 + 30, j + 80, 158 / 2 - 1, 12, - Text.translatable("wildfire_gender.breast_customization.presets.delete"), button -> { - - })).setActive(false); - btnDeletePreset.visible = false; //Customization Tab Below + int tabOffsetY = j-3 - 21; - - this.addDrawableChild(this.breastSlider = new WildfireSlider(this.width / 2 + 30, j - 48, 158, 20, Configuration.BUST_SIZE, plr.getBustSize(), + this.addDrawableChild(this.breastSlider = new WildfireSlider(this.width / 2 - 36, tabOffsetY - 4, 156, 20, Configuration.BUST_SIZE, plr.getBustSize(), plr::updateBustSize, value -> Text.translatable("wildfire_gender.wardrobe.slider.breast_size", Math.round(value * 1.25f * 100)), onSave)); this.breastSlider.setArrowKeyStep(0.01); //Customization - this.addDrawableChild(this.xOffsetBoobSlider = new WildfireSlider(this.width / 2 + 30, j - 27, 158, 20, Configuration.BREASTS_OFFSET_X, breasts.getXOffset(), + this.addDrawableChild(this.xOffsetBoobSlider = new WildfireSlider(this.width / 2 - 36, tabOffsetY + 20, 156 / 2 - 2, 20, Configuration.BREASTS_OFFSET_X, breasts.getXOffset(), breasts::updateXOffset, value -> Text.translatable("wildfire_gender.wardrobe.slider.separation", Math.round((Math.round(value * 100f) / 100f) * 10)), onSave)); - this.addDrawableChild(this.yOffsetBoobSlider = new WildfireSlider(this.width / 2 + 30, j - 6, 158, 20, Configuration.BREASTS_OFFSET_Y, breasts.getYOffset(), + this.addDrawableChild(this.yOffsetBoobSlider = new WildfireSlider(this.width / 2 - 36 + 156/2 + 2, tabOffsetY + 20, 156 / 2 - 2, 20, Configuration.BREASTS_OFFSET_Y, breasts.getYOffset(), breasts::updateYOffset, value -> Text.translatable("wildfire_gender.wardrobe.slider.height", Math.round((Math.round(value * 100f) / 100f) * 10)), onSave)); - this.addDrawableChild(this.zOffsetBoobSlider = new WildfireSlider(this.width / 2 + 30, j + 15, 158, 20, Configuration.BREASTS_OFFSET_Z, breasts.getZOffset(), + + this.addDrawableChild(this.zOffsetBoobSlider = new WildfireSlider(this.width / 2 - 36, tabOffsetY + 44, 156 / 2 - 2, 20, Configuration.BREASTS_OFFSET_Z, breasts.getZOffset(), breasts::updateZOffset, value -> Text.translatable("wildfire_gender.wardrobe.slider.depth", Math.round((Math.round(value * 100f) / 100f) * 10)), onSave)); this.zOffsetBoobSlider.setArrowKeyStep(0.1); - - this.addDrawableChild(this.cleavageSlider = new WildfireSlider(this.width / 2 + 30, j + 36, 158, 20, Configuration.BREASTS_CLEAVAGE, breasts.getCleavage(), + this.addDrawableChild(this.cleavageSlider = new WildfireSlider(this.width / 2 - 36 + 156/2 + 2, tabOffsetY + 44, 156 / 2 - 2, 20, Configuration.BREASTS_CLEAVAGE, breasts.getCleavage(), breasts::updateCleavage, value -> Text.translatable("wildfire_gender.wardrobe.slider.rotation", Math.round((Math.round(value * 100f) / 100f) * 100)), onSave)); this.cleavageSlider.setArrowKeyStep(0.1); - this.addDrawableChild(this.btnDualPhysics =new WildfireButton(this.width / 2 + 30, j + 57, 158, 20, + /*this.addDrawableChild(this.btnDualPhysics = new WildfireButton(this.width / 2 + 230, j + 57, 158, 20, Text.translatable("wildfire_gender.breast_customization.dual_physics", Text.translatable(breasts.isUniboob() ? "wildfire_gender.label.no" : "wildfire_gender.label.yes")), button -> { boolean isUniboob = !breasts.isUniboob(); if (breasts.updateUniboob(isUniboob)) { button.setMessage(Text.translatable("wildfire_gender.breast_customization.dual_physics", Text.translatable(isUniboob ? "wildfire_gender.label.no" : "wildfire_gender.label.yes"))); PlayerConfig.saveGenderInfo(plr); } - })); + }));*/ //Preset Tab Below @@ -143,14 +136,22 @@ public void init() { this.addSelectableChild(this.PRESET_LIST); - this.addDrawableChild(new WildfireButton(this.width / 2 + 178, j - 72, 9, 9, Text.literal("X"), - button -> close(), text -> GuiUtils.doneNarrationText())); + super.init(); + } - this.currentTab = 0; + private void updateTabs() { + btnCustomization.active = currentTab != 0; + btnPhysics.active = currentTab != 1; + btnMiscellaneous.active = currentTab != 2; - super.init(); + this.breastSlider.visible = currentTab == 0; + this.xOffsetBoobSlider.visible = currentTab == 0; + this.yOffsetBoobSlider.visible = currentTab == 0; + this.zOffsetBoobSlider.visible = currentTab == 0; + this.cleavageSlider.visible = currentTab == 0; } + private void createNewPreset(String presetName) { BreastPresetConfiguration cfg = new BreastPresetConfiguration(presetName); PlayerConfig plr = Objects.requireNonNull(getPlayer(), "getPlayer()"); @@ -183,40 +184,55 @@ private void updatePresetTab() { public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delta) { this.renderInGameBackground(ctx); - int x = this.width / 2; - int y = this.height / 2; - ctx.fill(x + 28, y - 64 - 21, x + 190, y + 68, 0x55000000); - ctx.fill(x + 29, y - 63 - 21, x + 189, y - 60, 0x55000000); - ctx.drawText(textRenderer, getTitle(), x + 32, y - 60 - 21, 0xFFFFFF, false); - if(currentTab == 1) { - ctx.fill(PRESET_LIST.getX(), PRESET_LIST.getY(), PRESET_LIST.getRight(), PRESET_LIST.getBottom(), 0x55000000); + PlayerConfig plr = getPlayer(); + if(plr == null) return; + Identifier backgroundTexture = switch(plr.getGender()) { + case Gender.MALE -> null; + case Gender.FEMALE -> BACKGROUND_FEMALE; + case Gender.OTHER -> BACKGROUND_OTHER; + }; + + if(backgroundTexture != null) { + ctx.drawTexture(RenderLayer::getGuiTextured, backgroundTexture, (this.width - 272) / 2, (this.height - 118) / 2, 0, 0, 272, 118, 512, 512); + } + + if(currentTab == 0) { + ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND_CUSTOMIZATION, (this.width) / 2 - 42, (this.height) / 2 - 45, 0, 0, 168, 80, 512, 512); } - @SuppressWarnings("DataFlowIssue") - PlayerEntity ent = client.world.getPlayerByUuid(this.playerUUID); - if(ent != null) { - int xP = this.width / 2 - 102; - int yP = this.height / 2 + 275; - ctx.enableScissor(this.width / 2 - 235, this.height / 2 - 150, this.width / 2 + 25, yP + 35); - GuiUtils.drawEntityOnScreen(ctx, xP, yP, 200, -20, -20, ent); - ctx.disableScissor(); + int x = this.width / 2; + int y = this.height / 2; + //ctx.fill(x + 28, y - 64 - 21, x + 190, y + 68, 0x55000000); + //ctx.fill(x + 29, y - 63 - 21, x + 189, y - 60, 0x55000000); + ctx.drawText(textRenderer, getTitle(), x - textRenderer.getWidth(getTitle()) / 2, y - 70, 0xFFFFFF, false); + + if(client != null && client.world != null) { + int xP = this.width / 2 - 90; + int yP = this.height / 2 + 18; + PlayerEntity ent = client.world.getPlayerByUuid(this.playerUUID); + if(ent != null) { + ctx.enableScissor(xP - 34, yP - 97, xP + 35, yP + 9); + GuiUtils.drawEntityOnScreen(ctx, xP, yP + 60, 65, (xP - mouseX), (yP - 46 - mouseY), ent); + ctx.disableScissor(); + } } } @Override public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { if(client == null || client.player == null || client.world == null) return; - - updatePresetTab(); + //updatePresetTab(); super.render(ctx, mouseX, mouseY, delta); int x = this.width / 2; int y = this.height / 2; + + //Breast physics if(currentTab == 1) { - PRESET_LIST.render(ctx, mouseX, mouseY, delta); + /*PRESET_LIST.render(ctx, mouseX, mouseY, delta); if(PRESET_LIST.getPresetList().length == 0) { ctx.drawText(textRenderer, "No Presets Found", x + ((190 + 28) / 2) - textRenderer.getWidth("No Presets Found") / 2, y - 4, 0xFFFFFF, false); - } + }*/ } } diff --git a/src/main/java/com/wildfire/main/WildfireLocalization.java b/src/main/java/com/wildfire/main/WildfireLocalization.java index 96cd59a7..5e4efb3a 100644 --- a/src/main/java/com/wildfire/main/WildfireLocalization.java +++ b/src/main/java/com/wildfire/main/WildfireLocalization.java @@ -32,12 +32,15 @@ public class WildfireLocalization { public static final Text SYNC_LOG_AUTHENTICATION_FAILED = Text.translatable("wildfire_gender.sync_log.authentication_failed"); public static final Text SYNC_LOG_AUTHENTICATION_SUCCESS = Text.translatable("wildfire_gender.sync_log.authentication_success"); public static final Text SYNC_LOG_REAUTHENTICATING = Text.translatable("wildfire_gender.sync_log.reauthenticating"); - public static final Text SYNC_LOG_ATTMEPTING_SYNC = Text.translatable("wildfire_gender.sync_log.attempting_sync"); + public static final Text SYNC_LOG_ATTEMPTING_SYNC = Text.translatable("wildfire_gender.sync_log.attempting_sync"); public static final Text SYNC_LOG_SYNC_SUCCESS = Text.translatable("wildfire_gender.sync_log.sync_success"); public static final Text SYNC_LOG_SYNC_TOO_FREQUENTLY = Text.translatable("wildfire_gender.sync_log.sync_too_frequently"); public static final Text SYNC_LOG_FAILED_TO_SYNC_DATA = Text.translatable("wildfire_gender.sync_log.failed_to_sync_data"); public static final Text SYNC_LOG_SYNC_TO_CLOUD = Text.translatable("wildfire_gender.sync_log.sync_to_cloud"); + public static final Text SYNC_LOG_GET_SINGLE_PROFILE = Text.translatable("wildfire_gender.sync_log.get_single_profile"); + public static final Text SYNC_LOG_GET_MULTIPLE_PROFILES = Text.translatable("wildfire_gender.sync_log.get_multiple_profiles"); + } diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index 97046a9b..b25272b8 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -233,7 +233,7 @@ private static CompletableFuture syncInternal(PlayerConfig config, boolean var url = URI.create(getCloudServer() + "/" + config.uuid); var json = config.toJson().toString(); - SyncLog.add(WildfireLocalization.SYNC_LOG_ATTMEPTING_SYNC); + SyncLog.add(WildfireLocalization.SYNC_LOG_ATTEMPTING_SYNC); var request = createRequest(url) .PUT(HttpRequest.BodyPublishers.ofString(json)) @@ -295,6 +295,10 @@ private static CompletableFuture syncInternal(PlayerConfig config, boolean throw new RuntimeException("Server responded " + response.statusCode() + ": " + response.body()); } + if(SyncLog.VERBOSITY_LEVEL == 2) { + SyncLog.add(WildfireLocalization.SYNC_LOG_GET_SINGLE_PROFILE); + } + var data = GSON.fromJson(response.body(), JsonObject.class); FETCH_CACHE.put(uuid, Optional.of(data)); return data; @@ -332,6 +336,9 @@ public static CompletableFuture> getMultiple(Collection FETCH_CACHE.put(uuid, Optional.ofNullable(data.get(uuid)))); return data; diff --git a/src/main/java/com/wildfire/main/cloud/SyncLog.java b/src/main/java/com/wildfire/main/cloud/SyncLog.java index 6afbbf83..b0e4e321 100644 --- a/src/main/java/com/wildfire/main/cloud/SyncLog.java +++ b/src/main/java/com/wildfire/main/cloud/SyncLog.java @@ -10,6 +10,10 @@ public final class SyncLog { public static final List SYNC_LOG = new ArrayList<>(); + public static final int VERBOSITY_LEVEL = 2; + + //1 = normal + //2 = log when profiles are retrieved public static void add(Text text) { SYNC_LOG.add(new Entry(text, Instant.now())); diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index 6c19ada5..55a84e17 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -15,7 +15,8 @@ "wildfire_gender.wardrobe.title": "Wildfire's Female Gender Mod", "wildfire_gender.breast_customization.tab_customization": "Customization", - "wildfire_gender.breast_customization.tab_presets": "Presets", + "wildfire_gender.breast_customization.tab_physics": "Breast Physics", + "wildfire_gender.breast_customization.tab_miscellaneous": "Miscellaneous", "wildfire_gender.breast_customization.presets.add_new": "Add New...", "wildfire_gender.breast_customization.presets.delete": "Delete", @@ -24,11 +25,11 @@ "wildfire_gender.wardrobe.slider.separation": "Separation: %s", "wildfire_gender.wardrobe.slider.height": "Height: %s", "wildfire_gender.wardrobe.slider.depth": "Depth: %s", - "wildfire_gender.wardrobe.slider.rotation": "Rotation: %s degrees", + "wildfire_gender.wardrobe.slider.rotation": "Rotation: %s°", "wildfire_gender.slider.voice_pitch": "Voice Pitch: %s%%", - "wildfire_gender.appearance_settings.title": "Appearance Settings", - "wildfire_gender.char_settings.title": "Character Settings", + "wildfire_gender.appearance_settings.title": "Character Personalization", + "wildfire_gender.char_settings.title": "Character Settings OLD", "wildfire_gender.char_settings.physics": "Breast Physics: %s", "wildfire_gender.char_settings.override_armor_physics": "Armor Physics: %s", @@ -96,6 +97,9 @@ "wildfire_gender.sync_log.sync_success": "Sync Successful.", "wildfire_gender.sync_log.sync_too_frequently": "Sync Rate Limited.", + "wildfire_gender.sync_log.get_single_profile": "Retrieving Profile...", + "wildfire_gender.sync_log.get_multiple_profiles": "Retrieving Batch of Profiles...", + "wildfire_gender.details.next_page": "Next Page", "wildfire_gender.details.prev_page": "Prev Page", diff --git a/src/main/resources/assets/wildfire_gender/textures/cloud.png b/src/main/resources/assets/wildfire_gender/textures/cloud.png index 660fb0b6bed4ec0d1be22d01a3a136803dfc1dab..43ae27c6dba864510c3d2c07163496ac12a0a744 100644 GIT binary patch delta 845 zcmV-T1G4h=iBL{Q4GJ0x0000DNk~Le0000W0000Q2nGNE0M?scKL7v$g=s@W zP)S2WAW(8|W@&6?001b@m61J2!%!53PgA9$6$LwpIAo|!7DPoHHIY#+e?kQ>Jl@B7 z?;P&C4-jf4rrRCkfNnTeGA@bf%&HiAMGyKBKomiR+2))qC(yRO?&&7gU5sbpf7PpH zZ3YBH;#p=|4)F%@^rmBS-Y1T*f}#$HXu$0C*>LxhS7iYUQBjCO+* z8%a8s+W3b&zf3NbTtzT)ET9Y>isuLagWt2YbCVN(Qdj~yUtITN1PJW{^{VTBAG>b- z1PDF@S4Puct^qTjq*t3-^a$wN1}?6fnz{#E?f`>NreZ0c6r?5Oe{#V48GTa*7`O$x z*ZjFP&T;wxq-a)&8{ps&7|m1mdYgB5wa)F&JKp>t{Qw*Ga)e(%u`2)o010qNS#tmY z4b}hv4b}mWZ+V;m00E9kL_t(YOU2f|F9Ts5$MF)PkF;2G6N8YrV5DP-ZzCoZsnC`{*;WjyFHgFBSV0TM8M$FGbB zT}jBVtTbuFAm>Um188-}C6!tOaU3C`MwMq5@0dU%?y-j@f84^K;*MO)2r?PO;hU@0 z?ZcZWlDZ5mgILvNVN4Blpi`HDW(x8s7sS*+4008MP78c%sO*B|B)x`pbKhF)0V6b!{~u*tBiZLD)Ioi-LjqkPN_Yir9>iM XEK1muzTRb400000NkvXXu0mjf6drLy delta 664 zcmV;J0%!g02Dt@MiBL{Q4GJ0x0000DNk~Le0000G0000D2nGNE0HC|>Bme*bglR)V zP)S2WAaHVTW@&6?001bFeUUv#!%!53PgA9$6$LwpIAo|!7DPoHwUJRSe`37wcpvB8 zb9nDQK&Y3QW_64Mnr@q^L|n{dSH;jPdeDymq6o^&GUg;H3E%pR+LrZbK)_RE=c^yb;aX1&IOkRo*6OIsd?fEu~=wh zrHxt9)QG2uqpGG;zL4=)f91TzSu0mr^Pc>L;hes*%ynABNMI35kRU=q6(y8mBSyPU ziiH%N$9?=mu3sXTLaq`RITlcX2HEw4|H1FsTKUNdFDV=cI$s>;V+0880*#vEd>=bb z;{*sk16O*>U#SB#pQP7XTJ#9$+XgPKTbi;5T000SaNLh0L01ejw01ejxLMWSf00007bV*G`2j~kB0VfhUwEWuu006d0 zL_t(2&!v(fZUO-ie?{MHvZjzg;0o2;0FAFA7&rwtsHh_00_6%QEE zW9-$}t1dfQ`@v0Xz~II}?E}36aaFUUcP+ y2A|m=R|Nt*s2^$?f1auNd*^$cflWkQ0KNgAJ>q_j3YdKW000038$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&eBzI1?3h%1o(|NsB7W5+^6L(R?2!@|O{v$MIlK&EnZZq)`-oFzei!9X=& zz!2NlEswJXa z-%mQAXI%VwvH0va>`4nx@6y|Mt^D+^IW>O;B6_~t*Y07JW02o=?)mfsvJLmUxE}By zcy1Rv?>YZ{dxk&tH*S2b{P%%Xz=7e8eFFp24cUhCEY073tSB-#aQ%dBoFP!`!9PY8 zpge>qXzm3m267nsr8YA#aVP*)a40amlZUAQ%9!7XfGYp6!jK!H0?nY^Ofl{6DjGPs zfU-OG3qPpQIq>$!M{9uw^`5)0|FtuGIDDe;cXT^L!8Z}8eL(XOmOC)av2_KzX#4hk zAFjT38$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&eBzI1?3h%1o(|NsB7W5+^6L(R?2!@|O{v$MIlK&EnZZq)`-oFzei!9X=& zz!2Nm_P_eJqnsIE zu9>||OVhJ2ir5wZsFC@yjsL!9<(F+@YyJw%==r`s=01~u!|JuS->Pn4`mk;V0~1HX zZ*~C(21b?xyBGaqs(Ev{)^_f7^|wDe{xf{AElloLKk$5JwOtZO!Cz($g$AGw5b=Ob znj6AluzNL!6>I{CDEJRm08+K%;4zTur)!R{w3bU~0J0h0y=U1W%e_IS{vRL91L1Rb zU;k@o_;7sY<8R*W3g>&ZEx-hc9thkeq4C64Q^9KSQ29aSfzYziB9JuMs za$xTK(7fD;23c+gz7Ma}BbiOkUjB7vx0%ENn~!&@O|Se7Z+Ln?;rc#)xupl?%KtMZ zY-K%=#ocgKjA6AdLs&G!nr#dbxr`fb4OIy>_8HIhZC|CWy)QiD05BObc)I$ztaD0e F0stCl_>lkr From bd1618172ba8cb2925e7694426f3e3c83059e06a Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sat, 23 Nov 2024 18:45:54 -0500 Subject: [PATCH 183/238] Forgot to push the images --- .../textures/gui/breast_customization.png | Bin 0 -> 1618 bytes .../textures/gui/breast_customization_other.png | Bin 0 -> 1665 bytes .../textures/gui/breast_customization_tab.png | Bin 0 -> 1535 bytes .../textures/gui/wardrobe_bg_default.png | Bin 0 -> 719 bytes .../textures/gui/wardrobe_bg_female.png | Bin 0 -> 1731 bytes .../textures/gui/wardrobe_bg_male.png | Bin 0 -> 1730 bytes .../textures/gui/wardrobe_bg_other.png | Bin 0 -> 1730 bytes 7 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/breast_customization.png create mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/breast_customization_other.png create mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/breast_customization_tab.png create mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/wardrobe_bg_default.png create mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/wardrobe_bg_female.png create mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/wardrobe_bg_male.png create mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/wardrobe_bg_other.png diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/breast_customization.png b/src/main/resources/assets/wildfire_gender/textures/gui/breast_customization.png new file mode 100644 index 0000000000000000000000000000000000000000..6852b6772a0c10fe1c177ff3053b4e2023b7cee0 GIT binary patch literal 1618 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&w@L)Zt|+Cx8@Vage(c!@6@aFM%AEbVpxD z28NCO+2=T><3{+3)Wy21_svYo-U3d6>)EG zap&DO5MgjUI{#MY;@|PxJ9VXP891lhn8_S4;U1G2XWs99zMD2T2ZMn^}oEW%oe9H}Ec(%Xky{7E!?+^RbKg1g^1A5i>4`;-M zkAYtM{*w_~7-b*IWrI zjB6Yi@|h%j7_WZ>O SZ1TWzo59o7&t;ucLK6Vf+VANA literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/breast_customization_other.png b/src/main/resources/assets/wildfire_gender/textures/gui/breast_customization_other.png new file mode 100644 index 0000000000000000000000000000000000000000..e8a9ffecbcbacd5272e2462b39b41558b5156560 GIT binary patch literal 1665 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&w@L)Zt|+Cx8@Vage(c!@6@aFM%AEbVpxD z28NCO++@Rm93pbX;m*2{{y*^X1v4)mKh9vVPW)Zz>WlLjj`6U6XEV?X=WBTV z_QAS(<{$e?>=|~%tAS@zjI>>#B;VTcZi;xC}! zW?)D-bCDa&XfSlw2Q#RxC;{kz<$Jo>4&*SLW_OT2z|L@);mz&hdtaFo#2RX)h4o{< z|9Q^*qo(a^_1%okKN+@^FdSi4kYo7B(82Ct!|;evfWM&vx6nO?cm2$}nG0s!W8mX! z*!+d{L6!9mZ3gw1+y{CXEL<-$Y^l#Lp1197X;?7R1D4#+b^BiaIe+1~W-tHycJq0T z-p^+cvVk~vjH<#DNuz-?8aPDcWm+VY33>PbGwfPj{YT<@kU6M;^K|udS?83{1OSeB B{BZyP literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/breast_customization_tab.png b/src/main/resources/assets/wildfire_gender/textures/gui/breast_customization_tab.png new file mode 100644 index 0000000000000000000000000000000000000000..be86429e4da5758f29a3896c38824f8887eae344 GIT binary patch literal 1535 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&w@L)Zt|+Cx8@Vage(c!@6@aFM%AEbVpxD z28NCO+ykadB~FXJ-S&rxjkk1Ee@hg8YJk zYQTUY*uO#uBv{}PSXSA?=3e=U5D0a3z#8 zNhmk$C}p|x^7p^5ECy*0gnpE#-QPQp;TQ+;rZIIyGp-T;@qI_VEy!Ir=7MzR$ugFI zia%go$k{`Ti{S3w&J>~l;eFx%Dkh0y)(zH-J(b-IazOT+38$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&eBzI1?3h%1o(|NsB7W5+^6L(R?2!@|O{v$MIlK&EnZZq)`-oFzei!9X=& zz!2S7*-C+qaL?=h*M~dvQ$s4d&6;Uk4|!K8OuuWD#Ka z&acqGz{Jr|?)sOl;{Nj6RaWc$Z#@?L&+wr7(Xn^@4gJR7D~^B^{AL$$U;yd>5eIn9 z#Gss-E0(|c8-A~}mP=@0;!t3C_nu{k9QOv9`hR>Z4}{O%{rs<;;luHnkH2}hGZcIi zV1*k}(A*2>%&~QWnhcb_laLJKFr@Pb!t4}aFuxH2;~co@EP~B7j4&&7{A(XCzqZXx z=RhB;0{f4T#=guZXD|Ocv)fGKfX&A{)uvbehBrLDpKyI2pWM=ea^?S-61K7)$l`9e xD#ox{mmw^gVa+y%h+M`Ew}z^O8vBgr{*E4{2g9cQS`AD{44$rjF6*2UngAM%^fCYd literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/wardrobe_bg_female.png b/src/main/resources/assets/wildfire_gender/textures/gui/wardrobe_bg_female.png new file mode 100644 index 0000000000000000000000000000000000000000..10f68bc42075c6a76d266b15c710d24156c4d0e1 GIT binary patch literal 1731 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&w@L)Zt|+Cx8@Vage(c!@6@aFM%AEbVpxD z28NCO+fxU#dek1-sZ z$1o45?82-~hCqt1B*-rqs09HSTo2p>XEv z%sWely!T=6zNt3+j{Pp&aQNZ7>!BEmLN&O5pv)3`qJLdl=w2JA$ zZ5E)b85j;oxa)%%496_07{M%qV;8x>j0VHLIEY+N;TKT&GR)w4ka(TZK>ENt<^{YD z*cvh!`c7x=`OV)D31mooT=)82yKhVie$Ss9-q4 ztRP1RA#>RS6)*RmGhg_27Vm?fpXA*R9P?%SaPwci49AUa+6~8-i&odLCfLmXeE;XN z4Ta$h+TWQD-1uV6xS>#-VSBlD`EQ{Ee|Ix(s1#=~u3}8ES^3}hqx$yuN(_%;8G8Jm zeEr3Fz&w*dLOOEo>nz3_)dYRT%fO%go$Elw+gyg6d#nd~{xSW~U@*&*YM8ChxMR-J zw^7qRi!CdOdi&|+RmK}zo^osdI*P*hrBevf qKh8#N9F1B!7ZY?zaueQF{9%qwTFkb7#XDY58S3fk=d#Wzp$Py4g)Z#? literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/wardrobe_bg_male.png b/src/main/resources/assets/wildfire_gender/textures/gui/wardrobe_bg_male.png new file mode 100644 index 0000000000000000000000000000000000000000..aa72c8dd43c713622e1301aebe005f7ecdd50511 GIT binary patch literal 1730 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&w@L)Zt|+Cx8@Vage(c!@6@aFM%AEbVpxD z28NCO+2=T><3{+3)Wy21_m~EPZ!6KinzCT z5Bfd!5OD}BZ+Itp;Q#;K&X2m-t~eWWPtPeie^<3j>sqyX;HFQT)}5YnZSBVwS9-mD zx2-wdZ<(7sxAfP#sjNDe%}RFmeT{xozxzstIm2{GTbn(-rMF)0E(7wz8IpOd%h?U& z=2`hUBdps>Yvd2OWF+g z)qu_hf@2m{j9`Yrv5VYbMuTBr9GJn-Q}_iGt_%zbJ&VOb^nnzH^UL`fZZi1EGvqSx zGx#uX(7XO|o;^bvW5jgBN5R+c{;y%ke_!w~PyAMn9m8BJhDVG7{0$WhN0=4l7(Oy| zushh$K`7s<;p3I`yLwK?O{EXq{#f7B&^(*x!Ro*NcL)^Zlr!kBxBm8t@xTuI=k?F0 zA3AuK@y#7;hUClhSq>b`g$Lq%wgV4y8PdM+9oP~4KmX|aJ9S+Q9orct?w|PmmAfJT z5`%%+rs(finF_uU^p!NjyzlRr8$RB7&sb0)-5~jw`++}WLBVAPn?L#tw^x_MoZc0) z-C*hKto?Sf4bHpkypF4V%e(z1eEIGF8}HgJExvW{!RpJ$SC<5c4M14ZKlVm#q*FE- h4QSd2^sdss>^6*xeKvfzdk!i=Jzf1=);T3K0RSoFFc1I$ literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/wardrobe_bg_other.png b/src/main/resources/assets/wildfire_gender/textures/gui/wardrobe_bg_other.png new file mode 100644 index 0000000000000000000000000000000000000000..81e2830f0e27b67fc64afbdaeef7a67183ce3997 GIT binary patch literal 1730 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&w@L)Zt|+Cx8@Vage(c!@6@aFM%AEbVpxD z28NCO+z`(}t>Eakt5%>1) zLBGcyA`XG&4eulm{Qtk(`B4|!6=!4a={Y6m@2YlbU8`0P-1KSFy3=#6t^N4oO0T!? zwl$~wEpwCSmi}5dl~w1mS;@}6uhDPncVEdcXP7Q&YqO`f^w!JWWk7y7Lo$zbIlF<} zJWB?-`DO1u+BVEz{!W}>`svH@*5AJ{9=MzoCNR2(yA5!$*b= zb_W|e2<2Nfe7ur=SI_CVsq}%{AM1M>nrHJoSpE0^4uOK4at8hN*55uc9@t_3y#D$0 zLkI6NzPV$~kbHSQ%YlQr@Iai;cHm(yL)sU<13QBM=O2B4r>={kV>_e7{S&{xayR5( zVlXh<6#e}wQ^7ZazLI8`_x&An!^b=C84D_;8zld7Kk#QPD7egE^GBcI_Ue+D)4O7} z8!UaDwck#*!FhL`*KxIPdAHw$FTeeN<6XO@#kcM~Sbh2U>XHDl0SHU_$KI%objl{9 g0ZsdW-c|aS-G*_o&xY@I&p{=qr>mdKI;Vst0H{tby8r+H literal 0 HcmV?d00001 From 8567745bc20cf115a4a3cc7ab20374931f4f5137 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sat, 23 Nov 2024 21:54:46 -0500 Subject: [PATCH 184/238] Forgot to push the images --- .../gui/screen/WardrobeBrowserScreen.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index d1a1e15c..1d623fb3 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -68,7 +68,7 @@ public void init() { int y = this.height / 2; PlayerConfig plr = Objects.requireNonNull(getPlayer(), "getPlayer()"); - this.addDrawableChild(btnFemale = new WildfireButton(this.width / 2 - 128, this.height / 2 + 35, 80, 15, plr.getGender().getDisplayName(), button -> { + this.addDrawableChild(btnFemale = new WildfireButton(this.width / 2 - 130, this.height / 2 + 33, 80, 15, plr.getGender().getDisplayName(), button -> { Gender gender = switch (plr.getGender()) { case MALE -> Gender.FEMALE; case FEMALE -> Gender.OTHER; @@ -81,16 +81,17 @@ public void init() { } })); - this.addDrawableChild(this.btnCharacterPersonalization = new WildfireButton(this.width / 2 - 34, this.height / 2 - 51, 158, 20, Text.translatable("wildfire_gender.appearance_settings.title").append("..."), + this.addDrawableChild(this.btnCharacterPersonalization = new WildfireButton(this.width / 2 - 36, this.height / 2 - 53, 158, 20, Text.translatable("wildfire_gender.appearance_settings.title").append("..."), button -> client.setScreen(new WildfireBreastCustomizationScreen(WardrobeBrowserScreen.this, this.playerUUID)))); this.btnCharacterPersonalization.active = plr.getGender().canHaveBreasts(); - /*this.addDrawableChild(new WildfireButton(this.width / 2 - 42, y - (plr.getGender().canHaveBreasts() ? 12 : 32), 158, 20, Text.translatable("wildfire_gender.char_settings.title").append("..."), - button -> client.setScreen(new WildfireCharacterSettingsScreen(WardrobeBrowserScreen.this, this.playerUUID))));*/ + + this.addDrawableChild(new WildfireButton(this.width / 2 - 42, y - (plr.getGender().canHaveBreasts() ? 12 : 32), 158, 20, Text.translatable("wildfire_gender.char_settings.title").append("..."), + button -> client.setScreen(new WildfireCharacterSettingsScreen(WardrobeBrowserScreen.this, this.playerUUID)))); //noinspection ExtractMethodRecommender var cloud = new WildfireButton( - this.width / 2 - 34, y + 32, 24, 18, Text.translatable("wildfire_gender.cloud_settings"), + this.width / 2 - 36, y + 30, 24, 18, Text.translatable("wildfire_gender.cloud_settings"), button -> client.setScreen(new WildfireCloudSyncScreen(this, this.playerUUID)) ) { @Override @@ -130,11 +131,11 @@ public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delt case Gender.OTHER -> BACKGROUND_OTHER; }; - ctx.drawTexture(RenderLayer::getGuiTextured, backgroundTexture, (this.width - 268) / 2, (this.height - 114) / 2, 0, 0, 268, 114, 512, 512); + ctx.drawTexture(RenderLayer::getGuiTextured, backgroundTexture, (this.width - 272) / 2, (this.height - 118) / 2, 0, 0, 268, 114, 512, 512); if(client != null && client.world != null) { - int xP = this.width / 2 - 88; - int yP = this.height / 2 + 20; + int xP = this.width / 2 - 90; + int yP = this.height / 2 + 18; PlayerEntity ent = client.world.getPlayerByUuid(this.playerUUID); if(ent != null) { ctx.enableScissor(xP - 34, yP - 97, xP + 35, yP + 9); From 83f13eea609767155a87d2fcd88ed87b628ade8b Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sat, 23 Nov 2024 22:48:44 -0500 Subject: [PATCH 185/238] Finish UI changes --- .../gui/screen/WardrobeBrowserScreen.java | 32 +++- .../WildfireBreastCustomizationScreen.java | 163 ++++++++++++++++-- .../wildfire/main/config/GlobalConfig.java | 3 + .../assets/wildfire_gender/lang/en_us.json | 9 +- .../textures/gui/breast_customization.png | Bin 1618 -> 1618 bytes .../gui/breast_customization_other.png | Bin 1665 -> 1618 bytes .../textures/gui/breast_customization_tab.png | Bin 1535 -> 0 bytes .../textures/gui/wardrobe_bg2.png | Bin 714 -> 0 bytes .../textures/gui/wardrobe_bg_female.png | Bin 1731 -> 1724 bytes .../textures/gui/wardrobe_bg_male.png | Bin 1730 -> 1721 bytes .../textures/gui/wardrobe_bg_other.png | Bin 1730 -> 1721 bytes 11 files changed, 180 insertions(+), 27 deletions(-) delete mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/breast_customization_tab.png delete mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/wardrobe_bg2.png diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index 1d623fb3..d74a755d 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -25,7 +25,9 @@ import java.util.*; import com.wildfire.gui.WildfireButton; +import com.wildfire.main.WildfireLocalization; import com.wildfire.main.cloud.CloudSync; +import com.wildfire.main.config.GlobalConfig; import com.wildfire.main.entitydata.PlayerConfig; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -57,7 +59,7 @@ public class WardrobeBrowserScreen extends BaseWildfireScreen { private static final boolean isBreastCancerAwarenessMonth = Calendar.getInstance().get(Calendar.MONTH) == Calendar.OCTOBER; - private WildfireButton btnMale, btnFemale, btnOther, btnCharacterPersonalization; + private WildfireButton btnMale, btnFemale, btnOther, btnCharacterPersonalization, btnAlwaysShowList; public WardrobeBrowserScreen(Screen parent, UUID uuid) { super(Text.translatable("wildfire_gender.wardrobe.title"), parent, uuid); } @@ -68,6 +70,17 @@ public void init() { int y = this.height / 2; PlayerConfig plr = Objects.requireNonNull(getPlayer(), "getPlayer()"); + + //TODO: Finish this + /*this.addDrawableChild(btnAlwaysShowList = new WildfireButton(126, 4, 140, 10, + Text.translatable("wildfire_gender.always_show_list", GlobalConfig.INSTANCE.get(GlobalConfig.ALWAYS_SHOW_LIST) ? WildfireLocalization.ENABLED : WildfireLocalization.DISABLED), + button -> { + var config = GlobalConfig.INSTANCE; + var newVal = !config.get(GlobalConfig.ALWAYS_SHOW_LIST); + config.set(GlobalConfig.ALWAYS_SHOW_LIST, newVal); + button.setMessage(Text.translatable("wildfire_gender.always_show_list", newVal ? WildfireLocalization.ENABLED : WildfireLocalization.DISABLED)); + }));*/ + this.addDrawableChild(btnFemale = new WildfireButton(this.width / 2 - 130, this.height / 2 + 33, 80, 15, plr.getGender().getDisplayName(), button -> { Gender gender = switch (plr.getGender()) { case MALE -> Gender.FEMALE; @@ -81,13 +94,14 @@ public void init() { } })); - this.addDrawableChild(this.btnCharacterPersonalization = new WildfireButton(this.width / 2 - 36, this.height / 2 - 53, 158, 20, Text.translatable("wildfire_gender.appearance_settings.title").append("..."), + this.addDrawableChild(this.btnCharacterPersonalization = new WildfireButton(this.width / 2 - 36, this.height / 2 - 63, 158, 20, Text.translatable("wildfire_gender.appearance_settings.title").append("..."), button -> client.setScreen(new WildfireBreastCustomizationScreen(WardrobeBrowserScreen.this, this.playerUUID)))); this.btnCharacterPersonalization.active = plr.getGender().canHaveBreasts(); - this.addDrawableChild(new WildfireButton(this.width / 2 - 42, y - (plr.getGender().canHaveBreasts() ? 12 : 32), 158, 20, Text.translatable("wildfire_gender.char_settings.title").append("..."), - button -> client.setScreen(new WildfireCharacterSettingsScreen(WardrobeBrowserScreen.this, this.playerUUID)))); + //old menu + /*this.addDrawableChild(new WildfireButton(this.width / 2 - 42, y - (plr.getGender().canHaveBreasts() ? 12 : 32), 158, 20, Text.translatable("wildfire_gender.char_settings.title").append("..."), + button -> client.setScreen(new WildfireCharacterSettingsScreen(WardrobeBrowserScreen.this, this.playerUUID))));*/ //noinspection ExtractMethodRecommender var cloud = new WildfireButton( @@ -131,15 +145,15 @@ public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delt case Gender.OTHER -> BACKGROUND_OTHER; }; - ctx.drawTexture(RenderLayer::getGuiTextured, backgroundTexture, (this.width - 272) / 2, (this.height - 118) / 2, 0, 0, 268, 114, 512, 512); + ctx.drawTexture(RenderLayer::getGuiTextured, backgroundTexture, (this.width - 272) / 2, (this.height - 138) / 2, 0, 0, 268, 124, 512, 512); if(client != null && client.world != null) { int xP = this.width / 2 - 90; int yP = this.height / 2 + 18; PlayerEntity ent = client.world.getPlayerByUuid(this.playerUUID); if(ent != null) { - ctx.enableScissor(xP - 34, yP - 97, xP + 35, yP + 9); - GuiUtils.drawEntityOnScreen(ctx, xP, yP + 60, 65, (xP - mouseX), (yP - 46 - mouseY), ent); + ctx.enableScissor(xP - 38, yP - 97, xP + 38, yP + 9); + GuiUtils.drawEntityOnScreen(ctx, xP, yP + 60, 70, (xP - mouseX), (yP - 46 - mouseY), ent); ctx.disableScissor(); } } @@ -150,7 +164,7 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { super.render(ctx, mouseX, mouseY, delta); int x = this.width / 2; int y = this.height / 2; - ctx.drawText(textRenderer, getTitle(), x - textRenderer.getWidth(getTitle()) / 2, y - 68, 0xFFFFFF, false); + ctx.drawText(textRenderer, getTitle(), x - textRenderer.getWidth(getTitle()) / 2, y - 82, 0xFFFFFF, false); if(client != null && client.player != null) { boolean withCreator = client.player.networkHandler.getPlayerList().stream() @@ -172,7 +186,7 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { } List syncedPlayers = collectPlayerEntries(); - if(!syncedPlayers.isEmpty()) { + if(!syncedPlayers.isEmpty() || GlobalConfig.INSTANCE.get(GlobalConfig.ALWAYS_SHOW_LIST)) { ctx.drawText(textRenderer, Text.translatable("wildfire_gender.wardrobe.players_using_mod").formatted(Formatting.AQUA), 5, 5, 0xFFFFFF, false); int yPos = 18; for(PlayerListEntry entry : syncedPlayers) { diff --git a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java index 9f4bb556..60b85564 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java @@ -37,7 +37,9 @@ import net.minecraft.client.gui.tooltip.Tooltip; import net.minecraft.client.render.RenderLayer; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.sound.SoundEvent; import net.minecraft.text.Text; +import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; import java.util.Objects; @@ -46,13 +48,29 @@ @Environment(EnvType.CLIENT) public class WildfireBreastCustomizationScreen extends BaseWildfireScreen { + private static final Text ENABLED = Text.translatable("wildfire_gender.label.enabled").formatted(Formatting.GREEN); + private static final Text DISABLED = Text.translatable("wildfire_gender.label.disabled").formatted(Formatting.RED); + private static final Identifier BACKGROUND_FEMALE = Identifier.of(WildfireGender.MODID, "textures/gui/breast_customization.png"); private static final Identifier BACKGROUND_OTHER = Identifier.of(WildfireGender.MODID, "textures/gui/breast_customization_other.png"); - private static final Identifier BACKGROUND_CUSTOMIZATION = Identifier.of(WildfireGender.MODID, "textures/gui/breast_customization_tab.png"); - private static final Identifier BACKGROUND_PHYSICS = Identifier.of(WildfireGender.MODID, "textures/gui/breast_physics_tab.png"); + private static final Identifier BACKGROUND_CUSTOMIZATION = Identifier.of(WildfireGender.MODID, "textures/gui/tabs/breast_customization_tab.png"); + private static final Identifier BACKGROUND_PHYSICS = Identifier.of(WildfireGender.MODID, "textures/gui/tabs/breast_physics_tab.png"); + private static final Identifier BACKGROUND_MISC = Identifier.of(WildfireGender.MODID, "textures/gui/tabs/miscellaneous_tab.png"); + + //Customization Tab private WildfireSlider breastSlider, xOffsetBoobSlider, yOffsetBoobSlider, zOffsetBoobSlider, cleavageSlider; private WildfireButton btnDualPhysics, btnPhysics, btnCustomization, btnMiscellaneous; + + //Breast Physics Tab + private WildfireSlider bounceSlider, floppySlider; + private WildfireButton btnHideInArmor, btnOverrideArmorPhys, btnBreastPhysics; + + //Miscellaneous Tab + private WildfireSlider voicePitchSlider; + private WildfireButton btnHurtSounds; + + //Presets Code //private WildfireButton btnAddPreset, btnDeletePreset; private WildfireBreastPresetList PRESET_LIST; @@ -66,6 +84,9 @@ public WildfireBreastCustomizationScreen(Screen parent, UUID uuid) { public void init() { int j = this.height / 2 - 11; + int xPos = this.width / 2; + int yPos = this.height / 2; + PlayerConfig plr = Objects.requireNonNull(getPlayer(), "getPlayer()"); Breasts breasts = plr.getBreasts(); FloatConsumer onSave = value -> { @@ -102,33 +123,128 @@ public void init() { //Customization Tab Below int tabOffsetY = j-3 - 21; - this.addDrawableChild(this.breastSlider = new WildfireSlider(this.width / 2 - 36, tabOffsetY - 4, 156, 20, Configuration.BUST_SIZE, plr.getBustSize(), + this.addDrawableChild(this.breastSlider = new WildfireSlider(this.width / 2 - 36, tabOffsetY - 4, 166, 20, Configuration.BUST_SIZE, plr.getBustSize(), plr::updateBustSize, value -> Text.translatable("wildfire_gender.wardrobe.slider.breast_size", Math.round(value * 1.25f * 100)), onSave)); this.breastSlider.setArrowKeyStep(0.01); //Customization - this.addDrawableChild(this.xOffsetBoobSlider = new WildfireSlider(this.width / 2 - 36, tabOffsetY + 20, 156 / 2 - 2, 20, Configuration.BREASTS_OFFSET_X, breasts.getXOffset(), + this.addDrawableChild(this.xOffsetBoobSlider = new WildfireSlider(this.width / 2 - 36, tabOffsetY + 20, 166 / 2 - 2, 20, Configuration.BREASTS_OFFSET_X, breasts.getXOffset(), breasts::updateXOffset, value -> Text.translatable("wildfire_gender.wardrobe.slider.separation", Math.round((Math.round(value * 100f) / 100f) * 10)), onSave)); - this.addDrawableChild(this.yOffsetBoobSlider = new WildfireSlider(this.width / 2 - 36 + 156/2 + 2, tabOffsetY + 20, 156 / 2 - 2, 20, Configuration.BREASTS_OFFSET_Y, breasts.getYOffset(), + this.addDrawableChild(this.yOffsetBoobSlider = new WildfireSlider(this.width / 2 - 36 + 166/2 + 2, tabOffsetY + 20, 166 / 2 - 2, 20, Configuration.BREASTS_OFFSET_Y, breasts.getYOffset(), breasts::updateYOffset, value -> Text.translatable("wildfire_gender.wardrobe.slider.height", Math.round((Math.round(value * 100f) / 100f) * 10)), onSave)); - this.addDrawableChild(this.zOffsetBoobSlider = new WildfireSlider(this.width / 2 - 36, tabOffsetY + 44, 156 / 2 - 2, 20, Configuration.BREASTS_OFFSET_Z, breasts.getZOffset(), + this.addDrawableChild(this.zOffsetBoobSlider = new WildfireSlider(this.width / 2 - 36, tabOffsetY + 44, 166 / 2 - 2, 20, Configuration.BREASTS_OFFSET_Z, breasts.getZOffset(), breasts::updateZOffset, value -> Text.translatable("wildfire_gender.wardrobe.slider.depth", Math.round((Math.round(value * 100f) / 100f) * 10)), onSave)); this.zOffsetBoobSlider.setArrowKeyStep(0.1); - this.addDrawableChild(this.cleavageSlider = new WildfireSlider(this.width / 2 - 36 + 156/2 + 2, tabOffsetY + 44, 156 / 2 - 2, 20, Configuration.BREASTS_CLEAVAGE, breasts.getCleavage(), + this.addDrawableChild(this.cleavageSlider = new WildfireSlider(this.width / 2 - 36 + 166/2 + 2, tabOffsetY + 44, 166 / 2 - 2, 20, Configuration.BREASTS_CLEAVAGE, breasts.getCleavage(), breasts::updateCleavage, value -> Text.translatable("wildfire_gender.wardrobe.slider.rotation", Math.round((Math.round(value * 100f) / 100f) * 100)), onSave)); this.cleavageSlider.setArrowKeyStep(0.1); - /*this.addDrawableChild(this.btnDualPhysics = new WildfireButton(this.width / 2 + 230, j + 57, 158, 20, + //Breast Physics Tab + + this.addDrawableChild(this.btnBreastPhysics = new WildfireButton(this.width / 2 - 36, tabOffsetY - 28, 166, 20, + Text.translatable("wildfire_gender.char_settings.physics", plr.hasBreastPhysics() ? ENABLED : DISABLED), button -> { + boolean enablePhysics = !plr.hasBreastPhysics(); + if (plr.updateBreastPhysics(enablePhysics)) { + + this.bounceSlider.active = plr.hasBreastPhysics(); + this.floppySlider.active = plr.hasBreastPhysics(); + this.btnOverrideArmorPhys.active = plr.hasBreastPhysics(); + this.btnDualPhysics.active = plr.hasBreastPhysics(); + + button.setMessage(Text.translatable("wildfire_gender.char_settings.physics", enablePhysics ? ENABLED : DISABLED)); + PlayerConfig.saveGenderInfo(plr); + } + })); + + this.addDrawableChild(this.btnDualPhysics = new WildfireButton(this.width / 2 - 36, tabOffsetY - 4, 166, 20, Text.translatable("wildfire_gender.breast_customization.dual_physics", Text.translatable(breasts.isUniboob() ? "wildfire_gender.label.no" : "wildfire_gender.label.yes")), button -> { boolean isUniboob = !breasts.isUniboob(); if (breasts.updateUniboob(isUniboob)) { button.setMessage(Text.translatable("wildfire_gender.breast_customization.dual_physics", Text.translatable(isUniboob ? "wildfire_gender.label.no" : "wildfire_gender.label.yes"))); PlayerConfig.saveGenderInfo(plr); } - }));*/ + })); + this.btnDualPhysics.active = plr.hasBreastPhysics(); + + //this.btnHideInArmor.active = aPlr.hasBreastPhysics(); + + + this.addDrawableChild(btnOverrideArmorPhys = new WildfireButton(this.width / 2 - 36, tabOffsetY + 44, 166, 20, + Text.translatable("wildfire_gender.char_settings.override_armor_physics", plr.getArmorPhysicsOverride() ? ENABLED : DISABLED), button -> { + boolean enableArmorPhysicsOverride = !plr.getArmorPhysicsOverride(); + if (plr.updateArmorPhysicsOverride(enableArmorPhysicsOverride )) { + button.setMessage(Text.translatable("wildfire_gender.char_settings.override_armor_physics", plr.getArmorPhysicsOverride() ? ENABLED : DISABLED)); + PlayerConfig.saveGenderInfo(plr); + } + }, Tooltip.of(Text.translatable("wildfire_gender.tooltip.override_armor_physics.line1") + .append("\n\n") + .append(Text.translatable("wildfire_gender.tooltip.override_armor_physics.line2"))) + )); + this.btnOverrideArmorPhys.active = plr.hasBreastPhysics(); + + this.addDrawableChild(this.bounceSlider = new WildfireSlider(this.width / 2 - 36, tabOffsetY + 20, 166 / 2 - 2, 20, Configuration.BOUNCE_MULTIPLIER, plr.getBounceMultiplier(), value -> { + }, value -> { + float bounceText = 3 * value; + int v = Math.round(bounceText * 100); + //bounceWarning = v > 100; + return Text.translatable("wildfire_gender.slider.bounce", v); + }, value -> { + if (plr.updateBounceMultiplier(value)) { + PlayerConfig.saveGenderInfo(plr); + } + })); + this.bounceSlider.active = plr.hasBreastPhysics(); + this.bounceSlider.setArrowKeyStep(0.005); + + this.addDrawableChild(this.floppySlider = new WildfireSlider(this.width / 2 - 36 + 166/2 + 2, tabOffsetY + 20, 166 / 2 - 2, 20, Configuration.FLOPPY_MULTIPLIER, plr.getFloppiness(), value -> { + }, value -> Text.translatable("wildfire_gender.slider.floppy", Math.round(value * 100)), value -> { + if (plr.updateFloppiness(value)) { + PlayerConfig.saveGenderInfo(plr); + } + })); + this.floppySlider.active = plr.hasBreastPhysics(); + this.floppySlider.setArrowKeyStep(0.01); + //Miscellaneous Tab + + + this.addDrawableChild(this.btnHurtSounds = new WildfireButton(this.width / 2 - 36, tabOffsetY - 4, 166, 20, + Text.translatable("wildfire_gender.char_settings.hurt_sounds", plr.hasHurtSounds() ? ENABLED : DISABLED), button -> { + boolean enableHurtSounds = !plr.hasHurtSounds(); + if (plr.updateHurtSounds(enableHurtSounds)) { + voicePitchSlider.active = plr.hasHurtSounds(); + button.setMessage(Text.translatable("wildfire_gender.char_settings.hurt_sounds", enableHurtSounds ? ENABLED : DISABLED)); + PlayerConfig.saveGenderInfo(plr); + } + }, Tooltip.of(Text.translatable("wildfire_gender.tooltip.hurt_sounds")))); + + this.addDrawableChild(this.voicePitchSlider = new WildfireSlider(this.width / 2 - 36, tabOffsetY + 20, 166 / 2 - 2, 20, Configuration.VOICE_PITCH, plr.getVoicePitch(), value -> { + }, value -> Text.translatable("wildfire_gender.slider.voice_pitch", Math.round(value * 100)), value -> { + if (plr.updateVoicePitch(value)) { + PlayerConfig.saveGenderInfo(plr); + if(client.player != null) { + SoundEvent hurtSound = plr.getGender().getHurtSound(); + if(hurtSound != null) { + float pitch = (client.player.getRandom().nextFloat() - client.player.getRandom().nextFloat()) * 0.2F /*+ 1.0F*/; // +1 is from getVoicePitch() + client.player.playSound(hurtSound, 1f, pitch + plr.getVoicePitch()); + } + } + } + })); + voicePitchSlider.active = plr.hasHurtSounds(); + this.voicePitchSlider.setArrowKeyStep(0.01); + + this.addDrawableChild(btnHideInArmor = new WildfireButton(this.width / 2 - 36, tabOffsetY + 44, 166, 20, + Text.translatable("wildfire_gender.char_settings.hide_in_armor", plr.showBreastsInArmor() ? DISABLED : ENABLED), button -> { + boolean enableShowInArmor = !plr.showBreastsInArmor(); + if (plr.updateShowBreastsInArmor(enableShowInArmor)) { + button.setMessage(Text.translatable("wildfire_gender.char_settings.hide_in_armor", enableShowInArmor ? DISABLED : ENABLED)); + PlayerConfig.saveGenderInfo(plr); + } + })); + //Preset Tab Below PRESET_LIST = new WildfireBreastPresetList(this, 156, (j - 48)); PRESET_LIST.setX(this.width / 2 + 30); @@ -136,6 +252,8 @@ public void init() { this.addSelectableChild(this.PRESET_LIST); + updateTabs(); + super.init(); } @@ -149,6 +267,16 @@ private void updateTabs() { this.yOffsetBoobSlider.visible = currentTab == 0; this.zOffsetBoobSlider.visible = currentTab == 0; this.cleavageSlider.visible = currentTab == 0; + + this.btnBreastPhysics.visible = currentTab == 1; + this.btnDualPhysics.visible = currentTab == 1; + this.bounceSlider.visible = currentTab == 1; + this.floppySlider.visible = currentTab == 1; + this.btnOverrideArmorPhys.visible = currentTab == 1; + + this.btnHideInArmor.visible = currentTab == 2; + this.btnHurtSounds.visible = currentTab == 2; + this.voicePitchSlider.visible = currentTab == 2; } @@ -193,26 +321,30 @@ public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delt }; if(backgroundTexture != null) { - ctx.drawTexture(RenderLayer::getGuiTextured, backgroundTexture, (this.width - 272) / 2, (this.height - 118) / 2, 0, 0, 272, 118, 512, 512); + ctx.drawTexture(RenderLayer::getGuiTextured, backgroundTexture, (this.width - 272) / 2, (this.height - 138) / 2, 0, 0, 272, 128, 512, 512); } if(currentTab == 0) { - ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND_CUSTOMIZATION, (this.width) / 2 - 42, (this.height) / 2 - 45, 0, 0, 168, 80, 512, 512); + ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND_CUSTOMIZATION, (this.width) / 2 - 42, (this.height) / 2 - 45, 0, 0, 178, 80, 512, 512); + } else if(currentTab == 1) { + ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND_PHYSICS, (this.width) / 2 - 42, (this.height) / 2 - 69, 0, 0, 178, 104, 512, 512); + } else if(currentTab == 2) { + ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND_MISC, (this.width) / 2 - 42, (this.height) / 2 - 45, 0, 0, 178, 80, 512, 512); } int x = this.width / 2; int y = this.height / 2; //ctx.fill(x + 28, y - 64 - 21, x + 190, y + 68, 0x55000000); //ctx.fill(x + 29, y - 63 - 21, x + 189, y - 60, 0x55000000); - ctx.drawText(textRenderer, getTitle(), x - textRenderer.getWidth(getTitle()) / 2, y - 70, 0xFFFFFF, false); + ctx.drawText(textRenderer, getTitle(), x - textRenderer.getWidth(getTitle()) / 2, y - 82, 0xFFFFFF, false); if(client != null && client.world != null) { int xP = this.width / 2 - 90; int yP = this.height / 2 + 18; PlayerEntity ent = client.world.getPlayerByUuid(this.playerUUID); if(ent != null) { - ctx.enableScissor(xP - 34, yP - 97, xP + 35, yP + 9); - GuiUtils.drawEntityOnScreen(ctx, xP, yP + 60, 65, (xP - mouseX), (yP - 46 - mouseY), ent); + ctx.enableScissor(xP - 38, yP - 97, xP + 38, yP + 9); + GuiUtils.drawEntityOnScreen(ctx, xP, yP + 60, 70, (xP - mouseX), (yP - 46 - mouseY), ent); ctx.disableScissor(); } } @@ -244,6 +376,9 @@ public boolean mouseReleased(double mouseX, double mouseY, int state) { yOffsetBoobSlider.save(); zOffsetBoobSlider.save(); cleavageSlider.save(); + floppySlider.save(); + bounceSlider.save(); + voicePitchSlider.save(); return super.mouseReleased(mouseX, mouseY, state); } } diff --git a/src/main/java/com/wildfire/main/config/GlobalConfig.java b/src/main/java/com/wildfire/main/config/GlobalConfig.java index 6bae4395..6aced195 100644 --- a/src/main/java/com/wildfire/main/config/GlobalConfig.java +++ b/src/main/java/com/wildfire/main/config/GlobalConfig.java @@ -13,11 +13,14 @@ private GlobalConfig() { // see CloudSync#DEFAULT_CLOUD_URL for the actual default public static final StringConfigKey CLOUD_SERVER = new StringConfigKey("cloud_server", ""); + public static final BooleanConfigKey ALWAYS_SHOW_LIST = new BooleanConfigKey("alwaysShowList", false); + static { INSTANCE.setDefault(FIRST_TIME_LOAD); INSTANCE.setDefault(CLOUD_SYNC_ENABLED); INSTANCE.setDefault(AUTOMATIC_CLOUD_SYNC); INSTANCE.setDefault(CLOUD_SERVER); + INSTANCE.setDefault(ALWAYS_SHOW_LIST); if(!INSTANCE.exists()) { INSTANCE.save(); } diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index 55a84e17..873a2bb8 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -12,6 +12,7 @@ "wildfire_gender.player_list.breast_momentum": "Breast Momentum: %s%%", "wildfire_gender.player_list.female_sounds": "Female Sounds: %s", "wildfire_gender.wardrobe.players_using_mod": "Players using the mod:", + "wildfire_gender.always_show_list": "Always Show List: %s", "wildfire_gender.wardrobe.title": "Wildfire's Female Gender Mod", "wildfire_gender.breast_customization.tab_customization": "Customization", @@ -26,7 +27,7 @@ "wildfire_gender.wardrobe.slider.height": "Height: %s", "wildfire_gender.wardrobe.slider.depth": "Depth: %s", "wildfire_gender.wardrobe.slider.rotation": "Rotation: %s°", - "wildfire_gender.slider.voice_pitch": "Voice Pitch: %s%%", + "wildfire_gender.slider.voice_pitch": "Pitch: %s%%", "wildfire_gender.appearance_settings.title": "Character Personalization", "wildfire_gender.char_settings.title": "Character Settings OLD", @@ -49,6 +50,7 @@ "wildfire_gender.label.enabled": "Enabled", "wildfire_gender.label.disabled": "Disabled", + "wildfire_gender.label.on": "On", "wildfire_gender.label.off": "Off", "wildfire_gender.label.yes": "Yes", "wildfire_gender.label.no": "No", @@ -56,9 +58,8 @@ "wildfire_gender.label.with_contributor": "You are playing on a server with a contributor of this mod!", "wildfire_gender.label.with_both": "You are playing on a server with the creator and a contributor of this mod!", - "wildfire_gender.slider.bounce": "Bounce Intensity: %s%%", - "wildfire_gender.slider.floppy": "Breast Momentum: %s%%", - "wildfire_gender.tooltip.bounce_warning": "Setting 'Bounce Intensity' to a high value will look very unnatural!", + "wildfire_gender.slider.bounce": "Intensity: %s%%", + "wildfire_gender.slider.floppy": "Momentum: %s%%", "wildfire_gender.cancer_awareness.title": "Hey, it's Breast Cancer Awareness Month!", diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/breast_customization.png b/src/main/resources/assets/wildfire_gender/textures/gui/breast_customization.png index 6852b6772a0c10fe1c177ff3053b4e2023b7cee0..05da8b6f75d9695157182b4417e4a51a2452e863 100644 GIT binary patch delta 243 zcmVEKE tB*j~y!B^&AvylU-0+Y}JF delta 239 zcmV z+a9x;LI4Zc_H9-G=D&jPDIJ^l;f^QpUo)mS0mM!G30TnaQA)o51b*-mEa2xHfR|IR zfJXqw*$u(H>%61j6=ehXoZvjY9Gr`w->k42b0jxj(ZvsUaOaLnoz?%RD6Tk`t z@Fsx41h4`Dya`}10j%H%;A@8U*3AaMlK|R^SD7bX3tv(&fPFjOo#6Z*w}OuwkrZ!* p249(fv*7|+1C!7KOc-t7{{q)v)u?u&1|I+b002ovPDHLkV1iK!XGZ`4 diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/breast_customization_other.png b/src/main/resources/assets/wildfire_gender/textures/gui/breast_customization_other.png index e8a9ffecbcbacd5272e2462b39b41558b5156560..cd2319103a3be2cc202a2200255e962397272d4c 100644 GIT binary patch literal 1618 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&w@L)Zt|+Cx8@Vage(c!@6@aFM%AEbVpxD z28NCO+O4?yNKR_zOyE4vpm+VC9#g~-R^ME||?Q?IYKN zx}1CMJGc`hnL30W<}f^B5m0ZiU_8R9;KxungoWOT9(Zea!I$B51*^dfhOm!34~k^p zC^fj+Fwbyg@SY)dAdIJITyUQdaqI8dXvE}{m0&WyY4dl^ZBs#-YzBy zPX=v)1G5>}gb#4Bd{ApR$g+WnaSvyL0pl76hI}Ro9|mm;rXRBs_m*yE{GigX?NR8n z&g+M!i|{1e?<-fg_%>|36b`!b+YK TSLdz+mfH-Tu6{1-oD!M+@Rm93pbX;m*2{{y*^X1v4)mKh9vVPW)Zz>WlLjj`6U6XEV?X=WBTV z_QAS(<{$e?>=|~%tAS@zjI>>#B;VTcZi;xC}! zW?)D-bCDa&XfSlw2Q#RxC;{kz<$Jo>4&*SLW_OT2z|L@);mz&hdtaFo#2RX)h4o{< z|9Q^*qo(a^_1%okKN+@^FdSi4kYo7B(82Ct!|;evfWM&vx6nO?cm2$}nG0s!W8mX! z*!+d{L6!9mZ3gw1+y{CXEL<-$Y^l#Lp1197X;?7R1D4#+b^BiaIe+1~W-tHycJq0T z-p^+cvVk~vjH<#DNuz-?8aPDcWm+VY33>PbGwfPj{YT<@kU6M;^K|udS?83{1OSeB B{BZyP diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/breast_customization_tab.png b/src/main/resources/assets/wildfire_gender/textures/gui/breast_customization_tab.png deleted file mode 100644 index be86429e4da5758f29a3896c38824f8887eae344..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1535 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&w@L)Zt|+Cx8@Vage(c!@6@aFM%AEbVpxD z28NCO+ykadB~FXJ-S&rxjkk1Ee@hg8YJk zYQTUY*uO#uBv{}PSXSA?=3e=U5D0a3z#8 zNhmk$C}p|x^7p^5ECy*0gnpE#-QPQp;TQ+;rZIIyGp-T;@qI_VEy!Ir=7MzR$ugFI zia%go$k{`Ti{S3w&J>~l;eFx%Dkh0y)(zH-J(b-IazOT+38$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&eBzI1?3h%1o(|NsB7W5+^6L(R?2!@|O{v$MIlK&EnZZq)`-oFzei!9X=& zz!2iE)Q`-6>Kfty_ZFdp-xMyfr074 zKL#cag$9NnD_87ikpEp0KkwXi_1xN{^$b6(3zPdrAACNucD;lH17krw10#z7kO3lM zHf)4wV|uXoTqInA;X6A_1tZG=R%vda!9dd)>|V`bg-akAbYLAre{i|Y0S1ulo%bv| zWVt`o{P}4u(BN-TzWz5q z0bm^Oxbv$3)*4#4j{_XNNR!1K$B zJp=G zHh?<-rU5uEr}6GT0A>L=E@Q;IuDk#F0A8Q4|IRPkY(4<(I+Gy;j%b?>V4S+u`6aYn zvjM!`{=*2^xXA!M+y9RP0o!&3@cgo3&+h;nKFT+Rjk^L^xi9pGA)4Kz(Y0DQ@^q*ZeyNsh- z+5fh^&VT%$YxS;A__%SEHp&9CQ3F{6lRyJO7~S#s2Qcz>;wCw2(f|Me07*qoM6N<$ Ef*YIH3jhEB delta 470 zcmV;{0V)2x4Z{tPrhf=YL_t(|UhUo6ZrV@~Kv9=m+9u)uf42b+YN9k3RX(z#z4n`t zWvrPc1P{o|@wiOtcy1i*y33t5TaL?*IUUY(optq9D+^erZRl_k@GbT~tTsCXILzqg zW&jw+JMMg|(M|xy7*_5IU>xo_0B-MA?)D{s`(5`FfWzVL*B{E=y#nxi&piNe0`T~> zVvhiPo2~$!#!uo^dII3v1d~<)AVdIwF^*mU000000E}_;0ssI20GJHmVb=jL8^G=Z zpbg*#fN21Z%W1s(4}e(!j>{PFuIui9K7i+U*nj66Z8jf(b{&&p19omV9l&_$z0NnG z?V1hX`SKq|z{X7m@Y()<90=I9D}cwR6?=RIU_77m-}$DnaaRB<_X5D!^g98taTfr$ zcPn@M62Set0kC-&05f_4U_7b+P4OqU?;-%=T>y+vuLemdy~Nd zAxsSb4{P@dzNL^_$UB2ZW@4_F9EomrR`dl^A@IbTJ7@b)dASF)E`U-Xj-6mbabii$4%_rF~GjOtaBEcs_?$KGtcukfU( z_*~P0%t*iDsB6a-JDabY^=8YQx-*%wuh(2QU#s)LB-i=*>4^PD|6O10wwuY~{EK(* zO4nTWyT9di=;Bz09>bhFyay^Oo-pjFzp>-@c7{5+UG5C$p0~$K*MI-Oci_G9ca{UJ z2R@%}vt|B}yNj_xvcA*&C2K>zFasDQ%j|_PBp$zD1+xxFxa&iNj#*SOf>{Q~E^>ny z2}TXeo!JlMFtqbGm^D~4EMtuDoBmPGo*@m$NIufOZukG63~~1#{)pATm1D=uAZs<* zk-0BpHeZ6x*TC!E8T-?iD{TK==D4x*B*VU2|ND&@f0r?T__HbT{CkEu_NVn*=k8{_ zvBR9<*fM*LhPly}T9it=%B`m-Ry=!<#!M4CVh>6Kbb^d%b0j zUi6Ghsaxac^Bq_hYqvC6IC^{Dx7C;5*56+@|B~g}{0~=OJ`TO5$-P;TrITs11G6Bb cM9Jw->_Keh8o!(+Vn6}m>FVdQ&MBb@0Bl3qz5oCK delta 478 zcmV<40U`dm4Z;nOrGEwoNklpLXu?J%FF5?iT=;%OTTWJNNVfz~5)?8GtK* zm)9M80pQzo2k<)n67SM00N*B)UI7MLmoef~*WLeo0B`Sb{%#lAY(4;;I+HO2 zc5ZJvfbrC4-7Z4MH5} zI{;(Tp9H|cZ2%shcJA>#fS;cRz~OBGX7mogxKsac#qZpHiU5eW0T|!N`}GTeZ_)rT zHnLmy)dFz#ld%IKN*e&4_wECL@!anLaCjSl8T||3PXTan(*QjDF96%CbX?22T(>Z# z>uSF~9e_i-eoDtC_HNtkSEt+mvE4qUbK2hEtJBA;bWj$vAp?K|lW+qv7`k=+2Rs3z UOtAMp=Kufz07*qoM6N<$g2%1knE(I) diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/wardrobe_bg_other.png b/src/main/resources/assets/wildfire_gender/textures/gui/wardrobe_bg_other.png index 81e2830f0e27b67fc64afbdaeef7a67183ce3997..dd4d3028a716a28ab012272ce381fbbd9dbe929a 100644 GIT binary patch delta 489 zcmX@ayOVdq(t6f^o-U3d6>)E`U-Xj-6mbabii$4%_rF~GjOtaBEcs_?$KGtcukfU( z_*~P0%t*iDsB6a-JDabY^=8YQx-*%wuh(2QU#s)LB-i=*>4^PD|6O10wwuY~{EK(* zO4nTWyT9di=;Bz09>bhFyay^Oo-pjFzp>-@c7{5+UG5C$p0~$K*MI-Oci_G9ca{UJ z2R@%}vt|B}yNj_xvcA*&C2K>zFasDQ%j|_PBp$zD1+xxFxa&iNj#*SOf>{Q~E^>ny z2}TXeo!JlMFtqbGm^D~4EMtuDoBmPGo*@m$NIufOZukG63~~1#{)pATm1D=uAZs<* zk-0BpHeZ6x*TC!E8T-?iD{TK==D4x*B*VU2|ND&@f0r?T__HbT{CkEu_NVn*=k8{_ zvBR9<*fM*LhPly}T9it=%B`m-Ry=!<#!M4CVh>6Kbb^d%b0j zUi6Ghsaxac^Bq_hYqvC6IC^{Dx7C;5*56+@|B~g}{0~=OJ`TO5$-P;TrITs11G6Bb cM9Jw->_Keh8o!(+Vn6}m>FVdQ&MBb@0Bl3qz5oCK delta 478 zcmV<40U`dm4Z;nOrGEwoNklpLXu?J%FF5?iT=;%OTTWJNNVfz~5)?8GtK* zm)9M80pQzo2k<)n67SM00N*B)UI7MLmoef~*WLeo0B`Sb{%#lAY(4;;I+HO2 zc5ZJvfbrC4-7Z4MH5} zI{;(Tp9H|cZ2%shcJA>#fS;cRz~OBGX7mogxKsac#qZpHiU5eW0T|!N`}GTeZ_)rT zHnLmy)dFz#ld%IKN*e&4_wECL@!anLaCjSl8T||3PXTan(*QjDF96%CbX?22T(>Z# z>uSF~9e_i-eoDtC_HNtkSEt+mvE4qUbK2hEtJBA;bWj$vAp?K|lW+qv7`k=+2Rs3z UOtAMp=Kufz07*qoM6N<$g2%1knE(I) From 4da25eeba871d7c5f213e009dc93e702211988be Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sat, 23 Nov 2024 22:49:00 -0500 Subject: [PATCH 186/238] Finish UI changes --- .../gui/tabs/breast_customization_tab.png | Bin 0 -> 1533 bytes .../textures/gui/tabs/breast_physics_tab.png | Bin 0 -> 1552 bytes .../textures/gui/tabs/miscellaneous_tab.png | Bin 0 -> 1539 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/tabs/breast_customization_tab.png create mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/tabs/breast_physics_tab.png create mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/tabs/miscellaneous_tab.png diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/tabs/breast_customization_tab.png b/src/main/resources/assets/wildfire_gender/textures/gui/tabs/breast_customization_tab.png new file mode 100644 index 0000000000000000000000000000000000000000..8ede70bbc82d827190fe0ff7916677cb94573f0e GIT binary patch literal 1533 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&w@L)Zt|+Cx8@Vage(c!@6@aFM%AEbVpxD z28NCO+ykadB~FXJ-S&rxjkk1Ee@hg8YJk zYQTUY*uO#uBv{}PSbP0l+XkKR3N~3 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/tabs/breast_physics_tab.png b/src/main/resources/assets/wildfire_gender/textures/gui/tabs/breast_physics_tab.png new file mode 100644 index 0000000000000000000000000000000000000000..75e3d0cddd3158a60a460982c838e0d895f18710 GIT binary patch literal 1552 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&w@L)Zt|+Cx8@Vage(c!@6@aFM%AEbVpxD z28NCO+ykadB~FXJ-S&rxjkk1Ee@hg8YJk zYQTUY*uO#uBv{}PSNAFpPFN?Z}lrO%#MBiScOYs+qqx=<>zzF_-fy9zIaa@m&8}Tg!A7x zm?X;BKRoA&XOh^?{G+-hpRq^SVGhG176J7J3&tay3VsZQOdYs|fST-@8IIj$oa4-J zm_=aw_6Fk$af9=U1_bn>ntq2b;r{ObZ&?i9@jZ}Xl2B{dv6r>ycU49pBTw}!fghV= z?KiDpXd~ztWJ7N=zN!A+EEg5RaQGd=ImL!VrjFau2PF4!&-i{t@q^$whR?)$5p3#P zhHra+-}hs93^A;o^@GrX4W(dk_AOi1!R3E_?G wMclD6stQk%9Sxk(z#*b!10|CsvDfX{_v?rK`oVA>SQ0aMy85}Sb4q9e0NQrU@c;k- literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/tabs/miscellaneous_tab.png b/src/main/resources/assets/wildfire_gender/textures/gui/tabs/miscellaneous_tab.png new file mode 100644 index 0000000000000000000000000000000000000000..a5c45650d8872502499b37e83f6b4be81c2ac16a GIT binary patch literal 1539 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&w@L)Zt|+Cx8@Vage(c!@6@aFM%AEbVpxD z28NCO+ykadB~FXJ-S&rxjkk1Ee@hg8YJk zYQTUY*uO#uBv{}PSWKI4Ey>&C^r-`bqG7mVR*zMpx$7?c!X2Ij{%QR8H0Vgz=0js4aFP@ zGZ-Fy6Et|2t)DZCp>6MLp&vHu?mICYCe9$Js<#Z^cK>#)PQ1)w@P_Y!43mUf!;V^( zIS2nUJp-92cD}onp^bo>P%XU8_~!VZ`ne3p?lR7CW;o2p{XnrH@paVtm2xf&i|0L# zsos>n;eX`%%5{_P%s(r_+)IMzQ7szPNGP$4h8LckG#WUAB5*RVf0gf;WVe+y++i`W P@MZ9H^>bP0l+XkKerU&9 literal 0 HcmV?d00001 From 37b17ddfbaf23ae20a5739ebbb8b81521176d088 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sat, 23 Nov 2024 23:01:41 -0500 Subject: [PATCH 187/238] UI tweaks, moved "Players Using the Mod" to the HUD renderer so you can always show it with a toggle. --- .../gui/screen/WardrobeBrowserScreen.java | 24 +---------- .../wildfire/main/WildfireEventHandler.java | 41 +++++++++++++++++++ .../assets/wildfire_gender/lang/en_us.json | 2 +- 3 files changed, 44 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index d74a755d..a2d31b10 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -72,14 +72,14 @@ public void init() { //TODO: Finish this - /*this.addDrawableChild(btnAlwaysShowList = new WildfireButton(126, 4, 140, 10, + this.addDrawableChild(btnAlwaysShowList = new WildfireButton(126, 4, 140, 10, Text.translatable("wildfire_gender.always_show_list", GlobalConfig.INSTANCE.get(GlobalConfig.ALWAYS_SHOW_LIST) ? WildfireLocalization.ENABLED : WildfireLocalization.DISABLED), button -> { var config = GlobalConfig.INSTANCE; var newVal = !config.get(GlobalConfig.ALWAYS_SHOW_LIST); config.set(GlobalConfig.ALWAYS_SHOW_LIST, newVal); button.setMessage(Text.translatable("wildfire_gender.always_show_list", newVal ? WildfireLocalization.ENABLED : WildfireLocalization.DISABLED)); - }));*/ + })); this.addDrawableChild(btnFemale = new WildfireButton(this.width / 2 - 130, this.height / 2 + 33, 80, 15, plr.getGender().getDisplayName(), button -> { Gender gender = switch (plr.getGender()) { @@ -185,16 +185,6 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { GuiUtils.drawCenteredText(ctx, this.textRenderer, Text.translatable("wildfire_gender.label.with_contributor"), this.width / 2, creatorY, 0xFF00FF); } - List syncedPlayers = collectPlayerEntries(); - if(!syncedPlayers.isEmpty() || GlobalConfig.INSTANCE.get(GlobalConfig.ALWAYS_SHOW_LIST)) { - ctx.drawText(textRenderer, Text.translatable("wildfire_gender.wardrobe.players_using_mod").formatted(Formatting.AQUA), 5, 5, 0xFFFFFF, false); - int yPos = 18; - for(PlayerListEntry entry : syncedPlayers) { - PlayerConfig cfg = WildfireGender.getPlayerById(entry.getProfile().getId()); - ctx.drawText(textRenderer, Text.literal(entry.getProfile().getName() + " - ").append(cfg.getGender().getDisplayName()), 10, yPos, 0xFFFFFF, false); - yPos += 10; - } - } } if(isBreastCancerAwarenessMonth) { @@ -206,14 +196,4 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { } - private List collectPlayerEntries() { - return this.client.player.networkHandler.getListedPlayerListEntries().stream() - .filter(entry -> !entry.getProfile().getId().equals(client.player.getUuid())) - .filter(entry -> { - var cfg = WildfireGender.getPlayerById(entry.getProfile().getId()); - return cfg != null && cfg.getSyncStatus() != PlayerConfig.SyncStatus.UNKNOWN; - }) - .limit(40L) - .toList(); - } } \ No newline at end of file diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index a85663e4..77c8109c 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -36,13 +36,19 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback; import net.fabricmc.fabric.api.client.rendering.v1.LivingEntityFeatureRendererRegistrationCallback; import net.fabricmc.fabric.api.networking.v1.EntityTrackingEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.network.PlayerListEntry; import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.render.RenderTickCounter; import net.minecraft.client.render.entity.ArmorStandEntityRenderer; import net.minecraft.client.render.entity.EntityRendererFactory; import net.minecraft.client.render.entity.LivingEntityRenderer; @@ -54,10 +60,14 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; import net.minecraft.util.Util; import net.minecraft.world.World; import org.lwjgl.glfw.GLFW; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CompletableFuture; public final class WildfireEventHandler { @@ -99,8 +109,25 @@ public static void registerClientEvents() { ClientTickEvents.END_CLIENT_TICK.register(WildfireEventHandler::onClientTick); ClientPlayConnectionEvents.DISCONNECT.register(WildfireEventHandler::clientDisconnect); LivingEntityFeatureRendererRegistrationCallback.EVENT.register(WildfireEventHandler::registerRenderLayers); + HudRenderCallback.EVENT.register(WildfireEventHandler::renderHud); } + @Environment(EnvType.CLIENT) + private static void renderHud(DrawContext context, RenderTickCounter tickCounter) { + TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + if(textRenderer == null) return; + + List syncedPlayers = collectPlayerEntries(); + if(!syncedPlayers.isEmpty() || GlobalConfig.INSTANCE.get(GlobalConfig.ALWAYS_SHOW_LIST)) { + context.drawText(textRenderer, Text.translatable("wildfire_gender.wardrobe.players_using_mod").formatted(Formatting.AQUA), 5, 5, 0xFFFFFF, false); + int yPos = 18; + for(PlayerListEntry entry : syncedPlayers) { + PlayerConfig cfg = WildfireGender.getPlayerById(entry.getProfile().getId()); + context.drawText(textRenderer, Text.literal(entry.getProfile().getName() + " - ").append(cfg.getGender().getDisplayName()), 10, yPos, 0xFFFFFF, false); + yPos += 10; + } + } + } /** * Attach breast render layers to players and armor stands */ @@ -203,4 +230,18 @@ private static void onBeginTracking(Entity tracked, ServerPlayerEntity syncTo) { WildfireSync.sendToClient(syncTo, genderToSync); } } + + + private static List collectPlayerEntries() { + if(MinecraftClient.getInstance().player == null) return new ArrayList(); + ClientPlayerEntity player = MinecraftClient.getInstance().player; + return player.networkHandler.getListedPlayerListEntries().stream() + .filter(entry -> !entry.getProfile().getId().equals(player.getUuid())) + .filter(entry -> { + var cfg = WildfireGender.getPlayerById(entry.getProfile().getId()); + return cfg != null && cfg.getSyncStatus() != PlayerConfig.SyncStatus.UNKNOWN; + }) + .limit(40L) + .toList(); + } } diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index 873a2bb8..2933e8df 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -11,7 +11,7 @@ "wildfire_gender.player_list.bounce_multiplier": "Bounce Multiplier: %sx", "wildfire_gender.player_list.breast_momentum": "Breast Momentum: %s%%", "wildfire_gender.player_list.female_sounds": "Female Sounds: %s", - "wildfire_gender.wardrobe.players_using_mod": "Players using the mod:", + "wildfire_gender.wardrobe.players_using_mod": "Players Using the Mod:", "wildfire_gender.always_show_list": "Always Show List: %s", "wildfire_gender.wardrobe.title": "Wildfire's Female Gender Mod", From 0be1308fa585d1c6ea82a0d8373dfa3015e8152a Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sat, 23 Nov 2024 23:11:16 -0500 Subject: [PATCH 188/238] Disable always showing if toggle is off --- src/main/java/com/wildfire/main/WildfireEventHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index 77c8109c..24eb6497 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -118,7 +118,7 @@ private static void renderHud(DrawContext context, RenderTickCounter tickCounter if(textRenderer == null) return; List syncedPlayers = collectPlayerEntries(); - if(!syncedPlayers.isEmpty() || GlobalConfig.INSTANCE.get(GlobalConfig.ALWAYS_SHOW_LIST)) { + if((!syncedPlayers.isEmpty() && !GlobalConfig.INSTANCE.get(GlobalConfig.ALWAYS_SHOW_LIST) && MinecraftClient.getInstance().currentScreen instanceof WardrobeBrowserScreen) || GlobalConfig.INSTANCE.get(GlobalConfig.ALWAYS_SHOW_LIST)) { context.drawText(textRenderer, Text.translatable("wildfire_gender.wardrobe.players_using_mod").formatted(Formatting.AQUA), 5, 5, 0xFFFFFF, false); int yPos = 18; for(PlayerListEntry entry : syncedPlayers) { From a8aae37f71160abebf49be8a85ce4fd0c670633f Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sat, 23 Nov 2024 23:22:15 -0500 Subject: [PATCH 189/238] Push version to 4.2 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 97906b1b..2df82252 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ yarn_build=1 loader_version=0.16.9 # Mod Properties -mod_version = 4.1 +mod_version = 4.2 maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod From f4c10d8816892096e032f7127039c6d5cb0cb0ce Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sun, 24 Nov 2024 00:22:00 -0500 Subject: [PATCH 190/238] Rework Contributor Text. --- gradle.properties | 2 +- .../gui/screen/WardrobeBrowserScreen.java | 52 ++++++++++++++++--- .../wildfire/main/WildfireEventHandler.java | 12 +++-- 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/gradle.properties b/gradle.properties index 2df82252..e717b3fe 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ yarn_build=1 loader_version=0.16.9 # Mod Properties -mod_version = 4.2 +mod_version = 4.2.1 maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index a2d31b10..ed4431b5 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -23,6 +23,7 @@ import com.wildfire.main.WildfireGender; import java.util.*; +import java.util.stream.Collectors; import com.wildfire.gui.WildfireButton; import com.wildfire.main.WildfireLocalization; @@ -33,9 +34,11 @@ import net.fabricmc.api.Environment; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.ScreenRect; import net.minecraft.client.gui.hud.PlayerListHud; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.gui.tooltip.TooltipState; import net.minecraft.client.network.PlayerListEntry; import net.minecraft.client.realms.dto.PlayerInfo; import net.minecraft.client.render.RenderLayer; @@ -48,6 +51,8 @@ import net.minecraft.util.Nullables; import net.minecraft.world.GameMode; +import static com.wildfire.main.WildfireEventHandler.collectPlayerEntries; + @Environment(EnvType.CLIENT) public class WardrobeBrowserScreen extends BaseWildfireScreen { private static final Identifier BACKGROUND_MALE = Identifier.of(WildfireGender.MODID, "textures/gui/wardrobe_bg_male.png"); @@ -59,6 +64,8 @@ public class WardrobeBrowserScreen extends BaseWildfireScreen { private static final boolean isBreastCancerAwarenessMonth = Calendar.getInstance().get(Calendar.MONTH) == Calendar.OCTOBER; + private final TooltipState contribTooltip = new TooltipState(); + private WildfireButton btnMale, btnFemale, btnOther, btnCharacterPersonalization, btnAlwaysShowList; public WardrobeBrowserScreen(Screen parent, UUID uuid) { super(Text.translatable("wildfire_gender.wardrobe.title"), parent, uuid); @@ -167,22 +174,43 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { ctx.drawText(textRenderer, getTitle(), x - textRenderer.getWidth(getTitle()) / 2, y - 82, 0xFFFFFF, false); if(client != null && client.player != null) { - boolean withCreator = client.player.networkHandler.getPlayerList().stream() - .anyMatch((player) -> player.getProfile().getId().equals(WildfireGender.CREATOR_UUID)); - boolean withContributor = client.player.networkHandler.getPlayerList().stream() - .anyMatch(player -> WildfireGender.CONTRIBUTOR_UUIDS.contains(player.getProfile().getId())); + boolean withCreator = client.player.networkHandler.getPlayerList().stream().anyMatch((player) -> player.getProfile().getId().equals(WildfireGender.CREATOR_UUID)); + + List foundContributors = client.player.networkHandler.getPlayerList().stream() + .filter(player -> WildfireGender.CONTRIBUTOR_UUIDS.contains(player.getProfile().getId())) + .toList(); int creatorY = y + 65; // move down so we don't overlap with the breast cancer awareness month banner if(isBreastCancerAwarenessMonth) creatorY += 30; - if(withCreator && withContributor) { //with both the creator and a contributor + if(withCreator && foundContributors.size() != 0) { //with both the creator and a contributor GuiUtils.drawCenteredTextWrapped(ctx, this.textRenderer, Text.translatable("wildfire_gender.label.with_both"), this.width / 2, creatorY, 250, 0xFF00FF); + int width = textRenderer.getWidth(Text.translatable("wildfire_gender.label.with_contributor")); + + String contributors = foundContributors.stream() + .map(entry -> entry.getProfile().getName()) + .collect(Collectors.joining(", ")); + contributors = "WildfireFGM, " + contributors; + if(mouseX > this.width / 2 - width / 2 && mouseX < this.width / 2 + width / 2 && mouseY > creatorY - 2 && mouseY < creatorY + 9) { + contribTooltip.setTooltip(Tooltip.of(Text.literal(contributors))); + contribTooltip.render(true, true, ScreenRect.empty()); + } } else if(withCreator) { //only with the creator GuiUtils.drawCenteredText(ctx, this.textRenderer, Text.translatable("wildfire_gender.label.with_creator"), this.width / 2, creatorY, 0xFF00FF); - } else if(withContributor) { //only with a contributor + } else if(!foundContributors.isEmpty()) { //only with a contributor + int width = textRenderer.getWidth(Text.translatable("wildfire_gender.label.with_contributor")); GuiUtils.drawCenteredText(ctx, this.textRenderer, Text.translatable("wildfire_gender.label.with_contributor"), this.width / 2, creatorY, 0xFF00FF); + + String contributors = foundContributors.stream() + .map(entry -> entry.getProfile().getName()) + .collect(Collectors.joining(", ")); + + if(mouseX > this.width / 2 - width / 2 && mouseX < this.width / 2 + width / 2 && mouseY > creatorY - 2 && mouseY < creatorY + 9) { + contribTooltip.setTooltip(Tooltip.of(Text.literal(contributors))); + contribTooltip.render(true, true, ScreenRect.empty()); + } } } @@ -193,6 +221,18 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { ctx.drawTextWithShadow(textRenderer, Text.translatable("wildfire_gender.cancer_awareness.title").formatted(Formatting.BOLD, Formatting.ITALIC), this.width / 2 - 148, bcaY + 117, 0xFFFFFF); ctx.drawTexture(RenderLayer::getGuiTextured, TXTR_RIBBON, x + 130, bcaY + 109, 0, 0, 26, 26, 20, 20, 20, 20); } + + //Render in front of the UI when it's open. + List syncedPlayers = collectPlayerEntries(); + if(!syncedPlayers.isEmpty() || GlobalConfig.INSTANCE.get(GlobalConfig.ALWAYS_SHOW_LIST)) { + ctx.drawText(textRenderer, Text.translatable("wildfire_gender.wardrobe.players_using_mod").formatted(Formatting.AQUA), 5, 5, 0xFFFFFF, false); + int yPos = 18; + for(PlayerListEntry entry : syncedPlayers) { + PlayerConfig cfg = WildfireGender.getPlayerById(entry.getProfile().getId()); + ctx.drawText(textRenderer, Text.literal(entry.getProfile().getName() + " - ").append(cfg.getGender().getDisplayName()), 10, yPos, 0xFFFFFF, false); + yPos += 10; + } + } } diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index 24eb6497..4d18b67a 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -44,6 +44,7 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.hud.PlayerListHud; import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.client.network.PlayerListEntry; @@ -117,15 +118,20 @@ private static void renderHud(DrawContext context, RenderTickCounter tickCounter TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; if(textRenderer == null) return; + //Render when in tab list, unless the toggle is enabled. Then always render it. List syncedPlayers = collectPlayerEntries(); - if((!syncedPlayers.isEmpty() && !GlobalConfig.INSTANCE.get(GlobalConfig.ALWAYS_SHOW_LIST) && MinecraftClient.getInstance().currentScreen instanceof WardrobeBrowserScreen) || GlobalConfig.INSTANCE.get(GlobalConfig.ALWAYS_SHOW_LIST)) { - context.drawText(textRenderer, Text.translatable("wildfire_gender.wardrobe.players_using_mod").formatted(Formatting.AQUA), 5, 5, 0xFFFFFF, false); + if( + (MinecraftClient.getInstance().options.playerListKey.isPressed() && !(MinecraftClient.getInstance().currentScreen instanceof WardrobeBrowserScreen)) || + GlobalConfig.INSTANCE.get(GlobalConfig.ALWAYS_SHOW_LIST)) { + + context.drawText(textRenderer, Text.translatable("wildfire_gender.wardrobe.players_using_mod").formatted(Formatting.AQUA), 5, 5, 0xFFFFFF, true); int yPos = 18; for(PlayerListEntry entry : syncedPlayers) { PlayerConfig cfg = WildfireGender.getPlayerById(entry.getProfile().getId()); context.drawText(textRenderer, Text.literal(entry.getProfile().getName() + " - ").append(cfg.getGender().getDisplayName()), 10, yPos, 0xFFFFFF, false); yPos += 10; } + } } /** @@ -232,7 +238,7 @@ private static void onBeginTracking(Entity tracked, ServerPlayerEntity syncTo) { } - private static List collectPlayerEntries() { + public static List collectPlayerEntries() { if(MinecraftClient.getInstance().player == null) return new ArrayList(); ClientPlayerEntity player = MinecraftClient.getInstance().player; return player.networkHandler.getListedPlayerListEntries().stream() From 855f56a33f16c1b6573b6567730c311932a2f872 Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 24 Nov 2024 00:24:20 -0700 Subject: [PATCH 191/238] clean up the contributor & synced player lists --- src/main/java/com/wildfire/gui/GuiUtils.java | 20 +++ .../gui/screen/WardrobeBrowserScreen.java | 122 ++++++++---------- .../wildfire/main/WildfireEventHandler.java | 38 +++--- .../com/wildfire/main/cloud/CloudSync.java | 9 +- .../java/com/wildfire/main/cloud/SyncLog.java | 33 ++++- .../wildfire/main/config/EnumConfigKey.java | 47 +++++++ .../wildfire/main/config/GenderConfigKey.java | 29 +---- .../wildfire/main/config/GlobalConfig.java | 25 +++- .../main/config/enums/ShowPlayerListMode.java | 50 +++++++ .../main/config/enums/SyncVerbosity.java | 30 +++++ .../assets/wildfire_gender/lang/en_us.json | 9 +- 11 files changed, 289 insertions(+), 123 deletions(-) create mode 100644 src/main/java/com/wildfire/main/config/EnumConfigKey.java create mode 100644 src/main/java/com/wildfire/main/config/enums/ShowPlayerListMode.java create mode 100644 src/main/java/com/wildfire/main/config/enums/SyncVerbosity.java diff --git a/src/main/java/com/wildfire/gui/GuiUtils.java b/src/main/java/com/wildfire/gui/GuiUtils.java index b227118d..ccfce8a6 100644 --- a/src/main/java/com/wildfire/gui/GuiUtils.java +++ b/src/main/java/com/wildfire/gui/GuiUtils.java @@ -18,21 +18,26 @@ package com.wildfire.gui; +import com.wildfire.main.WildfireGender; +import com.wildfire.main.entitydata.PlayerConfig; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.ingame.InventoryScreen; +import net.minecraft.client.network.PlayerListEntry; import net.minecraft.entity.LivingEntity; import net.minecraft.text.MutableText; import net.minecraft.text.OrderedText; import net.minecraft.text.StringVisitable; import net.minecraft.text.Text; +import net.minecraft.util.Formatting; import net.minecraft.util.Util; import net.minecraft.util.math.MathHelper; import org.joml.Quaternionf; import org.joml.Vector3f; +import java.util.List; import java.util.Objects; @Environment(EnvType.CLIENT) @@ -128,4 +133,19 @@ public static void drawEntityOnScreen(DrawContext ctx, int x, int y, int size, f entity.headYaw = o; ctx.getMatrices().pop(); } + + public static void drawSyncedPlayers(DrawContext context, TextRenderer textRenderer, List syncedPlayers) { + if(syncedPlayers.isEmpty()) return; + var header = Text.translatable("wildfire_gender.wardrobe.players_using_mod").formatted(Formatting.AQUA); + context.drawText(textRenderer, header, 5, 5, 0xFFFFFF, true); + + int yPos = 18; + for(PlayerListEntry entry : syncedPlayers) { + PlayerConfig cfg = WildfireGender.getPlayerById(entry.getProfile().getId()); + if(cfg == null) continue; + var text = Text.literal(entry.getProfile().getName()).append(" - ").append(cfg.getGender().getDisplayName()); + context.drawText(textRenderer, text, 10, yPos, 0xFFFFFF, false); + yPos += 10; + } + } } diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index ed4431b5..aa0a8aba 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -23,33 +23,28 @@ import com.wildfire.main.WildfireGender; import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; import com.wildfire.gui.WildfireButton; -import com.wildfire.main.WildfireLocalization; import com.wildfire.main.cloud.CloudSync; import com.wildfire.main.config.GlobalConfig; import com.wildfire.main.entitydata.PlayerConfig; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; -import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.ScreenRect; -import net.minecraft.client.gui.hud.PlayerListHud; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.tooltip.Tooltip; import net.minecraft.client.gui.tooltip.TooltipState; import net.minecraft.client.network.PlayerListEntry; -import net.minecraft.client.realms.dto.PlayerInfo; import net.minecraft.client.render.RenderLayer; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.scoreboard.Team; -import net.minecraft.server.dedicated.gui.PlayerListGui; import net.minecraft.text.Text; +import net.minecraft.text.Texts; import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; -import net.minecraft.util.Nullables; -import net.minecraft.world.GameMode; import static com.wildfire.main.WildfireEventHandler.collectPlayerEntries; @@ -66,7 +61,6 @@ public class WardrobeBrowserScreen extends BaseWildfireScreen { private final TooltipState contribTooltip = new TooltipState(); - private WildfireButton btnMale, btnFemale, btnOther, btnCharacterPersonalization, btnAlwaysShowList; public WardrobeBrowserScreen(Screen parent, UUID uuid) { super(Text.translatable("wildfire_gender.wardrobe.title"), parent, uuid); } @@ -77,18 +71,20 @@ public void init() { int y = this.height / 2; PlayerConfig plr = Objects.requireNonNull(getPlayer(), "getPlayer()"); - - //TODO: Finish this - this.addDrawableChild(btnAlwaysShowList = new WildfireButton(126, 4, 140, 10, - Text.translatable("wildfire_gender.always_show_list", GlobalConfig.INSTANCE.get(GlobalConfig.ALWAYS_SHOW_LIST) ? WildfireLocalization.ENABLED : WildfireLocalization.DISABLED), + WildfireButton listButton; + this.addDrawableChild(listButton = new WildfireButton(126, 4, 185, 10, + Text.translatable("wildfire_gender.always_show_list", GlobalConfig.INSTANCE.get(GlobalConfig.ALWAYS_SHOW_LIST).text()), button -> { var config = GlobalConfig.INSTANCE; - var newVal = !config.get(GlobalConfig.ALWAYS_SHOW_LIST); + var newVal = config.get(GlobalConfig.ALWAYS_SHOW_LIST).next(); config.set(GlobalConfig.ALWAYS_SHOW_LIST, newVal); - button.setMessage(Text.translatable("wildfire_gender.always_show_list", newVal ? WildfireLocalization.ENABLED : WildfireLocalization.DISABLED)); + config.save(); + button.setMessage(Text.translatable("wildfire_gender.always_show_list", newVal.text())); + button.setTooltip(newVal.tooltip()); })); + listButton.setTooltip(GlobalConfig.INSTANCE.get(GlobalConfig.ALWAYS_SHOW_LIST).tooltip()); - this.addDrawableChild(btnFemale = new WildfireButton(this.width / 2 - 130, this.height / 2 + 33, 80, 15, plr.getGender().getDisplayName(), button -> { + this.addDrawableChild(new WildfireButton(this.width / 2 - 130, this.height / 2 + 33, 80, 15, plr.getGender().getDisplayName(), button -> { Gender gender = switch (plr.getGender()) { case MALE -> Gender.FEMALE; case FEMALE -> Gender.OTHER; @@ -101,10 +97,11 @@ public void init() { } })); - this.addDrawableChild(this.btnCharacterPersonalization = new WildfireButton(this.width / 2 - 36, this.height / 2 - 63, 158, 20, Text.translatable("wildfire_gender.appearance_settings.title").append("..."), + WildfireButton btnCharacterPersonalization; + this.addDrawableChild(btnCharacterPersonalization = new WildfireButton(this.width / 2 - 36, this.height / 2 - 63, 158, 20, Text.translatable("wildfire_gender.appearance_settings.title").append("..."), button -> client.setScreen(new WildfireBreastCustomizationScreen(WardrobeBrowserScreen.this, this.playerUUID)))); - this.btnCharacterPersonalization.active = plr.getGender().canHaveBreasts(); + btnCharacterPersonalization.active = plr.getGender().canHaveBreasts(); //old menu /*this.addDrawableChild(new WildfireButton(this.width / 2 - 42, y - (plr.getGender().canHaveBreasts() ? 12 : 32), 158, 20, Text.translatable("wildfire_gender.char_settings.title").append("..."), @@ -173,47 +170,7 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { int y = this.height / 2; ctx.drawText(textRenderer, getTitle(), x - textRenderer.getWidth(getTitle()) / 2, y - 82, 0xFFFFFF, false); - if(client != null && client.player != null) { - boolean withCreator = client.player.networkHandler.getPlayerList().stream().anyMatch((player) -> player.getProfile().getId().equals(WildfireGender.CREATOR_UUID)); - - List foundContributors = client.player.networkHandler.getPlayerList().stream() - .filter(player -> WildfireGender.CONTRIBUTOR_UUIDS.contains(player.getProfile().getId())) - .toList(); - - int creatorY = y + 65; - - // move down so we don't overlap with the breast cancer awareness month banner - if(isBreastCancerAwarenessMonth) creatorY += 30; - - if(withCreator && foundContributors.size() != 0) { //with both the creator and a contributor - GuiUtils.drawCenteredTextWrapped(ctx, this.textRenderer, Text.translatable("wildfire_gender.label.with_both"), this.width / 2, creatorY, 250, 0xFF00FF); - int width = textRenderer.getWidth(Text.translatable("wildfire_gender.label.with_contributor")); - - String contributors = foundContributors.stream() - .map(entry -> entry.getProfile().getName()) - .collect(Collectors.joining(", ")); - contributors = "WildfireFGM, " + contributors; - if(mouseX > this.width / 2 - width / 2 && mouseX < this.width / 2 + width / 2 && mouseY > creatorY - 2 && mouseY < creatorY + 9) { - contribTooltip.setTooltip(Tooltip.of(Text.literal(contributors))); - contribTooltip.render(true, true, ScreenRect.empty()); - } - } else if(withCreator) { //only with the creator - GuiUtils.drawCenteredText(ctx, this.textRenderer, Text.translatable("wildfire_gender.label.with_creator"), this.width / 2, creatorY, 0xFF00FF); - } else if(!foundContributors.isEmpty()) { //only with a contributor - int width = textRenderer.getWidth(Text.translatable("wildfire_gender.label.with_contributor")); - GuiUtils.drawCenteredText(ctx, this.textRenderer, Text.translatable("wildfire_gender.label.with_contributor"), this.width / 2, creatorY, 0xFF00FF); - - String contributors = foundContributors.stream() - .map(entry -> entry.getProfile().getName()) - .collect(Collectors.joining(", ")); - - if(mouseX > this.width / 2 - width / 2 && mouseX < this.width / 2 + width / 2 && mouseY > creatorY - 2 && mouseY < creatorY + 9) { - contribTooltip.setTooltip(Tooltip.of(Text.literal(contributors))); - contribTooltip.render(true, true, ScreenRect.empty()); - } - } - - } + drawCreatorContributorText(ctx, mouseX, mouseY, y + 65 + (isBreastCancerAwarenessMonth ? 30 : 0)); if(isBreastCancerAwarenessMonth) { int bcaY = y - 45; @@ -224,16 +181,47 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { //Render in front of the UI when it's open. List syncedPlayers = collectPlayerEntries(); - if(!syncedPlayers.isEmpty() || GlobalConfig.INSTANCE.get(GlobalConfig.ALWAYS_SHOW_LIST)) { - ctx.drawText(textRenderer, Text.translatable("wildfire_gender.wardrobe.players_using_mod").formatted(Formatting.AQUA), 5, 5, 0xFFFFFF, false); - int yPos = 18; - for(PlayerListEntry entry : syncedPlayers) { - PlayerConfig cfg = WildfireGender.getPlayerById(entry.getProfile().getId()); - ctx.drawText(textRenderer, Text.literal(entry.getProfile().getName() + " - ").append(cfg.getGender().getDisplayName()), 10, yPos, 0xFFFFFF, false); - yPos += 10; - } - } + GuiUtils.drawSyncedPlayers(ctx, textRenderer, syncedPlayers); } + private void drawCreatorContributorText(DrawContext ctx, int mouseX, int mouseY, int creatorY) { + final var client = Objects.requireNonNull(this.client); + if(client.player == null || client.world == null) return; + Map entries = client.player.networkHandler.getPlayerList() + .stream().collect(Collectors.toMap(entry -> entry.getProfile().getId(), Function.identity())); + + final boolean withCreator = entries.containsKey(WildfireGender.CREATOR_UUID); + final var foundContributors = WildfireGender.CONTRIBUTOR_UUIDS.stream().map(entries::get).filter(Objects::nonNull).toList(); + if(!withCreator && foundContributors.isEmpty()) { + return; + } + + final Text text; + final var toList = new ArrayList<>(foundContributors); + if(withCreator && !foundContributors.isEmpty()) { + text = Text.translatable("wildfire_gender.label.with_both"); + toList.addFirst(entries.get(WildfireGender.CREATOR_UUID)); + } else if(withCreator) { + text = Text.translatable("wildfire_gender.label.with_creator"); + } else { + text = Text.translatable("wildfire_gender.label.with_contributor"); + } + + int textWidth = textRenderer.getWidth(text); + GuiUtils.drawCenteredTextWrapped(ctx, this.textRenderer, text, this.width / 2, creatorY, 300, 0xFF00FF); + + // Render a tooltip with the relevant player names when hovered over + int lines = (int) Math.ceil(textWidth / 300.0); + if(!toList.isEmpty() + && mouseX > this.width / 2 - textWidth / 2 && mouseX < this.width / 2 + textWidth / 2 + && mouseY > creatorY - 2 && mouseY < creatorY + (9 * lines)) { + var contributorNames = toList.stream().filter(Objects::nonNull) + .map(entry -> Team.decorateName(entry.getScoreboardTeam(), Text.of(entry.getProfile().getName()))) + .toList(); + + contribTooltip.setTooltip(Tooltip.of(Texts.join(contributorNames, Text.literal("\n")))); + contribTooltip.render(true, true, ScreenRect.empty()); + } + } } \ No newline at end of file diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index 4d18b67a..3c401ee1 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -18,6 +18,7 @@ package com.wildfire.main; +import com.wildfire.gui.GuiUtils; import com.wildfire.gui.screen.BaseWildfireScreen; import com.wildfire.gui.screen.WardrobeBrowserScreen; import com.wildfire.gui.screen.WildfireFirstTimeSetupScreen; @@ -42,9 +43,7 @@ import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.MinecraftClient; -import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.hud.PlayerListHud; import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.client.network.PlayerListEntry; @@ -61,14 +60,13 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.text.Text; -import net.minecraft.util.Formatting; import net.minecraft.util.Util; import net.minecraft.world.World; import org.lwjgl.glfw.GLFW; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.concurrent.CompletableFuture; public final class WildfireEventHandler { @@ -115,25 +113,21 @@ public static void registerClientEvents() { @Environment(EnvType.CLIENT) private static void renderHud(DrawContext context, RenderTickCounter tickCounter) { - TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; - if(textRenderer == null) return; - - //Render when in tab list, unless the toggle is enabled. Then always render it. - List syncedPlayers = collectPlayerEntries(); - if( - (MinecraftClient.getInstance().options.playerListKey.isPressed() && !(MinecraftClient.getInstance().currentScreen instanceof WardrobeBrowserScreen)) || - GlobalConfig.INSTANCE.get(GlobalConfig.ALWAYS_SHOW_LIST)) { - - context.drawText(textRenderer, Text.translatable("wildfire_gender.wardrobe.players_using_mod").formatted(Formatting.AQUA), 5, 5, 0xFFFFFF, true); - int yPos = 18; - for(PlayerListEntry entry : syncedPlayers) { - PlayerConfig cfg = WildfireGender.getPlayerById(entry.getProfile().getId()); - context.drawText(textRenderer, Text.literal(entry.getProfile().getName() + " - ").append(cfg.getGender().getDisplayName()), 10, yPos, 0xFFFFFF, false); - yPos += 10; - } - + var textRenderer = Objects.requireNonNull(MinecraftClient.getInstance().textRenderer, "textRenderer"); + if(MinecraftClient.getInstance().currentScreen instanceof WardrobeBrowserScreen) { + return; } + + boolean shouldShow = switch(GlobalConfig.INSTANCE.get(GlobalConfig.ALWAYS_SHOW_LIST)) { + case MOD_UI_ONLY -> false; + case TAB_LIST_OPEN -> MinecraftClient.getInstance().options.playerListKey.isPressed(); + case ALWAYS -> true; + }; + if(!shouldShow) return; + + GuiUtils.drawSyncedPlayers(context, textRenderer, collectPlayerEntries()); } + /** * Attach breast render layers to players and armor stands */ @@ -239,7 +233,7 @@ private static void onBeginTracking(Entity tracked, ServerPlayerEntity syncTo) { public static List collectPlayerEntries() { - if(MinecraftClient.getInstance().player == null) return new ArrayList(); + if(MinecraftClient.getInstance().player == null) return new ArrayList<>(); ClientPlayerEntity player = MinecraftClient.getInstance().player; return player.networkHandler.getListedPlayerListEntries().stream() .filter(entry -> !entry.getProfile().getId().equals(player.getUuid())) diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index b25272b8..cf8ebcdc 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -31,6 +31,7 @@ import com.wildfire.main.WildfireGender; import com.wildfire.main.WildfireHelper; import com.wildfire.main.config.GlobalConfig; +import com.wildfire.main.config.enums.SyncVerbosity; import com.wildfire.main.entitydata.PlayerConfig; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -295,9 +296,7 @@ private static CompletableFuture syncInternal(PlayerConfig config, boolean throw new RuntimeException("Server responded " + response.statusCode() + ": " + response.body()); } - if(SyncLog.VERBOSITY_LEVEL == 2) { - SyncLog.add(WildfireLocalization.SYNC_LOG_GET_SINGLE_PROFILE); - } + SyncLog.add(WildfireLocalization.SYNC_LOG_GET_SINGLE_PROFILE, SyncVerbosity.SHOW_FETCHES); var data = GSON.fromJson(response.body(), JsonObject.class); FETCH_CACHE.put(uuid, Optional.of(data)); @@ -336,9 +335,7 @@ public static CompletableFuture> getMultiple(Collection FETCH_CACHE.put(uuid, Optional.ofNullable(data.get(uuid)))); return data; diff --git a/src/main/java/com/wildfire/main/cloud/SyncLog.java b/src/main/java/com/wildfire/main/cloud/SyncLog.java index b0e4e321..0434eb70 100644 --- a/src/main/java/com/wildfire/main/cloud/SyncLog.java +++ b/src/main/java/com/wildfire/main/cloud/SyncLog.java @@ -1,5 +1,25 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + package com.wildfire.main.cloud; +import com.wildfire.main.config.GlobalConfig; +import com.wildfire.main.config.enums.SyncVerbosity; import net.minecraft.text.Text; import net.minecraft.util.math.ColorHelper; import net.minecraft.util.math.MathHelper; @@ -10,10 +30,17 @@ public final class SyncLog { public static final List SYNC_LOG = new ArrayList<>(); - public static final int VERBOSITY_LEVEL = 2; - //1 = normal - //2 = log when profiles are retrieved + public static int verbosity() { + return GlobalConfig.INSTANCE.get(GlobalConfig.SYNC_VERBOSITY).ordinal(); + } + + public static void add(Text text, SyncVerbosity verbosity) { + if(verbosity() < verbosity.ordinal()) { + return; + } + add(text); + } public static void add(Text text) { SYNC_LOG.add(new Entry(text, Instant.now())); diff --git a/src/main/java/com/wildfire/main/config/EnumConfigKey.java b/src/main/java/com/wildfire/main/config/EnumConfigKey.java new file mode 100644 index 00000000..20feac1a --- /dev/null +++ b/src/main/java/com/wildfire/main/config/EnumConfigKey.java @@ -0,0 +1,47 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.main.config; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +import java.util.function.IntFunction; + +public class EnumConfigKey> extends ConfigKey { + private final IntFunction ordinal; + + public EnumConfigKey(String key, TYPE defaultValue, IntFunction ordinalMapper) { + super(key, defaultValue); + this.ordinal = ordinalMapper; + } + + @Override + protected TYPE read(JsonElement element) { + if(element instanceof JsonPrimitive prim && prim.isNumber()) { + return ordinal.apply(prim.getAsInt()); + } + return defaultValue; + } + + @Override + public void save(JsonObject object, TYPE value) { + object.addProperty(key, value.ordinal()); + } +} diff --git a/src/main/java/com/wildfire/main/config/GenderConfigKey.java b/src/main/java/com/wildfire/main/config/GenderConfigKey.java index bbced0a0..0dd4b61b 100644 --- a/src/main/java/com/wildfire/main/config/GenderConfigKey.java +++ b/src/main/java/com/wildfire/main/config/GenderConfigKey.java @@ -19,37 +19,20 @@ package com.wildfire.main.config; import com.google.gson.JsonElement; -import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import com.wildfire.main.Gender; -public class GenderConfigKey extends ConfigKey { - - //Do not modify - private static final Gender[] GENDERS = Gender.values(); - +public class GenderConfigKey extends EnumConfigKey { public GenderConfigKey(String key) { - super(key, Gender.MALE); + super(key, Gender.MALE, Gender.BY_ID); } @Override protected Gender read(JsonElement element) { - if (element.isJsonPrimitive()) { - JsonPrimitive primitive = element.getAsJsonPrimitive(); - if (primitive.isNumber()) { - int ordinal = primitive.getAsInt(); - if (ordinal >= 0 && ordinal < GENDERS.length) { - return GENDERS[ordinal]; - } - } else { - return primitive.getAsBoolean() ? Gender.MALE : Gender.FEMALE; - } + // TODO is this still necessary? only extraordinarily old configs should still have this as a boolean + if(element instanceof JsonPrimitive primitive && primitive.isBoolean()) { + return primitive.getAsBoolean() ? Gender.MALE : Gender.FEMALE; } - return defaultValue; - } - - @Override - public void save(JsonObject object, Gender value) { - object.addProperty(key, value.ordinal()); + return super.read(element); } } \ No newline at end of file diff --git a/src/main/java/com/wildfire/main/config/GlobalConfig.java b/src/main/java/com/wildfire/main/config/GlobalConfig.java index 6aced195..9f2b00cb 100644 --- a/src/main/java/com/wildfire/main/config/GlobalConfig.java +++ b/src/main/java/com/wildfire/main/config/GlobalConfig.java @@ -1,5 +1,26 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + package com.wildfire.main.config; +import com.wildfire.main.config.enums.ShowPlayerListMode; +import com.wildfire.main.config.enums.SyncVerbosity; + public class GlobalConfig extends AbstractConfiguration { public static final GlobalConfig INSTANCE = new GlobalConfig(); @@ -12,14 +33,16 @@ private GlobalConfig() { public static final BooleanConfigKey AUTOMATIC_CLOUD_SYNC = new BooleanConfigKey("sync_player_data", false); // see CloudSync#DEFAULT_CLOUD_URL for the actual default public static final StringConfigKey CLOUD_SERVER = new StringConfigKey("cloud_server", ""); + public static final EnumConfigKey SYNC_VERBOSITY = new EnumConfigKey<>("sync_log_verbosity", SyncVerbosity.DEFAULT, SyncVerbosity.BY_ID); - public static final BooleanConfigKey ALWAYS_SHOW_LIST = new BooleanConfigKey("alwaysShowList", false); + public static final EnumConfigKey ALWAYS_SHOW_LIST = new EnumConfigKey<>("alwaysShowList", ShowPlayerListMode.MOD_UI_ONLY, ShowPlayerListMode.BY_ID); static { INSTANCE.setDefault(FIRST_TIME_LOAD); INSTANCE.setDefault(CLOUD_SYNC_ENABLED); INSTANCE.setDefault(AUTOMATIC_CLOUD_SYNC); INSTANCE.setDefault(CLOUD_SERVER); + INSTANCE.setDefault(SYNC_VERBOSITY); INSTANCE.setDefault(ALWAYS_SHOW_LIST); if(!INSTANCE.exists()) { INSTANCE.save(); diff --git a/src/main/java/com/wildfire/main/config/enums/ShowPlayerListMode.java b/src/main/java/com/wildfire/main/config/enums/ShowPlayerListMode.java new file mode 100644 index 00000000..827f21d2 --- /dev/null +++ b/src/main/java/com/wildfire/main/config/enums/ShowPlayerListMode.java @@ -0,0 +1,50 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.main.config.enums; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.text.Text; +import net.minecraft.util.function.ValueLists; + +import java.util.function.IntFunction; + +public enum ShowPlayerListMode { + MOD_UI_ONLY, + TAB_LIST_OPEN, + ALWAYS; + + public static final IntFunction BY_ID = ValueLists.createIdToValueFunction(ShowPlayerListMode::ordinal, values(), ValueLists.OutOfBoundsHandling.WRAP); + + public ShowPlayerListMode next() { + return BY_ID.apply(this.ordinal() + 1); + } + + public Text text() { + return Text.translatable("wildfire_gender.always_show_list." + name().toLowerCase()); + } + + public Tooltip tooltip() { + if(this == TAB_LIST_OPEN) { + var button = MinecraftClient.getInstance().options.playerListKey.getBoundKeyLocalizedText(); + return Tooltip.of(Text.translatable("wildfire_gender.always_show_list." + name().toLowerCase() + ".tooltip", button)); + } + return Tooltip.of(Text.translatable("wildfire_gender.always_show_list." + name().toLowerCase() + ".tooltip")); + } +} diff --git a/src/main/java/com/wildfire/main/config/enums/SyncVerbosity.java b/src/main/java/com/wildfire/main/config/enums/SyncVerbosity.java new file mode 100644 index 00000000..0ec6d3f7 --- /dev/null +++ b/src/main/java/com/wildfire/main/config/enums/SyncVerbosity.java @@ -0,0 +1,30 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.main.config.enums; + +import net.minecraft.util.function.ValueLists; + +import java.util.function.IntFunction; + +public enum SyncVerbosity { + DEFAULT, + SHOW_FETCHES; + + public static final IntFunction BY_ID = ValueLists.createIdToValueFunction(SyncVerbosity::ordinal, values(), ValueLists.OutOfBoundsHandling.CLAMP); +} diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index 2933e8df..ee570bb0 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -12,7 +12,14 @@ "wildfire_gender.player_list.breast_momentum": "Breast Momentum: %s%%", "wildfire_gender.player_list.female_sounds": "Female Sounds: %s", "wildfire_gender.wardrobe.players_using_mod": "Players Using the Mod:", - "wildfire_gender.always_show_list": "Always Show List: %s", + + "wildfire_gender.always_show_list": "Show Synced Players: %s", + "wildfire_gender.always_show_list.mod_ui_only": "This screen", + "wildfire_gender.always_show_list.mod_ui_only.tooltip": "The synced player list will only show while in this menu", + "wildfire_gender.always_show_list.tab_list_open": "Player list", + "wildfire_gender.always_show_list.tab_list_open.tooltip": "The synced player list will show while in this menu or by pressing %s", + "wildfire_gender.always_show_list.always": "Always", + "wildfire_gender.always_show_list.always.tooltip": "The synced player list will always show", "wildfire_gender.wardrobe.title": "Wildfire's Female Gender Mod", "wildfire_gender.breast_customization.tab_customization": "Customization", From 8034e1555463e2a07fb89c3ee3780a972e084dcc Mon Sep 17 00:00:00 2001 From: PinguinLars1105 Date: Sun, 24 Nov 2024 08:57:59 +0100 Subject: [PATCH 192/238] Origanized Made the file look nicer and added new lines --- .../assets/wildfire_gender/lang/nl_nl.json | 200 ++++++++++-------- 1 file changed, 106 insertions(+), 94 deletions(-) diff --git a/src/main/resources/assets/wildfire_gender/lang/nl_nl.json b/src/main/resources/assets/wildfire_gender/lang/nl_nl.json index 07662a58..68a0d3f8 100644 --- a/src/main/resources/assets/wildfire_gender/lang/nl_nl.json +++ b/src/main/resources/assets/wildfire_gender/lang/nl_nl.json @@ -1,96 +1,108 @@ { - "category.wildfire_gender.generic": "Wildfire's Vrouwelijke Geslachts Mod", - "key.wildfire_gender.gender_menu": "Vrouwelijke Geslachts Menu", - "toast.wildfire_gender.get_started": "Druk op '%s' om te starten!", - - "wildfire_gender.player_list.title": "Vrouwelijke Geslachts Mod", - "wildfire_gender.player_list.settings_button": "Instellingen", - "wildfire_gender.player_list.sync_status": "Status Synchroniseren", - "wildfire_gender.player_list.state.loading": "Data Laden...", - "wildfire_gender.player_list.state.synced": "Speler Gesynchroniseerd", - "wildfire_gender.player_list.bounce_multiplier": "Veerkracht Vermenigvuldiger: %sx", - "wildfire_gender.player_list.breast_momentum": "Borst Momentum: %s%%", - "wildfire_gender.player_list.female_sounds": "Vrouwelijk Geluiden: %s", - - "wildfire_gender.wardrobe.title": "Wildfire's Vrouwelijke Geslachts Mod", - "wildfire_gender.breast_customization.tab_customization": "Aanpassen", - "wildfire_gender.breast_customization.tab_presets": "Voorbeelden", - - "wildfire_gender.breast_customization.presets.add_new": "Iets nieuw toevoegen...", - "wildfire_gender.breast_customization.presets.delete": "Verwijderen", - - "wildfire_gender.wardrobe.slider.breast_size": "Borst Grote: %s%%", - "wildfire_gender.wardrobe.slider.separation": "Tussenruimte: %s", - "wildfire_gender.wardrobe.slider.height": "Hoogte: %s", - "wildfire_gender.wardrobe.slider.depth": "Diepte: %s", - "wildfire_gender.wardrobe.slider.rotation": "Rotatie: %s graden", - - "wildfire_gender.appearance_settings.title": "Uiterlijk instellingen", - "wildfire_gender.char_settings.title": "Karakter Instellingen", - "wildfire_gender.char_settings.physics": "Borst Bewegingen: %s", - - "wildfire_gender.char_settings.override_armor_physics": "Beweegt in Harnas: %s", - "wildfire_gender.tooltip.override_armor_physics.line1": "De borst bewegingen zullen niet langer onderdrukt worden bij jouw aangetrokken harnas, als dit aan staat", - "wildfire_gender.tooltip.override_armor_physics.line2": "Dit is bedoeld voor het gebruik met bronpakketen die harnas verstoppen of iets in die geest", - - "wildfire_gender.char_settings.hide_in_armor": "Verstop In Harnas: %s", - "wildfire_gender.char_settings.hurt_sounds": "Vrouwelijke Pijn Geluiden: %s", - "wildfire_gender.tooltip.hurt_sounds": "Jou karakter zal een vrouwelijke pijn geluid spelen als je schade neemt en als je geslacht is ingesteld op Vrouwelijk of Anders", - - "wildfire_gender.breast_customization.dual_physics": "Dubbele Bewegingen: %s", - - "wildfire_gender.label.gender": "Geslacht", - "wildfire_gender.label.female": "Vrouwelijk", - "wildfire_gender.label.male": "Mannelijk", - "wildfire_gender.label.other": "Anders", - - "wildfire_gender.label.enabled": "Aan", - "wildfire_gender.label.disabled": "Uit", - "wildfire_gender.label.yes": "Ja", - "wildfire_gender.label.no": "Nee", - "wildfire_gender.label.with_creator": "Je speelt op een server met de maker van deze mod!", - "wildfire_gender.label.with_contributor": "Je speelt op een server met een bijdrager van deze mod!", - "wildfire_gender.label.with_both": "Je speelt op een server met de maker en een bijdrager van deze mod!", - - "wildfire_gender.slider.bounce": "Veerkracht Intensiteit: %s%%", - "wildfire_gender.slider.floppy": "Borst Momentum: %s%%", - "wildfire_gender.tooltip.bounce_warning": "Instelling 'Veerkracht Intensiteit' op een hoog nummer ziet er erg niet natuurlijk uit!", - - "wildfire_gender.cancer_awareness.title": "Hé, het is Borst Kanker Aandacht Maand", - - "wildfire_gender.first_time_setup.title": "Welkom bij Wildfire's Vrouwelijke Geslachts Mod!", - "wildfire_gender.first_time_setup.description": "Wil je de optie aanzetten om je geslachts instellingen online te Synchroniseren? Hier mee kunnen anderen jouw aangepaste geslacht uiterlijk, zelfs al heeft de server deze mod niet geïnstalleerd.", - "wildfire_gender.first_time_setup.notice": "Je kunt dit later altijd aanpassen in het mod menu.", - "wildfire_gender.first_time_setup.enable": "Zet Online Synchronisatie Aan", - "wildfire_gender.first_time_setup.disable": "Zet Online Synchronisatie Uit", - - "wildfire_gender.cloud_settings": "Online Synchronisatie Instellingen", - "wildfire_gender.cloud.available_online": "Online Synchronisatie", - "wildfire_gender.cloud.unavailable_offline": "Online synchronisatie is niet beschikbaar, omdat je niet bent ingelogd in een geldig Minecraft account", - "wildfire_gender.cloud.status": "Synchroniseren: %s", - "wildfire_gender.cloud.automatic": "Automatische Synchronisatie: %s", - "wildfire_gender.cloud.automatic.tooltip.line1": "Wanner dit aanstaat, jouw configuratie wordt automatisch online gesynchroniseerd na dat je aanpassingen maakt.", - "wildfire_gender.cloud.sync": "Synchroniseer", - "wildfire_gender.cloud.automatic.tooltip.line2": "Je kan nog steeds manueel synchroniseren met de knop hier onder, als dit uitstaat.", - "wildfire_gender.cloud.syncing": "Synchroniseren...", - "wildfire_gender.cloud.syncing.success": "Gesynchroniseerd", - "wildfire_gender.cloud.syncing.fail": "Synchronisatie Niet Gelukt", - "wildfire_gender.wardrobe.players_using_mod": "Spelers die de mod gebruiken:", - "wildfire_gender.label.off": "Uit", - "wildfire_gender.cloud.status_log": "Status Log", - "wildfire_gender.sync_log.authenticating": "Account Verifiëren...", - "wildfire_gender.sync_log.authentication_success": "Verificatie Gelukt.", - "wildfire_gender.sync_log.authentication_failed": "Verificatie Gefaald.", - "wildfire_gender.sync_log.reauthenticating": "Account Opnieuw Verifiëren...", - "wildfire_gender.sync_log.failed_to_sync_data": "Gefaald om Data te Sychroniseren.", - "wildfire_gender.sync_log.sync_to_cloud": "Data Online Sychroniseren...", - "wildfire_gender.sync_log.attempting_sync": "Profiel Sychroniseren...", - "wildfire_gender.sync_log.sync_success": "Sychronisatie Gelukt.", - "wildfire_gender.sync_log.sync_too_frequently": "Sychronisatie Tarief is Beperkt.", - "wildfire_gender.cloud.disclaimer.line1": "Server Sychronisatie date wordt mogelijk gecachd voor minimaal 30 minuten.", - "wildfire_gender.cloud.disclaimer.line2": "Als de aanpassen niet zichtbaar zijn, synchroniseer later opnieuw aub.", - "wildfire_gender.details.next_page": "Volgende Pagina", - "wildfire_gender.details.prev_page": "Vorige Pagina", - "wildfire_gender.cloud_details.title": "Online Synchronisatie Server Informatie" -} + "category.wildfire_gender.generic": "Wildfire's Vrouwelijke Geslachts Mod", + "key.wildfire_gender.gender_menu": "Vrouwelijke Geslachts Menu", + "toast.wildfire_gender.get_started": "Druk op '%s' om te starten!", + "wildfire_gender.player_list.title": "Vrouwelijke Geslachts Mod", + "wildfire_gender.player_list.settings_button": "Instellingen", + "wildfire_gender.player_list.sync_status": "Status Synchroniseren", + "wildfire_gender.player_list.state.loading": "Data Laden...", + "wildfire_gender.player_list.state.synced": "Speler Gesynchroniseerd", + "wildfire_gender.player_list.bounce_multiplier": "Veerkracht Vermenigvuldiger: %sx", + "wildfire_gender.player_list.breast_momentum": "Borst Momentum: %s%%", + "wildfire_gender.player_list.female_sounds": "Vrouwelijk Geluiden: %s", + "wildfire_gender.wardrobe.players_using_mod": "Spelers die de Mod Gebruiken:", + "wildfire_gender.always_show_list": "Altijd de Lijst Laten Zien: %s", + + "wildfire_gender.wardrobe.title": "Wildfire's Vrouwelijke Geslachts Mod", + "wildfire_gender.breast_customization.tab_customization": "Aanpassen", + "wildfire_gender.breast_customization.tab_physics": "Borst Bewegingen", + "wildfire_gender.breast_customization.tab_miscellaneous": "Overig", + + "wildfire_gender.breast_customization.presets.add_new": "Iets nieuw toevoegen...", + "wildfire_gender.breast_customization.presets.delete": "Verwijderen", + + "wildfire_gender.wardrobe.slider.breast_size": "Borst Grote: %s%%", + "wildfire_gender.wardrobe.slider.separation": "Tussenruimte: %s", + "wildfire_gender.wardrobe.slider.height": "Hoogte: %s", + "wildfire_gender.wardrobe.slider.depth": "Diepte: %s", + "wildfire_gender.wardrobe.slider.rotation": "Rotatie: %s°", + "wildfire_gender.slider.voice_pitch": "Toonhoogte: %s%%", + + "wildfire_gender.appearance_settings.title": "Karakter Personalisatie", + "wildfire_gender.char_settings.title": "Karakter Instellingen OUD/OLD", + "wildfire_gender.char_settings.physics": "Borst Bewegingen: %s", + + "wildfire_gender.char_settings.override_armor_physics": "Beweegt in Harnas: %s", + "wildfire_gender.tooltip.override_armor_physics.line1": "De borst bewegingen zullen niet langer onderdrukt worden bij jouw aangetrokken harnas, als dit aan staat", + "wildfire_gender.tooltip.override_armor_physics.line2": "Dit is bedoeld voor het gebruik met bronpakketen die harnas verstoppen of iets in die geest", + + "wildfire_gender.char_settings.hide_in_armor": "Verstop In Harnas: %s", + "wildfire_gender.char_settings.hurt_sounds": "Vrouwelijke Pijn Geluiden: %s", + "wildfire_gender.tooltip.hurt_sounds": "Jou karakter zal een vrouwelijke pijn geluid spelen als je schade neemt en als je geslacht is ingesteld op Vrouwelijk of Anders", + + "wildfire_gender.breast_customization.dual_physics": "Dubbele Bewegingen: %s", + + "wildfire_gender.label.gender": "Geslacht", + "wildfire_gender.label.female": "Vrouwelijk", + "wildfire_gender.label.male": "Mannelijk", + "wildfire_gender.label.other": "Anders", + + "wildfire_gender.label.enabled": "Aan", + "wildfire_gender.label.disabled": "Uit", + "wildfire_gender.label.on": "Aan", + "wildfire_gender.label.off": "Uit", + "wildfire_gender.label.yes": "Ja", + "wildfire_gender.label.no": "Nee", + "wildfire_gender.label.with_creator": "Je speelt op een server met de maker van deze mod!", + "wildfire_gender.label.with_contributor": "Je speelt op een server met een bijdrager van deze mod!", + "wildfire_gender.label.with_both": "Je speelt op een server met de maker en een bijdrager van deze mod!", + + "wildfire_gender.slider.bounce": "Intensiteit: %s%%", + "wildfire_gender.slider.floppy": "Momentum: %s%%", + + "wildfire_gender.cancer_awareness.title": "Hé, het is Borst Kanker Aandacht Maand", + + "wildfire_gender.first_time_setup.title": "Welkom bij Wildfire's Vrouwelijke Geslachts Mod!", + "wildfire_gender.first_time_setup.description": "Wil je de optie aanzetten om je geslachts instellingen online te Synchroniseren? Hier mee kunnen anderen jouw aangepaste geslacht uiterlijk, zelfs al heeft de server deze mod niet geïnstalleerd.", + "wildfire_gender.first_time_setup.notice": "Je kunt dit later altijd aanpassen in het mod menu.", + "wildfire_gender.first_time_setup.enable": "Zet Online Synchronisatie Aan", + "wildfire_gender.first_time_setup.disable": "Zet Online Synchronisatie Uit", + + + "wildfire_gender.cloud_settings": "Online Synchronisatie Instellingen", + "wildfire_gender.cloud.available_online": "Online Synchronisatie", + "wildfire_gender.cloud.unavailable_offline": "Online synchronisatie is niet beschikbaar, omdat je niet bent ingelogd in een geldig Minecraft account", + "wildfire_gender.cloud.status": "Synchroniseren: %s", + "wildfire_gender.cloud.automatic": "Automatische Synchronisatie: %s", + "wildfire_gender.cloud.automatic.tooltip.line1": "Wanner dit aanstaat, jouw configuratie wordt automatisch online gesynchroniseerd na dat je aanpassingen maakt.", + "wildfire_gender.cloud.sync": "Synchroniseer", + "wildfire_gender.cloud.automatic.tooltip.line2": "Je kan nog steeds manueel synchroniseren met de knop hier onder, als dit uitstaat.", + "wildfire_gender.cloud.syncing": "Synchroniseren...", + + "wildfire_gender.cloud.status_log": "Status Log", + + "wildfire_gender.cloud.syncing.success": "Gesynchroniseerd", + "wildfire_gender.cloud.syncing.fail": "Synchronisatie Niet Gelukt", + "wildfire_gender.cloud.disclaimer.line1": "Server Sychronisatie date wordt mogelijk gecachd voor minimaal 30 minuten.", + "wildfire_gender.cloud.disclaimer.line2": "Als de aanpassen niet zichtbaar zijn, synchroniseer later opnieuw aub.", + + "wildfire_gender.sync_log.authenticating": "Account Verifiëren...", + "wildfire_gender.sync_log.authentication_success": "Verificatie Gelukt.", + "wildfire_gender.sync_log.authentication_failed": "Verificatie Gefaald.", + "wildfire_gender.sync_log.reauthenticating": "Account Opnieuw Verifiëren...", + + "wildfire_gender.sync_log.failed_to_sync_data": "Gefaald om Data te Sychroniseren.", + "wildfire_gender.sync_log.sync_to_cloud": "Data Online Sychroniseren...", + "wildfire_gender.sync_log.attempting_sync": "Profiel Sychroniseren...", + "wildfire_gender.sync_log.sync_success": "Sychronisatie Gelukt.", + "wildfire_gender.sync_log.sync_too_frequently": "Sychronisatie Tarief is Beperkt.", + + "wildfire_gender.sync_log.get_single_profile": "Profiel Verzamelen...", + "wildfire_gender.sync_log.get_multiple_profiles": "Meedere Profielen Verzamelen...", + + "wildfire_gender.details.next_page": "Volgende Pagina", + "wildfire_gender.details.prev_page": "Vorige Pagina", + + "wildfire_gender.cloud_details.title": "Online Synchronisatie Server Informatie" +} \ No newline at end of file From 8dc9c01e093d3803c3fbb9f0fe845c16594e2813 Mon Sep 17 00:00:00 2001 From: PinguinLars1105 Date: Sun, 24 Nov 2024 09:26:34 +0100 Subject: [PATCH 193/238] Added new lines added in pr #236 --- .../assets/wildfire_gender/lang/nl_nl.json | 219 +++++++++--------- 1 file changed, 113 insertions(+), 106 deletions(-) diff --git a/src/main/resources/assets/wildfire_gender/lang/nl_nl.json b/src/main/resources/assets/wildfire_gender/lang/nl_nl.json index 68a0d3f8..dc8d7c86 100644 --- a/src/main/resources/assets/wildfire_gender/lang/nl_nl.json +++ b/src/main/resources/assets/wildfire_gender/lang/nl_nl.json @@ -1,108 +1,115 @@ { - "category.wildfire_gender.generic": "Wildfire's Vrouwelijke Geslachts Mod", - "key.wildfire_gender.gender_menu": "Vrouwelijke Geslachts Menu", - "toast.wildfire_gender.get_started": "Druk op '%s' om te starten!", - - "wildfire_gender.player_list.title": "Vrouwelijke Geslachts Mod", - "wildfire_gender.player_list.settings_button": "Instellingen", - "wildfire_gender.player_list.sync_status": "Status Synchroniseren", - "wildfire_gender.player_list.state.loading": "Data Laden...", - "wildfire_gender.player_list.state.synced": "Speler Gesynchroniseerd", - "wildfire_gender.player_list.bounce_multiplier": "Veerkracht Vermenigvuldiger: %sx", - "wildfire_gender.player_list.breast_momentum": "Borst Momentum: %s%%", - "wildfire_gender.player_list.female_sounds": "Vrouwelijk Geluiden: %s", - "wildfire_gender.wardrobe.players_using_mod": "Spelers die de Mod Gebruiken:", - "wildfire_gender.always_show_list": "Altijd de Lijst Laten Zien: %s", - - "wildfire_gender.wardrobe.title": "Wildfire's Vrouwelijke Geslachts Mod", - "wildfire_gender.breast_customization.tab_customization": "Aanpassen", - "wildfire_gender.breast_customization.tab_physics": "Borst Bewegingen", - "wildfire_gender.breast_customization.tab_miscellaneous": "Overig", - - "wildfire_gender.breast_customization.presets.add_new": "Iets nieuw toevoegen...", - "wildfire_gender.breast_customization.presets.delete": "Verwijderen", - - "wildfire_gender.wardrobe.slider.breast_size": "Borst Grote: %s%%", - "wildfire_gender.wardrobe.slider.separation": "Tussenruimte: %s", - "wildfire_gender.wardrobe.slider.height": "Hoogte: %s", - "wildfire_gender.wardrobe.slider.depth": "Diepte: %s", - "wildfire_gender.wardrobe.slider.rotation": "Rotatie: %s°", - "wildfire_gender.slider.voice_pitch": "Toonhoogte: %s%%", - - "wildfire_gender.appearance_settings.title": "Karakter Personalisatie", - "wildfire_gender.char_settings.title": "Karakter Instellingen OUD/OLD", - "wildfire_gender.char_settings.physics": "Borst Bewegingen: %s", - - "wildfire_gender.char_settings.override_armor_physics": "Beweegt in Harnas: %s", - "wildfire_gender.tooltip.override_armor_physics.line1": "De borst bewegingen zullen niet langer onderdrukt worden bij jouw aangetrokken harnas, als dit aan staat", - "wildfire_gender.tooltip.override_armor_physics.line2": "Dit is bedoeld voor het gebruik met bronpakketen die harnas verstoppen of iets in die geest", - - "wildfire_gender.char_settings.hide_in_armor": "Verstop In Harnas: %s", - "wildfire_gender.char_settings.hurt_sounds": "Vrouwelijke Pijn Geluiden: %s", - "wildfire_gender.tooltip.hurt_sounds": "Jou karakter zal een vrouwelijke pijn geluid spelen als je schade neemt en als je geslacht is ingesteld op Vrouwelijk of Anders", - - "wildfire_gender.breast_customization.dual_physics": "Dubbele Bewegingen: %s", - - "wildfire_gender.label.gender": "Geslacht", - "wildfire_gender.label.female": "Vrouwelijk", - "wildfire_gender.label.male": "Mannelijk", - "wildfire_gender.label.other": "Anders", - - "wildfire_gender.label.enabled": "Aan", - "wildfire_gender.label.disabled": "Uit", - "wildfire_gender.label.on": "Aan", - "wildfire_gender.label.off": "Uit", - "wildfire_gender.label.yes": "Ja", - "wildfire_gender.label.no": "Nee", - "wildfire_gender.label.with_creator": "Je speelt op een server met de maker van deze mod!", - "wildfire_gender.label.with_contributor": "Je speelt op een server met een bijdrager van deze mod!", - "wildfire_gender.label.with_both": "Je speelt op een server met de maker en een bijdrager van deze mod!", - - "wildfire_gender.slider.bounce": "Intensiteit: %s%%", - "wildfire_gender.slider.floppy": "Momentum: %s%%", - - "wildfire_gender.cancer_awareness.title": "Hé, het is Borst Kanker Aandacht Maand", - - "wildfire_gender.first_time_setup.title": "Welkom bij Wildfire's Vrouwelijke Geslachts Mod!", - "wildfire_gender.first_time_setup.description": "Wil je de optie aanzetten om je geslachts instellingen online te Synchroniseren? Hier mee kunnen anderen jouw aangepaste geslacht uiterlijk, zelfs al heeft de server deze mod niet geïnstalleerd.", - "wildfire_gender.first_time_setup.notice": "Je kunt dit later altijd aanpassen in het mod menu.", - "wildfire_gender.first_time_setup.enable": "Zet Online Synchronisatie Aan", - "wildfire_gender.first_time_setup.disable": "Zet Online Synchronisatie Uit", - - - "wildfire_gender.cloud_settings": "Online Synchronisatie Instellingen", - "wildfire_gender.cloud.available_online": "Online Synchronisatie", - "wildfire_gender.cloud.unavailable_offline": "Online synchronisatie is niet beschikbaar, omdat je niet bent ingelogd in een geldig Minecraft account", - "wildfire_gender.cloud.status": "Synchroniseren: %s", - "wildfire_gender.cloud.automatic": "Automatische Synchronisatie: %s", - "wildfire_gender.cloud.automatic.tooltip.line1": "Wanner dit aanstaat, jouw configuratie wordt automatisch online gesynchroniseerd na dat je aanpassingen maakt.", - "wildfire_gender.cloud.sync": "Synchroniseer", - "wildfire_gender.cloud.automatic.tooltip.line2": "Je kan nog steeds manueel synchroniseren met de knop hier onder, als dit uitstaat.", - "wildfire_gender.cloud.syncing": "Synchroniseren...", - - "wildfire_gender.cloud.status_log": "Status Log", - - "wildfire_gender.cloud.syncing.success": "Gesynchroniseerd", - "wildfire_gender.cloud.syncing.fail": "Synchronisatie Niet Gelukt", - "wildfire_gender.cloud.disclaimer.line1": "Server Sychronisatie date wordt mogelijk gecachd voor minimaal 30 minuten.", - "wildfire_gender.cloud.disclaimer.line2": "Als de aanpassen niet zichtbaar zijn, synchroniseer later opnieuw aub.", - - "wildfire_gender.sync_log.authenticating": "Account Verifiëren...", - "wildfire_gender.sync_log.authentication_success": "Verificatie Gelukt.", - "wildfire_gender.sync_log.authentication_failed": "Verificatie Gefaald.", - "wildfire_gender.sync_log.reauthenticating": "Account Opnieuw Verifiëren...", - - "wildfire_gender.sync_log.failed_to_sync_data": "Gefaald om Data te Sychroniseren.", - "wildfire_gender.sync_log.sync_to_cloud": "Data Online Sychroniseren...", - "wildfire_gender.sync_log.attempting_sync": "Profiel Sychroniseren...", - "wildfire_gender.sync_log.sync_success": "Sychronisatie Gelukt.", - "wildfire_gender.sync_log.sync_too_frequently": "Sychronisatie Tarief is Beperkt.", - - "wildfire_gender.sync_log.get_single_profile": "Profiel Verzamelen...", - "wildfire_gender.sync_log.get_multiple_profiles": "Meedere Profielen Verzamelen...", - - "wildfire_gender.details.next_page": "Volgende Pagina", - "wildfire_gender.details.prev_page": "Vorige Pagina", - - "wildfire_gender.cloud_details.title": "Online Synchronisatie Server Informatie" + "category.wildfire_gender.generic": "Wildfire's Vrouwelijke Geslachts Mod", + "key.wildfire_gender.gender_menu": "Vrouwelijke Geslachts Menu", + "toast.wildfire_gender.get_started": "Druk op '%s' om te starten!", + + "wildfire_gender.player_list.title": "Vrouwelijke Geslachts Mod", + "wildfire_gender.player_list.settings_button": "Instellingen", + "wildfire_gender.player_list.sync_status": "Status Synchroniseren", + "wildfire_gender.player_list.state.loading": "Data Laden...", + "wildfire_gender.player_list.state.synced": "Speler Gesynchroniseerd", + "wildfire_gender.player_list.bounce_multiplier": "Veerkracht Vermenigvuldiger: %sx", + "wildfire_gender.player_list.breast_momentum": "Borst Momentum: %s%%", + "wildfire_gender.player_list.female_sounds": "Vrouwelijk Geluiden: %s", + "wildfire_gender.wardrobe.players_using_mod": "Spelers die de Mod Gebruiken:", + + "wildfire_gender.always_show_list": "Laat Gesynchroniseerde Spelers Zien: %s", + "wildfire_gender.always_show_list.mod_ui_only": "Dit scherm", + "wildfire_gender.always_show_list.mod_ui_only.tooltip": "De synchroniseerde spelers lijst wordt alleen in dit menu weergeven", + "wildfire_gender.always_show_list.tab_list_open": "Spelers Lijst", + "wildfire_gender.always_show_list.tab_list_open.tooltip": "De synchroniseerde spelers lijst wordt weergeven in dit menu of als je op %s drukt", + "wildfire_gender.always_show_list.always": "Altijd", + "wildfire_gender.always_show_list.always.tooltip": "De synchroniseerde spelers lijst wordt altijd weergeven", + + "wildfire_gender.wardrobe.title": "Wildfire's Vrouwelijke Geslachts Mod", + "wildfire_gender.breast_customization.tab_customization": "Aanpassen", + "wildfire_gender.breast_customization.tab_physics": "Borst Bewegingen", + "wildfire_gender.breast_customization.tab_miscellaneous": "Overig", + + "wildfire_gender.breast_customization.presets.add_new": "Iets nieuw toevoegen...", + "wildfire_gender.breast_customization.presets.delete": "Verwijderen", + + "wildfire_gender.wardrobe.slider.breast_size": "Borst Grote: %s%%", + "wildfire_gender.wardrobe.slider.separation": "Tussenruimte: %s", + "wildfire_gender.wardrobe.slider.height": "Hoogte: %s", + "wildfire_gender.wardrobe.slider.depth": "Diepte: %s", + "wildfire_gender.wardrobe.slider.rotation": "Rotatie: %s°", + "wildfire_gender.slider.voice_pitch": "Toonhoogte: %s%%", + + "wildfire_gender.appearance_settings.title": "Karakter Personalisatie", + "wildfire_gender.char_settings.title": "Karakter Instellingen OUD/OLD", + "wildfire_gender.char_settings.physics": "Borst Bewegingen: %s", + + "wildfire_gender.char_settings.override_armor_physics": "Beweegt in Harnas: %s", + "wildfire_gender.tooltip.override_armor_physics.line1": "De borst bewegingen zullen niet langer onderdrukt worden bij jouw aangetrokken harnas, als dit aan staat", + "wildfire_gender.tooltip.override_armor_physics.line2": "Dit is bedoeld voor het gebruik met bronpakketen die harnas verstoppen of iets in die geest", + + "wildfire_gender.char_settings.hide_in_armor": "Verstop In Harnas: %s", + "wildfire_gender.char_settings.hurt_sounds": "Vrouwelijke Pijn Geluiden: %s", + "wildfire_gender.tooltip.hurt_sounds": "Jou karakter zal een vrouwelijke pijn geluid spelen als je schade neemt en als je geslacht is ingesteld op Vrouwelijk of Anders", + + "wildfire_gender.breast_customization.dual_physics": "Dubbele Bewegingen: %s", + + "wildfire_gender.label.gender": "Geslacht", + "wildfire_gender.label.female": "Vrouwelijk", + "wildfire_gender.label.male": "Mannelijk", + "wildfire_gender.label.other": "Anders", + + "wildfire_gender.label.enabled": "Aan", + "wildfire_gender.label.disabled": "Uit", + "wildfire_gender.label.on": "Aan", + "wildfire_gender.label.off": "Uit", + "wildfire_gender.label.yes": "Ja", + "wildfire_gender.label.no": "Nee", + "wildfire_gender.label.with_creator": "Je speelt op een server met de maker van deze mod!", + "wildfire_gender.label.with_contributor": "Je speelt op een server met een bijdrager van deze mod!", + "wildfire_gender.label.with_both": "Je speelt op een server met de maker en een bijdrager van deze mod!", + + "wildfire_gender.slider.bounce": "Intensiteit: %s%%", + "wildfire_gender.slider.floppy": "Momentum: %s%%", + + "wildfire_gender.cancer_awareness.title": "Hé, het is Borst Kanker Aandacht Maand", + + "wildfire_gender.first_time_setup.title": "Welkom bij Wildfire's Vrouwelijke Geslachts Mod!", + "wildfire_gender.first_time_setup.description": "Wil je de optie aanzetten om je geslachts instellingen online te Synchroniseren? Hier mee kunnen anderen jouw aangepaste geslacht uiterlijk, zelfs al heeft de server deze mod niet geïnstalleerd.", + "wildfire_gender.first_time_setup.notice": "Je kunt dit later altijd aanpassen in het mod menu.", + "wildfire_gender.first_time_setup.enable": "Zet Online Synchronisatie Aan", + "wildfire_gender.first_time_setup.disable": "Zet Online Synchronisatie Uit", + + + "wildfire_gender.cloud_settings": "Online Synchronisatie Instellingen", + "wildfire_gender.cloud.available_online": "Online Synchronisatie", + "wildfire_gender.cloud.unavailable_offline": "Online synchronisatie is niet beschikbaar, omdat je niet bent ingelogd in een geldig Minecraft account", + "wildfire_gender.cloud.status": "Synchroniseren: %s", + "wildfire_gender.cloud.automatic": "Automatische Synchronisatie: %s", + "wildfire_gender.cloud.automatic.tooltip.line1": "Wanner dit aanstaat, jouw configuratie wordt automatisch online gesynchroniseerd na dat je aanpassingen maakt.", + "wildfire_gender.cloud.sync": "Synchroniseer", + "wildfire_gender.cloud.automatic.tooltip.line2": "Je kan nog steeds manueel synchroniseren met de knop hier onder, als dit uitstaat.", + "wildfire_gender.cloud.syncing": "Synchroniseren...", + + "wildfire_gender.cloud.status_log": "Status Log", + + "wildfire_gender.cloud.syncing.success": "Gesynchroniseerd", + "wildfire_gender.cloud.syncing.fail": "Synchronisatie Niet Gelukt", + "wildfire_gender.cloud.disclaimer.line1": "Server Sychronisatie date wordt mogelijk gecachd voor minimaal 30 minuten.", + "wildfire_gender.cloud.disclaimer.line2": "Als de aanpassen niet zichtbaar zijn, synchroniseer later opnieuw aub.", + + "wildfire_gender.sync_log.authenticating": "Account Verifiëren...", + "wildfire_gender.sync_log.authentication_success": "Verificatie Gelukt.", + "wildfire_gender.sync_log.authentication_failed": "Verificatie Gefaald.", + "wildfire_gender.sync_log.reauthenticating": "Account Opnieuw Verifiëren...", + + "wildfire_gender.sync_log.failed_to_sync_data": "Gefaald om Data te Sychroniseren.", + "wildfire_gender.sync_log.sync_to_cloud": "Data Online Sychroniseren...", + "wildfire_gender.sync_log.attempting_sync": "Profiel Sychroniseren...", + "wildfire_gender.sync_log.sync_success": "Sychronisatie Gelukt.", + "wildfire_gender.sync_log.sync_too_frequently": "Sychronisatie Tarief is Beperkt.", + + "wildfire_gender.sync_log.get_single_profile": "Profiel Verzamelen...", + "wildfire_gender.sync_log.get_multiple_profiles": "Meedere Profielen Verzamelen...", + + "wildfire_gender.details.next_page": "Volgende Pagina", + "wildfire_gender.details.prev_page": "Vorige Pagina", + + "wildfire_gender.cloud_details.title": "Online Synchronisatie Server Informatie" } \ No newline at end of file From 05da9b182a02450c8716817299af3566aa438c2b Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sun, 24 Nov 2024 12:58:04 -0500 Subject: [PATCH 194/238] a change --- .../java/com/wildfire/gui/screen/WardrobeBrowserScreen.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index aa0a8aba..b9838de0 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -98,7 +98,7 @@ public void init() { })); WildfireButton btnCharacterPersonalization; - this.addDrawableChild(btnCharacterPersonalization = new WildfireButton(this.width / 2 - 36, this.height / 2 - 63, 158, 20, Text.translatable("wildfire_gender.appearance_settings.title").append("..."), + this.addDrawableChild(btnCharacterPersonalization = new WildfireButton(this.width / 2 - 36, this.height / 2 - 63, 157, 20, Text.translatable("wildfire_gender.appearance_settings.title").append("..."), button -> client.setScreen(new WildfireBreastCustomizationScreen(WardrobeBrowserScreen.this, this.playerUUID)))); btnCharacterPersonalization.active = plr.getGender().canHaveBreasts(); From 152b77fea0eac50503bde7425e2d625f4cf71ff1 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sun, 24 Nov 2024 18:45:02 -0500 Subject: [PATCH 195/238] Changed UI - Customization Navbar moved to top. - Top justified menu tabs --- .../WildfireBreastCustomizationScreen.java | 42 +++++++++--------- .../textures/gui/breast_customization.png | Bin 1618 -> 1620 bytes .../gui/breast_customization_other.png | Bin 1618 -> 1620 bytes 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java index 60b85564..7ea14d58 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java @@ -95,7 +95,7 @@ public void init() { }; //Customization Tab - this.addDrawableChild(btnCustomization = new WildfireButton(this.width / 2 - 130, j + 54, 172/2 - 2, 10, + this.addDrawableChild(btnCustomization = new WildfireButton(this.width / 2 - 130, j - 52, 172/2 - 2, 12, Text.translatable("wildfire_gender.breast_customization.tab_customization"), button -> { currentTab = 0; updateTabs(); @@ -103,7 +103,7 @@ public void init() { })).setActive(false); //Breast Physics Tab - this.addDrawableChild(btnPhysics = new WildfireButton(this.width / 2 - 42, j + 54, 172 / 2 - 2, 10, + this.addDrawableChild(btnPhysics = new WildfireButton(this.width / 2 - 42, j - 52, 172 / 2 - 2, 12, Text.translatable("wildfire_gender.breast_customization.tab_physics"), button -> { currentTab = 1; @@ -112,7 +112,7 @@ public void init() { })); //Miscellaneous - this.addDrawableChild(btnMiscellaneous = new WildfireButton(this.width / 2 + 46, j + 54, 172 / 2 - 2, 10, + this.addDrawableChild(btnMiscellaneous = new WildfireButton(this.width / 2 + 46, j - 52, 172 / 2 - 2, 12, Text.translatable("wildfire_gender.breast_customization.tab_miscellaneous"), button -> { currentTab = 2; @@ -123,26 +123,26 @@ public void init() { //Customization Tab Below int tabOffsetY = j-3 - 21; - this.addDrawableChild(this.breastSlider = new WildfireSlider(this.width / 2 - 36, tabOffsetY - 4, 166, 20, Configuration.BUST_SIZE, plr.getBustSize(), + this.addDrawableChild(this.breastSlider = new WildfireSlider(this.width / 2 - 36, tabOffsetY - 2, 166, 20, Configuration.BUST_SIZE, plr.getBustSize(), plr::updateBustSize, value -> Text.translatable("wildfire_gender.wardrobe.slider.breast_size", Math.round(value * 1.25f * 100)), onSave)); this.breastSlider.setArrowKeyStep(0.01); //Customization - this.addDrawableChild(this.xOffsetBoobSlider = new WildfireSlider(this.width / 2 - 36, tabOffsetY + 20, 166 / 2 - 2, 20, Configuration.BREASTS_OFFSET_X, breasts.getXOffset(), + this.addDrawableChild(this.xOffsetBoobSlider = new WildfireSlider(this.width / 2 - 36, tabOffsetY + 22, 166 / 2 - 2, 20, Configuration.BREASTS_OFFSET_X, breasts.getXOffset(), breasts::updateXOffset, value -> Text.translatable("wildfire_gender.wardrobe.slider.separation", Math.round((Math.round(value * 100f) / 100f) * 10)), onSave)); - this.addDrawableChild(this.yOffsetBoobSlider = new WildfireSlider(this.width / 2 - 36 + 166/2 + 2, tabOffsetY + 20, 166 / 2 - 2, 20, Configuration.BREASTS_OFFSET_Y, breasts.getYOffset(), + this.addDrawableChild(this.yOffsetBoobSlider = new WildfireSlider(this.width / 2 - 36 + 166/2 + 2, tabOffsetY + 22, 166 / 2 - 2, 20, Configuration.BREASTS_OFFSET_Y, breasts.getYOffset(), breasts::updateYOffset, value -> Text.translatable("wildfire_gender.wardrobe.slider.height", Math.round((Math.round(value * 100f) / 100f) * 10)), onSave)); - this.addDrawableChild(this.zOffsetBoobSlider = new WildfireSlider(this.width / 2 - 36, tabOffsetY + 44, 166 / 2 - 2, 20, Configuration.BREASTS_OFFSET_Z, breasts.getZOffset(), + this.addDrawableChild(this.zOffsetBoobSlider = new WildfireSlider(this.width / 2 - 36, tabOffsetY + 46, 166 / 2 - 2, 20, Configuration.BREASTS_OFFSET_Z, breasts.getZOffset(), breasts::updateZOffset, value -> Text.translatable("wildfire_gender.wardrobe.slider.depth", Math.round((Math.round(value * 100f) / 100f) * 10)), onSave)); this.zOffsetBoobSlider.setArrowKeyStep(0.1); - this.addDrawableChild(this.cleavageSlider = new WildfireSlider(this.width / 2 - 36 + 166/2 + 2, tabOffsetY + 44, 166 / 2 - 2, 20, Configuration.BREASTS_CLEAVAGE, breasts.getCleavage(), + this.addDrawableChild(this.cleavageSlider = new WildfireSlider(this.width / 2 - 36 + 166/2 + 2, tabOffsetY + 46, 166 / 2 - 2, 20, Configuration.BREASTS_CLEAVAGE, breasts.getCleavage(), breasts::updateCleavage, value -> Text.translatable("wildfire_gender.wardrobe.slider.rotation", Math.round((Math.round(value * 100f) / 100f) * 100)), onSave)); this.cleavageSlider.setArrowKeyStep(0.1); //Breast Physics Tab - this.addDrawableChild(this.btnBreastPhysics = new WildfireButton(this.width / 2 - 36, tabOffsetY - 28, 166, 20, + this.addDrawableChild(this.btnBreastPhysics = new WildfireButton(this.width / 2 - 36, tabOffsetY - 2, 166, 20, Text.translatable("wildfire_gender.char_settings.physics", plr.hasBreastPhysics() ? ENABLED : DISABLED), button -> { boolean enablePhysics = !plr.hasBreastPhysics(); if (plr.updateBreastPhysics(enablePhysics)) { @@ -157,7 +157,7 @@ public void init() { } })); - this.addDrawableChild(this.btnDualPhysics = new WildfireButton(this.width / 2 - 36, tabOffsetY - 4, 166, 20, + this.addDrawableChild(this.btnDualPhysics = new WildfireButton(this.width / 2 - 36, tabOffsetY + 22, 166, 20, Text.translatable("wildfire_gender.breast_customization.dual_physics", Text.translatable(breasts.isUniboob() ? "wildfire_gender.label.no" : "wildfire_gender.label.yes")), button -> { boolean isUniboob = !breasts.isUniboob(); if (breasts.updateUniboob(isUniboob)) { @@ -170,7 +170,7 @@ public void init() { //this.btnHideInArmor.active = aPlr.hasBreastPhysics(); - this.addDrawableChild(btnOverrideArmorPhys = new WildfireButton(this.width / 2 - 36, tabOffsetY + 44, 166, 20, + this.addDrawableChild(btnOverrideArmorPhys = new WildfireButton(this.width / 2 - 36, tabOffsetY + 70, 166, 20, Text.translatable("wildfire_gender.char_settings.override_armor_physics", plr.getArmorPhysicsOverride() ? ENABLED : DISABLED), button -> { boolean enableArmorPhysicsOverride = !plr.getArmorPhysicsOverride(); if (plr.updateArmorPhysicsOverride(enableArmorPhysicsOverride )) { @@ -183,7 +183,7 @@ public void init() { )); this.btnOverrideArmorPhys.active = plr.hasBreastPhysics(); - this.addDrawableChild(this.bounceSlider = new WildfireSlider(this.width / 2 - 36, tabOffsetY + 20, 166 / 2 - 2, 20, Configuration.BOUNCE_MULTIPLIER, plr.getBounceMultiplier(), value -> { + this.addDrawableChild(this.bounceSlider = new WildfireSlider(this.width / 2 - 36, tabOffsetY + 46, 166 / 2 - 2, 20, Configuration.BOUNCE_MULTIPLIER, plr.getBounceMultiplier(), value -> { }, value -> { float bounceText = 3 * value; int v = Math.round(bounceText * 100); @@ -197,7 +197,7 @@ public void init() { this.bounceSlider.active = plr.hasBreastPhysics(); this.bounceSlider.setArrowKeyStep(0.005); - this.addDrawableChild(this.floppySlider = new WildfireSlider(this.width / 2 - 36 + 166/2 + 2, tabOffsetY + 20, 166 / 2 - 2, 20, Configuration.FLOPPY_MULTIPLIER, plr.getFloppiness(), value -> { + this.addDrawableChild(this.floppySlider = new WildfireSlider(this.width / 2 - 36 + 166/2 + 2, tabOffsetY + 46, 166 / 2 - 2, 20, Configuration.FLOPPY_MULTIPLIER, plr.getFloppiness(), value -> { }, value -> Text.translatable("wildfire_gender.slider.floppy", Math.round(value * 100)), value -> { if (plr.updateFloppiness(value)) { PlayerConfig.saveGenderInfo(plr); @@ -210,7 +210,7 @@ public void init() { //Miscellaneous Tab - this.addDrawableChild(this.btnHurtSounds = new WildfireButton(this.width / 2 - 36, tabOffsetY - 4, 166, 20, + this.addDrawableChild(this.btnHurtSounds = new WildfireButton(this.width / 2 - 36, tabOffsetY - 2, 166, 20, Text.translatable("wildfire_gender.char_settings.hurt_sounds", plr.hasHurtSounds() ? ENABLED : DISABLED), button -> { boolean enableHurtSounds = !plr.hasHurtSounds(); if (plr.updateHurtSounds(enableHurtSounds)) { @@ -220,7 +220,7 @@ public void init() { } }, Tooltip.of(Text.translatable("wildfire_gender.tooltip.hurt_sounds")))); - this.addDrawableChild(this.voicePitchSlider = new WildfireSlider(this.width / 2 - 36, tabOffsetY + 20, 166 / 2 - 2, 20, Configuration.VOICE_PITCH, plr.getVoicePitch(), value -> { + this.addDrawableChild(this.voicePitchSlider = new WildfireSlider(this.width / 2 - 36, tabOffsetY + 22, 166 / 2 - 2, 20, Configuration.VOICE_PITCH, plr.getVoicePitch(), value -> { }, value -> Text.translatable("wildfire_gender.slider.voice_pitch", Math.round(value * 100)), value -> { if (plr.updateVoicePitch(value)) { PlayerConfig.saveGenderInfo(plr); @@ -236,7 +236,7 @@ public void init() { voicePitchSlider.active = plr.hasHurtSounds(); this.voicePitchSlider.setArrowKeyStep(0.01); - this.addDrawableChild(btnHideInArmor = new WildfireButton(this.width / 2 - 36, tabOffsetY + 44, 166, 20, + this.addDrawableChild(btnHideInArmor = new WildfireButton(this.width / 2 - 36, tabOffsetY + 46, 166, 20, Text.translatable("wildfire_gender.char_settings.hide_in_armor", plr.showBreastsInArmor() ? DISABLED : ENABLED), button -> { boolean enableShowInArmor = !plr.showBreastsInArmor(); if (plr.updateShowBreastsInArmor(enableShowInArmor)) { @@ -321,15 +321,15 @@ public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delt }; if(backgroundTexture != null) { - ctx.drawTexture(RenderLayer::getGuiTextured, backgroundTexture, (this.width - 272) / 2, (this.height - 138) / 2, 0, 0, 272, 128, 512, 512); + ctx.drawTexture(RenderLayer::getGuiTextured, backgroundTexture, (this.width - 272) / 2, (this.height - 138) / 2, 0, 0, 272, 130, 512, 512); } if(currentTab == 0) { - ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND_CUSTOMIZATION, (this.width) / 2 - 42, (this.height) / 2 - 45, 0, 0, 178, 80, 512, 512); + ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND_CUSTOMIZATION, (this.width) / 2 - 42, (this.height) / 2 - 43, 0, 0, 178, 80, 512, 512); } else if(currentTab == 1) { - ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND_PHYSICS, (this.width) / 2 - 42, (this.height) / 2 - 69, 0, 0, 178, 104, 512, 512); + ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND_PHYSICS, (this.width) / 2 - 42, (this.height) / 2 - 43, 0, 0, 178, 104, 512, 512); } else if(currentTab == 2) { - ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND_MISC, (this.width) / 2 - 42, (this.height) / 2 - 45, 0, 0, 178, 80, 512, 512); + ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND_MISC, (this.width) / 2 - 42, (this.height) / 2 - 43, 0, 0, 178, 80, 512, 512); } int x = this.width / 2; @@ -340,7 +340,7 @@ public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delt if(client != null && client.world != null) { int xP = this.width / 2 - 90; - int yP = this.height / 2 + 18; + int yP = this.height / 2 + 44; PlayerEntity ent = client.world.getPlayerByUuid(this.playerUUID); if(ent != null) { ctx.enableScissor(xP - 38, yP - 97, xP + 38, yP + 9); diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/breast_customization.png b/src/main/resources/assets/wildfire_gender/textures/gui/breast_customization.png index 05da8b6f75d9695157182b4417e4a51a2452e863..17b1b953ad7cf4713cd603d6cecb2947cf23aa98 100644 GIT binary patch delta 405 zcmV;G0c!rz4AcyeQX)4uH&#|w3JMBqYirD|%-nd~{{a8S0LGpGo&W#C7)$^&5Wt6j00tAl4E6xNjxgW4*#OuGpzT}?U|pX3W)}=# zUG}{boc}X`$KL?E?MEpIZkg`{5cddRfBOx$KY<_ZxG#f(A6d-kc@J=mlLBV&3Shqn z8LN1vn$U4KfdZJq7Qp2?*hK)7*aEoz8|*58NxTEN-3Ph}U>ffL?)%|=h`Rs+rXiET z1I|C*0o?8b-2^a=cL0M4U=~{dm+N2`0Zd{G;0OYk!eIfN1u%tWdClq&0+_(EE=L7m z{xf(?X+OLVw?Ba&&6u-s1IYrD&;r01Y3uqQ=__p}_Z8N)00000NkvXXu0mjf7Z0qo delta 408 zcmV;J0cZZy4AKmcQX&j2ph!7c(=#4~{FufeVYSi~!U+kK#$ z0G9Cz;C^i0hqw!183KqCK%4;L1P~{an*+pu?iIl8KG017%XkGam;hGs4B&Dd>>_|g zJOh|P085w_z*zuG*tW;4rVzjawtbrwfcdZBdrHUVeYoQZ{MU>rP5^Nee*zYCe3X*! zKY<^-1Pk~%2jJz@E8r2padty+?>g@&ctzO&K4+tz@Fak?;#KB}*TR<+3}D}m zcPBXi$F1PwMkK{sp}|+?U$c<|s{)gd1L7EM-~R$tV5tnq)t#;Y0000zzhWNA%MXIFarU62w*UO0n9)E9|9Om05cH4hX4i>zzhWNkq+SN2=lF* z4S0 zmCcNv_W;K@DPRV#0QP&3v5IG^2_1J6D1a$!0bH(wT?8#xDC0$9W=fZKhbn*f&a3gCWh-iNpY3t$>_|gJOh|P085w_z*zuG*tW;4rVzjawtbrwfcdZBdrHUVeYoQZ{MU>rP5^Nee*zYC ze3X*!KY<^-1Pk~%2jJz@E8r2padty+?>g@&ctzO&K4+tz@Fak?;#KB}*TR<+ z3}D}mcPBXi$F1PwMkK{sp}|+?U$apI>jIOI1ELsh-~R$tV5tnq)t#;Y0000 Date: Mon, 25 Nov 2024 07:54:58 +0100 Subject: [PATCH 196/238] Updated German Translation Basically a complete remake of the German Translation --- .../assets/wildfire_gender/lang/de_de.json | 116 ++++++++++++++---- 1 file changed, 95 insertions(+), 21 deletions(-) diff --git a/src/main/resources/assets/wildfire_gender/lang/de_de.json b/src/main/resources/assets/wildfire_gender/lang/de_de.json index 500bf21f..7133b61d 100644 --- a/src/main/resources/assets/wildfire_gender/lang/de_de.json +++ b/src/main/resources/assets/wildfire_gender/lang/de_de.json @@ -1,41 +1,115 @@ { - "category.wildfire_gender.generic": "Wildfire's Weibliche Geschlechtsmod", - "key.wildfire_gender.gender_menu": "Geschlechtsmenü", + "category.wildfire_gender.generic": "Wildfire's Weibliche Geschlechts Mod", + "key.wildfire_gender.gender_menu": "Weibliches Geschelchts Menü", + "toast.wildfire_gender.get_started": "Drücke '%s' um zu starten!", "wildfire_gender.player_list.title": "Weibliche Geschlechts Mod", "wildfire_gender.player_list.settings_button": "Einstellungen", - "wildfire_gender.player_list.sync_status": "Synchronisierungsstatus", - "wildfire_gender.player_list.state.loading": "Lade Spieler..", - "wildfire_gender.player_list.state.synced": "Geladener Spieler", + "wildfire_gender.player_list.sync_status": "Synchronisier Status", + "wildfire_gender.player_list.state.loading": "Daten laden...", + "wildfire_gender.player_list.state.synced": "Synchronisirte Spieler", + "wildfire_gender.player_list.bounce_multiplier": "Bounce Multiplikator: %sx", + "wildfire_gender.player_list.breast_momentum": "Brust Momentum: %s%%", + "wildfire_gender.player_list.female_sounds": "Weibliche Geräusche: %s", + "wildfire_gender.wardrobe.players_using_mod": "Spieler mit der Mod:", + + "wildfire_gender.always_show_list": "Zeige Synchronisierte Spieler: %s", + "wildfire_gender.always_show_list.mod_ui_only": "In diesem Fenster", + "wildfire_gender.always_show_list.mod_ui_only.tooltip": "Die Liste der Synchronisierten Spieler wir nur in diesem Fenster gezeigt", + "wildfire_gender.always_show_list.tab_list_open": "In der Spielerliste", + "wildfire_gender.always_show_list.tab_list_open.tooltip": "Die Liste der Synchronisierten Spieler wird gezeigt wenn du in diesem Fenster bist oder wenn du im Spiel %s drückst", + "wildfire_gender.always_show_list.always": "Immer", + "wildfire_gender.always_show_list.always.tooltip": "Die Liste der Synchronisierten Spieler wird immer gezeigt", + + "wildfire_gender.wardrobe.title": "Wildfire's Weibliche Geschlechts Mod", + "wildfire_gender.breast_customization.tab_customization": "Anpassung", + "wildfire_gender.breast_customization.tab_physics": "Brust Physik", + "wildfire_gender.breast_customization.tab_miscellaneous": "Weiteres", + + "wildfire_gender.breast_customization.presets.add_new": "Neue...", + "wildfire_gender.breast_customization.presets.delete": "Löschen", - "wildfire_gender.wardrobe.title": "Anpassungsmenü", "wildfire_gender.wardrobe.slider.breast_size": "Brustgröße: %s%%", - "wildfire_gender.wardrobe.slider.separation": "Separierung: %s", + "wildfire_gender.wardrobe.slider.separation": "Distanz: %s", "wildfire_gender.wardrobe.slider.height": "Höhe: %s", "wildfire_gender.wardrobe.slider.depth": "Tiefe: %s", - "wildfire_gender.wardrobe.slider.rotation": "Drehung: %s degrees", + "wildfire_gender.wardrobe.slider.rotation": "Drehung: %s°", + "wildfire_gender.slider.voice_pitch": "Stimmlage: %s%%", + + "wildfire_gender.appearance_settings.title": "Charakter Einstellungen", + "wildfire_gender.char_settings.title": "Charaketer Einstellungen ALT", + "wildfire_gender.char_settings.physics": "Brust Physik: %s", - "wildfire_gender.appearance_settings.title": "Aussehen", - "wildfire_gender.char_settings.title": "Spieler Einstellungen", - "wildfire_gender.char_settings.physics": "Brustphysik: %s", - "wildfire_gender.char_settings.hide_in_armor": "Versteckt mit Rüstung: %s", + "wildfire_gender.char_settings.override_armor_physics": "Rüstungs Physik: %s", + "wildfire_gender.tooltip.override_armor_physics.line1": "Brustphysik wird nicht mehr durch Rüstung reduziert/gestoppt, wenn es aktiviert ist.", + "wildfire_gender.tooltip.override_armor_physics.line2": "Dies ist hauptsächlich da für Resource Packs die Rüstungen (oder ähnliches) entfernen", + + "wildfire_gender.char_settings.hide_in_armor": "In Rüstung verstecken: %s", + "wildfire_gender.char_settings.hurt_sounds": "Weibliche Verletzungsgeräusche: %s", + "wildfire_gender.tooltip.hurt_sounds": "Dein Charakter macht Weibliche Verletzungsgeräusche wenn du Schaden kriegst, wenn dein Geschlecht Weiblich oder Divers ist", + + "wildfire_gender.breast_customization.dual_physics": "Dual Physik: %s", "wildfire_gender.label.gender": "Geschlecht", "wildfire_gender.label.female": "Weiblich", "wildfire_gender.label.male": "Männlich", - "wildfire_gender.label.other": "Andere", + "wildfire_gender.label.other": "Divers", "wildfire_gender.label.enabled": "Aktiviert", "wildfire_gender.label.disabled": "Deaktiviert", + "wildfire_gender.label.on": "An", + "wildfire_gender.label.off": "Aus", "wildfire_gender.label.yes": "Ja", "wildfire_gender.label.no": "Nein", - "wildfire_gender.label.with_creator": "Du spielst auf einem Server mit dem Programmierer dieser Mod!", + "wildfire_gender.label.with_creator": "Du spielst auf einem Server mit der Macherin der Mod!", + "wildfire_gender.label.with_contributor": "Du spielst auf einem Server mit einer der Mithelfer:innen der Mod!", + "wildfire_gender.label.with_both": "Du spielst auf einem Server mit der Macherin und einer der Mithelfer:innen der Mod!", + + "wildfire_gender.slider.bounce": "Intensität: %s%%", + "wildfire_gender.slider.floppy": "Momentum: %s%%", + + "wildfire_gender.cancer_awareness.title": "Hey, es ist der Brustkrebsmonat!", + + "wildfire_gender.first_time_setup.title": "Willkommen bei Wildfire's Weiblichen Geschlechts Mod!", + "wildfire_gender.first_time_setup.description": "Möchtest du die Cloud Synchronizierung aktivieren? Dies erlaubt es anderen Spielern mit der Mod deine Geschlechtseinstellungen zu sehen.", + "wildfire_gender.first_time_setup.notice": "Dies kannst du später in den Einstellungen jederzeit ändern.", + "wildfire_gender.first_time_setup.enable": "Aktiviere Cloud Synchronizierung", + "wildfire_gender.first_time_setup.disable": "Deaktiviere Cloud Synchronizierung", + + + "wildfire_gender.cloud_settings": "Synchronizierungs Einstellungen", + "wildfire_gender.cloud.available_online": "Cloud Synchronizierung", + "wildfire_gender.cloud.unavailable_offline": "Cloud Synchronizierung ist zurzeit nicht verfügbar da du nicht mit einem gültigen Minecraft Account eingeloggt bist", + "wildfire_gender.cloud.status": "Cloud Synchronizierung: %s", + "wildfire_gender.cloud.automatic": "Automatische Synchronizierung: %s", + "wildfire_gender.cloud.automatic.tooltip.line1": "Wenn dies aktiviert ist werden deine Einstellungen nach jeder Änderung automatisch mit der Cloud synchronisiert.", + "wildfire_gender.cloud.automatic.tooltip.line2": "Wenn dies deaktiviert ist kannst du die Einstellungen dennoch manuell über den Knopf synchronisieren.", + "wildfire_gender.cloud.sync": "Jetzt synchronizieren", + "wildfire_gender.cloud.syncing": "Synchroniziere...", + + "wildfire_gender.cloud.status_log": "Status Log", + + "wildfire_gender.cloud.syncing.success": "Synchroniziert", + "wildfire_gender.cloud.syncing.fail": "Synchronizierung fehlgeschlagen", + "wildfire_gender.cloud.disclaimer.line1": "Synchronizierungsdaten werden für ungefähr 30 Minuten auf dem Server gespeichert.", + "wildfire_gender.cloud.disclaimer.line2": "Wenn keine Veränderungen sichtbar sind, dann versuche die Synchronisierung später erneut durchzuführen..", + + "wildfire_gender.sync_log.authenticating": "Überprüfe Account...", + "wildfire_gender.sync_log.authentication_success": "Überprüfung erfolgreich.", + "wildfire_gender.sync_log.authentication_failed": "Überprüfe fehlgeschlagen.", + "wildfire_gender.sync_log.reauthenticating": "Überprüfe Account erneut...", + + "wildfire_gender.sync_log.failed_to_sync_data": "Daten können nicht synchronisiert werden.", + "wildfire_gender.sync_log.sync_to_cloud": "Synchronisiere Daten zu Cloud...", + "wildfire_gender.sync_log.attempting_sync": "Synchronisiere Profil...", + "wildfire_gender.sync_log.sync_success": "Synchronisiere erfolgreich.", + "wildfire_gender.sync_log.sync_too_frequently": "Synchronisierrate begrenzt.", + + "wildfire_gender.sync_log.get_single_profile": "Abrufen von Profil...", + "wildfire_gender.sync_log.get_multiple_profiles": "Abrufen von mehreren Profil...", - "wildfire_gender.slider.bounce": "Wackel Intensität: %s%%", - "wildfire_gender.slider.floppy": "Brust Schwung: %s%%", - "wildfire_gender.slider.min_bounce": "Warum ist die Physik überhaupt an?", - "wildfire_gender.slider.max_bounce": "Anime Brust Physik!", - "wildfire_gender.tooltip.bounce_warning": "Wenn die 'Wackel Intensität' zu stark einstellen ist sieht es nicht sehr natürlich aus!", + "wildfire_gender.details.next_page": "Nächste Seite", + "wildfire_gender.details.prev_page": "Vorherige Seite", - "wildfire_gender.cancer_awareness.title": "Hey, es ist Monat des Brustkrebses!" -} \ No newline at end of file + "wildfire_gender.cloud_details.title": "Cloud Synchronizierungserver Information" +} From d95fefc7a788e1dc4972544c1a0eeae9e5e52a90 Mon Sep 17 00:00:00 2001 From: Arcti <71222107+ArcticWah@users.noreply.github.com> Date: Mon, 25 Nov 2024 08:03:19 +0100 Subject: [PATCH 197/238] Fixed German translation a little --- .../resources/assets/wildfire_gender/lang/de_de.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/resources/assets/wildfire_gender/lang/de_de.json b/src/main/resources/assets/wildfire_gender/lang/de_de.json index 7133b61d..42e90fae 100644 --- a/src/main/resources/assets/wildfire_gender/lang/de_de.json +++ b/src/main/resources/assets/wildfire_gender/lang/de_de.json @@ -7,7 +7,7 @@ "wildfire_gender.player_list.settings_button": "Einstellungen", "wildfire_gender.player_list.sync_status": "Synchronisier Status", "wildfire_gender.player_list.state.loading": "Daten laden...", - "wildfire_gender.player_list.state.synced": "Synchronisirte Spieler", + "wildfire_gender.player_list.state.synced": "Synchronisierte Spieler", "wildfire_gender.player_list.bounce_multiplier": "Bounce Multiplikator: %sx", "wildfire_gender.player_list.breast_momentum": "Brust Momentum: %s%%", "wildfire_gender.player_list.female_sounds": "Weibliche Geräusche: %s", @@ -16,7 +16,7 @@ "wildfire_gender.always_show_list": "Zeige Synchronisierte Spieler: %s", "wildfire_gender.always_show_list.mod_ui_only": "In diesem Fenster", "wildfire_gender.always_show_list.mod_ui_only.tooltip": "Die Liste der Synchronisierten Spieler wir nur in diesem Fenster gezeigt", - "wildfire_gender.always_show_list.tab_list_open": "In der Spielerliste", + "wildfire_gender.always_show_list.tab_list_open": "Mit der Spielerliste", "wildfire_gender.always_show_list.tab_list_open.tooltip": "Die Liste der Synchronisierten Spieler wird gezeigt wenn du in diesem Fenster bist oder wenn du im Spiel %s drückst", "wildfire_gender.always_show_list.always": "Immer", "wildfire_gender.always_show_list.always.tooltip": "Die Liste der Synchronisierten Spieler wird immer gezeigt", @@ -42,7 +42,7 @@ "wildfire_gender.char_settings.override_armor_physics": "Rüstungs Physik: %s", "wildfire_gender.tooltip.override_armor_physics.line1": "Brustphysik wird nicht mehr durch Rüstung reduziert/gestoppt, wenn es aktiviert ist.", - "wildfire_gender.tooltip.override_armor_physics.line2": "Dies ist hauptsächlich da für Resource Packs die Rüstungen (oder ähnliches) entfernen", + "wildfire_gender.tooltip.override_armor_physics.line2": "Dies ist hauptsächlich da für Resource Packs die Rüstungen entfernen oder verringern", "wildfire_gender.char_settings.hide_in_armor": "In Rüstung verstecken: %s", "wildfire_gender.char_settings.hurt_sounds": "Weibliche Verletzungsgeräusche: %s", @@ -99,10 +99,10 @@ "wildfire_gender.sync_log.authentication_failed": "Überprüfe fehlgeschlagen.", "wildfire_gender.sync_log.reauthenticating": "Überprüfe Account erneut...", - "wildfire_gender.sync_log.failed_to_sync_data": "Daten können nicht synchronisiert werden.", + "wildfire_gender.sync_log.failed_to_sync_data": "Daten konnten nicht synchronisiert werden.", "wildfire_gender.sync_log.sync_to_cloud": "Synchronisiere Daten zu Cloud...", "wildfire_gender.sync_log.attempting_sync": "Synchronisiere Profil...", - "wildfire_gender.sync_log.sync_success": "Synchronisiere erfolgreich.", + "wildfire_gender.sync_log.sync_success": "Synchronisierung erfolgreich.", "wildfire_gender.sync_log.sync_too_frequently": "Synchronisierrate begrenzt.", "wildfire_gender.sync_log.get_single_profile": "Abrufen von Profil...", @@ -111,5 +111,5 @@ "wildfire_gender.details.next_page": "Nächste Seite", "wildfire_gender.details.prev_page": "Vorherige Seite", - "wildfire_gender.cloud_details.title": "Cloud Synchronizierungserver Information" + "wildfire_gender.cloud_details.title": "Cloud Synchronizierungsserver Information" } From c909d0bc57b077b0af62a33cd1f2da930ef2bed8 Mon Sep 17 00:00:00 2001 From: TransBluelight <69445885+TransBluelight@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:07:38 -0600 Subject: [PATCH 198/238] Update translations Updated Spanish Mexico and added Spanish Spain --- .../assets/wildfire_gender/lang/es_es.json | 115 ++++++++++++ .../assets/wildfire_gender/lang/es_mx.json | 163 ++++++++++++------ 2 files changed, 230 insertions(+), 48 deletions(-) create mode 100644 src/main/resources/assets/wildfire_gender/lang/es_es.json diff --git a/src/main/resources/assets/wildfire_gender/lang/es_es.json b/src/main/resources/assets/wildfire_gender/lang/es_es.json new file mode 100644 index 00000000..475cf63d --- /dev/null +++ b/src/main/resources/assets/wildfire_gender/lang/es_es.json @@ -0,0 +1,115 @@ +{ + "category.wildfire_gender.generic": "Mod del Género Femenino de Wildfire", + "key.wildfire_gender.gender_menu": "Menu de Genero", + "toast.wildfire_gender.get_started": "¡Pulsa '%s' para iniciar!", + + "wildfire_gender.player_list.title": "Mod del Género Femenino", + "wildfire_gender.player_list.settings_button": "Configuración", + "wildfire_gender.player_list.sync_status": "Estatus de Sincronización", + "wildfire_gender.player_list.state.loading": "Cargando datos...", + "wildfire_gender.player_list.state.synced": "Jugador Sinvronizado", + "wildfire_gender.player_list.bounce_multiplier": "Multiplicador de Rebote: %sx", + "wildfire_gender.player_list.breast_momentum": "Impetu del Pecho: %s%%", + "wildfire_gender.player_list.female_sounds": "Sonidos de Mujer: %s", + "wildfire_gender.wardrobe.players_using_mod": "Jugadores usando el mod:", + + "wildfire_gender.always_show_list": "Mostrar jugadores sincronizados: %s", + "wildfire_gender.always_show_list.mod_ui_only": "Ésta pantalla", + "wildfire_gender.always_show_list.mod_ui_only.tooltip": "La lista de jugadores sincronizados solo se mostrará en ésta pantalla.", + "wildfire_gender.always_show_list.tab_list_open": "Lista de jugadores", + "wildfire_gender.always_show_list.tab_list_open.tooltip": "La lista de jugadores sincronizados se mostrará al pulsar %s", + "wildfire_gender.always_show_list.always": "Siempre", + "wildfire_gender.always_show_list.always.tooltip": "La lista de jugadores sincronizados siempre se mostrará.", + + "wildfire_gender.wardrobe.title": "Mod del Género Femenino de Wildfire", + "wildfire_gender.breast_customization.tab_customization": "Personalización", + "wildfire_gender.breast_customization.tab_physics": "Físicas del Pecho", + "wildfire_gender.breast_customization.tab_miscellaneous": "Misceláneo", + + "wildfire_gender.breast_customization.presets.add_new": "Agregar nuevo...", + "wildfire_gender.breast_customization.presets.delete": "Eliminar", + + "wildfire_gender.wardrobe.slider.breast_size": "Tamaño del Pecho: %s%%", + "wildfire_gender.wardrobe.slider.separation": "Separación: %s", + "wildfire_gender.wardrobe.slider.height": "Altura: %s", + "wildfire_gender.wardrobe.slider.depth": "Profundidad: %s", + "wildfire_gender.wardrobe.slider.rotation": "Rotación: %s°", + "wildfire_gender.slider.voice_pitch": "Tono de Voz: %s%%", + + "wildfire_gender.appearance_settings.title": "Personalización de Personaje", + "wildfire_gender.char_settings.title": "Configuración de Personaje (VIEJA)", + "wildfire_gender.char_settings.physics": "Físicas del Pecho: %s", + + "wildfire_gender.char_settings.override_armor_physics": "Físicas en la armadura: %s", + "wildfire_gender.tooltip.override_armor_physics.line1": "Las físicas ya no se verán limitadas por el tipo de armadura que uses", + "wildfire_gender.tooltip.override_armor_physics.line2": "Ésto está hecho por si usas paquetes de recursos que escondan la armadura o la minimicen.", + + "wildfire_gender.char_settings.hide_in_armor": "Esconder en la armadura: %s", + "wildfire_gender.char_settings.hurt_sounds": "Sonidos de Mujer: %s", + "wildfire_gender.tooltip.hurt_sounds": "Tu personaje producirá sonidos de mujer al recibir daño cuándo se use el género 'Mujer' u 'Otro'", + + "wildfire_gender.breast_customization.dual_physics": "Físicas Duales: %s", + + "wildfire_gender.label.gender": "Género", + "wildfire_gender.label.female": "Mujer", + "wildfire_gender.label.male": "Hombre", + "wildfire_gender.label.other": "Otro", + + "wildfire_gender.label.enabled": "Activado", + "wildfire_gender.label.disabled": "Desactivado", + "wildfire_gender.label.on": "Sí", + "wildfire_gender.label.off": "No", + "wildfire_gender.label.yes": "Sí", + "wildfire_gender.label.no": "No", + "wildfire_gender.label.with_creator": "¡Estás jugando en un servidor con la creadora del mod!", + "wildfire_gender.label.with_contributor": "¡Estás en un servidor con un/a contribuidor/a del mod!", + "wildfire_gender.label.with_both": "¡Estás jugando en un servidor con la creadora y un/a contribuidor/a!", + + "wildfire_gender.slider.bounce": "Intensidad: %s%%", + "wildfire_gender.slider.floppy": "Ímpetu: %s%%", + + "wildfire_gender.cancer_awareness.title": "Hey, ¡Es el mes de concientización sobre el cáncer de mama!", + + "wildfire_gender.first_time_setup.title": "¡Bienvenido/a al mod del género femenino de Wildfire!", + "wildfire_gender.first_time_setup.description": "¿Deseas activar la sincronización de configuración en la nube? Ésto permitirá a otros jugadores con el mod el ver tus ajustes sin que el servidor tenga el mod instalado.", + "wildfire_gender.first_time_setup.notice": "Siempre podrás cambiar ésta opción después en el menú del mod.", + "wildfire_gender.first_time_setup.enable": "Activar Sincronizado en la Nube", + "wildfire_gender.first_time_setup.disable": "Desactivar Sincronizado en la Nube", + + + "wildfire_gender.cloud_settings": "Configuración del Sincronizado en la Nube", + "wildfire_gender.cloud.available_online": "Sincronizado en la Nube", + "wildfire_gender.cloud.unavailable_offline": "El Sincronizado en la Nube no está disponible debido a que no iniciaste sesión en una cuenta de Minecraft válida.", + "wildfire_gender.cloud.status": "Sincronizado: %s", + "wildfire_gender.cloud.automatic": "Sincro. Automática: %s", + "wildfire_gender.cloud.automatic.tooltip.line1": "Cuando se active se enviaran los cambios que hagas a la nube.", + "wildfire_gender.cloud.automatic.tooltip.line2": "Aún puedes sincronizar manualmente si la opción no está disponible.", + "wildfire_gender.cloud.sync": "Sincronizar Ya", + "wildfire_gender.cloud.syncing": "Sincronizando...", + + "wildfire_gender.cloud.status_log": "Registro de estado", + + "wildfire_gender.cloud.syncing.success": "Sincronizado!", + "wildfire_gender.cloud.syncing.fail": "Sincro. Fallida...", + "wildfire_gender.cloud.disclaimer.line1": "Los datos del servidor de sincronización se tienen que almacenar en caché durante al menos 30 minutos.", + "wildfire_gender.cloud.disclaimer.line2": "Si los cambios no son visibles, inténtalo más tarde.", + + "wildfire_gender.sync_log.authenticating": "Autenticando cuenta...", + "wildfire_gender.sync_log.authentication_success": "Autenticación Exitosa.", + "wildfire_gender.sync_log.authentication_failed": "Autenticación Falló.", + "wildfire_gender.sync_log.reauthenticating": "Re-Autenticando Cuenta...", + + "wildfire_gender.sync_log.failed_to_sync_data": "Fallo al Sincronizar Datos", + "wildfire_gender.sync_log.sync_to_cloud": "Sincronizado datos a la nube...", + "wildfire_gender.sync_log.attempting_sync": "Sincronizando Perfil...", + "wildfire_gender.sync_log.sync_success": "Sincronizado con éxito.", + "wildfire_gender.sync_log.sync_too_frequently": "Has sincronizado muy rápido, inténtalo en unos segundos.", + + "wildfire_gender.sync_log.get_single_profile": "Obteniendo Perfil...", + "wildfire_gender.sync_log.get_multiple_profiles": "Obteniendo Perfiles en Lote...", + + "wildfire_gender.details.next_page": "Sig. Página", + "wildfire_gender.details.prev_page": "Ant. Página", + + "wildfire_gender.cloud_details.title": "Información del Servidor en la Nube" +} diff --git a/src/main/resources/assets/wildfire_gender/lang/es_mx.json b/src/main/resources/assets/wildfire_gender/lang/es_mx.json index 6a190022..9befa38b 100644 --- a/src/main/resources/assets/wildfire_gender/lang/es_mx.json +++ b/src/main/resources/assets/wildfire_gender/lang/es_mx.json @@ -1,48 +1,115 @@ -{ - "category.wildfire_gender.generic": "Mod del genero femenino de Wildfire", - "key.wildfire_gender.gender_menu": "Menu de genero", - - "wildfire_gender.player_list.title": "Mod del genero femenino", - "wildfire_gender.player_list.settings_button": "Ajustes", - "wildfire_gender.player_list.sync_status": "Estatus de sincronización", - "wildfire_gender.player_list.state.loading": "Cargando datos...", - "wildfire_gender.player_list.state.synced": "Jugador Sincronizado", - "wildfire_gender.player_list.bounce_multiplier": "Multiplicador de rebote: %sx", - "wildfire_gender.player_list.breast_momentum": "Ímpetu del busto: %s%%", - "wildfire_gender.player_list.female_sounds": "Sonidos femeninos: %s", - - "wildfire_gender.wardrobe.title": "Menú de Personalización", - "wildfire_gender.wardrobe.slider.breast_size": "Tamaño del busto: %s%%", - "wildfire_gender.wardrobe.slider.separation": "Separación: %s", - "wildfire_gender.wardrobe.slider.height": "Altura: %s", - "wildfire_gender.wardrobe.slider.depth": "Profundidad: %s", - "wildfire_gender.wardrobe.slider.rotation": "Rotación: %s grados", - - "wildfire_gender.appearance_settings.title": "Ajustes de apariencia", - "wildfire_gender.char_settings.title": "Ajustes de personaje", - "wildfire_gender.char_settings.physics": "Físicas del busto: %s", - "wildfire_gender.char_settings.hide_in_armor": "Esconder en la armadura: %s", - "wildfire_gender.char_settings.hurt_sounds": "Sonidos de dolor: %s", - "wildfire_gender.tooltip.hurt_sounds": "Habilita sonidos nuevos femeninos", - - "wildfire_gender.breast_customization.dual_physics": "Físicas duales: %s", - - "wildfire_gender.label.gender": "Género", - "wildfire_gender.label.female": "Mujer", - "wildfire_gender.label.male": "Hombre", - "wildfire_gender.label.other": "Otro", - - "wildfire_gender.label.enabled": "Si", - "wildfire_gender.label.disabled": "No", - "wildfire_gender.label.yes": "Sí", - "wildfire_gender.label.no": "No", - "wildfire_gender.label.with_creator": "¡Estás jugando en un servidor con la creadora del mod!", - - "wildfire_gender.slider.bounce": "Intensidad de rebote: %s%%", - "wildfire_gender.slider.floppy": "Ímpetu del busto: %s%%", - "wildfire_gender.slider.min_bounce": "¿Para que encendiste las físicas?", - "wildfire_gender.slider.max_bounce": "¡¡¡FÍSICAS DE ANIME!!!", - "wildfire_gender.tooltip.bounce_warning": "Colocar la 'Intensidad de rebote' muy alta se vera muy anti-natural.", - - "wildfire_gender.cancer_awareness.title": "Hey, es el mes de concientización sobre el cáncer de mama" -} +{ + "category.wildfire_gender.generic": "Mod del Género Femenino de Wildfire", + "key.wildfire_gender.gender_menu": "Menu de Genero", + "toast.wildfire_gender.get_started": "¡Pulsa '%s' para iniciar!", + + "wildfire_gender.player_list.title": "Mod del Género Femenino", + "wildfire_gender.player_list.settings_button": "Configuración", + "wildfire_gender.player_list.sync_status": "Estatus de Sincronización", + "wildfire_gender.player_list.state.loading": "Cargando datos...", + "wildfire_gender.player_list.state.synced": "Jugador Sinvronizado", + "wildfire_gender.player_list.bounce_multiplier": "Multiplicador de Rebote: %sx", + "wildfire_gender.player_list.breast_momentum": "Impetu del Pecho: %s%%", + "wildfire_gender.player_list.female_sounds": "Sonidos de Mujer: %s", + "wildfire_gender.wardrobe.players_using_mod": "Jugadores usando el mod:", + + "wildfire_gender.always_show_list": "Mostrar jugadores sincronizados: %s", + "wildfire_gender.always_show_list.mod_ui_only": "Ésta pantalla", + "wildfire_gender.always_show_list.mod_ui_only.tooltip": "La lista de jugadores sincronizados solo se mostrará en ésta pantalla.", + "wildfire_gender.always_show_list.tab_list_open": "Lista de jugadores", + "wildfire_gender.always_show_list.tab_list_open.tooltip": "La lista de jugadores sincronizados se mostrará al pulsar %s", + "wildfire_gender.always_show_list.always": "Siempre", + "wildfire_gender.always_show_list.always.tooltip": "La lista de jugadores sincronizados siempre se mostrará.", + + "wildfire_gender.wardrobe.title": "Mod del Género Femenino de Wildfire", + "wildfire_gender.breast_customization.tab_customization": "Personalización", + "wildfire_gender.breast_customization.tab_physics": "Físicas del Busto", + "wildfire_gender.breast_customization.tab_miscellaneous": "Misceláneo", + + "wildfire_gender.breast_customization.presets.add_new": "Agregar nuevo...", + "wildfire_gender.breast_customization.presets.delete": "Eliminar", + + "wildfire_gender.wardrobe.slider.breast_size": "Tamaño del Busto: %s%%", + "wildfire_gender.wardrobe.slider.separation": "Separación: %s", + "wildfire_gender.wardrobe.slider.height": "Altura: %s", + "wildfire_gender.wardrobe.slider.depth": "Profundidad: %s", + "wildfire_gender.wardrobe.slider.rotation": "Rotación: %s°", + "wildfire_gender.slider.voice_pitch": "Tono de Voz: %s%%", + + "wildfire_gender.appearance_settings.title": "Personalización de Personaje", + "wildfire_gender.char_settings.title": "Configuración de Personaje (VIEJA)", + "wildfire_gender.char_settings.physics": "Físicas del Busto: %s", + + "wildfire_gender.char_settings.override_armor_physics": "Físicas en la armadura: %s", + "wildfire_gender.tooltip.override_armor_physics.line1": "Las físicas ya no se verán limitadas por el tipo de armadura que uses", + "wildfire_gender.tooltip.override_armor_physics.line2": "Ésto está hecho por si usas paquetes de recursos que escondan la armadura o la minimicen.", + + "wildfire_gender.char_settings.hide_in_armor": "Esconder en la armadura: %s", + "wildfire_gender.char_settings.hurt_sounds": "Sonidos de Mujer: %s", + "wildfire_gender.tooltip.hurt_sounds": "Tu personaje producirá sonidos de mujer al recibir daño cuándo se use el género 'Mujer' u 'Otro'", + + "wildfire_gender.breast_customization.dual_physics": "Físicas Dobles: %s", + + "wildfire_gender.label.gender": "Género", + "wildfire_gender.label.female": "Mujer", + "wildfire_gender.label.male": "Hombre", + "wildfire_gender.label.other": "Otro", + + "wildfire_gender.label.enabled": "Activado", + "wildfire_gender.label.disabled": "Desactivado", + "wildfire_gender.label.on": "Sí", + "wildfire_gender.label.off": "No", + "wildfire_gender.label.yes": "Sí", + "wildfire_gender.label.no": "No", + "wildfire_gender.label.with_creator": "¡Estás jugando en un servidor con la creadora del mod!", + "wildfire_gender.label.with_contributor": "¡Estás en un servidor con un/a contribuidor/a del mod!", + "wildfire_gender.label.with_both": "¡Estás jugando en un servidor con la creadora y un/a contribuidor/a!", + + "wildfire_gender.slider.bounce": "Intensidad: %s%%", + "wildfire_gender.slider.floppy": "Ímpetu: %s%%", + + "wildfire_gender.cancer_awareness.title": "Hey, ¡Es el mes de concientización sobre el cáncer de mama!", + + "wildfire_gender.first_time_setup.title": "¡Bienvenido/a al mod del género femenino de Wildfire!", + "wildfire_gender.first_time_setup.description": "¿Deseas activar la sincronización de configuración en la nube? Ésto permitirá a otros jugadores con el mod el ver tus ajustes sin que el servidor tenga el mod instalado.", + "wildfire_gender.first_time_setup.notice": "Siempre podrás cambiar ésta opción después en el menú del mod.", + "wildfire_gender.first_time_setup.enable": "Activar Sincronizado en la Nube", + "wildfire_gender.first_time_setup.disable": "Desactivar Sincronizado en la Nube", + + + "wildfire_gender.cloud_settings": "Configuración del Sincronizado en la Nube", + "wildfire_gender.cloud.available_online": "Sincronizado en la Nube", + "wildfire_gender.cloud.unavailable_offline": "El Sincronizado en la Nube no está disponible debido a que no iniciaste sesión en una cuenta de Minecraft válida.", + "wildfire_gender.cloud.status": "Sincronizado: %s", + "wildfire_gender.cloud.automatic": "Sincro. Automática: %s", + "wildfire_gender.cloud.automatic.tooltip.line1": "Cuando se active se enviaran los cambios que hagas a la nube.", + "wildfire_gender.cloud.automatic.tooltip.line2": "Aún puedes sincronizar manualmente si la opción no está disponible.", + "wildfire_gender.cloud.sync": "Sincronizar Ya", + "wildfire_gender.cloud.syncing": "Sincronizando...", + + "wildfire_gender.cloud.status_log": "Registro de estado", + + "wildfire_gender.cloud.syncing.success": "Sincronizado!", + "wildfire_gender.cloud.syncing.fail": "Sincro. Fallida...", + "wildfire_gender.cloud.disclaimer.line1": "Los datos del servidor de sincronización se tienen que almacenar en caché durante al menos 30 minutos.", + "wildfire_gender.cloud.disclaimer.line2": "Si los cambios no son visibles, inténtalo más tarde.", + + "wildfire_gender.sync_log.authenticating": "Autenticando cuenta...", + "wildfire_gender.sync_log.authentication_success": "Autenticación Exitosa.", + "wildfire_gender.sync_log.authentication_failed": "Autenticación Falló.", + "wildfire_gender.sync_log.reauthenticating": "Re-Autenticando Cuenta...", + + "wildfire_gender.sync_log.failed_to_sync_data": "Fallo al Sincronizar Datos", + "wildfire_gender.sync_log.sync_to_cloud": "Sincronizado datos a la nube...", + "wildfire_gender.sync_log.attempting_sync": "Sincronizando Perfil...", + "wildfire_gender.sync_log.sync_success": "Sincronizado con éxito.", + "wildfire_gender.sync_log.sync_too_frequently": "Has sincronizado muy rápido, inténtalo en unos segundos.", + + "wildfire_gender.sync_log.get_single_profile": "Obteniendo Perfil...", + "wildfire_gender.sync_log.get_multiple_profiles": "Obteniendo Perfiles en Lote...", + + "wildfire_gender.details.next_page": "Sig. Página", + "wildfire_gender.details.prev_page": "Ant. Página", + + "wildfire_gender.cloud_details.title": "Información del Servidor en la Nube" +} From eae57713a31d2bf5d5f9f6a7a31c7844a43b1277 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Mon, 25 Nov 2024 23:59:17 -0500 Subject: [PATCH 199/238] somethin --- build.gradle | 1 + .../com/wildfire/main/WildfireEventHandler.java | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/build.gradle b/build.gradle index 61633abf..d0b13ff9 100644 --- a/build.gradle +++ b/build.gradle @@ -23,6 +23,7 @@ dependencies { modImplementation fabricApi.module("fabric-networking-api-v1", project.fabric_version) modImplementation fabricApi.module("fabric-key-binding-api-v1", project.fabric_version) modImplementation fabricApi.module("fabric-lifecycle-events-v1", project.fabric_version) + modImplementation fabricApi.module("fabric-item-api-v1", project.fabric_version) modImplementation fabricApi.module("fabric-rendering-v1", project.fabric_version) modImplementation fabricApi.module("fabric-resource-loader-v0", project.fabric_version) modRuntimeOnly fabricApi.module("fabric-registry-sync-v0", project.fabric_version) diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index 3c401ee1..7b20914b 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -35,6 +35,7 @@ import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback; @@ -57,9 +58,16 @@ import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.item.tooltip.TooltipType; +import net.minecraft.nbt.NbtCompound; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; import net.minecraft.util.Util; import net.minecraft.world.World; import org.lwjgl.glfw.GLFW; @@ -109,8 +117,17 @@ public static void registerClientEvents() { ClientPlayConnectionEvents.DISCONNECT.register(WildfireEventHandler::clientDisconnect); LivingEntityFeatureRendererRegistrationCallback.EVENT.register(WildfireEventHandler::registerRenderLayers); HudRenderCallback.EVENT.register(WildfireEventHandler::renderHud); + //ItemTooltipCallback.EVENT.register(WildfireEventHandler::renderTooltip); disabled for now } + @Environment(EnvType.CLIENT) + private static void renderTooltip(ItemStack stack, Item.TooltipContext tooltipContext, TooltipType type, List lines) { + if (stack.getItem() == Items.LEATHER_CHESTPLATE) { + + lines.add(1, Text.literal("+1 Breast Support") + .formatted(Formatting.AQUA)); + } + } @Environment(EnvType.CLIENT) private static void renderHud(DrawContext context, RenderTickCounter tickCounter) { var textRenderer = Objects.requireNonNull(MinecraftClient.getInstance().textRenderer, "textRenderer"); From aa958414798a43c9620fc106b14334c12c41abe8 Mon Sep 17 00:00:00 2001 From: Kichura <68134602+Kichura@users.noreply.github.com> Date: Wed, 27 Nov 2024 00:13:48 +0100 Subject: [PATCH 200/238] Update workflows, 1.21.3. --- .github/workflows/fabric.yml | 7 ++- build.gradle | 2 +- gradle.properties | 4 +- gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 43583 bytes gradle/wrapper/gradle-wrapper.properties | 5 +- gradlew | 54 ++++++++++++------ gradlew.bat | 39 +++++++------ src/main/resources/fabric.mod.json | 4 +- .../resources/wildfire_gender.mixins.json | 2 +- 9 files changed, 71 insertions(+), 46 deletions(-) diff --git a/.github/workflows/fabric.yml b/.github/workflows/fabric.yml index 053ed99b..127556de 100644 --- a/.github/workflows/fabric.yml +++ b/.github/workflows/fabric.yml @@ -3,7 +3,7 @@ on: push: jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout repository uses: actions/checkout@v4 @@ -12,11 +12,12 @@ jobs: - name: Extract build version information id: ref run: .github/extract_refs.sh - - name: Setup JDK + - name: Setup JDK 21 uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: 21 + check-latest: true - name: Initialize caches uses: actions/cache@v4 with: @@ -27,7 +28,7 @@ jobs: key: ${{ runner.os }}-build-fabric-${{ steps.ref.outputs.minecraft_version }} restore-keys: | ${{ runner.os }}-build-fabric- - - name: Compile + - name: Compile with Grade run: ./gradlew build - name: Upload compiled artifacts uses: actions/upload-artifact@v4 diff --git a/build.gradle b/build.gradle index d0b13ff9..7d8ecc5e 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ dependencies { // Allow logging into an actual Minecraft account in a dev env // See https://github.com/DJtheRedstoner/DevAuth - modLocalRuntime "me.djtheredstoner:DevAuth-fabric:1.2.0" + modLocalRuntime "me.djtheredstoner:DevAuth-fabric:1.2.1" } processResources { diff --git a/gradle.properties b/gradle.properties index e717b3fe..5eae10c7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://fabricmc.net/develop # Note that this variable does *not* define the minimum required version in fabric.mod.json! -minecraft_version=1.21.2 +minecraft_version=1.21.3 yarn_build=1 loader_version=0.16.9 @@ -14,4 +14,4 @@ maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod # Dependencies -fabric_version=0.105.4+1.21.2 +fabric_version=0.110.0+1.21.3 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..a4b76b9530d66f5e68d973ea569d8e19de379189 100644 GIT binary patch literal 43583 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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 GZ~q4|mN%#X literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 707e499a..8fc91c82 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists +zipStorePath=wrapper/dists \ No newline at end of file diff --git a/gradlew b/gradlew index c53aefaa..d95bf613 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright 2015-2021 the original authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -32,10 +34,10 @@ # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; -# * expansions $var, ${var}, ${var:-default}, ${var+SET}, -# ${var#prefix}, ${var%suffix}, and $( cmd ); -# * compound commands having a testable exit status, especially case; -# * various built-in commands including command, set, and ulimit. +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +82,12 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +134,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,11 +201,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ @@ -205,6 +217,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index 107acd32..640d6868 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,8 +13,10 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +27,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -33,20 +36,20 @@ set APP_HOME=%DIRNAME% for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" +set DEFAULT_JVM_OPTS=-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -75,13 +78,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index acee56b1..b297bcc2 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -40,14 +40,14 @@ ], "accessWidener": "wildfire_gender.accesswidener", "depends": { - "fabricloader": ">=0.15", + "fabricloader": ">=0.15.0", "fabric-networking-api-v1": "*", "fabric-key-binding-api-v1": "*", "fabric-lifecycle-events-v1": "*", "fabric-rendering-v1": "*", "fabric-resource-loader-v0": "*", "fabric-registry-sync-v0": "*", - "minecraft": ">=1.21.2-beta.2 <1.22", + "minecraft": ">=1.21.2", "java": ">=21" }, "conflicts": { diff --git a/src/main/resources/wildfire_gender.mixins.json b/src/main/resources/wildfire_gender.mixins.json index 142cb3b6..0c5d63f5 100644 --- a/src/main/resources/wildfire_gender.mixins.json +++ b/src/main/resources/wildfire_gender.mixins.json @@ -2,7 +2,7 @@ "required": true, "minVersion": "0.8", "package": "com.wildfire.mixins", - "compatibilityLevel": "JAVA_17", + "compatibilityLevel": "JAVA_21", "mixins": [ "ArmorStandEntityMixin" ], From 00e74bbe8cede6388afde03dfb3c03f8b5bf1fcb Mon Sep 17 00:00:00 2001 From: Kichura <68134602+Kichura@users.noreply.github.com> Date: Wed, 27 Nov 2024 00:16:58 +0100 Subject: [PATCH 201/238] Further workflow enhancements. --- .github/workflows/fabric.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/fabric.yml b/.github/workflows/fabric.yml index 127556de..7c602cee 100644 --- a/.github/workflows/fabric.yml +++ b/.github/workflows/fabric.yml @@ -1,14 +1,14 @@ -name: Build Fabric -on: - push: +name: Build Fabric mod with Gradle +on: [pull_request, push] + jobs: build: runs-on: ubuntu-24.04 steps: - name: Checkout repository uses: actions/checkout@v4 - with: - clean: false + - name: Validate Gradle Wrapper + uses: gradle/actions/wrapper-validation@v4 - name: Extract build version information id: ref run: .github/extract_refs.sh @@ -28,7 +28,7 @@ jobs: key: ${{ runner.os }}-build-fabric-${{ steps.ref.outputs.minecraft_version }} restore-keys: | ${{ runner.os }}-build-fabric- - - name: Compile with Grade + - name: Compile with Gradle run: ./gradlew build - name: Upload compiled artifacts uses: actions/upload-artifact@v4 From 6d91a71c3d3ca17e547d84d8fe57d279dff52897 Mon Sep 17 00:00:00 2001 From: Kichura <68134602+Kichura@users.noreply.github.com> Date: Wed, 27 Nov 2024 00:23:40 +0100 Subject: [PATCH 202/238] Update Yarn to build 2. --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 5eae10c7..42528278 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.jvmargs=-Xmx1G # check these on https://fabricmc.net/develop # Note that this variable does *not* define the minimum required version in fabric.mod.json! minecraft_version=1.21.3 -yarn_build=1 +yarn_build=2 loader_version=0.16.9 # Mod Properties From 6786b70b8f5a4270f898f33b19f83de639e5ac7a Mon Sep 17 00:00:00 2001 From: Amelia <69445885+TransBluelight@users.noreply.github.com> Date: Wed, 27 Nov 2024 00:49:36 -0600 Subject: [PATCH 203/238] Pirate Speak The Seven Seas --- .../assets/wildfire_gender/lang/en_pt.json | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 src/main/resources/assets/wildfire_gender/lang/en_pt.json diff --git a/src/main/resources/assets/wildfire_gender/lang/en_pt.json b/src/main/resources/assets/wildfire_gender/lang/en_pt.json new file mode 100644 index 00000000..bcb953ab --- /dev/null +++ b/src/main/resources/assets/wildfire_gender/lang/en_pt.json @@ -0,0 +1,115 @@ +{ + "category.wildfire_gender.generic": "Za Wildfire Feminine Gender Mod", + "key.wildfire_gender.gender_menu": "Female Gender Menu", + "toast.wildfire_gender.get_started": "Push ze '%s' barrel to get goin!", + + "wildfire_gender.player_list.title": "Feminine Menu!!!", + "wildfire_gender.player_list.settings_button": "Tit Wrench", + "wildfire_gender.player_list.sync_status": "Obtain ze tit info", + "wildfire_gender.player_list.state.loading": "Obtainin' tit info...", + "wildfire_gender.player_list.state.synced": "Fellow Pirate Analyze!", + "wildfire_gender.player_list.bounce_multiplier": "Tit Boing-Boing Multiplaiah!: %sx", + "wildfire_gender.player_list.breast_momentum": "Tit speed!: %s%%", + "wildfire_gender.player_list.female_sounds": "Feminine Grunts: %s", + "wildfire_gender.wardrobe.players_using_mod": "Fellow mateys using ze mod:", + + "wildfire_gender.always_show_list": "Show me ze tit mateys: %s", + "wildfire_gender.always_show_list.mod_ui_only": "On zis canvas", + "wildfire_gender.always_show_list.mod_ui_only.tooltip": "Ze analyzed matey list vill only show on zis canvas!", + "wildfire_gender.always_show_list.tab_list_open": "Matey list", + "wildfire_gender.always_show_list.tab_list_open.tooltip": "Ze analyzed matey list vill show on zis canvas or by pushing the %s barrel!", + "wildfire_gender.always_show_list.always": "Always", + "wildfire_gender.always_show_list.always.tooltip": "Ze analyzed mateys will always sho'", + + "wildfire_gender.wardrobe.title": "Comrade Wildfaiah's tit mod", + "wildfire_gender.breast_customization.tab_customization": "Tit Wrench", + "wildfire_gender.breast_customization.tab_physics": "Tit Boing-Boing!!!", + "wildfire_gender.breast_customization.tab_miscellaneous": "Ze Barrel!", + + "wildfire_gender.breast_customization.presets.add_new": "New tit preset...", + "wildfire_gender.breast_customization.presets.delete": "No tit!!", + + "wildfire_gender.wardrobe.slider.breast_size": "Tit Awooga!: %s%%", + "wildfire_gender.wardrobe.slider.separation": "Tit Fresh Air Space: %s", + "wildfire_gender.wardrobe.slider.height": "Tit Altitude: %s", + "wildfire_gender.wardrobe.slider.depth": "Tit Depth: %s", + "wildfire_gender.wardrobe.slider.rotation": "Tit tilt: %s°", + "wildfire_gender.slider.voice_pitch": "Wohman Voice Pitch: %s%%", + + "wildfire_gender.appearance_settings.title": "Ze Matey Appearance", + "wildfire_gender.char_settings.title": "Character Settings OLD", + "wildfire_gender.char_settings.physics": "Tit Boing-Boing action!: %s", + + "wildfire_gender.char_settings.override_armor_physics": "Tit Boing-Boings in armor: %s", + "wildfire_gender.tooltip.override_armor_physics.line1": "Ze tit shall remain free even under ze constraints of matey attire!", + "wildfire_gender.tooltip.override_armor_physics.line2": "Zis is for custom matey attire zat iz minimal or non-existent, usually added via image barrels.", + + "wildfire_gender.char_settings.hide_in_armor": "Hide ze tit in matey attire: %s", + "wildfire_gender.char_settings.hurt_sounds": "Wohman Noises: %s", + "wildfire_gender.tooltip.hurt_sounds": "Ze matey will grunt like ze wohman if ze gender is set as 'Wohman' or 'Unidentified'", + + "wildfire_gender.breast_customization.dual_physics": "Ze tits disconnected!: %s", + + "wildfire_gender.label.gender": "Gender", + "wildfire_gender.label.female": "Wohman", + "wildfire_gender.label.male": "Man", + "wildfire_gender.label.other": "Unidentified", + + "wildfire_gender.label.enabled": "Arr! Yas!", + "wildfire_gender.label.disabled": "Arrrgh! Nah!", + "wildfire_gender.label.on": "Yah", + "wildfire_gender.label.off": "Nah", + "wildfire_gender.label.yes": "Yah", + "wildfire_gender.label.no": "Nah", + "wildfire_gender.label.with_creator": "Matey, you're aboard with ze creator!", + "wildfire_gender.label.with_contributor": "Matey, you're aboard with a fellow comrade of ze creator!", + "wildfire_gender.label.with_both": "Arrrgh! Ze creator and a comrade of her's are aboard!", + + "wildfire_gender.slider.bounce": "Ze tit Boing-Boings harder: %s%%", + "wildfire_gender.slider.floppy": "Ze tit Boing-Boings faster: %s%%", + + "wildfire_gender.cancer_awareness.title": "Aye matey! It's ze breast cancer awareness month!", + + "wildfire_gender.first_time_setup.title": "Ahoy, matey! Welcome aboard to ze female gender mod by Wildfire!", + "wildfire_gender.first_time_setup.description": "Would ya like to enable ze sky ship analyzing? sky ship analyzing will make ya able to see mateys tits without ze ship having ze mod!", + "wildfire_gender.first_time_setup.notice": "Ya can always change em analyzing latah!", + "wildfire_gender.first_time_setup.enable": "Sky Ship Analyzing iz On", + "wildfire_gender.first_time_setup.disable": "Sky Ship Analyzing iz Off", + + + "wildfire_gender.cloud_settings": "Sky Ship Wrench", + "wildfire_gender.cloud.available_online": "Sky Ship Analysis", + "wildfire_gender.cloud.unavailable_offline": "Sky Ship Analyzing is unavailable as we couldn't identify ya, matey! Are ya a bad pirate?", + "wildfire_gender.cloud.status": "Sky Ship Analyzing: %s", + "wildfire_gender.cloud.automatic": "Automatic Analysis: %s", + "wildfire_gender.cloud.automatic.tooltip.line1": "While on, ya tit info will be sent to ze sky ship.", + "wildfire_gender.cloud.automatic.tooltip.line2": "Ya can still send analysis info if ze option is off.", + "wildfire_gender.cloud.sync": "To the Sky Ship!", + "wildfire_gender.cloud.syncing": "Sky Ship processing...", + + "wildfire_gender.cloud.status_log": "Sky Ship Scroll", + + "wildfire_gender.cloud.syncing.success": "Analyzed", + "wildfire_gender.cloud.syncing.fail": "Analysis failed!", + "wildfire_gender.cloud.disclaimer.line1": "Ze Sky Ship may need to have ze info for 30 minutes", + "wildfire_gender.cloud.disclaimer.line2": "If ze changes don't apply try to re-analyze latah, will ya matey?", + + "wildfire_gender.sync_log.authenticating": "Identifying Matey...", + "wildfire_gender.sync_log.authentication_success": "Matey Identified.", + "wildfire_gender.sync_log.authentication_failed": "Matey fell off ze ship.", + "wildfire_gender.sync_log.reauthenticating": "Identifying Matey Again...", + + "wildfire_gender.sync_log.failed_to_sync_data": "Sky Ship Info Send Failed.", + "wildfire_gender.sync_log.sync_to_cloud": "Sending ze info to Sky Ship...", + "wildfire_gender.sync_log.attempting_sync": "Analyzing tit profile...", + "wildfire_gender.sync_log.sync_success": "Tit info successfully sent to Sky Ship.", + "wildfire_gender.sync_log.sync_too_frequently": "Matey, ze Sky Ship overloaded!", + + "wildfire_gender.sync_log.get_single_profile": "Ze Profile iz Coming...", + "wildfire_gender.sync_log.get_multiple_profiles": "Ze profiles are coming...", + + "wildfire_gender.details.next_page": "Next Book", + "wildfire_gender.details.prev_page": "Prev Boom", + + "wildfire_gender.cloud_details.title": "Sky Ship Information" +} From 8c371c8d4bf2a7b963e9e9047d5fc1a2a4f7142a Mon Sep 17 00:00:00 2001 From: celeste Date: Wed, 27 Nov 2024 20:47:11 -0700 Subject: [PATCH 204/238] fix removing chestplates from armor stands --- src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java b/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java index 2f78d7a5..594ddfca 100644 --- a/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java +++ b/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java @@ -49,7 +49,7 @@ protected ArmorStandEntityMixin(EntityType entityType, W ) public ItemStack wildfiregender$attachBreastData(ItemStack stack, @Local(argsOnly = true) EquipmentSlot slot, @Local(argsOnly = true) PlayerEntity player) { - if(player == null || getWorld().isClient() || slot != EquipmentSlot.CHEST) { + if(player == null || getWorld().isClient() || slot != EquipmentSlot.CHEST || stack.isEmpty()) { return stack; } From 621f14c4be03e2db347c01684a0bedf2e6f6b36d Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Wed, 27 Nov 2024 23:45:12 -0500 Subject: [PATCH 205/238] Add BluelightAmelia to contributors --- src/main/resources/fabric.mod.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index b297bcc2..2d70713f 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -10,6 +10,7 @@ "contributors": [ "Archl", "Arcti_cc", + "BluelightAmelia", "celeste", "diademiemi", "IzzyBizzy45", From 80fb644d1848151da523be53fd2deaaa4e97bf8b Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Thu, 28 Nov 2024 20:45:19 -0500 Subject: [PATCH 206/238] Removed unnecessary rendering code --- .../java/com/wildfire/render/GenderLayer.java | 2 +- .../render/WildfireModelRenderer.java | 139 +++++------------- 2 files changed, 38 insertions(+), 103 deletions(-) diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index 21ac1b78..9fcd6d5d 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -320,7 +320,7 @@ protected void renderSides(S state, M model, MatrixStack matrixStack, Consumer Date: Fri, 29 Nov 2024 20:09:24 -0500 Subject: [PATCH 207/238] What could this be --- .../wildfire/gui/screen/BaseWildfireScreen.java | 11 +++++++++++ .../gui/screen/WardrobeBrowserScreen.java | 1 + .../textures/gui/mascot/keira_leather.png | Bin 0 -> 14939 bytes .../textures/gui/mascot/keira_look.png | Bin 0 -> 17661 bytes .../textures/gui/mascot/keira_netherite.png | Bin 0 -> 16617 bytes .../textures/gui/mascot/keira_wave.png | Bin 0 -> 16281 bytes 6 files changed, 12 insertions(+) create mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/mascot/keira_leather.png create mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/mascot/keira_look.png create mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/mascot/keira_netherite.png create mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/mascot/keira_wave.png diff --git a/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java b/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java index 41337f42..27d95a7d 100644 --- a/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java +++ b/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java @@ -25,7 +25,9 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.render.RenderLayer; import net.minecraft.text.Text; +import net.minecraft.util.Identifier; import org.jetbrains.annotations.Nullable; @Environment(EnvType.CLIENT) @@ -34,6 +36,15 @@ public abstract class BaseWildfireScreen extends Screen { protected final UUID playerUUID; protected final Screen parent; + //Keira Emberlyn - The Mod's New Mascot + protected static final Identifier KEIRA_LOOK = Identifier.of(WildfireGender.MODID, "textures/gui/mascot/keira_look.png"); + protected static final Identifier KEIRA_WAVE = Identifier.of(WildfireGender.MODID, "textures/gui/mascot/keira_wave.png"); + protected static final Identifier KEIRA_LEATHER = Identifier.of(WildfireGender.MODID, "textures/gui/mascot/keira_leather.png"); + protected static final Identifier KEIRA_NETHERITE = Identifier.of(WildfireGender.MODID, "textures/gui/mascot/keira_netherite.png"); + protected static final int KEIRA_WIDTH = 610; + protected static final int KEIRA_HEIGHT = 736; + //Keira test ctx.drawTexture(RenderLayer::getGuiTextured, KEIRA_LOOK, x, y, 0, 0, 26, 26, KEIRA_WIDTH, KEIRA_HEIGHT, KEIRA_WIDTH, KEIRA_HEIGHT); + protected BaseWildfireScreen(Text title, Screen parent, UUID uuid) { super(title); this.parent = parent; diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index b9838de0..597a9d0a 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -182,6 +182,7 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { //Render in front of the UI when it's open. List syncedPlayers = collectPlayerEntries(); GuiUtils.drawSyncedPlayers(ctx, textRenderer, syncedPlayers); + } private void drawCreatorContributorText(DrawContext ctx, int mouseX, int mouseY, int creatorY) { diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/mascot/keira_leather.png b/src/main/resources/assets/wildfire_gender/textures/gui/mascot/keira_leather.png new file mode 100644 index 0000000000000000000000000000000000000000..a110f733f50105f3f84c5fadb1352109f7e1ee49 GIT binary patch literal 14939 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/mascot/keira_look.png b/src/main/resources/assets/wildfire_gender/textures/gui/mascot/keira_look.png new file mode 100644 index 0000000000000000000000000000000000000000..d71548ed0878fb261bd7cd16c316ba7e57a12a9a GIT binary patch literal 17661 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 Z`%Bh6nP^w=zs~qa=eU7pg}U9P{{>{azBd2> literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/mascot/keira_netherite.png b/src/main/resources/assets/wildfire_gender/textures/gui/mascot/keira_netherite.png new file mode 100644 index 0000000000000000000000000000000000000000..fe2596603513b996a8d1b65cfffc5c8587470ff3 GIT binary patch literal 16617 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 Date: Fri, 29 Nov 2024 21:08:50 -0500 Subject: [PATCH 208/238] Changes to First Time Setup Screen --- .../screen/WildfireFirstTimeSetupScreen.java | 26 ++++++++++++++---- .../textures/gui/first_time_bg.png | Bin 31054 -> 38374 bytes 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java index 76148a60..68a22da1 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java @@ -22,6 +22,7 @@ import com.wildfire.gui.WildfireButton; import com.wildfire.main.WildfireGender; import com.wildfire.main.WildfireGenderClient; +import com.wildfire.main.WildfireHelper; import com.wildfire.main.config.GlobalConfig; import com.wildfire.main.entitydata.PlayerConfig; import net.fabricmc.api.EnvType; @@ -33,6 +34,8 @@ import net.minecraft.text.Text; import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; +import net.minecraft.util.math.MathHelper; +import org.joml.Quaternionf; import java.util.Objects; import java.util.UUID; @@ -67,7 +70,7 @@ public void init() { WildfireButton no = null; }; - this.addDrawableChild(new WildfireButton(x, y + 74, 128 - 5 - 1, 20, + this.addDrawableChild(new WildfireButton(x + 3, y + 74, 128, 20, ENABLE_CLOUD_SYNCING, button -> { var config = GlobalConfig.INSTANCE; @@ -85,7 +88,7 @@ public void init() { })); - this.addDrawableChild(ref.no = new WildfireButton(x - 128 + 6, y + 74, 128 - 5 - 1, 20, + this.addDrawableChild(ref.no = new WildfireButton(x - 131, y + 74, 128, 20, DISABLE_CLOUD_SYNCING, button -> { var config = GlobalConfig.INSTANCE; @@ -128,7 +131,7 @@ private CompletableFuture doInitialSync() { @Override public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delta) { this.renderInGameBackground(ctx); - ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND, (this.width - 256) / 2, (this.height - 200) / 2, 0, 0, 256, 200, 256, 256); + ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND, (this.width - 274) / 2, (this.height - 200) / 2, 0, 0, 274, 200, 512, 512); } @Override @@ -141,19 +144,30 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { int x = this.width / 2; int y = this.height / 2; - GuiUtils.drawCenteredText(ctx, textRenderer, TITLE, x, y - 20, 0x000000); + GuiUtils.drawCenteredText(ctx, textRenderer, TITLE, x, y - 24, 4210752); - GuiUtils.drawCenteredTextWrapped(ctx, textRenderer, DESCRIPTION, x, y - 5, (int) ((256-10)), 4210752); + GuiUtils.drawCenteredTextWrapped(ctx, textRenderer, DESCRIPTION, x + 32, y - 8, (int) ((256-65)), 0xFFFFFF); mStack.push(); mStack.translate(x, y + 47, 0); mStack.scale(0.8f, 0.8f, 1); mStack.translate(-x, -y - 47, 0); - GuiUtils.drawCenteredTextWrapped(ctx, textRenderer, NOTICE, x, y + 65, (int) ((256-10) * 1.2f), 4210752); + GuiUtils.drawCenteredTextWrapped(ctx, textRenderer, NOTICE, x, y + 68, (int) ((256-10) * 1.2f), 4210752); mStack.pop(); + int keiraX = x - 133; + int keiraY = y - 12; + int keiraW = 60; + int keiraH = (int) (keiraW * ((float)KEIRA_HEIGHT / KEIRA_WIDTH)); + ctx.drawTexture(RenderLayer::getGuiTextured, KEIRA_WAVE, keiraX, keiraY, 0, 0, keiraW, keiraH, KEIRA_WIDTH, KEIRA_HEIGHT, KEIRA_WIDTH, KEIRA_HEIGHT); + + /*mStack.push(); + mStack.translate(keiraX + (keiraW / 2), keiraY + (keiraH / 2), 0); + mStack.multiply(new Quaternionf().rotateZ(-25 * MathHelper.RADIANS_PER_DEGREE)); + ctx.drawTexture(RenderLayer::getGuiTextured, KEIRA_LOOK, -keiraW / 2, -keiraH / 2, 0, 0, keiraW, keiraH, KEIRA_WIDTH, KEIRA_HEIGHT, KEIRA_WIDTH, KEIRA_HEIGHT); + mStack.pop();*/ } @Override diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/first_time_bg.png b/src/main/resources/assets/wildfire_gender/textures/gui/first_time_bg.png index a4c79f9d8b2d570a211a64abdaa6581c00bf22fc..e3c4d894bd2f47b18c6276b12dd22dd6526abdb9 100644 GIT binary patch literal 38374 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 From 8a93187437bab472a8c8d90c5751292179c101dc Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Fri, 29 Nov 2024 21:11:00 -0500 Subject: [PATCH 209/238] More Changes to First Time Setup Screen --- .../wildfire/gui/screen/WildfireFirstTimeSetupScreen.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java index 68a22da1..07f95629 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java @@ -37,6 +37,7 @@ import net.minecraft.util.math.MathHelper; import org.joml.Quaternionf; +import java.text.Normalizer; import java.util.Objects; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -146,7 +147,10 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { GuiUtils.drawCenteredText(ctx, textRenderer, TITLE, x, y - 24, 4210752); - GuiUtils.drawCenteredTextWrapped(ctx, textRenderer, DESCRIPTION, x + 32, y - 8, (int) ((256-65)), 0xFFFFFF); + GuiUtils.drawCenteredTextWrapped(ctx, textRenderer, Text.literal("Keira Emberlyn:").formatted(Formatting.LIGHT_PURPLE), x + 32, y - 10, (int) ((256-65)), 0xFFFFFF); + + //TODO: Vertical scroll bar for longer text? + GuiUtils.drawCenteredTextWrapped(ctx, textRenderer, DESCRIPTION, x + 32, y + 2, (int) ((256-65)), 0xFFFFFF); mStack.push(); From c8c633852760cbbc48bc88b8a5f3de9f75a09a6a Mon Sep 17 00:00:00 2001 From: betawolfy_ Date: Sat, 30 Nov 2024 11:15:33 +0100 Subject: [PATCH 210/238] Update fr_fr.json Updating the french translation. Used the en_us version as a base. --- .../assets/wildfire_gender/lang/fr_fr.json | 153 +++++++++++++----- 1 file changed, 110 insertions(+), 43 deletions(-) diff --git a/src/main/resources/assets/wildfire_gender/lang/fr_fr.json b/src/main/resources/assets/wildfire_gender/lang/fr_fr.json index bd6146ce..b24f3d23 100644 --- a/src/main/resources/assets/wildfire_gender/lang/fr_fr.json +++ b/src/main/resources/assets/wildfire_gender/lang/fr_fr.json @@ -1,48 +1,115 @@ { "category.wildfire_gender.generic": "Wildfire's Female Gender Mod", - "key.wildfire_gender.gender_menu": "Menu Genre Féminin", + "key.wildfire_gender.gender_menu": "Female Gender Menu", + "toast.wildfire_gender.get_started": "Press '%s' to get started!", "wildfire_gender.player_list.title": "Female Gender Mod", - "wildfire_gender.player_list.settings_button": "Paramètres", - "wildfire_gender.player_list.sync_status": "Sync Statut", - "wildfire_gender.player_list.state.loading": "Chargement des Données...", - "wildfire_gender.player_list.state.synced": "Joueur Synchronisé", - "wildfire_gender.player_list.bounce_multiplier": "Multiplicateur de Rebond: %s%", - "wildfire_gender.player_list.breast_momentum": "Élan de la Poitrine: %s%%", - "wildfire_gender.player_list.female_sounds": "Sons Féminins: %s", - - "wildfire_gender.wardrobe.title": "Menu de Personnalisation", - "wildfire_gender.wardrobe.slider.breast_size": "Taille de la Poitrine: %s%%", - "wildfire_gender.wardrobe.slider.separation": "Séparation: %s", - "wildfire_gender.wardrobe.slider.height": "Hauteur: %s", - "wildfire_gender.wardrobe.slider.depth": "Profondeur: %s", - "wildfire_gender.wardrobe.slider.rotation": "Rotation: %s degrés", - - "wildfire_gender.appearance_settings.title": "Paramètres d'Apparence", - "wildfire_gender.char_settings.title": "Paramètres de Personnage", - "wildfire_gender.char_settings.physics": "Physiques de la Poitrine: %s", - "wildfire_gender.char_settings.hide_in_armor": "Cacher dans l'Armure: %s", - "wildfire_gender.char_settings.hurt_sounds": "Sons Blessés Féminins: %s", - "wildfire_gender.tooltip.hurt_sounds": "Active les Sons Blessés Féminins", - - "wildfire_gender.breast_customization.dual_physics": "Double-Physiques: %s", - - "wildfire_gender.label.gender": "Genre", - "wildfire_gender.label.female": "Féminin", - "wildfire_gender.label.male": "Masculin", - "wildfire_gender.label.other": "Autre", - - "wildfire_gender.label.enabled": "Activé", - "wildfire_gender.label.disabled": "Desactivé", - "wildfire_gender.label.yes": "Oui", - "wildfire_gender.label.no": "Non", - "wildfire_gender.label.with_creator": "Vous êtes en train de jouer sur un serveur avec la créatrice de ce mod!", - - "wildfire_gender.slider.bounce": "Intensité de Rebond: %s%%", - "wildfire_gender.slider.floppy": "Élan de la Poitrine: %s%%", - "wildfire_gender.slider.min_bounce": "Pourquoi Activer la Physique?", - "wildfire_gender.slider.max_bounce": "Anime Breast Physics!!!", - "wildfire_gender.tooltip.bounce_warning": "Mettre 'Intensité de Rebond' sur une valeur élevée aura pas l'air naturel!", - - "wildfire_gender.cancer_awareness.title": "Hey, c'est le Mois de la Sensibilisation au Cancer du Sein!" + "wildfire_gender.player_list.settings_button": "Settings", + "wildfire_gender.player_list.sync_status": "Sync Status", + "wildfire_gender.player_list.state.loading": "Loading Data...", + "wildfire_gender.player_list.state.synced": "Synced Player", + "wildfire_gender.player_list.bounce_multiplier": "Bounce Multiplier: %sx", + "wildfire_gender.player_list.breast_momentum": "Breast Momentum: %s%%", + "wildfire_gender.player_list.female_sounds": "Female Sounds: %s", + "wildfire_gender.wardrobe.players_using_mod": "Players Using the Mod:", + + "wildfire_gender.always_show_list": "Show Synced Players: %s", + "wildfire_gender.always_show_list.mod_ui_only": "This screen", + "wildfire_gender.always_show_list.mod_ui_only.tooltip": "The synced player list will only show while in this menu", + "wildfire_gender.always_show_list.tab_list_open": "Player list", + "wildfire_gender.always_show_list.tab_list_open.tooltip": "The synced player list will show while in this menu or by pressing %s", + "wildfire_gender.always_show_list.always": "Always", + "wildfire_gender.always_show_list.always.tooltip": "The synced player list will always show", + + "wildfire_gender.wardrobe.title": "Wildfire's Female Gender Mod", + "wildfire_gender.breast_customization.tab_customization": "Customization", + "wildfire_gender.breast_customization.tab_physics": "Breast Physics", + "wildfire_gender.breast_customization.tab_miscellaneous": "Miscellaneous", + + "wildfire_gender.breast_customization.presets.add_new": "Add New...", + "wildfire_gender.breast_customization.presets.delete": "Delete", + + "wildfire_gender.wardrobe.slider.breast_size": "Breast Size: %s%%", + "wildfire_gender.wardrobe.slider.separation": "Separation: %s", + "wildfire_gender.wardrobe.slider.height": "Height: %s", + "wildfire_gender.wardrobe.slider.depth": "Depth: %s", + "wildfire_gender.wardrobe.slider.rotation": "Rotation: %s°", + "wildfire_gender.slider.voice_pitch": "Pitch: %s%%", + + "wildfire_gender.appearance_settings.title": "Character Personalization", + "wildfire_gender.char_settings.title": "Character Settings OLD", + "wildfire_gender.char_settings.physics": "Breast Physics: %s", + + "wildfire_gender.char_settings.override_armor_physics": "Armor Physics: %s", + "wildfire_gender.tooltip.override_armor_physics.line1": "Breast physics will no longer be reduced/suppressed by your equipped armor while enabled", + "wildfire_gender.tooltip.override_armor_physics.line2": "This is intended for use with resource packs that hide armor, or any similar minimal armor packs", + + "wildfire_gender.char_settings.hide_in_armor": "Hide In Armor: %s", + "wildfire_gender.char_settings.hurt_sounds": "Female Hurt Sounds: %s", + "wildfire_gender.tooltip.hurt_sounds": "Your character will play a female hurt sound when taking damage if your gender is set to either Female or Other", + + "wildfire_gender.breast_customization.dual_physics": "Dual-Physics: %s", + + "wildfire_gender.label.gender": "Gender", + "wildfire_gender.label.female": "Female", + "wildfire_gender.label.male": "Male", + "wildfire_gender.label.other": "Other", + + "wildfire_gender.label.enabled": "Enabled", + "wildfire_gender.label.disabled": "Disabled", + "wildfire_gender.label.on": "On", + "wildfire_gender.label.off": "Off", + "wildfire_gender.label.yes": "Yes", + "wildfire_gender.label.no": "No", + "wildfire_gender.label.with_creator": "You are playing on a server with the creator of this mod!", + "wildfire_gender.label.with_contributor": "You are playing on a server with a contributor of this mod!", + "wildfire_gender.label.with_both": "You are playing on a server with the creator and a contributor of this mod!", + + "wildfire_gender.slider.bounce": "Intensity: %s%%", + "wildfire_gender.slider.floppy": "Momentum: %s%%", + + "wildfire_gender.cancer_awareness.title": "Hey, it's Breast Cancer Awareness Month!", + + "wildfire_gender.first_time_setup.title": "Welcome to Wildfire's Female Gender Mod!", + "wildfire_gender.first_time_setup.description": "Voulez-vous activer la synchronisation du Cloud pour vos paramètres de genre? Cette fonctionnalité permet à d'autres joueurs de voir votre apparence de genre, même si le serveur n'a pas installé le mod.", + "wildfire_gender.first_time_setup.notice": "Vous pouvez activer ou désactiver cette fonctionnalité à tout moment dans les paramètres du mod.", + "wildfire_gender.first_time_setup.enable": "Activer la synchronisation du Cloud", + "wildfire_gender.first_time_setup.disable": "Désactiver la synchronisation du Cloud", + + + "wildfire_gender.cloud_settings": "paramètres de synchronisation du Cloud", + "wildfire_gender.cloud.available_online": "Cloud Sync", + "wildfire_gender.cloud.unavailable_offline": "La synchronisation du Cloud n'est pas disponible car vous n'êtes pas connecté à un compte microsoft.", + "wildfire_gender.cloud.status": "Status de la Synchonalisation: %s", + "wildfire_gender.cloud.automatic": "Synchonalisation automatique: %s", + "wildfire_gender.cloud.automatic.tooltip.line1": "Si activé, votre configuration sera automatiquement synchronisée avec le cloud après toute modification.", + "wildfire_gender.cloud.automatic.tooltip.line2": "Vous pouvez toujours synchroniser manuellement avec le bouton ci-dessous si cela est désactivé.", + "wildfire_gender.cloud.sync": "Synchroniser maintenant", + "wildfire_gender.cloud.syncing": "Synchronisation...", + + "wildfire_gender.cloud.status_log": "Historique des Statuts", + + "wildfire_gender.cloud.syncing.success": "Synchronisé", + "wildfire_gender.cloud.syncing.fail": "Synchonalisation échouée", + "wildfire_gender.cloud.disclaimer.line1": "Le serveur de synchronisation des données peut mettre jusqu'à 30 minutes pour actualiser les données.", + "wildfire_gender.cloud.disclaimer.line2": "Si les modifications ne sont pas visibles, veuillez réessayer de resynchroniser plus tard.", + + "wildfire_gender.sync_log.authenticating": "Authentication...", + "wildfire_gender.sync_log.authentication_success": "Authentication réussi.", + "wildfire_gender.sync_log.authentication_failed": "Authentication échouée.", + "wildfire_gender.sync_log.reauthenticating": "Ré-authentication...", + + "wildfire_gender.sync_log.failed_to_sync_data": "La synchronisation à échoué.", + "wildfire_gender.sync_log.sync_to_cloud": "synchronisation au Cloud...", + "wildfire_gender.sync_log.attempting_sync": "Tentative de synchronisation du profil...", + "wildfire_gender.sync_log.sync_success": "Synchronisation réussie.", + "wildfire_gender.sync_log.sync_too_frequently": "Synchronisation trop fréquente.", + + "wildfire_gender.sync_log.get_single_profile": "Récupération du Profil...", + "wildfire_gender.sync_log.get_multiple_profiles": "Récupération des Profils...", + + "wildfire_gender.details.next_page": "Page Suivante", + "wildfire_gender.details.prev_page": "Page Précédente", + + "wildfire_gender.cloud_details.title": "Informations sur le serveur Cloud" } From c3d4e1e3543569379a8fc7138885936b8d7649f6 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sat, 30 Nov 2024 21:42:58 -0500 Subject: [PATCH 211/238] Added Keybinding for Toggling Breast Rendering - Not set by default. Recommended default is 'N'. - Does not save between game sessions. Will automatically reset to true after game restart. --- .../wildfire/main/WildfireEventHandler.java | 18 ++++++++++++++++++ .../java/com/wildfire/render/GenderLayer.java | 3 ++- .../assets/wildfire_gender/lang/en_us.json | 1 + 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index 7b20914b..c3d420f5 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -54,6 +54,7 @@ import net.minecraft.client.render.entity.EntityRendererFactory; import net.minecraft.client.render.entity.LivingEntityRenderer; import net.minecraft.client.render.entity.PlayerEntityRenderer; +import net.minecraft.client.util.InputUtil; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; @@ -83,8 +84,14 @@ private WildfireEventHandler() { } private static final KeyBinding CONFIG_KEYBIND; + private static final KeyBinding TOGGLE_KEYBIND; private static int timer = 0; + private static boolean RENDER_BREASTS = true; //This is just a toggle to render it in game quickly, I'm not putting this in the config to be saved. + + public static boolean getRenderBreasts() { + return RENDER_BREASTS; + } static { if(FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { // this has to be wrapped in a lambda to ensure that a dedicated server won't crash during startup @@ -94,8 +101,15 @@ private WildfireEventHandler() { KeyBindingHelper.registerKeyBinding(keybind); return keybind; }); + TOGGLE_KEYBIND = Util.make(() -> { + KeyBinding keybind = new KeyBinding("key.wildfire_gender.toggle", InputUtil.UNKNOWN_KEY.getCode(), "category.wildfire_gender.generic"); + KeyBindingHelper.registerKeyBinding(keybind); + return keybind; + }); + } else { CONFIG_KEYBIND = null; + TOGGLE_KEYBIND = null; } } @@ -207,6 +221,10 @@ private static void onClientTick(MinecraftClient client) { } } + + if(TOGGLE_KEYBIND.wasPressed() && client.currentScreen == null) { + RENDER_BREASTS ^= true; + } if(CONFIG_KEYBIND.wasPressed() && client.currentScreen == null) { if(GlobalConfig.INSTANCE.get(GlobalConfig.FIRST_TIME_LOAD) && CloudSync.isAvailable()) { client.setScreen(new WildfireFirstTimeSetupScreen(null, client.player.getUuid())); diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index 9fcd6d5d..1525e5e3 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -19,6 +19,7 @@ package com.wildfire.render; import com.wildfire.api.IGenderArmor; +import com.wildfire.main.WildfireEventHandler; import com.wildfire.main.entitydata.Breasts; import com.wildfire.main.WildfireGender; import com.wildfire.main.WildfireHelper; @@ -118,7 +119,7 @@ public GenderLayer(FeatureRendererContext render) { @Override public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light, S state, float limbAngle, float limbDistance) { MinecraftClient client = MinecraftClient.getInstance(); - if(client.player == null) { + if(client.player == null || !WildfireEventHandler.getRenderBreasts()) { // we're currently in a menu; we won't have any data loaded to begin with, so just give up early return; } diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index ee570bb0..9302bcb9 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -1,6 +1,7 @@ { "category.wildfire_gender.generic": "Wildfire's Female Gender Mod", "key.wildfire_gender.gender_menu": "Female Gender Menu", + "key.wildfire_gender.toggle": "Toggle Breast Rendering", "toast.wildfire_gender.get_started": "Press '%s' to get started!", "wildfire_gender.player_list.title": "Female Gender Mod", From 350b4da2f28935f10892a121762ac65f2dd05f22 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Sat, 30 Nov 2024 21:51:05 -0500 Subject: [PATCH 212/238] Bump to v4.3 Added changelog file to keep track of major changes --- gradle.properties | 2 +- src/main/resources/CHANGELOG | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/CHANGELOG diff --git a/gradle.properties b/gradle.properties index 42528278..74b5bfa8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ yarn_build=2 loader_version=0.16.9 # Mod Properties -mod_version = 4.2.1 +mod_version = 4.3 maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod diff --git a/src/main/resources/CHANGELOG b/src/main/resources/CHANGELOG new file mode 100644 index 00000000..95eddb8e --- /dev/null +++ b/src/main/resources/CHANGELOG @@ -0,0 +1,10 @@ +Creating this to keep track of most major changes made without having to sift through the commits. + +VERSION 4.3 +- Added BluelightAmelia to Contributors +- Added Keybinding for Breast Rendering +- Fixed Removing Chestplates From Armor Stands +- Added Keira Emberlyn (???) +- Updated French Translation +- Added Pirate Speak Translation +- Removed Unnecessary Code From 970d373782518a9204d9aba838e8126a53de5e2f Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 7 Nov 2024 16:03:56 -0700 Subject: [PATCH 213/238] 1.21.4-rc1 --- doc/armor_configs.md | 4 +- gradle.properties | 6 +- .../java/com/wildfire/api/WildfireAPI.java | 2 +- .../gui/WildfireBreastPresetList.java | 5 +- .../wildfire/main/WildfireEventHandler.java | 4 +- .../com/wildfire/main/entitydata/Breasts.java | 2 +- .../main/networking/AbstractSyncPacket.java | 7 +-- .../accessors/EquipmentRendererAccessor.java | 35 ++++++++++++ .../accessors/TextureManagerAccessor.java | 30 ++++++++++ .../TrimSpriteKeyConstructorAccessor.java | 41 +++++++++++++ .../com/wildfire/render/GenderArmorLayer.java | 57 ++++++++++++------- .../resources/GenderArmorResourceManager.java | 7 ++- .../assets/wildfire_gender/CREDITS.txt | 1 - src/main/resources/fabric.mod.json | 2 +- .../resources/wildfire_gender.accesswidener | 1 + .../resources/wildfire_gender.mixins.json | 3 + 16 files changed, 167 insertions(+), 40 deletions(-) create mode 100644 src/main/java/com/wildfire/mixins/accessors/EquipmentRendererAccessor.java create mode 100644 src/main/java/com/wildfire/mixins/accessors/TextureManagerAccessor.java create mode 100644 src/main/java/com/wildfire/mixins/accessors/TrimSpriteKeyConstructorAccessor.java diff --git a/doc/armor_configs.md b/doc/armor_configs.md index fdefc031..54cded6d 100644 --- a/doc/armor_configs.md +++ b/doc/armor_configs.md @@ -1,7 +1,7 @@ # Armor Resource Configs -Armor physics values can be modified by a resource pack by placing a JSON file at `assets/NAMESPACE/wildfire_gender_data/MODEL.json`, -where `NAMESPACE` and `MODEL` are replaced with the respective values (such as `assets/minecraft/wildfire_gender_data/iron.json`). +Armor physics values can be modified by a resource pack by placing a JSON file at `assets/NAMESPACE/wildfire_gender_data/ASSET_ID.json`, +where `NAMESPACE` and `ASSET_ID` are replaced with the respective values (such as `assets/minecraft/wildfire_gender_data/iron.json`). The full schema with default values is as follows: diff --git a/gradle.properties b/gradle.properties index 74b5bfa8..44a75c34 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,8 +4,8 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://fabricmc.net/develop # Note that this variable does *not* define the minimum required version in fabric.mod.json! -minecraft_version=1.21.3 -yarn_build=2 +minecraft_version=1.21.4-rc1 +yarn_build=1 loader_version=0.16.9 # Mod Properties @@ -14,4 +14,4 @@ maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod # Dependencies -fabric_version=0.110.0+1.21.3 +fabric_version=0.110.2+1.21.4 diff --git a/src/main/java/com/wildfire/api/WildfireAPI.java b/src/main/java/com/wildfire/api/WildfireAPI.java index 2af5ecf2..4149340a 100644 --- a/src/main/java/com/wildfire/api/WildfireAPI.java +++ b/src/main/java/com/wildfire/api/WildfireAPI.java @@ -48,7 +48,7 @@ public class WildfireAPI { * of resource pack configurations in the future. * * @implNote Implementations added through this method are presently ignored if a resource pack defines armor data - * at {@code NAMESPACE:wildfire_gender_data/MODEL.json}, and are only used as a default implementation. + * at {@code NAMESPACE:wildfire_gender_data/ASSET_ID.json}, and are only used as a default implementation. * * @param item the item that you are linking this {@link IGenderArmor} to * @param genderArmor the class implementing the {@link IGenderArmor} to apply to the item diff --git a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java index 193186e0..9b39cd3b 100644 --- a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java +++ b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java @@ -49,7 +49,6 @@ public BreastPresetListEntry(String name, BreastPresetConfiguration data) { public WildfireBreastPresetList(WildfireBreastCustomizationScreen parent, int listWidth, int top) { super(MinecraftClient.getInstance(), 156, parent.height, top, 32); - this.setRenderHeader(false, 0); this.parent = parent; this.listWidth = listWidth; this.refreshList(); @@ -83,7 +82,7 @@ protected void renderList(DrawContext context, int mouseX, int mouseY, float del @Override public int getRowTop(int index) { - return this.getY() - (int)this.getScrollAmount() + index * this.itemHeight + this.headerHeight; + return this.getY() - (int)this.getScrollY() + index * this.itemHeight + this.headerHeight; } @Override @@ -106,7 +105,7 @@ public void refreshList() { System.out.println("Preset Name: " + presetCfg.get(BreastPresetConfiguration.PRESET_NAME)); tmpPresets.add(new BreastPresetListEntry(presetCfg.get(BreastPresetConfiguration.PRESET_NAME), presetCfg)); } - BREAST_PRESETS = tmpPresets.toArray(new BreastPresetListEntry[tmpPresets.size()]); + BREAST_PRESETS = tmpPresets.toArray(BreastPresetListEntry[]::new); if(this.client.world == null || this.client.player == null) return; diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index c3d420f5..2944eff2 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -168,9 +168,9 @@ private static void registerRenderLayers(EntityType enti EntityRendererFactory.Context context) { if(entityRenderer instanceof PlayerEntityRenderer playerRenderer) { registrationHelper.register(new GenderLayer<>(playerRenderer)); - registrationHelper.register(new GenderArmorLayer<>(playerRenderer, context.getModelManager(), context.getEquipmentModelLoader())); + registrationHelper.register(new GenderArmorLayer<>(playerRenderer, context.getEquipmentModelLoader(), context.getEquipmentRenderer())); } else if(entityRenderer instanceof ArmorStandEntityRenderer armorStandRenderer) { - registrationHelper.register(new GenderArmorLayer<>(armorStandRenderer, context.getModelManager(), context.getEquipmentModelLoader())); + registrationHelper.register(new GenderArmorLayer<>(armorStandRenderer, context.getEquipmentModelLoader(), context.getEquipmentRenderer())); } } diff --git a/src/main/java/com/wildfire/main/entitydata/Breasts.java b/src/main/java/com/wildfire/main/entitydata/Breasts.java index a50b25ff..38ddf8f8 100644 --- a/src/main/java/com/wildfire/main/entitydata/Breasts.java +++ b/src/main/java/com/wildfire/main/entitydata/Breasts.java @@ -37,7 +37,7 @@ public final class Breasts { PacketCodecs.FLOAT, Breasts::getXOffset, PacketCodecs.FLOAT, Breasts::getYOffset, PacketCodecs.FLOAT, Breasts::getZOffset, - PacketCodecs.BOOL, Breasts::isUniboob, + PacketCodecs.BOOLEAN, Breasts::isUniboob, PacketCodecs.FLOAT, Breasts::getCleavage, (x, y, z, uniboob, cleavage) -> { Breasts breasts = new Breasts(); diff --git a/src/main/java/com/wildfire/main/networking/AbstractSyncPacket.java b/src/main/java/com/wildfire/main/networking/AbstractSyncPacket.java index d0287e3f..59246f13 100644 --- a/src/main/java/com/wildfire/main/networking/AbstractSyncPacket.java +++ b/src/main/java/com/wildfire/main/networking/AbstractSyncPacket.java @@ -18,7 +18,6 @@ package com.wildfire.main.networking; -import com.mojang.datafixers.util.Function6; import com.mojang.datafixers.util.Function7; import com.wildfire.main.entitydata.Breasts; import com.wildfire.main.entitydata.PlayerConfig; @@ -37,7 +36,7 @@ protected static PacketCodec codec(Sy Uuids.PACKET_CODEC, p -> p.uuid, Gender.CODEC, p -> p.gender, PacketCodecs.FLOAT, p -> p.bustSize, - PacketCodecs.BOOL, p -> p.hurtSounds, + PacketCodecs.BOOLEAN, p -> p.hurtSounds, PacketCodecs.FLOAT, p -> p.voicePitch, BreastPhysics.CODEC, p -> p.physics, Breasts.CODEC, p -> p.breasts, @@ -79,8 +78,8 @@ protected void updatePlayerFromPacket(PlayerConfig plr) { protected record BreastPhysics(boolean physics, boolean showInArmor, float bounceMultiplier, float floppyMultiplier) { public static final PacketCodec CODEC = PacketCodec.tuple( - PacketCodecs.BOOL, BreastPhysics::physics, - PacketCodecs.BOOL, BreastPhysics::showInArmor, + PacketCodecs.BOOLEAN, BreastPhysics::physics, + PacketCodecs.BOOLEAN, BreastPhysics::showInArmor, PacketCodecs.FLOAT, BreastPhysics::bounceMultiplier, PacketCodecs.FLOAT, BreastPhysics::floppyMultiplier, BreastPhysics::new diff --git a/src/main/java/com/wildfire/mixins/accessors/EquipmentRendererAccessor.java b/src/main/java/com/wildfire/mixins/accessors/EquipmentRendererAccessor.java new file mode 100644 index 00000000..206016fc --- /dev/null +++ b/src/main/java/com/wildfire/mixins/accessors/EquipmentRendererAccessor.java @@ -0,0 +1,35 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.mixins.accessors; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.render.entity.equipment.EquipmentRenderer; +import net.minecraft.client.texture.Sprite; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.function.Function; + +@Mixin(EquipmentRenderer.class) +@Environment(EnvType.CLIENT) +public interface EquipmentRendererAccessor { + @Accessor + Function getTrimSprites(); +} diff --git a/src/main/java/com/wildfire/mixins/accessors/TextureManagerAccessor.java b/src/main/java/com/wildfire/mixins/accessors/TextureManagerAccessor.java new file mode 100644 index 00000000..a3117285 --- /dev/null +++ b/src/main/java/com/wildfire/mixins/accessors/TextureManagerAccessor.java @@ -0,0 +1,30 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.mixins.accessors; + +import net.minecraft.client.texture.TextureManager; +import net.minecraft.resource.ResourceManager; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(TextureManager.class) +public interface TextureManagerAccessor { + @Accessor + ResourceManager getResourceContainer(); +} diff --git a/src/main/java/com/wildfire/mixins/accessors/TrimSpriteKeyConstructorAccessor.java b/src/main/java/com/wildfire/mixins/accessors/TrimSpriteKeyConstructorAccessor.java new file mode 100644 index 00000000..5e232c6d --- /dev/null +++ b/src/main/java/com/wildfire/mixins/accessors/TrimSpriteKeyConstructorAccessor.java @@ -0,0 +1,41 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.mixins.accessors; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.render.entity.equipment.EquipmentModel; +import net.minecraft.client.render.entity.equipment.EquipmentRenderer; +import net.minecraft.item.equipment.EquipmentAsset; +import net.minecraft.item.equipment.trim.ArmorTrim; +import net.minecraft.registry.RegistryKey; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(EquipmentRenderer.TrimSpriteKey.class) +@Environment(EnvType.CLIENT) +public interface TrimSpriteKeyConstructorAccessor { + // Yes, it would've been possible to simply access widener the constructor along with the record itself, but I + // am absolutely not willing to maintain such an access widener entry when I could alternatively use Mixin, which + // at least will fail in a way that's significantly less of a headache to update. + @Invoker("") + static EquipmentRenderer.TrimSpriteKey newKey(ArmorTrim armorTrim, EquipmentModel.LayerType layerType, RegistryKey registryKey) { + throw new UnsupportedOperationException("Something's gone very seriously wrong if we've gotten here!"); + } +} diff --git a/src/main/java/com/wildfire/render/GenderArmorLayer.java b/src/main/java/com/wildfire/render/GenderArmorLayer.java index ede23925..07a47605 100644 --- a/src/main/java/com/wildfire/render/GenderArmorLayer.java +++ b/src/main/java/com/wildfire/render/GenderArmorLayer.java @@ -22,22 +22,24 @@ import com.wildfire.api.impl.BreastArmorTexture; import com.wildfire.main.WildfireGender; import com.wildfire.main.entitydata.EntityConfig; +import com.wildfire.mixins.accessors.EquipmentRendererAccessor; +import com.wildfire.mixins.accessors.TextureManagerAccessor; +import com.wildfire.mixins.accessors.TrimSpriteKeyConstructorAccessor; import com.wildfire.render.WildfireModelRenderer.BreastModelBox; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.*; +import net.minecraft.client.render.entity.equipment.EquipmentModel; import net.minecraft.client.render.entity.equipment.EquipmentModelLoader; +import net.minecraft.client.render.entity.equipment.EquipmentRenderer; import net.minecraft.client.render.entity.feature.FeatureRendererContext; import net.minecraft.client.render.entity.model.BipedEntityModel; import net.minecraft.client.render.entity.state.ArmorStandEntityRenderState; import net.minecraft.client.render.entity.state.BipedEntityRenderState; import net.minecraft.client.render.entity.state.PlayerEntityRenderState; import net.minecraft.client.render.item.ItemRenderer; -import net.minecraft.client.render.model.BakedModelManager; -import net.minecraft.client.texture.MissingSprite; import net.minecraft.client.texture.Sprite; -import net.minecraft.client.texture.SpriteAtlasTexture; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.component.DataComponentTypes; import net.minecraft.component.type.DyedColorComponent; @@ -45,34 +47,43 @@ import net.minecraft.entity.LivingEntity; import net.minecraft.entity.decoration.ArmorStandEntity; import net.minecraft.item.ItemStack; -import net.minecraft.item.equipment.EquipmentModel; +import net.minecraft.item.equipment.EquipmentAsset; import net.minecraft.item.equipment.trim.ArmorTrim; +import net.minecraft.registry.RegistryKey; import net.minecraft.registry.tag.ItemTags; import net.minecraft.util.Identifier; +import net.minecraft.util.Util; import net.minecraft.util.math.ColorHelper; import org.jetbrains.annotations.NotNull; import java.util.Objects; +import java.util.function.Function; @Environment(EnvType.CLIENT) public class GenderArmorLayer> extends GenderLayer { - private final SpriteAtlasTexture armorTrimsAtlas; + private final EquipmentRenderer equipmentRenderer; private final EquipmentModelLoader equipmentModelLoader; protected BreastModelBox lBoobArmor, rBoobArmor; protected static final BreastModelBox lTrim, rTrim; private EntityConfig entityConfig; private @NotNull IBreastArmorTexture textureData = BreastArmorTexture.DEFAULT; + private static final Function TEXTURE_EXISTS = Util.memoize(id -> { + var texManager = MinecraftClient.getInstance().getTextureManager(); + var resourceManager = ((TextureManagerAccessor) texManager).getResourceContainer(); + return resourceManager.getResource(id).isPresent(); + }); + static { // apply a very slight delta to fix z-fighting with the armor lTrim = new BreastModelBox(64, 32, 16, 17, -4F, 0.0F, 0F, 4, 5, 4, 0.001F, false); rTrim = new BreastModelBox(64, 32, 20, 17, 0, 0.0F, 0F, 4, 5, 4, 0.001F, false); } - public GenderArmorLayer(FeatureRendererContext render, BakedModelManager bakery, EquipmentModelLoader equipmentModelLoader) { + public GenderArmorLayer(FeatureRendererContext render, EquipmentModelLoader equipmentModelLoader, EquipmentRenderer equipmentRenderer) { super(render); - this.armorTrimsAtlas = bakery.getAtlas(TexturedRenderLayers.ARMOR_TRIMS_ATLAS_TEXTURE); + this.equipmentRenderer = equipmentRenderer; this.equipmentModelLoader = equipmentModelLoader; lBoobArmor = new BreastModelBox(64, 32, 16, 17, -4F, 0.0F, 0F, 4, 5, 3, 0.0F, false); rBoobArmor = new BreastModelBox(64, 32, 20, 17, 0, 0.0F, 0F, 4, 5, 3, 0.0F, false); @@ -92,7 +103,7 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume final ItemStack chestplate = state.equippedChestStack; // Check if the worn item in the chest slot is actually equippable in the chest slot, and has a model to render var component = chestplate.get(DataComponentTypes.EQUIPPABLE); - if(component == null || component.slot() != EquipmentSlot.CHEST || component.model().isEmpty()) return; + if(component == null || component.slot() != EquipmentSlot.CHEST || component.assetId().isEmpty()) return; try { entityConfig = EntityConfig.getEntity(ent); @@ -104,9 +115,9 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume boolean glint = chestplate.hasGlint(); renderSides(state, getContextModel(), matrixStack, side -> { - var modelId = component.model().orElseThrow(); + var asset = component.assetId().orElseThrow(); // TODO is there still a need to allow for overriding the armor texture identifier? - equipmentModelLoader.get(modelId).getLayers(EquipmentModel.LayerType.HUMANOID).forEach(layer -> { + equipmentModelLoader.get(asset).getLayers(EquipmentModel.LayerType.HUMANOID).forEach(layer -> { // mojang what the Optional hell is this int layerColor = layer.dyeable().map(dye -> { int defaultColor = dye.colorWhenUndyed().map(ColorHelper::fullAlpha).orElse(-1); @@ -118,7 +129,7 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume var trim = armorStack.get(DataComponentTypes.TRIM); if(trim != null) { - renderArmorTrim(modelId, matrixStack, vertexConsumerProvider, light, trim, glint, side); + renderArmorTrim(asset, matrixStack, vertexConsumerProvider, light, trim, glint, side); } }); } catch(Exception e) { @@ -161,7 +172,7 @@ protected void setupTransformations(S state, M model, MatrixStack matrixStack, B // TODO eventually expose some way for mods to override this, maybe through a default impl in IGenderArmor or similar protected void renderBreastArmor(Identifier texture, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light, BreastSide side, int color, boolean glint) { - if(MinecraftClient.getInstance().getTextureManager().getTexture(texture) == MissingSprite.getMissingSpriteTexture()) { + if(!TEXTURE_EXISTS.apply(texture)) { return; } @@ -171,21 +182,27 @@ protected void renderBreastArmor(Identifier texture, MatrixStack matrixStack, Ve renderBox(armor, matrixStack, armorVertexConsumer, light, OverlayTexture.DEFAULT_UV, ColorHelper.fullAlpha(color)); } - protected void renderArmorTrim(Identifier model, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, - int packedLightIn, ArmorTrim trim, boolean hasGlint, BreastSide side) { + protected void renderArmorTrim(RegistryKey armorModel, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, + int light, ArmorTrim trim, boolean hasGlint, BreastSide side) { BreastModelBox trimModelBox = side.isLeft ? lTrim : rTrim; - Sprite sprite = this.armorTrimsAtlas.getSprite(trim.getTexture(EquipmentModel.LayerType.HUMANOID, model)); - VertexConsumer vertexConsumer = sprite.getTextureSpecificVertexConsumer( - vertexConsumerProvider.getBuffer(TexturedRenderLayers.getArmorTrims(trim.pattern().value().decal()))); + + // this sucks, but it sucks less than simply copy/pasting the entire relevant block of code, and is + // (at least theoretically) more compatible with other mods, assuming they simply mixin to TrimSpriteKey + // to modify the armor trim sprite location. + var key = TrimSpriteKeyConstructorAccessor.newKey(trim, EquipmentModel.LayerType.HUMANOID, armorModel); + Sprite sprite = ((EquipmentRendererAccessor)equipmentRenderer).getTrimSprites().apply(key); + + var buffer = vertexConsumerProvider.getBuffer(TexturedRenderLayers.getArmorTrims(trim.pattern().value().decal())); + var vertexConsumer = sprite.getTextureSpecificVertexConsumer(buffer); // Render the armor trim itself - renderBox(trimModelBox, matrixStack, vertexConsumer, packedLightIn, OverlayTexture.DEFAULT_UV, -1); + renderBox(trimModelBox, matrixStack, vertexConsumer, light, OverlayTexture.DEFAULT_UV, -1); // The enchantment glint however requires special handling; due to how Minecraft's enchant glint rendering works, rendering // it at the same time as the trim itself results in the glint not rendering in sync with the rest of the armor. // We *also* can't simply render the glint for both the trim and armor at the same time, due to the slight delta we apply // to fix z-fighting between the trim and armor - and as such - a glint has to be rendered for each respective layer. if(hasGlint) { - renderBox(trimModelBox, matrixStack, vertexConsumerProvider.getBuffer(RenderLayer.getArmorEntityGlint()), - packedLightIn, OverlayTexture.DEFAULT_UV, -1); + var glintBuffer = vertexConsumerProvider.getBuffer(RenderLayer.getArmorEntityGlint()); + renderBox(trimModelBox, matrixStack, glintBuffer, light, OverlayTexture.DEFAULT_UV, -1); } } } diff --git a/src/main/java/com/wildfire/resources/GenderArmorResourceManager.java b/src/main/java/com/wildfire/resources/GenderArmorResourceManager.java index bdf09ffb..26a100a1 100644 --- a/src/main/java/com/wildfire/resources/GenderArmorResourceManager.java +++ b/src/main/java/com/wildfire/resources/GenderArmorResourceManager.java @@ -26,7 +26,9 @@ import net.minecraft.component.DataComponentTypes; import net.minecraft.component.type.EquippableComponent; import net.minecraft.item.ItemStack; +import net.minecraft.registry.RegistryKey; import net.minecraft.resource.JsonDataLoader; +import net.minecraft.resource.ResourceFinder; import net.minecraft.resource.ResourceManager; import net.minecraft.util.Identifier; import net.minecraft.util.profiler.Profiler; @@ -40,7 +42,7 @@ @Environment(EnvType.CLIENT) public final class GenderArmorResourceManager extends JsonDataLoader implements IdentifiableResourceReloadListener { private GenderArmorResourceManager() { - super(IGenderArmor.CODEC, "wildfire_gender_data"); + super(IGenderArmor.CODEC, ResourceFinder.json("wildfire_gender_data")); } public static final GenderArmorResourceManager INSTANCE = new GenderArmorResourceManager(); @@ -52,7 +54,8 @@ private GenderArmorResourceManager() { public static Optional get(ItemStack item) { return Optional.ofNullable(item.get(DataComponentTypes.EQUIPPABLE)) - .flatMap(EquippableComponent::model) + .flatMap(EquippableComponent::assetId) + .map(RegistryKey::getValue) .map(GenderArmorResourceManager::get); } diff --git a/src/main/resources/assets/wildfire_gender/CREDITS.txt b/src/main/resources/assets/wildfire_gender/CREDITS.txt index 7b3801f3..778f4963 100644 --- a/src/main/resources/assets/wildfire_gender/CREDITS.txt +++ b/src/main/resources/assets/wildfire_gender/CREDITS.txt @@ -1,2 +1 @@ textures/cloud.png is sourced from Font Awesome Free 6.6.0 (https://fontawesome.com/), which is licensed under the CC-BY-4.0 license (https://fontawesome.com/license/free). - diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 2d70713f..a2d4b27b 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -48,7 +48,7 @@ "fabric-rendering-v1": "*", "fabric-resource-loader-v0": "*", "fabric-registry-sync-v0": "*", - "minecraft": ">=1.21.2", + "minecraft": ">=1.21.2-alpha.24.46.a <1.22", "java": ">=21" }, "conflicts": { diff --git a/src/main/resources/wildfire_gender.accesswidener b/src/main/resources/wildfire_gender.accesswidener index b69a6683..7aee5247 100644 --- a/src/main/resources/wildfire_gender.accesswidener +++ b/src/main/resources/wildfire_gender.accesswidener @@ -1,3 +1,4 @@ accessWidener v2 named accessible method net/minecraft/entity/LivingEntity getHandSwingDuration ()I +accessible class net/minecraft/client/render/entity/equipment/EquipmentRenderer$TrimSpriteKey diff --git a/src/main/resources/wildfire_gender.mixins.json b/src/main/resources/wildfire_gender.mixins.json index 0c5d63f5..a1e24d18 100644 --- a/src/main/resources/wildfire_gender.mixins.json +++ b/src/main/resources/wildfire_gender.mixins.json @@ -9,6 +9,9 @@ "client": [ "BreastPhysicsTickMixin", "LivingEntityMixin", + "accessors.EquipmentRendererAccessor", + "accessors.TextureManagerAccessor", + "accessors.TrimSpriteKeyConstructorAccessor", "renderstate.LivingEntityRendererMixin", "renderstate.LivingEntityRenderStateMixin" ], From 31a2c8e3ad26535ef20d412529d22dab407dcfa8 Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 28 Nov 2024 14:42:53 -0700 Subject: [PATCH 214/238] make the client cloud sync cache less aggressive --- src/main/java/com/wildfire/main/cloud/CloudSync.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index cf8ebcdc..27958b6b 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -88,7 +88,7 @@ private CloudSync() { private static final Queue QUEUED = new ConcurrentLinkedDeque<>(); private static final Cache> FETCH_CACHE = CacheBuilder.newBuilder() - .expireAfterAccess(Duration.ofMinutes(10)).concurrencyLevel(6).build(); + .expireAfterWrite(Duration.ofMinutes(10)).concurrencyLevel(6).build(); private static final String DEFAULT_CLOUD_URL = "https://wfgm.celestialfault.dev"; private static final Duration SYNC_COOLDOWN = Duration.ofSeconds(10); From 17a54d06e86ea6576ea6e1b9953519341b66597f Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 28 Nov 2024 14:45:11 -0700 Subject: [PATCH 215/238] add some missing license headers --- .../com/wildfire/main/cloud/BulkFetch.java | 18 ++++++++++++++++++ .../com/wildfire/main/cloud/CloudAuth.java | 18 ++++++++++++++++++ .../com/wildfire/main/cloud/QueuedFetch.java | 18 ++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/src/main/java/com/wildfire/main/cloud/BulkFetch.java b/src/main/java/com/wildfire/main/cloud/BulkFetch.java index b7439892..49c0fef8 100644 --- a/src/main/java/com/wildfire/main/cloud/BulkFetch.java +++ b/src/main/java/com/wildfire/main/cloud/BulkFetch.java @@ -1,3 +1,21 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + package com.wildfire.main.cloud; import com.google.gson.JsonObject; diff --git a/src/main/java/com/wildfire/main/cloud/CloudAuth.java b/src/main/java/com/wildfire/main/cloud/CloudAuth.java index 9f448287..5a3b6fa0 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudAuth.java +++ b/src/main/java/com/wildfire/main/cloud/CloudAuth.java @@ -1,3 +1,21 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + package com.wildfire.main.cloud; import net.minecraft.client.MinecraftClient; diff --git a/src/main/java/com/wildfire/main/cloud/QueuedFetch.java b/src/main/java/com/wildfire/main/cloud/QueuedFetch.java index 7cb05964..6540eb2c 100644 --- a/src/main/java/com/wildfire/main/cloud/QueuedFetch.java +++ b/src/main/java/com/wildfire/main/cloud/QueuedFetch.java @@ -1,3 +1,21 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + package com.wildfire.main.cloud; import com.google.gson.JsonObject; From a9c2e95a9eb49b3fd3ff4d32b7ef3e2fbd108058 Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 28 Nov 2024 15:13:50 -0700 Subject: [PATCH 216/238] add 204 as a valid sync query response the server doesn't actually use this currently, but might in the future to allow for proper server-side caching again (ideally without a minimum cache time of 2 hours this time) --- src/main/java/com/wildfire/main/cloud/CloudSync.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index 27958b6b..e36ea7ad 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -287,7 +287,7 @@ private static CompletableFuture syncInternal(PlayerConfig config, boolean var url = URI.create(getCloudServer() + "/" + uuid); var request = createRequest(url).GET().build(); var response = CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join(); - if(response.statusCode() == 404) { + if(response.statusCode() == 404 || response.statusCode() == 204) { WildfireGender.LOGGER.debug("Server replied no data for {}", uuid); FETCH_CACHE.put(uuid, Optional.empty()); return null; From dd468f709769b9862a461ed227060745b28e24b1 Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 29 Nov 2024 13:07:09 -0700 Subject: [PATCH 217/238] cloud sync cleanup - commented out the caching help tooltip button, as the sync server no longer has the aggressive caching the tooltip suggests it does - mark cloud sync as unavailable on offline mode servers - moved the cloud syncing code from the client tick handler to a new method in PlayerConfig - added javadocs to PlayerConfig methods that made sense to have one --- .../gui/screen/WardrobeBrowserScreen.java | 9 ++- .../gui/screen/WildfireCloudSyncScreen.java | 32 ++++----- .../wildfire/main/WildfireEventHandler.java | 22 +------ .../com/wildfire/main/cloud/CloudSync.java | 19 +++++- .../wildfire/main/cloud/SyncUnavailable.java | 31 +++++++++ .../main/entitydata/PlayerConfig.java | 66 ++++++++++++++++++- .../assets/wildfire_gender/lang/de_de.json | 6 +- .../assets/wildfire_gender/lang/en_pt.json | 6 +- .../assets/wildfire_gender/lang/en_us.json | 7 +- .../assets/wildfire_gender/lang/es_es.json | 6 +- .../assets/wildfire_gender/lang/es_mx.json | 6 +- .../assets/wildfire_gender/lang/nl_nl.json | 4 +- 12 files changed, 146 insertions(+), 68 deletions(-) create mode 100644 src/main/java/com/wildfire/main/cloud/SyncUnavailable.java diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index 597a9d0a..f45a5fd2 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -107,7 +107,6 @@ public void init() { /*this.addDrawableChild(new WildfireButton(this.width / 2 - 42, y - (plr.getGender().canHaveBreasts() ? 12 : 32), 158, 20, Text.translatable("wildfire_gender.char_settings.title").append("..."), button -> client.setScreen(new WildfireCharacterSettingsScreen(WardrobeBrowserScreen.this, this.playerUUID))));*/ - //noinspection ExtractMethodRecommender var cloud = new WildfireButton( this.width / 2 - 36, y + 30, 24, 18, Text.translatable("wildfire_gender.cloud_settings"), button -> client.setScreen(new WildfireCloudSyncScreen(this, this.playerUUID)) @@ -118,13 +117,13 @@ protected void drawInner(DrawContext ctx, int mouseX, int mouseY, float partialT } }; - if(!CloudSync.isAvailable()) { - cloud.setTooltip(Tooltip.of(Text.translatable("wildfire_gender.cloud.unavailable_offline"))); + var cloudUnavailable = CloudSync.unavailableReason(); + if(cloudUnavailable != null) { + cloud.setTooltip(Tooltip.of(cloudUnavailable.text())); cloud.setActive(false); } else { - cloud.setTooltip(Tooltip.of(Text.translatable("wildfire_gender.cloud.available_online"))); + cloud.setTooltip(Tooltip.of(Text.translatable("wildfire_gender.cloud.tooltip"))); } - this.addDrawableChild(cloud); /*this.addDrawableChild(new WildfireButton(this.width / 2 + 111, y - 63, 9, 9, Text.literal("X"), diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java index 23397e8a..a271b909 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java @@ -45,10 +45,6 @@ public class WildfireCloudSyncScreen extends BaseWildfireScreen { private static final Identifier BACKGROUND = Identifier.of(WildfireGender.MODID, "textures/gui/sync_bg_v2.png"); - private WildfireButton btnAutomaticSync = null; - private WildfireButton btnSyncNow = null; - private WildfireButton btnHelp = null; - protected WildfireCloudSyncScreen(Screen parent, UUID uuid) { super(Text.translatable("wildfire_gender.cloud_settings"), parent, uuid); } @@ -60,6 +56,9 @@ public void init() { int yPos = y - 47; int xPos = x - 156 / 2 - 1; + final var ref = new Object() { + WildfireButton btnSyncNow, btnAutomaticSync; + }; this.addDrawableChild(new WildfireButton(xPos, yPos, 157, 20, Text.translatable("wildfire_gender.cloud.status", CloudSync.isEnabled() ? WildfireLocalization.ENABLED : WildfireLocalization.DISABLED), @@ -67,12 +66,12 @@ public void init() { var config = GlobalConfig.INSTANCE; config.set(GlobalConfig.CLOUD_SYNC_ENABLED, !config.get(GlobalConfig.CLOUD_SYNC_ENABLED)); button.setMessage(Text.translatable("wildfire_gender.cloud.status", CloudSync.isEnabled() ? WildfireLocalization.ENABLED : WildfireLocalization.DISABLED)); - btnAutomaticSync.setActive(CloudSync.isEnabled()); - btnAutomaticSync.setMessage(Text.translatable("wildfire_gender.cloud.automatic", CloudSync.isEnabled() ? (GlobalConfig.INSTANCE.get(GlobalConfig.AUTOMATIC_CLOUD_SYNC) ? WildfireLocalization.ENABLED : WildfireLocalization.DISABLED) : WildfireLocalization.OFF)); - btnSyncNow.visible = GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SYNC_ENABLED); + ref.btnAutomaticSync.setActive(CloudSync.isEnabled()); + ref.btnAutomaticSync.setMessage(Text.translatable("wildfire_gender.cloud.automatic", CloudSync.isEnabled() ? (GlobalConfig.INSTANCE.get(GlobalConfig.AUTOMATIC_CLOUD_SYNC) ? WildfireLocalization.ENABLED : WildfireLocalization.DISABLED) : WildfireLocalization.OFF)); + ref.btnSyncNow.visible = GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SYNC_ENABLED); })); - this.addDrawableChild(btnAutomaticSync = new WildfireButton(xPos, yPos + 20, 157, 20, + this.addDrawableChild(ref.btnAutomaticSync = new WildfireButton(xPos, yPos + 20, 157, 20, Text.translatable("wildfire_gender.cloud.automatic", CloudSync.isEnabled() ? (GlobalConfig.INSTANCE.get(GlobalConfig.AUTOMATIC_CLOUD_SYNC) ? WildfireLocalization.ENABLED : WildfireLocalization.DISABLED) : WildfireLocalization.OFF), button -> { var config = GlobalConfig.INSTANCE; @@ -80,29 +79,26 @@ public void init() { config.set(GlobalConfig.AUTOMATIC_CLOUD_SYNC, newVal); button.setMessage(Text.translatable("wildfire_gender.cloud.automatic", newVal ? WildfireLocalization.ENABLED : WildfireLocalization.DISABLED)); })); - btnAutomaticSync.setTooltip(Tooltip.of(Text.empty() + ref.btnAutomaticSync.setTooltip(Tooltip.of(Text.empty() .append(Text.translatable("wildfire_gender.cloud.automatic.tooltip.line1")) .append("\n\n") .append(Text.translatable("wildfire_gender.cloud.automatic.tooltip.line2")))); - btnAutomaticSync.setActive(CloudSync.isEnabled()); + ref.btnAutomaticSync.setActive(CloudSync.isEnabled()); - btnSyncNow = new WildfireButton(xPos + 98, yPos + 42, 60, 15, Text.translatable("wildfire_gender.cloud.sync"), this::sync); + ref.btnSyncNow = new WildfireButton(xPos + 98, yPos + 42, 60, 15, Text.translatable("wildfire_gender.cloud.sync"), this::sync); //btnSyncNow.setTooltip(Tooltip.of(Text.empty() // .append(Text.literal("Sync Server data is cached for a minimum time of 30 minutes. If you do not see any changes please try to re-sync later.")))); - btnSyncNow.visible = GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SYNC_ENABLED); - this.addDrawableChild(btnSyncNow); + ref.btnSyncNow.visible = GlobalConfig.INSTANCE.get(GlobalConfig.CLOUD_SYNC_ENABLED); + this.addDrawableChild(ref.btnSyncNow); this.addDrawableChild(new WildfireButton(this.width / 2 + 73, yPos - 11, 9, 9, Text.literal("X"), button -> close(), text -> GuiUtils.doneNarrationText())); - this.addDrawableChild(btnHelp = new WildfireButton(this.width / 2 + 73 - 10, yPos - 11, 9, 9, Text.literal("?"), + /*this.addDrawableChild(btnHelp = new WildfireButton(this.width / 2 + 73 - 10, yPos - 11, 9, 9, Text.literal("?"), button -> { //client.setScreen(new WildfireCloudDetailsScreen(this, client.player.getUuid())); // Disabled for now. Not complete // BUTTON IS SUPPOSED TO DO NOTHING AT THE MOMENT - }, text -> GuiUtils.doneNarrationText())); - btnHelp.setTooltip(Tooltip.of(Text.translatable("wildfire_gender.cloud.disclaimer.line1") - .append("\n\n") - .append(Text.translatable("wildfire_gender.cloud.disclaimer.line2")))); + }));*/ super.init(); } diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index 2944eff2..7d681aec 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -19,11 +19,9 @@ package com.wildfire.main; import com.wildfire.gui.GuiUtils; -import com.wildfire.gui.screen.BaseWildfireScreen; import com.wildfire.gui.screen.WardrobeBrowserScreen; import com.wildfire.gui.screen.WildfireFirstTimeSetupScreen; import com.wildfire.main.cloud.CloudSync; -import com.wildfire.main.cloud.SyncLog; import com.wildfire.main.config.GlobalConfig; import com.wildfire.main.entitydata.EntityConfig; import com.wildfire.main.entitydata.PlayerConfig; @@ -35,7 +33,6 @@ import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; -import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback; @@ -63,7 +60,6 @@ import net.minecraft.item.ItemStack; import net.minecraft.item.Items; import net.minecraft.item.tooltip.TooltipType; -import net.minecraft.nbt.NbtCompound; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayerEntity; @@ -76,7 +72,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.concurrent.CompletableFuture; public final class WildfireEventHandler { private WildfireEventHandler() { @@ -142,6 +137,7 @@ private static void renderTooltip(ItemStack stack, Item.TooltipContext tooltipCo .formatted(Formatting.AQUA)); } } + @Environment(EnvType.CLIENT) private static void renderHud(DrawContext context, RenderTickCounter tickCounter) { var textRenderer = Objects.requireNonNull(MinecraftClient.getInstance().textRenderer, "textRenderer"); @@ -204,21 +200,7 @@ private static void onClientTick(MinecraftClient client) { if(timer % 40 == 0) { CloudSync.sendNextQueueBatch(); - if(clientConfig != null && clientConfig.needsCloudSync && !(client.currentScreen instanceof BaseWildfireScreen)) { - if(GlobalConfig.INSTANCE.get(GlobalConfig.AUTOMATIC_CLOUD_SYNC) && !CloudSync.syncOnCooldown()) { - CompletableFuture.runAsync(() -> { - try { - CloudSync.sync(clientConfig).join(); - WildfireGender.LOGGER.info("Synced player data to the cloud"); - SyncLog.add(WildfireLocalization.SYNC_LOG_SYNC_TO_CLOUD); - } catch(Exception e) { - WildfireGender.LOGGER.error("Failed to sync player data", e); - SyncLog.add(WildfireLocalization.SYNC_LOG_FAILED_TO_SYNC_DATA); - } - }); - clientConfig.needsCloudSync = false; - } - } + if(clientConfig != null) clientConfig.attemptCloudSync(); } diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index e36ea7ad..14274172 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -110,10 +110,25 @@ public static boolean syncOnCooldown() { } /** - * @return {@code true} if syncing is available; currently, this only checks for a valid Minecraft session. + * @return A {@link SyncUnavailable} enum indicating the reason for syncing being unavailable, or {@code null} if available + */ + public static @Nullable SyncUnavailable unavailableReason() { + if(MinecraftClient.getInstance().getSession().getAccountType() != Session.AccountType.MSA) { + return SyncUnavailable.INVALID_ACCOUNT; + } + var client = MinecraftClient.getInstance(); + var netHandler = client.getNetworkHandler(); + if(!client.isInSingleplayer() && netHandler != null && !netHandler.getConnection().isEncrypted()) { + return SyncUnavailable.OFFLINE_SERVER; + } + return null; + } + + /** + * @return {@code true} if syncing is available; this method is shorthand for {@code unavailableReason() == null}. */ public static boolean isAvailable() { - return MinecraftClient.getInstance().getSession().getAccountType() == Session.AccountType.MSA; + return unavailableReason() == null; } /** diff --git a/src/main/java/com/wildfire/main/cloud/SyncUnavailable.java b/src/main/java/com/wildfire/main/cloud/SyncUnavailable.java new file mode 100644 index 00000000..c9539702 --- /dev/null +++ b/src/main/java/com/wildfire/main/cloud/SyncUnavailable.java @@ -0,0 +1,31 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.main.cloud; + +import net.minecraft.text.Text; + +public enum SyncUnavailable { + INVALID_ACCOUNT, + OFFLINE_SERVER, + ; + + public Text text() { + return Text.translatable("wildfire_gender.cloud.unavailable." + name().toLowerCase()); + } +} diff --git a/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java b/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java index 48a33c21..ea0e2001 100644 --- a/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java +++ b/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java @@ -19,14 +19,22 @@ package com.wildfire.main.entitydata; import com.google.gson.JsonObject; +import com.wildfire.gui.screen.BaseWildfireScreen; import com.wildfire.main.WildfireGender; +import com.wildfire.main.WildfireLocalization; +import com.wildfire.main.cloud.CloudSync; +import com.wildfire.main.cloud.SyncLog; import com.wildfire.main.config.ConfigKey; import com.wildfire.main.config.Configuration; import com.wildfire.main.Gender; +import com.wildfire.main.config.GlobalConfig; +import net.minecraft.client.MinecraftClient; import net.minecraft.item.ItemStack; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; /** @@ -150,20 +158,40 @@ public static JsonObject toJsonObject(PlayerConfig plr) { return plr.toJson(); } + /** + * Returns a copy of the player's current configuration. Note that there are no guarantees of any values being valid + * (either type or number ranges), as this taken directly from the loaded JSON file, which may have been modified + * by the user. + * + * @return A new copy of the player's {@link JsonObject saved config values} + */ public JsonObject toJson() { return cfg.SAVE_VALUES.deepCopy(); } + /** + * @return {@code true} if the current player {@link Configuration#exists() has a local config file} + */ public boolean hasLocalConfig() { return cfg.exists(); } + /** + * Loads the current player's settings from a file on disk + * + * @param markForSync {@code true} if {@link #needsSync} should be set to true + */ public void loadFromDisk(boolean markForSync) { this.syncStatus = SyncStatus.CACHED; cfg.load(); loadFromConfig(markForSync); } + /** + * Loads the current player's settings from the local {@link Configuration} + * + * @param markForSync {@code true} if {@link #needsSync} should be set to true + */ public void loadFromConfig(boolean markForSync) { updateGender(cfg.get(Configuration.GENDER)); updateBustSize(cfg.get(Configuration.BUST_SIZE)); @@ -200,6 +228,12 @@ public static PlayerConfig loadCachedPlayer(UUID uuid, boolean markForSync) { return plr; } + /** + * Save the settings stored in the provided {@link PlayerConfig} to the underlying {@link Configuration}, + * and then {@link Configuration#save() attempt to save it to disk}. + * + * @param plr The {@link PlayerConfig} to save + */ public static void saveGenderInfo(PlayerConfig plr) { Configuration config = plr.getConfig(); config.set(Configuration.USERNAME, plr.uuid); @@ -231,7 +265,37 @@ public boolean hasJacketLayer() { throw new UnsupportedOperationException("PlayerConfig does not support #hasJacketLayer(); use PlayerEntity#isPartVisible instead"); } - public void updateFromJson(JsonObject json) { + @ApiStatus.Internal + public void attemptCloudSync() { + var client = MinecraftClient.getInstance(); + if(client.player == null || !this.uuid.equals(client.player.getUuid())) return; + if(!needsCloudSync) return; + if(client.currentScreen instanceof BaseWildfireScreen) return; + if(!GlobalConfig.INSTANCE.get(GlobalConfig.AUTOMATIC_CLOUD_SYNC)) return; + if(CloudSync.syncOnCooldown()) return; + + CompletableFuture.runAsync(() -> { + try { + CloudSync.sync(this).join(); + WildfireGender.LOGGER.info("Synced player data to the cloud"); + SyncLog.add(WildfireLocalization.SYNC_LOG_SYNC_TO_CLOUD); + } catch(Exception e) { + WildfireGender.LOGGER.error("Failed to sync player data", e); + SyncLog.add(WildfireLocalization.SYNC_LOG_FAILED_TO_SYNC_DATA); + } + }); + needsCloudSync = false; + } + + /** + * Update player data from the provided {@link JsonObject} + * + * @apiNote This method will set the player's {@link #getSyncStatus() sync status} to {@link SyncStatus#SYNCED}, + * as it's expected that this method is only used in such cases where this would be applicable. + * + * @param json The {@link JsonObject} to merge with the existing config for this player + */ + public void updateFromJson(@NotNull JsonObject json) { json.asMap().forEach(this.cfg.SAVE_VALUES::add); loadFromConfig(false); this.syncStatus = SyncStatus.SYNCED; diff --git a/src/main/resources/assets/wildfire_gender/lang/de_de.json b/src/main/resources/assets/wildfire_gender/lang/de_de.json index 42e90fae..39ea2e88 100644 --- a/src/main/resources/assets/wildfire_gender/lang/de_de.json +++ b/src/main/resources/assets/wildfire_gender/lang/de_de.json @@ -78,8 +78,8 @@ "wildfire_gender.cloud_settings": "Synchronizierungs Einstellungen", - "wildfire_gender.cloud.available_online": "Cloud Synchronizierung", - "wildfire_gender.cloud.unavailable_offline": "Cloud Synchronizierung ist zurzeit nicht verfügbar da du nicht mit einem gültigen Minecraft Account eingeloggt bist", + "wildfire_gender.cloud.tooltip": "Cloud Synchronizierung", + "wildfire_gender.cloud.unavailable.invalid_account": "Cloud Synchronizierung ist zurzeit nicht verfügbar da du nicht mit einem gültigen Minecraft Account eingeloggt bist", "wildfire_gender.cloud.status": "Cloud Synchronizierung: %s", "wildfire_gender.cloud.automatic": "Automatische Synchronizierung: %s", "wildfire_gender.cloud.automatic.tooltip.line1": "Wenn dies aktiviert ist werden deine Einstellungen nach jeder Änderung automatisch mit der Cloud synchronisiert.", @@ -91,8 +91,6 @@ "wildfire_gender.cloud.syncing.success": "Synchroniziert", "wildfire_gender.cloud.syncing.fail": "Synchronizierung fehlgeschlagen", - "wildfire_gender.cloud.disclaimer.line1": "Synchronizierungsdaten werden für ungefähr 30 Minuten auf dem Server gespeichert.", - "wildfire_gender.cloud.disclaimer.line2": "Wenn keine Veränderungen sichtbar sind, dann versuche die Synchronisierung später erneut durchzuführen..", "wildfire_gender.sync_log.authenticating": "Überprüfe Account...", "wildfire_gender.sync_log.authentication_success": "Überprüfung erfolgreich.", diff --git a/src/main/resources/assets/wildfire_gender/lang/en_pt.json b/src/main/resources/assets/wildfire_gender/lang/en_pt.json index bcb953ab..60769ae8 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_pt.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_pt.json @@ -78,8 +78,8 @@ "wildfire_gender.cloud_settings": "Sky Ship Wrench", - "wildfire_gender.cloud.available_online": "Sky Ship Analysis", - "wildfire_gender.cloud.unavailable_offline": "Sky Ship Analyzing is unavailable as we couldn't identify ya, matey! Are ya a bad pirate?", + "wildfire_gender.cloud.tooltip": "Sky Ship Analysis", + "wildfire_gender.cloud.unavailable.invalid_account": "Sky Ship Analyzing is unavailable as we couldn't identify ya, matey! Are ya a bad pirate?", "wildfire_gender.cloud.status": "Sky Ship Analyzing: %s", "wildfire_gender.cloud.automatic": "Automatic Analysis: %s", "wildfire_gender.cloud.automatic.tooltip.line1": "While on, ya tit info will be sent to ze sky ship.", @@ -91,8 +91,6 @@ "wildfire_gender.cloud.syncing.success": "Analyzed", "wildfire_gender.cloud.syncing.fail": "Analysis failed!", - "wildfire_gender.cloud.disclaimer.line1": "Ze Sky Ship may need to have ze info for 30 minutes", - "wildfire_gender.cloud.disclaimer.line2": "If ze changes don't apply try to re-analyze latah, will ya matey?", "wildfire_gender.sync_log.authenticating": "Identifying Matey...", "wildfire_gender.sync_log.authentication_success": "Matey Identified.", diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index 9302bcb9..60f4e218 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -79,8 +79,9 @@ "wildfire_gender.cloud_settings": "Cloud Sync Server Settings", - "wildfire_gender.cloud.available_online": "Cloud Sync", - "wildfire_gender.cloud.unavailable_offline": "Cloud syncing is unavailable as you aren't currently logged into a valid Minecraft account", + "wildfire_gender.cloud.tooltip": "Cloud Sync", + "wildfire_gender.cloud.unavailable.invalid_account": "Cloud syncing is unavailable as you aren't currently logged into a valid Minecraft account", + "wildfire_gender.cloud.unavailable.offline_server": "Cloud syncing is unavailable as the server you're connected to is in offline mode", "wildfire_gender.cloud.status": "Cloud Sync: %s", "wildfire_gender.cloud.automatic": "Automatic Sync: %s", "wildfire_gender.cloud.automatic.tooltip.line1": "While enabled, your config will automatically be synced to the cloud after making any changes.", @@ -92,8 +93,6 @@ "wildfire_gender.cloud.syncing.success": "Synced", "wildfire_gender.cloud.syncing.fail": "Sync Failed", - "wildfire_gender.cloud.disclaimer.line1": "Sync Server data may be cached for at least 30 minutes.", - "wildfire_gender.cloud.disclaimer.line2": "If changes are not visible, please try re-syncing later.", "wildfire_gender.sync_log.authenticating": "Authenticating Account...", "wildfire_gender.sync_log.authentication_success": "Authentication Successful.", diff --git a/src/main/resources/assets/wildfire_gender/lang/es_es.json b/src/main/resources/assets/wildfire_gender/lang/es_es.json index 475cf63d..bf5f33aa 100644 --- a/src/main/resources/assets/wildfire_gender/lang/es_es.json +++ b/src/main/resources/assets/wildfire_gender/lang/es_es.json @@ -78,8 +78,8 @@ "wildfire_gender.cloud_settings": "Configuración del Sincronizado en la Nube", - "wildfire_gender.cloud.available_online": "Sincronizado en la Nube", - "wildfire_gender.cloud.unavailable_offline": "El Sincronizado en la Nube no está disponible debido a que no iniciaste sesión en una cuenta de Minecraft válida.", + "wildfire_gender.cloud.tooltip": "Sincronizado en la Nube", + "wildfire_gender.cloud.unavailable.invalid_account": "El Sincronizado en la Nube no está disponible debido a que no iniciaste sesión en una cuenta de Minecraft válida.", "wildfire_gender.cloud.status": "Sincronizado: %s", "wildfire_gender.cloud.automatic": "Sincro. Automática: %s", "wildfire_gender.cloud.automatic.tooltip.line1": "Cuando se active se enviaran los cambios que hagas a la nube.", @@ -91,8 +91,6 @@ "wildfire_gender.cloud.syncing.success": "Sincronizado!", "wildfire_gender.cloud.syncing.fail": "Sincro. Fallida...", - "wildfire_gender.cloud.disclaimer.line1": "Los datos del servidor de sincronización se tienen que almacenar en caché durante al menos 30 minutos.", - "wildfire_gender.cloud.disclaimer.line2": "Si los cambios no son visibles, inténtalo más tarde.", "wildfire_gender.sync_log.authenticating": "Autenticando cuenta...", "wildfire_gender.sync_log.authentication_success": "Autenticación Exitosa.", diff --git a/src/main/resources/assets/wildfire_gender/lang/es_mx.json b/src/main/resources/assets/wildfire_gender/lang/es_mx.json index 9befa38b..e9953b69 100644 --- a/src/main/resources/assets/wildfire_gender/lang/es_mx.json +++ b/src/main/resources/assets/wildfire_gender/lang/es_mx.json @@ -78,8 +78,8 @@ "wildfire_gender.cloud_settings": "Configuración del Sincronizado en la Nube", - "wildfire_gender.cloud.available_online": "Sincronizado en la Nube", - "wildfire_gender.cloud.unavailable_offline": "El Sincronizado en la Nube no está disponible debido a que no iniciaste sesión en una cuenta de Minecraft válida.", + "wildfire_gender.cloud.tooltip": "Sincronizado en la Nube", + "wildfire_gender.cloud.unavailable.invalid_account": "El Sincronizado en la Nube no está disponible debido a que no iniciaste sesión en una cuenta de Minecraft válida.", "wildfire_gender.cloud.status": "Sincronizado: %s", "wildfire_gender.cloud.automatic": "Sincro. Automática: %s", "wildfire_gender.cloud.automatic.tooltip.line1": "Cuando se active se enviaran los cambios que hagas a la nube.", @@ -91,8 +91,6 @@ "wildfire_gender.cloud.syncing.success": "Sincronizado!", "wildfire_gender.cloud.syncing.fail": "Sincro. Fallida...", - "wildfire_gender.cloud.disclaimer.line1": "Los datos del servidor de sincronización se tienen que almacenar en caché durante al menos 30 minutos.", - "wildfire_gender.cloud.disclaimer.line2": "Si los cambios no son visibles, inténtalo más tarde.", "wildfire_gender.sync_log.authenticating": "Autenticando cuenta...", "wildfire_gender.sync_log.authentication_success": "Autenticación Exitosa.", diff --git a/src/main/resources/assets/wildfire_gender/lang/nl_nl.json b/src/main/resources/assets/wildfire_gender/lang/nl_nl.json index dc8d7c86..9443ee94 100644 --- a/src/main/resources/assets/wildfire_gender/lang/nl_nl.json +++ b/src/main/resources/assets/wildfire_gender/lang/nl_nl.json @@ -78,8 +78,8 @@ "wildfire_gender.cloud_settings": "Online Synchronisatie Instellingen", - "wildfire_gender.cloud.available_online": "Online Synchronisatie", - "wildfire_gender.cloud.unavailable_offline": "Online synchronisatie is niet beschikbaar, omdat je niet bent ingelogd in een geldig Minecraft account", + "wildfire_gender.cloud.tooltip": "Online Synchronisatie", + "wildfire_gender.cloud.unavailable.invalid_account": "Online synchronisatie is niet beschikbaar, omdat je niet bent ingelogd in een geldig Minecraft account", "wildfire_gender.cloud.status": "Synchroniseren: %s", "wildfire_gender.cloud.automatic": "Automatische Synchronisatie: %s", "wildfire_gender.cloud.automatic.tooltip.line1": "Wanner dit aanstaat, jouw configuratie wordt automatisch online gesynchroniseerd na dat je aanpassingen maakt.", From f9eb589bb22166e2638089f0ee33cd5400b992bc Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 1 Dec 2024 02:01:29 -0700 Subject: [PATCH 218/238] add essential-loader as a conflict too since fabric doesn't ever actually see the essential mod when conflicts are checked due to this loader --- src/main/resources/fabric.mod.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index a2d4b27b..5045c497 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -53,7 +53,8 @@ }, "conflicts": { "skinlayers": "*", - "essential": "*" + "essential": "*", + "essential-loader": "*" }, "custom": { "modmenu": { From 1ab749e8f290411188cd976b6410bf9452e0d791 Mon Sep 17 00:00:00 2001 From: PinguinLars1105 <68437296+PinguinLars@users.noreply.github.com> Date: Sun, 1 Dec 2024 10:05:09 +0100 Subject: [PATCH 219/238] New lines Added the new lines added in commit https://github.com/WildfireRomeo/WildfireFemaleGenderMod/commit/8a93187437bab472a8c8d90c5751292179c101dc --- src/main/resources/assets/wildfire_gender/lang/nl_nl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/assets/wildfire_gender/lang/nl_nl.json b/src/main/resources/assets/wildfire_gender/lang/nl_nl.json index dc8d7c86..b52bdb12 100644 --- a/src/main/resources/assets/wildfire_gender/lang/nl_nl.json +++ b/src/main/resources/assets/wildfire_gender/lang/nl_nl.json @@ -1,6 +1,7 @@ { "category.wildfire_gender.generic": "Wildfire's Vrouwelijke Geslachts Mod", "key.wildfire_gender.gender_menu": "Vrouwelijke Geslachts Menu", + "key.wildfire_gender.toggle": "Schakel Borsten Rendering", "toast.wildfire_gender.get_started": "Druk op '%s' om te starten!", "wildfire_gender.player_list.title": "Vrouwelijke Geslachts Mod", @@ -112,4 +113,4 @@ "wildfire_gender.details.prev_page": "Vorige Pagina", "wildfire_gender.cloud_details.title": "Online Synchronisatie Server Informatie" -} \ No newline at end of file +} From 7e1e35216d367d642c924e3d7d0ce7c8af32247a Mon Sep 17 00:00:00 2001 From: le0nlol Date: Sun, 1 Dec 2024 17:06:35 +0300 Subject: [PATCH 220/238] Delete src/main/resources/assets/wildfire_gender/lang/ru_ru.json --- .../assets/wildfire_gender/lang/ru_ru.json | 60 ------------------- 1 file changed, 60 deletions(-) delete mode 100644 src/main/resources/assets/wildfire_gender/lang/ru_ru.json diff --git a/src/main/resources/assets/wildfire_gender/lang/ru_ru.json b/src/main/resources/assets/wildfire_gender/lang/ru_ru.json deleted file mode 100644 index c3fb194d..00000000 --- a/src/main/resources/assets/wildfire_gender/lang/ru_ru.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "category.wildfire_gender.generic": "Wildfire's Female Gender Mod", - "key.wildfire_gender.gender_menu": "Меню", - - "wildfire_gender.hurt.female": "Female Player Hurt", - - "wildfire_gender.player_list.title": "Female Gender Mod", - "wildfire_gender.player_list.settings_button": "Настройки", - "wildfire_gender.player_list.sync_status": "Состояние синхронизации", - "wildfire_gender.player_list.state.loading": "Загрузка данных...", - "wildfire_gender.player_list.state.synced": "Игроки синхронизированны", - - "wildfire_gender.wardrobe.title": "Меню кастомизации", - "wildfire_gender.wardrobe.slider.breast_size": "Размер груди: %s%%", - "wildfire_gender.wardrobe.slider.separation": "Расстояние: %s", - "wildfire_gender.wardrobe.slider.height": "Высота: %s", - "wildfire_gender.wardrobe.slider.depth": "Глубина: %s", - "wildfire_gender.wardrobe.slider.rotation": "Вращение: %s градусов", - - "wildfire_gender.appearance_settings.title": "Настройки внешности", - "wildfire_gender.char_settings.title": "Настройки персонажа", - "wildfire_gender.char_settings.physics": "Физика груди: %s", - "wildfire_gender.tooltip.breast_physics": "Обеспечивает физику груди", - "wildfire_gender.char_settings.hide_in_armor": "Скрывать под броней: %s", - "wildfire_gender.tooltip.hide_in_armor": "Скрывает грудь при ношении Брони", - "wildfire_gender.char_settings.hurt_sounds": "Звуки при ранении: %s", - "wildfire_gender.tooltip.hurt_sounds": "Женские звуки при ранении", - - "wildfire_gender.breast_customization.dual_physics": "Двойная физика: %s", - - "wildfire_gender.player_list.bounce_multiplier": "Множитель отскока: %sx", - "wildfire_gender.player_list.breast_momentum": "Импульс груди: %s%%", - "wildfire_gender.player_list.female_sounds": "Женские звуки при ранении: %s", - - "wildfire_gender.settings.title": "Меню настроек Wildfire's", - - "wildfire_gender.acknowledge.confirm": "Окей", - - "wildfire_gender.label.gender": "Пол", - "wildfire_gender.label.female": "Женщина", - "wildfire_gender.label.male": "Мужчина", - "wildfire_gender.label.other": "Другой", - - "wildfire_gender.label.enabled": "Вкл", - "wildfire_gender.label.disabled": "Выкл", - "wildfire_gender.label.yes": "Да", - "wildfire_gender.label.no": "Нет", - "wildfire_gender.label.exit": "X", - "wildfire_gender.label.too_far": "Слишком Далеко", - "wildfire_gender.label.with_creator": "Вы играете на одном сервере с создателем этого мода!", - - "wildfire_gender.slider.bounce": "Упругость: %sx", - "wildfire_gender.slider.floppy": "Импульс груди: %s%%", - "wildfire_gender.slider.min_bounce": "Почему вообще включена Физика?", - "wildfire_gender.slider.max_bounce": "Физика груди как в Аниме!!!", - "wildfire_gender.tooltip.bounce_warning": "Установка высокого значения «Упругость» будет выглядеть очень неестественно!", - - "wildfire_gender.cancer_awareness.title": "Эй, сегодня месяц повышения осведомленности о раке молочной железы!", - "wildfire_gender.cancer_awareness.description": "Нажмите здесь, чтобы сделать пожертвование в фонд §dСьюзан Г. Комен§f!" -} \ No newline at end of file From ed88b7893c515b3a4de918d6fa00698c46fc39d3 Mon Sep 17 00:00:00 2001 From: Arcti <71222107+ArcticWah@users.noreply.github.com> Date: Sun, 1 Dec 2024 14:37:19 +0000 Subject: [PATCH 221/238] Added new line, and fixed writing mistake --- src/main/resources/assets/wildfire_gender/lang/de_de.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/resources/assets/wildfire_gender/lang/de_de.json b/src/main/resources/assets/wildfire_gender/lang/de_de.json index 42e90fae..30d82197 100644 --- a/src/main/resources/assets/wildfire_gender/lang/de_de.json +++ b/src/main/resources/assets/wildfire_gender/lang/de_de.json @@ -1,7 +1,8 @@ { "category.wildfire_gender.generic": "Wildfire's Weibliche Geschlechts Mod", - "key.wildfire_gender.gender_menu": "Weibliches Geschelchts Menü", - "toast.wildfire_gender.get_started": "Drücke '%s' um zu starten!", + "key.wildfire_gender.gender_menu": "Weibliches Geschlechts Menü", + "key.wildfire_gender.toggle": "Brust Rendering umschalten", + "toast.wildfire_gender.get_started": "Drücke '%s' um loszulegen!", "wildfire_gender.player_list.title": "Weibliche Geschlechts Mod", "wildfire_gender.player_list.settings_button": "Einstellungen", From ccc7ac5ffb5e5bfaae325ce0141a90f21108d32a Mon Sep 17 00:00:00 2001 From: le0nlol Date: Sun, 1 Dec 2024 17:51:44 +0300 Subject: [PATCH 222/238] Create ru_ru.json --- .../assets/wildfire_gender/lang/ru_ru.json | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 src/main/resources/assets/wildfire_gender/lang/ru_ru.json diff --git a/src/main/resources/assets/wildfire_gender/lang/ru_ru.json b/src/main/resources/assets/wildfire_gender/lang/ru_ru.json new file mode 100644 index 00000000..4d52233f --- /dev/null +++ b/src/main/resources/assets/wildfire_gender/lang/ru_ru.json @@ -0,0 +1,116 @@ +{ + "category.wildfire_gender.generic": "Wildfire's Female Gender Mod", + "key.wildfire_gender.gender_menu": "Меню", + "key.wildfire_gender.toggle": "Переключить визуализацию груди", + "toast.wildfire_gender.get_started": "Нажмите'%s' чтобы начать!", + + "wildfire_gender.player_list.title": "Female Gender Mod", + "wildfire_gender.player_list.settings_button": "Настройки", + "wildfire_gender.player_list.sync_status": "Состояние синхронизации", + "wildfire_gender.player_list.state.loading": "Загрузка данных...", + "wildfire_gender.player_list.state.synced": "Игроки синхронизированны", + "wildfire_gender.player_list.bounce_multiplier": "Множитель отскока: %sx", + "wildfire_gender.player_list.breast_momentum": "Импульс груди: %s%%", + "wildfire_gender.player_list.female_sounds": "Женские звуки: %s", + "wildfire_gender.wardrobe.players_using_mod": "Игроков использующих мод:", + + "wildfire_gender.always_show_list": "Показать синхронизированных игроков: %s", + "wildfire_gender.always_show_list.mod_ui_only": "Этот экран", + "wildfire_gender.always_show_list.mod_ui_only.tooltip": "Синхронизированный список игроков будет отображаться только в этом меню.", + "wildfire_gender.always_show_list.tab_list_open": "Список игроков", + "wildfire_gender.always_show_list.tab_list_open.tooltip": "Синхронизированный список игроков будет отображаться в этом меню или при нажатии %s", + "wildfire_gender.always_show_list.always": "Всегда", + "wildfire_gender.always_show_list.always.tooltip": "Список синхронизированных игроков будет всегда отображаться", + + "wildfire_gender.wardrobe.title": "Меню кастомизации", + "wildfire_gender.breast_customization.tab_customization": "Настройка", + "wildfire_gender.breast_customization.tab_physics": "Физика груди", + "wildfire_gender.breast_customization.tab_miscellaneous": "Прочее", + + "wildfire_gender.breast_customization.presets.add_new": "Добавить...", + "wildfire_gender.breast_customization.presets.delete": "Удалить", + + "wildfire_gender.wardrobe.slider.breast_size": "Размер груди: %s%%", + "wildfire_gender.wardrobe.slider.separation": "Расстояние: %s", + "wildfire_gender.wardrobe.slider.height": "Высота: %s", + "wildfire_gender.wardrobe.slider.depth": "Глубина: %s", + "wildfire_gender.wardrobe.slider.rotation": "Вращение: %s градусов", + "wildfire_gender.slider.voice_pitch": "Питч: %s%%", + + "wildfire_gender.appearance_settings.title": "Персонализация персонажа", + "wildfire_gender.char_settings.title": "Старые настройки персонажа", + "wildfire_gender.char_settings.physics": "Физика груди: %s", + + "wildfire_gender.char_settings.override_armor_physics": "Физика брони: %s", + "wildfire_gender.tooltip.override_armor_physics.line1": "При включении этой функции физика груди больше не будет снижаться/подавляться экипированной броней.", + "wildfire_gender.tooltip.override_armor_physics.line2": "Это предназначено для использования с наборами ресурсов, скрывающими броню, или любыми аналогичными наборами ресурсов минимальной брони.", + + "wildfire_gender.char_settings.hide_in_armor": "Спрятать в доспехах: %s", + "wildfire_gender.char_settings.hurt_sounds": "Женские звуки урона: %s", + "wildfire_gender.tooltip.hurt_sounds": "Ваш персонаж будет издавать женский звук при получении урона, если ваш пол установлен как Женский или Другой.", + + "wildfire_gender.breast_customization.dual_physics": "Двойная физика: %s", + + "wildfire_gender.label.gender": "Пол", + "wildfire_gender.label.female": "Женщина", + "wildfire_gender.label.male": "Мужчина", + "wildfire_gender.label.other": "Другой", + + "wildfire_gender.label.enabled": "Вкл", + "wildfire_gender.label.disabled": "Выкл", + "wildfire_gender.label.on": "Вкл.", + "wildfire_gender.label.off": "Выкл.", + "wildfire_gender.label.yes": "Да", + "wildfire_gender.label.no": "Нет", + "wildfire_gender.label.with_creator": "Вы играете на одном сервере с создателем этого мода!", + "wildfire_gender.label.with_contributor": "Вы играете на сервере с разработчиком этого мода!", + "wildfire_gender.label.with_both": "Вы играете на сервере с создателем и разработчиком этого мода!", + + "wildfire_gender.slider.bounce": "Интенсивность: %s%%", + "wildfire_gender.slider.floppy": "Импульс: %s%%", + + "wildfire_gender.cancer_awareness.title": "Эй, сегодня месяц повышения осведомленности о раке молочной железы!", + + "wildfire_gender.first_time_setup.title": "Wildfire's Female Gender Mod", + "wildfire_gender.first_time_setup.description": "Хотите включить синхронизацию с облачным сервером для ваших настроек пола? Эта функция позволяет другим игрокам просматривать ваш настроенный пол, даже если на сервере не установлен мод.", + "wildfire_gender.first_time_setup.notice": "Эту настройку вы всегда сможете изменить в меню мода.", + "wildfire_gender.first_time_setup.enable": "Включить облачную синхронизацию", + "wildfire_gender.first_time_setup.disable": "Выключить облачную синхронизацию", + + + "wildfire_gender.cloud_settings": "Настройки сервера синхронизации облака", + "wildfire_gender.cloud.available_online": "Облачная синхронизация", + "wildfire_gender.cloud.unavailable_offline": "Синхронизация с облаком недоступна, так как вы не вошли в действующую учетную запись Minecraft.", + "wildfire_gender.cloud.status": "Облачная синхронизация: %s", + "wildfire_gender.cloud.automatic": "Автоматическаия синхронизация: %s", + "wildfire_gender.cloud.automatic.tooltip.line1": "Если эта функция включена, ваша конфигурация будет автоматически синхронизироваться с облаком после внесения любых изменений.", + "wildfire_gender.cloud.automatic.tooltip.line2": "Если эта функция отключена, вы по-прежнему можете выполнить синхронизацию вручную с помощью кнопки ниже.", + "wildfire_gender.cloud.sync": "Синхронизировать сейчас", + "wildfire_gender.cloud.syncing": "Сихнронизация...", + + "wildfire_gender.cloud.status_log": "Журнал статуса", + + "wildfire_gender.cloud.syncing.success": "Синхронизировано", + "wildfire_gender.cloud.syncing.fail": "Ошибка синхронизации", + "wildfire_gender.cloud.disclaimer.line1": "Данные сервера синхронизации могут кэшироваться не менее 30 минут.", + "wildfire_gender.cloud.disclaimer.line2": "Если изменения не видны, попробуйте повторить синхронизацию позже.", + + "wildfire_gender.sync_log.authenticating": "Авторизация аккаунта...", + "wildfire_gender.sync_log.authentication_success": "Авторизация успешна!", + "wildfire_gender.sync_log.authentication_failed": "Ошибка авторизации.", + "wildfire_gender.sync_log.reauthenticating": "Повторная авторизация...", + + "wildfire_gender.sync_log.failed_to_sync_data": "Не удалось синхронизировать данные.", + "wildfire_gender.sync_log.sync_to_cloud": "Синхронизация данных в облако...", + "wildfire_gender.sync_log.attempting_sync": "Синхронизация профиля...", + "wildfire_gender.sync_log.sync_success": "Успешная синхронизация", + "wildfire_gender.sync_log.sync_too_frequently": "Скорость синхронизации ограничена.", + + "wildfire_gender.sync_log.get_single_profile": "Получение профиля...", + "wildfire_gender.sync_log.get_multiple_profiles": "Извлечение пакета профилей...", + + "wildfire_gender.details.next_page": "Следующая страница", + "wildfire_gender.details.prev_page": "Предыдущая страница", + + "wildfire_gender.cloud_details.title": "Информация об облачном синхранизации" +} From 261693335d1d4faaa894a75c3ccf0aa463db2e4e Mon Sep 17 00:00:00 2001 From: le0nlol Date: Sun, 1 Dec 2024 18:04:08 +0300 Subject: [PATCH 223/238] Update ru_ru.json --- src/main/resources/assets/wildfire_gender/lang/ru_ru.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/resources/assets/wildfire_gender/lang/ru_ru.json b/src/main/resources/assets/wildfire_gender/lang/ru_ru.json index 4d52233f..61c390db 100644 --- a/src/main/resources/assets/wildfire_gender/lang/ru_ru.json +++ b/src/main/resources/assets/wildfire_gender/lang/ru_ru.json @@ -8,8 +8,8 @@ "wildfire_gender.player_list.settings_button": "Настройки", "wildfire_gender.player_list.sync_status": "Состояние синхронизации", "wildfire_gender.player_list.state.loading": "Загрузка данных...", - "wildfire_gender.player_list.state.synced": "Игроки синхронизированны", - "wildfire_gender.player_list.bounce_multiplier": "Множитель отскока: %sx", + "wildfire_gender.player_list.state.synced": "Игроки синхронизированы", + "wildfire_gender.player_list.bounce_multiplier": "Множитель отскока: %s", "wildfire_gender.player_list.breast_momentum": "Импульс груди: %s%%", "wildfire_gender.player_list.female_sounds": "Женские звуки: %s", "wildfire_gender.wardrobe.players_using_mod": "Игроков использующих мод:", @@ -82,11 +82,11 @@ "wildfire_gender.cloud.available_online": "Облачная синхронизация", "wildfire_gender.cloud.unavailable_offline": "Синхронизация с облаком недоступна, так как вы не вошли в действующую учетную запись Minecraft.", "wildfire_gender.cloud.status": "Облачная синхронизация: %s", - "wildfire_gender.cloud.automatic": "Автоматическаия синхронизация: %s", + "wildfire_gender.cloud.automatic": "Автоматическая синхронизация: %s", "wildfire_gender.cloud.automatic.tooltip.line1": "Если эта функция включена, ваша конфигурация будет автоматически синхронизироваться с облаком после внесения любых изменений.", "wildfire_gender.cloud.automatic.tooltip.line2": "Если эта функция отключена, вы по-прежнему можете выполнить синхронизацию вручную с помощью кнопки ниже.", "wildfire_gender.cloud.sync": "Синхронизировать сейчас", - "wildfire_gender.cloud.syncing": "Сихнронизация...", + "wildfire_gender.cloud.syncing": "Синхронизация...", "wildfire_gender.cloud.status_log": "Журнал статуса", From 7a911886942763d52a08d9f1869f98f71324cf81 Mon Sep 17 00:00:00 2001 From: le0nlol Date: Sun, 1 Dec 2024 23:13:05 +0300 Subject: [PATCH 224/238] Update ru_ru.json Shorted some messages and made other corrections --- .../assets/wildfire_gender/lang/ru_ru.json | 227 +++++++++--------- 1 file changed, 113 insertions(+), 114 deletions(-) diff --git a/src/main/resources/assets/wildfire_gender/lang/ru_ru.json b/src/main/resources/assets/wildfire_gender/lang/ru_ru.json index 61c390db..55aed1ba 100644 --- a/src/main/resources/assets/wildfire_gender/lang/ru_ru.json +++ b/src/main/resources/assets/wildfire_gender/lang/ru_ru.json @@ -1,116 +1,115 @@ { - "category.wildfire_gender.generic": "Wildfire's Female Gender Mod", - "key.wildfire_gender.gender_menu": "Меню", - "key.wildfire_gender.toggle": "Переключить визуализацию груди", - "toast.wildfire_gender.get_started": "Нажмите'%s' чтобы начать!", - - "wildfire_gender.player_list.title": "Female Gender Mod", - "wildfire_gender.player_list.settings_button": "Настройки", - "wildfire_gender.player_list.sync_status": "Состояние синхронизации", - "wildfire_gender.player_list.state.loading": "Загрузка данных...", - "wildfire_gender.player_list.state.synced": "Игроки синхронизированы", - "wildfire_gender.player_list.bounce_multiplier": "Множитель отскока: %s", - "wildfire_gender.player_list.breast_momentum": "Импульс груди: %s%%", - "wildfire_gender.player_list.female_sounds": "Женские звуки: %s", - "wildfire_gender.wardrobe.players_using_mod": "Игроков использующих мод:", - - "wildfire_gender.always_show_list": "Показать синхронизированных игроков: %s", - "wildfire_gender.always_show_list.mod_ui_only": "Этот экран", - "wildfire_gender.always_show_list.mod_ui_only.tooltip": "Синхронизированный список игроков будет отображаться только в этом меню.", - "wildfire_gender.always_show_list.tab_list_open": "Список игроков", - "wildfire_gender.always_show_list.tab_list_open.tooltip": "Синхронизированный список игроков будет отображаться в этом меню или при нажатии %s", - "wildfire_gender.always_show_list.always": "Всегда", - "wildfire_gender.always_show_list.always.tooltip": "Список синхронизированных игроков будет всегда отображаться", - - "wildfire_gender.wardrobe.title": "Меню кастомизации", - "wildfire_gender.breast_customization.tab_customization": "Настройка", - "wildfire_gender.breast_customization.tab_physics": "Физика груди", - "wildfire_gender.breast_customization.tab_miscellaneous": "Прочее", - - "wildfire_gender.breast_customization.presets.add_new": "Добавить...", - "wildfire_gender.breast_customization.presets.delete": "Удалить", - - "wildfire_gender.wardrobe.slider.breast_size": "Размер груди: %s%%", - "wildfire_gender.wardrobe.slider.separation": "Расстояние: %s", - "wildfire_gender.wardrobe.slider.height": "Высота: %s", - "wildfire_gender.wardrobe.slider.depth": "Глубина: %s", - "wildfire_gender.wardrobe.slider.rotation": "Вращение: %s градусов", - "wildfire_gender.slider.voice_pitch": "Питч: %s%%", - - "wildfire_gender.appearance_settings.title": "Персонализация персонажа", - "wildfire_gender.char_settings.title": "Старые настройки персонажа", - "wildfire_gender.char_settings.physics": "Физика груди: %s", - - "wildfire_gender.char_settings.override_armor_physics": "Физика брони: %s", - "wildfire_gender.tooltip.override_armor_physics.line1": "При включении этой функции физика груди больше не будет снижаться/подавляться экипированной броней.", - "wildfire_gender.tooltip.override_armor_physics.line2": "Это предназначено для использования с наборами ресурсов, скрывающими броню, или любыми аналогичными наборами ресурсов минимальной брони.", - - "wildfire_gender.char_settings.hide_in_armor": "Спрятать в доспехах: %s", - "wildfire_gender.char_settings.hurt_sounds": "Женские звуки урона: %s", - "wildfire_gender.tooltip.hurt_sounds": "Ваш персонаж будет издавать женский звук при получении урона, если ваш пол установлен как Женский или Другой.", - - "wildfire_gender.breast_customization.dual_physics": "Двойная физика: %s", - - "wildfire_gender.label.gender": "Пол", - "wildfire_gender.label.female": "Женщина", - "wildfire_gender.label.male": "Мужчина", - "wildfire_gender.label.other": "Другой", - - "wildfire_gender.label.enabled": "Вкл", - "wildfire_gender.label.disabled": "Выкл", - "wildfire_gender.label.on": "Вкл.", - "wildfire_gender.label.off": "Выкл.", - "wildfire_gender.label.yes": "Да", - "wildfire_gender.label.no": "Нет", - "wildfire_gender.label.with_creator": "Вы играете на одном сервере с создателем этого мода!", - "wildfire_gender.label.with_contributor": "Вы играете на сервере с разработчиком этого мода!", - "wildfire_gender.label.with_both": "Вы играете на сервере с создателем и разработчиком этого мода!", - - "wildfire_gender.slider.bounce": "Интенсивность: %s%%", - "wildfire_gender.slider.floppy": "Импульс: %s%%", - - "wildfire_gender.cancer_awareness.title": "Эй, сегодня месяц повышения осведомленности о раке молочной железы!", - - "wildfire_gender.first_time_setup.title": "Wildfire's Female Gender Mod", - "wildfire_gender.first_time_setup.description": "Хотите включить синхронизацию с облачным сервером для ваших настроек пола? Эта функция позволяет другим игрокам просматривать ваш настроенный пол, даже если на сервере не установлен мод.", - "wildfire_gender.first_time_setup.notice": "Эту настройку вы всегда сможете изменить в меню мода.", - "wildfire_gender.first_time_setup.enable": "Включить облачную синхронизацию", - "wildfire_gender.first_time_setup.disable": "Выключить облачную синхронизацию", - - - "wildfire_gender.cloud_settings": "Настройки сервера синхронизации облака", - "wildfire_gender.cloud.available_online": "Облачная синхронизация", - "wildfire_gender.cloud.unavailable_offline": "Синхронизация с облаком недоступна, так как вы не вошли в действующую учетную запись Minecraft.", - "wildfire_gender.cloud.status": "Облачная синхронизация: %s", - "wildfire_gender.cloud.automatic": "Автоматическая синхронизация: %s", - "wildfire_gender.cloud.automatic.tooltip.line1": "Если эта функция включена, ваша конфигурация будет автоматически синхронизироваться с облаком после внесения любых изменений.", - "wildfire_gender.cloud.automatic.tooltip.line2": "Если эта функция отключена, вы по-прежнему можете выполнить синхронизацию вручную с помощью кнопки ниже.", - "wildfire_gender.cloud.sync": "Синхронизировать сейчас", - "wildfire_gender.cloud.syncing": "Синхронизация...", - - "wildfire_gender.cloud.status_log": "Журнал статуса", - - "wildfire_gender.cloud.syncing.success": "Синхронизировано", - "wildfire_gender.cloud.syncing.fail": "Ошибка синхронизации", - "wildfire_gender.cloud.disclaimer.line1": "Данные сервера синхронизации могут кэшироваться не менее 30 минут.", - "wildfire_gender.cloud.disclaimer.line2": "Если изменения не видны, попробуйте повторить синхронизацию позже.", - - "wildfire_gender.sync_log.authenticating": "Авторизация аккаунта...", - "wildfire_gender.sync_log.authentication_success": "Авторизация успешна!", - "wildfire_gender.sync_log.authentication_failed": "Ошибка авторизации.", - "wildfire_gender.sync_log.reauthenticating": "Повторная авторизация...", - - "wildfire_gender.sync_log.failed_to_sync_data": "Не удалось синхронизировать данные.", - "wildfire_gender.sync_log.sync_to_cloud": "Синхронизация данных в облако...", - "wildfire_gender.sync_log.attempting_sync": "Синхронизация профиля...", - "wildfire_gender.sync_log.sync_success": "Успешная синхронизация", - "wildfire_gender.sync_log.sync_too_frequently": "Скорость синхронизации ограничена.", - - "wildfire_gender.sync_log.get_single_profile": "Получение профиля...", - "wildfire_gender.sync_log.get_multiple_profiles": "Извлечение пакета профилей...", - - "wildfire_gender.details.next_page": "Следующая страница", - "wildfire_gender.details.prev_page": "Предыдущая страница", - - "wildfire_gender.cloud_details.title": "Информация об облачном синхранизации" +"category.wildfire_gender.generic": "Wildfire's Female Gender Mod", +"key.wildfire_gender.gender_menu": "Меню", +"key.wildfire_gender.toggle": "Переключить визуализацию груди", +"toast.wildfire_gender.get_started": "Нажмите'%s' чтобы начать!", + +"wildfire_gender.player_list.title": "Female Gender Mod", +"wildfire_gender.player_list.settings_button": "Настройки", +"wildfire_gender.player_list.sync_status": "Состояние синхронизации", +"wildfire_gender.player_list.state.loading": "Загрузка данных...", +"wildfire_gender.player_list.state.synced": "Игроки синхронизированы", +"wildfire_gender.player_list.bounce_multiplier": "Множитель отскока: %s", +"wildfire_gender.player_list.breast_momentum": "Импульс груди: %s%%", +"wildfire_gender.player_list.female_sounds": "Женские звуки: %s", +"wildfire_gender.wardrobe.players_using_mod": "Игроков использующих мод:", + +"wildfire_gender.always_show_list": "Показать синхронизированных игроков: %s", +"wildfire_gender.always_show_list.mod_ui_only": "Этот экран", +"wildfire_gender.always_show_list.mod_ui_only.tooltip": "Синхронизированный список игроков будет отображаться только в этом меню.", +"wildfire_gender.always_show_list.tab_list_open": "Список игроков", +"wildfire_gender.always_show_list.tab_list_open.tooltip": "Синхронизированный список игроков будет отображаться в этом меню или при нажатии %s", +"wildfire_gender.always_show_list.always": "Всегда", +"wildfire_gender.always_show_list.always.tooltip": "Список синхронизированных игроков будет всегда отображаться", + +"wildfire_gender.wardrobe.title": "Меню кастомизации", +"wildfire_gender.breast_customization.tab_customization": "Настройка", +"wildfire_gender.breast_customization.tab_physics": "Физика груди", +"wildfire_gender.breast_customization.tab_miscellaneous": "Прочее", + +"wildfire_gender.breast_customization.presets.add_new": "Добавить...", +"wildfire_gender.breast_customization.presets.delete": "Удалить", + +"wildfire_gender.wardrobe.slider.breast_size": "Размер груди: %s%%", +"wildfire_gender.wardrobe.slider.separation": "Расстояние: %s", +"wildfire_gender.wardrobe.slider.height": "Высота: %s", +"wildfire_gender.wardrobe.slider.depth": "Глубина: %s", +"wildfire_gender.wardrobe.slider.rotation": "Вращение: %s градусов", +"wildfire_gender.slider.voice_pitch": "Питч: %s%%", + +"wildfire_gender.appearance_settings.title": "Персонализация персонажа", +"wildfire_gender.char_settings.title": "Старые настройки персонажа", +"wildfire_gender.char_settings.physics": "Физика груди: %s", + +"wildfire_gender.char_settings.override_armor_physics": "Физика брони: %s", +"wildfire_gender.tooltip.override_armor_physics.line1": "При включении этой функции физика груди больше не будет снижаться/подавляться экипированной броней.", +"wildfire_gender.tooltip.override_armor_physics.line2": "Это предназначено для использования с наборами ресурсов, скрывающими броню, или любыми аналогичными наборами ресурсов минимальной брони.", + +"wildfire_gender.char_settings.hide_in_armor": "Спрятать в доспехах: %s", +"wildfire_gender.char_settings.hurt_sounds": "Женские звуки урона: %s", +"wildfire_gender.tooltip.hurt_sounds": "Ваш персонаж будет издавать женский звук при получении урона, если ваш пол установлен как Женский или Другой.", + +"wildfire_gender.breast_customization.dual_physics": "Двойная физика: %s", + +"wildfire_gender.label.gender": "Пол", +"wildfire_gender.label.female": "Женщина", +"wildfire_gender.label.male": "Мужчина", +"wildfire_gender.label.other": "Другой", + +"wildfire_gender.label.enabled": "Вкл", +"wildfire_gender.label.disabled": "Выкл", +"wildfire_gender.label.on": "Вкл.", +"wildfire_gender.label.off": "Выкл.", +"wildfire_gender.label.yes": "Да", +"wildfire_gender.label.no": "Нет", +"wildfire_gender.label.with_creator": "Вы играете на одном сервере с создателем этого мода!", +"wildfire_gender.label.with_contributor": "Вы играете на сервере с разработчиком этого мода!", +"wildfire_gender.label.with_both": "Вы играете на сервере с создателем и разработчиком этого мода!", + +"wildfire_gender.slider.bounce": "Интенсивность: %s%%", +"wildfire_gender.slider.floppy": "Импульс: %s%%", + +"wildfire_gender.cancer_awareness.title": "Эй, сегодня месяц повышения осведомленности о раке молочной железы!", + +"wildfire_gender.first_time_setup.title": "Wildfire's Female Gender Mod", +"wildfire_gender.first_time_setup.description": "Хотите включить синхронизацию с облачным сервером для ваших настроек пола? Эта функция позволяет другим игрокам просматривать ваш настроенный пол, даже если на сервере не установлен мод.", +"wildfire_gender.first_time_setup.notice": "Эту настройку вы всегда сможете изменить в меню мода.", +"wildfire_gender.first_time_setup.enable": "Включить синхронизацию", +"wildfire_gender.first_time_setup.disable": "Выключить синхронизацию", + + +"wildfire_gender.cloud_settings": "Настройки синхронизации", +"wildfire_gender.cloud.available_online": "Облачная синхронизация", +"wildfire_gender.cloud.unavailable_offline": "Синхронизация с облаком недоступна, так как вы не вошли в действующую учетную запись Minecraft.", +"wildfire_gender.cloud.status": "Облачная синхронизация: %s", +"wildfire_gender.cloud.automatic": "Авто-синхронизация: %s", +"wildfire_gender.cloud.automatic.tooltip.line1": "Если эта функция включена, ваша конфигурация будет автоматически синхронизироваться с облаком после внесения любых изменений.", +"wildfire_gender.cloud.automatic.tooltip.line2": "Если эта функция отключена, вы по-прежнему можете выполнить синхронизацию вручную с помощью кнопки ниже.", +"wildfire_gender.cloud.sync": "Синхронизировать сейчас", +"wildfire_gender.cloud.syncing": "Синхронизация...", +"wildfire_gender.cloud.status_log": "Журнал статуса", + +"wildfire_gender.cloud.syncing.success": "Синхронизировано", +"wildfire_gender.cloud.syncing.fail": "Ошибка синхронизации", +"wildfire_gender.cloud.disclaimer.line1": "Данные сервера синхронизации могут кэшироваться не менее 30 минут.", +"wildfire_gender.cloud.disclaimer.line2": "Если изменения не видны, попробуйте повторить синхронизацию позже.", + +"wildfire_gender.sync_log.authenticating": "Авторизация аккаунта...", +"wildfire_gender.sync_log.authentication_success": "Авторизация успешна!", +"wildfire_gender.sync_log.authentication_failed": "Ошибка авторизации.", +"wildfire_gender.sync_log.reauthenticating": "Повторная авторизация...", + +"wildfire_gender.sync_log.failed_to_sync_data": "Не удалось синхронизировать данные.", +"wildfire_gender.sync_log.sync_to_cloud": "Синхронизация данных...", +"wildfire_gender.sync_log.attempting_sync": "Синхронизация профиля...", +"wildfire_gender.sync_log.sync_success": "Успешная синхронизация!", +"wildfire_gender.sync_log.sync_too_frequently": "Скорость синхронизации ограничена.", + +"wildfire_gender.sync_log.get_single_profile": "Получение профиля...", +"wildfire_gender.sync_log.get_multiple_profiles": "Извлечение пакета профилей...", + +"wildfire_gender.details.next_page": "Следующая страница", +"wildfire_gender.details.prev_page": "Предыдущая страница", + +"wildfire_gender.cloud_details.title": "Информация об облачном синхранизации" } From 26b5fb159b7e3b007aca75c018ef63b83a2a292e Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 3 Dec 2024 10:35:10 -0700 Subject: [PATCH 225/238] update to full release --- src/main/resources/CHANGELOG => CHANGELOG | 0 gradle.properties | 4 ++-- src/main/resources/fabric.mod.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/main/resources/CHANGELOG => CHANGELOG (100%) diff --git a/src/main/resources/CHANGELOG b/CHANGELOG similarity index 100% rename from src/main/resources/CHANGELOG rename to CHANGELOG diff --git a/gradle.properties b/gradle.properties index 44a75c34..8cf924fd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://fabricmc.net/develop # Note that this variable does *not* define the minimum required version in fabric.mod.json! -minecraft_version=1.21.4-rc1 +minecraft_version=1.21.4 yarn_build=1 loader_version=0.16.9 @@ -14,4 +14,4 @@ maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod # Dependencies -fabric_version=0.110.2+1.21.4 +fabric_version=0.110.5+1.21.4 diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 5045c497..85171940 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -48,7 +48,7 @@ "fabric-rendering-v1": "*", "fabric-resource-loader-v0": "*", "fabric-registry-sync-v0": "*", - "minecraft": ">=1.21.2-alpha.24.46.a <1.22", + "minecraft": ">=1.21.4 <1.22", "java": ">=21" }, "conflicts": { From b44787a44ce49a7fa9f7ced66812b823d7d9764c Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 3 Dec 2024 10:41:20 -0700 Subject: [PATCH 226/238] update translation keys, remove now unused ones --- src/main/resources/assets/wildfire_gender/lang/nl_nl.json | 2 -- src/main/resources/assets/wildfire_gender/lang/ru_ru.json | 6 ++---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/resources/assets/wildfire_gender/lang/nl_nl.json b/src/main/resources/assets/wildfire_gender/lang/nl_nl.json index 0782f7ab..cd327882 100644 --- a/src/main/resources/assets/wildfire_gender/lang/nl_nl.json +++ b/src/main/resources/assets/wildfire_gender/lang/nl_nl.json @@ -92,8 +92,6 @@ "wildfire_gender.cloud.syncing.success": "Gesynchroniseerd", "wildfire_gender.cloud.syncing.fail": "Synchronisatie Niet Gelukt", - "wildfire_gender.cloud.disclaimer.line1": "Server Sychronisatie date wordt mogelijk gecachd voor minimaal 30 minuten.", - "wildfire_gender.cloud.disclaimer.line2": "Als de aanpassen niet zichtbaar zijn, synchroniseer later opnieuw aub.", "wildfire_gender.sync_log.authenticating": "Account Verifiëren...", "wildfire_gender.sync_log.authentication_success": "Verificatie Gelukt.", diff --git a/src/main/resources/assets/wildfire_gender/lang/ru_ru.json b/src/main/resources/assets/wildfire_gender/lang/ru_ru.json index 55aed1ba..1189f6a6 100644 --- a/src/main/resources/assets/wildfire_gender/lang/ru_ru.json +++ b/src/main/resources/assets/wildfire_gender/lang/ru_ru.json @@ -79,8 +79,8 @@ "wildfire_gender.cloud_settings": "Настройки синхронизации", -"wildfire_gender.cloud.available_online": "Облачная синхронизация", -"wildfire_gender.cloud.unavailable_offline": "Синхронизация с облаком недоступна, так как вы не вошли в действующую учетную запись Minecraft.", +"wildfire_gender.cloud.tooltip": "Облачная синхронизация", +"wildfire_gender.cloud.unavailable.invalid_account": "Синхронизация с облаком недоступна, так как вы не вошли в действующую учетную запись Minecraft.", "wildfire_gender.cloud.status": "Облачная синхронизация: %s", "wildfire_gender.cloud.automatic": "Авто-синхронизация: %s", "wildfire_gender.cloud.automatic.tooltip.line1": "Если эта функция включена, ваша конфигурация будет автоматически синхронизироваться с облаком после внесения любых изменений.", @@ -91,8 +91,6 @@ "wildfire_gender.cloud.syncing.success": "Синхронизировано", "wildfire_gender.cloud.syncing.fail": "Ошибка синхронизации", -"wildfire_gender.cloud.disclaimer.line1": "Данные сервера синхронизации могут кэшироваться не менее 30 минут.", -"wildfire_gender.cloud.disclaimer.line2": "Если изменения не видны, попробуйте повторить синхронизацию позже.", "wildfire_gender.sync_log.authenticating": "Авторизация аккаунта...", "wildfire_gender.sync_log.authentication_success": "Авторизация успешна!", From 7293b7ae4f3031541fe7628fbfb8a751ed4cceff Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 3 Dec 2024 11:56:24 -0700 Subject: [PATCH 227/238] add translation https://github.com/WildfireRomeo/WildfireFemaleGenderMod/pull/252#issuecomment-2515334625 --- src/main/resources/assets/wildfire_gender/lang/nl_nl.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/assets/wildfire_gender/lang/nl_nl.json b/src/main/resources/assets/wildfire_gender/lang/nl_nl.json index cd327882..dd602e78 100644 --- a/src/main/resources/assets/wildfire_gender/lang/nl_nl.json +++ b/src/main/resources/assets/wildfire_gender/lang/nl_nl.json @@ -81,6 +81,7 @@ "wildfire_gender.cloud_settings": "Online Synchronisatie Instellingen", "wildfire_gender.cloud.tooltip": "Online Synchronisatie", "wildfire_gender.cloud.unavailable.invalid_account": "Online synchronisatie is niet beschikbaar, omdat je niet bent ingelogd in een geldig Minecraft account", + "wildfire_gender.cloud.unavailable.offline_server": "Online synchronisatie is niet beschikbaar, want de server waar mee je bent verbondenis in offlinemodus", "wildfire_gender.cloud.status": "Synchroniseren: %s", "wildfire_gender.cloud.automatic": "Automatische Synchronisatie: %s", "wildfire_gender.cloud.automatic.tooltip.line1": "Wanner dit aanstaat, jouw configuratie wordt automatisch online gesynchroniseerd na dat je aanpassingen maakt.", From e84a1e6d644f6bac8ebade6b424005904bccc586 Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 3 Dec 2024 16:34:48 -0700 Subject: [PATCH 228/238] reword comment to avoid first person references --- .../mixins/accessors/TrimSpriteKeyConstructorAccessor.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/wildfire/mixins/accessors/TrimSpriteKeyConstructorAccessor.java b/src/main/java/com/wildfire/mixins/accessors/TrimSpriteKeyConstructorAccessor.java index 5e232c6d..d663264c 100644 --- a/src/main/java/com/wildfire/mixins/accessors/TrimSpriteKeyConstructorAccessor.java +++ b/src/main/java/com/wildfire/mixins/accessors/TrimSpriteKeyConstructorAccessor.java @@ -31,9 +31,9 @@ @Mixin(EquipmentRenderer.TrimSpriteKey.class) @Environment(EnvType.CLIENT) public interface TrimSpriteKeyConstructorAccessor { - // Yes, it would've been possible to simply access widener the constructor along with the record itself, but I - // am absolutely not willing to maintain such an access widener entry when I could alternatively use Mixin, which - // at least will fail in a way that's significantly less of a headache to update. + // While it would've been possible to also simply access widener the constructor along with the class, updating + // such an access widener in the event that Mojang changes this in the future is _far_ more of a headache + // than simply using a mixin. @Invoker("") static EquipmentRenderer.TrimSpriteKey newKey(ArmorTrim armorTrim, EquipmentModel.LayerType layerType, RegistryKey registryKey) { throw new UnsupportedOperationException("Something's gone very seriously wrong if we've gotten here!"); From 7b8ba42302ff810cc9c096a5e3552c9f3926e950 Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 3 Dec 2024 16:35:43 -0700 Subject: [PATCH 229/238] update fabric loom to 1.9 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7d8ecc5e..0555e967 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '1.8-SNAPSHOT' + id 'fabric-loom' version '1.9-SNAPSHOT' id 'maven-publish' } From 31fac85c56a3c5fae9f3b4b9d50a09cd3e2deb12 Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 3 Dec 2024 16:42:57 -0700 Subject: [PATCH 230/238] update license header, add template --- .gitignore | 1 + .idea/copyright/LGPL.xml | 6 ++++ .idea/copyright/profiles_settings.xml | 3 ++ .../com/wildfire/api/IBreastArmorTexture.java | 32 +++++++++---------- .../java/com/wildfire/api/IGenderArmor.java | 32 +++++++++---------- src/main/java/com/wildfire/api/Vec2i.java | 32 +++++++++---------- .../java/com/wildfire/api/WildfireAPI.java | 32 +++++++++---------- .../wildfire/api/impl/BreastArmorTexture.java | 32 +++++++++---------- .../com/wildfire/api/impl/GenderArmor.java | 32 +++++++++---------- src/main/java/com/wildfire/gui/GuiUtils.java | 32 +++++++++---------- .../gui/WildfireBreastPresetList.java | 18 +++++++++++ .../java/com/wildfire/gui/WildfireButton.java | 32 +++++++++---------- .../java/com/wildfire/gui/WildfireSlider.java | 32 +++++++++---------- .../gui/screen/BaseWildfireScreen.java | 32 +++++++++---------- .../gui/screen/WardrobeBrowserScreen.java | 32 +++++++++---------- .../WildfireBreastCustomizationScreen.java | 32 +++++++++---------- .../WildfireCharacterSettingsScreen.java | 32 +++++++++---------- .../screen/WildfireCloudDetailsScreen.java | 32 +++++++++---------- .../gui/screen/WildfireCloudSyncScreen.java | 32 +++++++++---------- .../screen/WildfireFirstTimeSetupScreen.java | 32 +++++++++---------- src/main/java/com/wildfire/main/Gender.java | 32 +++++++++---------- .../wildfire/main/WildfireEventHandler.java | 32 +++++++++---------- .../com/wildfire/main/WildfireGender.java | 32 +++++++++---------- .../wildfire/main/WildfireGenderClient.java | 32 +++++++++---------- .../com/wildfire/main/WildfireHelper.java | 32 +++++++++---------- .../wildfire/main/WildfireLocalization.java | 32 +++++++++---------- .../com/wildfire/main/WildfireSounds.java | 32 +++++++++---------- .../com/wildfire/main/cloud/BulkFetch.java | 32 +++++++++---------- .../com/wildfire/main/cloud/CloudAuth.java | 32 +++++++++---------- .../com/wildfire/main/cloud/CloudSync.java | 32 +++++++++---------- .../com/wildfire/main/cloud/QueuedFetch.java | 32 +++++++++---------- .../java/com/wildfire/main/cloud/SyncLog.java | 32 +++++++++---------- .../wildfire/main/cloud/SyncUnavailable.java | 32 +++++++++---------- .../cloud/SyncingTooFrequentlyException.java | 32 +++++++++---------- .../main/config/AbstractConfiguration.java | 32 +++++++++---------- .../main/config/BooleanConfigKey.java | 32 +++++++++---------- .../config/BreastPresetConfiguration.java | 32 +++++++++---------- .../com/wildfire/main/config/ConfigKey.java | 32 +++++++++---------- .../wildfire/main/config/Configuration.java | 32 +++++++++---------- .../wildfire/main/config/EnumConfigKey.java | 32 +++++++++---------- .../wildfire/main/config/FloatConfigKey.java | 32 +++++++++---------- .../wildfire/main/config/GenderConfigKey.java | 32 +++++++++---------- .../wildfire/main/config/GlobalConfig.java | 32 +++++++++---------- .../wildfire/main/config/NumberConfigKey.java | 32 +++++++++---------- .../wildfire/main/config/StringConfigKey.java | 32 +++++++++---------- .../wildfire/main/config/UUIDConfigKey.java | 32 +++++++++---------- .../main/config/enums/ShowPlayerListMode.java | 32 +++++++++---------- .../main/config/enums/SyncVerbosity.java | 32 +++++++++---------- .../main/entitydata/BreastDataComponent.java | 18 +++++++++++ .../com/wildfire/main/entitydata/Breasts.java | 32 +++++++++---------- .../main/entitydata/EntityConfig.java | 32 +++++++++---------- .../main/entitydata/PlayerConfig.java | 32 +++++++++---------- .../main/networking/AbstractSyncPacket.java | 32 +++++++++---------- .../networking/ClientboundSyncPacket.java | 32 +++++++++---------- .../networking/ServerboundSyncPacket.java | 32 +++++++++---------- .../main/networking/WildfireSync.java | 32 +++++++++---------- .../mixins/ArmorStandEntityMixin.java | 32 +++++++++---------- .../mixins/BreastPhysicsTickMixin.java | 32 +++++++++---------- .../wildfire/mixins/LivingEntityMixin.java | 32 +++++++++---------- .../accessors/EquipmentRendererAccessor.java | 32 +++++++++---------- .../accessors/TextureManagerAccessor.java | 32 +++++++++---------- .../TrimSpriteKeyConstructorAccessor.java | 32 +++++++++---------- .../LivingEntityRenderStateMixin.java | 32 +++++++++---------- .../LivingEntityRendererMixin.java | 32 +++++++++---------- .../com/wildfire/physics/BreastPhysics.java | 32 +++++++++---------- .../java/com/wildfire/render/BreastSide.java | 32 +++++++++---------- .../com/wildfire/render/GenderArmorLayer.java | 32 +++++++++---------- .../java/com/wildfire/render/GenderLayer.java | 32 +++++++++---------- .../render/RenderStateEntityCapture.java | 32 +++++++++---------- .../render/WildfireModelRenderer.java | 32 +++++++++---------- .../resources/GenderArmorResourceManager.java | 32 +++++++++---------- 71 files changed, 1102 insertions(+), 1056 deletions(-) create mode 100644 .idea/copyright/LGPL.xml create mode 100644 .idea/copyright/profiles_settings.xml diff --git a/.gitignore b/.gitignore index 9c6c5a06..25f5baf7 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ classes/ # idea .idea/ +!.idea/copyright/ *.iml *.ipr *.iws diff --git a/.idea/copyright/LGPL.xml b/.idea/copyright/LGPL.xml new file mode 100644 index 00000000..eb26b749 --- /dev/null +++ b/.idea/copyright/LGPL.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 00000000..bbb36746 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/java/com/wildfire/api/IBreastArmorTexture.java b/src/main/java/com/wildfire/api/IBreastArmorTexture.java index 059fd2b0..135cb6d3 100644 --- a/src/main/java/com/wildfire/api/IBreastArmorTexture.java +++ b/src/main/java/com/wildfire/api/IBreastArmorTexture.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.api; diff --git a/src/main/java/com/wildfire/api/IGenderArmor.java b/src/main/java/com/wildfire/api/IGenderArmor.java index 4a8e7b9c..8b5c2811 100644 --- a/src/main/java/com/wildfire/api/IGenderArmor.java +++ b/src/main/java/com/wildfire/api/IGenderArmor.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.api; diff --git a/src/main/java/com/wildfire/api/Vec2i.java b/src/main/java/com/wildfire/api/Vec2i.java index 44fbd250..8b5fc86a 100644 --- a/src/main/java/com/wildfire/api/Vec2i.java +++ b/src/main/java/com/wildfire/api/Vec2i.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.api; diff --git a/src/main/java/com/wildfire/api/WildfireAPI.java b/src/main/java/com/wildfire/api/WildfireAPI.java index 4149340a..fb83245b 100644 --- a/src/main/java/com/wildfire/api/WildfireAPI.java +++ b/src/main/java/com/wildfire/api/WildfireAPI.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.api; diff --git a/src/main/java/com/wildfire/api/impl/BreastArmorTexture.java b/src/main/java/com/wildfire/api/impl/BreastArmorTexture.java index dfd777e6..5741d519 100644 --- a/src/main/java/com/wildfire/api/impl/BreastArmorTexture.java +++ b/src/main/java/com/wildfire/api/impl/BreastArmorTexture.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.api.impl; diff --git a/src/main/java/com/wildfire/api/impl/GenderArmor.java b/src/main/java/com/wildfire/api/impl/GenderArmor.java index 9011516c..0b164ee2 100644 --- a/src/main/java/com/wildfire/api/impl/GenderArmor.java +++ b/src/main/java/com/wildfire/api/impl/GenderArmor.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.api.impl; diff --git a/src/main/java/com/wildfire/gui/GuiUtils.java b/src/main/java/com/wildfire/gui/GuiUtils.java index ccfce8a6..31d95bff 100644 --- a/src/main/java/com/wildfire/gui/GuiUtils.java +++ b/src/main/java/com/wildfire/gui/GuiUtils.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.gui; diff --git a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java index 9b39cd3b..262be68d 100644 --- a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java +++ b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java @@ -1,3 +1,21 @@ +/* + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.wildfire.gui; import com.wildfire.gui.screen.WildfireBreastCustomizationScreen; diff --git a/src/main/java/com/wildfire/gui/WildfireButton.java b/src/main/java/com/wildfire/gui/WildfireButton.java index baef2688..9ab75a29 100644 --- a/src/main/java/com/wildfire/gui/WildfireButton.java +++ b/src/main/java/com/wildfire/gui/WildfireButton.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.gui; diff --git a/src/main/java/com/wildfire/gui/WildfireSlider.java b/src/main/java/com/wildfire/gui/WildfireSlider.java index 6381d1f5..148f8836 100644 --- a/src/main/java/com/wildfire/gui/WildfireSlider.java +++ b/src/main/java/com/wildfire/gui/WildfireSlider.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.gui; diff --git a/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java b/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java index 27d95a7d..067f1936 100644 --- a/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java +++ b/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.gui.screen; diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index f45a5fd2..5fcc7d41 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.gui.screen; diff --git a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java index 7ea14d58..d9efc603 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.gui.screen; diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java index 456d6ebe..ab2303f7 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.gui.screen; diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCloudDetailsScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCloudDetailsScreen.java index 721ce8d1..8f715426 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCloudDetailsScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCloudDetailsScreen.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.gui.screen; diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java index a271b909..62370fc2 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCloudSyncScreen.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.gui.screen; diff --git a/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java index 07f95629..b3a50aea 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireFirstTimeSetupScreen.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.gui.screen; diff --git a/src/main/java/com/wildfire/main/Gender.java b/src/main/java/com/wildfire/main/Gender.java index 52b88d56..57ca2ac2 100644 --- a/src/main/java/com/wildfire/main/Gender.java +++ b/src/main/java/com/wildfire/main/Gender.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main; diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index 7d681aec..a07c89be 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main; diff --git a/src/main/java/com/wildfire/main/WildfireGender.java b/src/main/java/com/wildfire/main/WildfireGender.java index 77e05583..61cb8db7 100644 --- a/src/main/java/com/wildfire/main/WildfireGender.java +++ b/src/main/java/com/wildfire/main/WildfireGender.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main; diff --git a/src/main/java/com/wildfire/main/WildfireGenderClient.java b/src/main/java/com/wildfire/main/WildfireGenderClient.java index 9619a41d..e751656c 100644 --- a/src/main/java/com/wildfire/main/WildfireGenderClient.java +++ b/src/main/java/com/wildfire/main/WildfireGenderClient.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main; diff --git a/src/main/java/com/wildfire/main/WildfireHelper.java b/src/main/java/com/wildfire/main/WildfireHelper.java index cbe5d628..2801d4e9 100644 --- a/src/main/java/com/wildfire/main/WildfireHelper.java +++ b/src/main/java/com/wildfire/main/WildfireHelper.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main; diff --git a/src/main/java/com/wildfire/main/WildfireLocalization.java b/src/main/java/com/wildfire/main/WildfireLocalization.java index 5e4efb3a..0712570b 100644 --- a/src/main/java/com/wildfire/main/WildfireLocalization.java +++ b/src/main/java/com/wildfire/main/WildfireLocalization.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main; diff --git a/src/main/java/com/wildfire/main/WildfireSounds.java b/src/main/java/com/wildfire/main/WildfireSounds.java index 8835b07a..eb8e9f54 100644 --- a/src/main/java/com/wildfire/main/WildfireSounds.java +++ b/src/main/java/com/wildfire/main/WildfireSounds.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main; diff --git a/src/main/java/com/wildfire/main/cloud/BulkFetch.java b/src/main/java/com/wildfire/main/cloud/BulkFetch.java index 49c0fef8..f2703732 100644 --- a/src/main/java/com/wildfire/main/cloud/BulkFetch.java +++ b/src/main/java/com/wildfire/main/cloud/BulkFetch.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.cloud; diff --git a/src/main/java/com/wildfire/main/cloud/CloudAuth.java b/src/main/java/com/wildfire/main/cloud/CloudAuth.java index 5a3b6fa0..2463c6e9 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudAuth.java +++ b/src/main/java/com/wildfire/main/cloud/CloudAuth.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.cloud; diff --git a/src/main/java/com/wildfire/main/cloud/CloudSync.java b/src/main/java/com/wildfire/main/cloud/CloudSync.java index 14274172..6558e149 100644 --- a/src/main/java/com/wildfire/main/cloud/CloudSync.java +++ b/src/main/java/com/wildfire/main/cloud/CloudSync.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.cloud; diff --git a/src/main/java/com/wildfire/main/cloud/QueuedFetch.java b/src/main/java/com/wildfire/main/cloud/QueuedFetch.java index 6540eb2c..f8b6e4de 100644 --- a/src/main/java/com/wildfire/main/cloud/QueuedFetch.java +++ b/src/main/java/com/wildfire/main/cloud/QueuedFetch.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.cloud; diff --git a/src/main/java/com/wildfire/main/cloud/SyncLog.java b/src/main/java/com/wildfire/main/cloud/SyncLog.java index 0434eb70..99ce6fa7 100644 --- a/src/main/java/com/wildfire/main/cloud/SyncLog.java +++ b/src/main/java/com/wildfire/main/cloud/SyncLog.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.cloud; diff --git a/src/main/java/com/wildfire/main/cloud/SyncUnavailable.java b/src/main/java/com/wildfire/main/cloud/SyncUnavailable.java index c9539702..70c0e45d 100644 --- a/src/main/java/com/wildfire/main/cloud/SyncUnavailable.java +++ b/src/main/java/com/wildfire/main/cloud/SyncUnavailable.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.cloud; diff --git a/src/main/java/com/wildfire/main/cloud/SyncingTooFrequentlyException.java b/src/main/java/com/wildfire/main/cloud/SyncingTooFrequentlyException.java index 34989524..9dec647e 100644 --- a/src/main/java/com/wildfire/main/cloud/SyncingTooFrequentlyException.java +++ b/src/main/java/com/wildfire/main/cloud/SyncingTooFrequentlyException.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.cloud; diff --git a/src/main/java/com/wildfire/main/config/AbstractConfiguration.java b/src/main/java/com/wildfire/main/config/AbstractConfiguration.java index e3a54a8f..804e41c9 100644 --- a/src/main/java/com/wildfire/main/config/AbstractConfiguration.java +++ b/src/main/java/com/wildfire/main/config/AbstractConfiguration.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.config; diff --git a/src/main/java/com/wildfire/main/config/BooleanConfigKey.java b/src/main/java/com/wildfire/main/config/BooleanConfigKey.java index 0bc3c958..98ad379d 100644 --- a/src/main/java/com/wildfire/main/config/BooleanConfigKey.java +++ b/src/main/java/com/wildfire/main/config/BooleanConfigKey.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.config; diff --git a/src/main/java/com/wildfire/main/config/BreastPresetConfiguration.java b/src/main/java/com/wildfire/main/config/BreastPresetConfiguration.java index 241aee08..163a05e3 100644 --- a/src/main/java/com/wildfire/main/config/BreastPresetConfiguration.java +++ b/src/main/java/com/wildfire/main/config/BreastPresetConfiguration.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.config; diff --git a/src/main/java/com/wildfire/main/config/ConfigKey.java b/src/main/java/com/wildfire/main/config/ConfigKey.java index 80238f42..3cee8c08 100644 --- a/src/main/java/com/wildfire/main/config/ConfigKey.java +++ b/src/main/java/com/wildfire/main/config/ConfigKey.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.config; diff --git a/src/main/java/com/wildfire/main/config/Configuration.java b/src/main/java/com/wildfire/main/config/Configuration.java index d5d88578..5aa25bed 100644 --- a/src/main/java/com/wildfire/main/config/Configuration.java +++ b/src/main/java/com/wildfire/main/config/Configuration.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.config; diff --git a/src/main/java/com/wildfire/main/config/EnumConfigKey.java b/src/main/java/com/wildfire/main/config/EnumConfigKey.java index 20feac1a..a1aa240e 100644 --- a/src/main/java/com/wildfire/main/config/EnumConfigKey.java +++ b/src/main/java/com/wildfire/main/config/EnumConfigKey.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.config; diff --git a/src/main/java/com/wildfire/main/config/FloatConfigKey.java b/src/main/java/com/wildfire/main/config/FloatConfigKey.java index 7fba727d..86a0f0ae 100644 --- a/src/main/java/com/wildfire/main/config/FloatConfigKey.java +++ b/src/main/java/com/wildfire/main/config/FloatConfigKey.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.config; diff --git a/src/main/java/com/wildfire/main/config/GenderConfigKey.java b/src/main/java/com/wildfire/main/config/GenderConfigKey.java index 0dd4b61b..d97d292c 100644 --- a/src/main/java/com/wildfire/main/config/GenderConfigKey.java +++ b/src/main/java/com/wildfire/main/config/GenderConfigKey.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.config; diff --git a/src/main/java/com/wildfire/main/config/GlobalConfig.java b/src/main/java/com/wildfire/main/config/GlobalConfig.java index 9f2b00cb..ccdd2ed4 100644 --- a/src/main/java/com/wildfire/main/config/GlobalConfig.java +++ b/src/main/java/com/wildfire/main/config/GlobalConfig.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.config; diff --git a/src/main/java/com/wildfire/main/config/NumberConfigKey.java b/src/main/java/com/wildfire/main/config/NumberConfigKey.java index 9c4a9cde..8ca05815 100644 --- a/src/main/java/com/wildfire/main/config/NumberConfigKey.java +++ b/src/main/java/com/wildfire/main/config/NumberConfigKey.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.config; diff --git a/src/main/java/com/wildfire/main/config/StringConfigKey.java b/src/main/java/com/wildfire/main/config/StringConfigKey.java index b921b44f..6688869f 100644 --- a/src/main/java/com/wildfire/main/config/StringConfigKey.java +++ b/src/main/java/com/wildfire/main/config/StringConfigKey.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.config; diff --git a/src/main/java/com/wildfire/main/config/UUIDConfigKey.java b/src/main/java/com/wildfire/main/config/UUIDConfigKey.java index 939236ab..17c99704 100644 --- a/src/main/java/com/wildfire/main/config/UUIDConfigKey.java +++ b/src/main/java/com/wildfire/main/config/UUIDConfigKey.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.config; diff --git a/src/main/java/com/wildfire/main/config/enums/ShowPlayerListMode.java b/src/main/java/com/wildfire/main/config/enums/ShowPlayerListMode.java index 827f21d2..d1f6d474 100644 --- a/src/main/java/com/wildfire/main/config/enums/ShowPlayerListMode.java +++ b/src/main/java/com/wildfire/main/config/enums/ShowPlayerListMode.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.config.enums; diff --git a/src/main/java/com/wildfire/main/config/enums/SyncVerbosity.java b/src/main/java/com/wildfire/main/config/enums/SyncVerbosity.java index 0ec6d3f7..9df71076 100644 --- a/src/main/java/com/wildfire/main/config/enums/SyncVerbosity.java +++ b/src/main/java/com/wildfire/main/config/enums/SyncVerbosity.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.config.enums; diff --git a/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java b/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java index 51d62fc5..deb3dcef 100644 --- a/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java +++ b/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java @@ -1,3 +1,21 @@ +/* + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.wildfire.main.entitydata; import com.mojang.serialization.Codec; diff --git a/src/main/java/com/wildfire/main/entitydata/Breasts.java b/src/main/java/com/wildfire/main/entitydata/Breasts.java index 38ddf8f8..6ed4624a 100644 --- a/src/main/java/com/wildfire/main/entitydata/Breasts.java +++ b/src/main/java/com/wildfire/main/entitydata/Breasts.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.entitydata; diff --git a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java index c9b846af..cf1513c7 100644 --- a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java +++ b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.entitydata; diff --git a/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java b/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java index ea0e2001..9e04b75c 100644 --- a/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java +++ b/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.entitydata; diff --git a/src/main/java/com/wildfire/main/networking/AbstractSyncPacket.java b/src/main/java/com/wildfire/main/networking/AbstractSyncPacket.java index 59246f13..9d29186c 100644 --- a/src/main/java/com/wildfire/main/networking/AbstractSyncPacket.java +++ b/src/main/java/com/wildfire/main/networking/AbstractSyncPacket.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.networking; diff --git a/src/main/java/com/wildfire/main/networking/ClientboundSyncPacket.java b/src/main/java/com/wildfire/main/networking/ClientboundSyncPacket.java index 8a137d4a..9215ba19 100644 --- a/src/main/java/com/wildfire/main/networking/ClientboundSyncPacket.java +++ b/src/main/java/com/wildfire/main/networking/ClientboundSyncPacket.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.networking; diff --git a/src/main/java/com/wildfire/main/networking/ServerboundSyncPacket.java b/src/main/java/com/wildfire/main/networking/ServerboundSyncPacket.java index cc58841f..be6c12e2 100644 --- a/src/main/java/com/wildfire/main/networking/ServerboundSyncPacket.java +++ b/src/main/java/com/wildfire/main/networking/ServerboundSyncPacket.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.networking; diff --git a/src/main/java/com/wildfire/main/networking/WildfireSync.java b/src/main/java/com/wildfire/main/networking/WildfireSync.java index 93fcf988..e1d4d626 100644 --- a/src/main/java/com/wildfire/main/networking/WildfireSync.java +++ b/src/main/java/com/wildfire/main/networking/WildfireSync.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.main.networking; diff --git a/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java b/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java index 594ddfca..acff2681 100644 --- a/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java +++ b/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.mixins; diff --git a/src/main/java/com/wildfire/mixins/BreastPhysicsTickMixin.java b/src/main/java/com/wildfire/mixins/BreastPhysicsTickMixin.java index fb4e0230..af9f385a 100644 --- a/src/main/java/com/wildfire/mixins/BreastPhysicsTickMixin.java +++ b/src/main/java/com/wildfire/mixins/BreastPhysicsTickMixin.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.mixins; diff --git a/src/main/java/com/wildfire/mixins/LivingEntityMixin.java b/src/main/java/com/wildfire/mixins/LivingEntityMixin.java index d9edc6b2..84b99643 100644 --- a/src/main/java/com/wildfire/mixins/LivingEntityMixin.java +++ b/src/main/java/com/wildfire/mixins/LivingEntityMixin.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.mixins; diff --git a/src/main/java/com/wildfire/mixins/accessors/EquipmentRendererAccessor.java b/src/main/java/com/wildfire/mixins/accessors/EquipmentRendererAccessor.java index 206016fc..b0ffd90c 100644 --- a/src/main/java/com/wildfire/mixins/accessors/EquipmentRendererAccessor.java +++ b/src/main/java/com/wildfire/mixins/accessors/EquipmentRendererAccessor.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.mixins.accessors; diff --git a/src/main/java/com/wildfire/mixins/accessors/TextureManagerAccessor.java b/src/main/java/com/wildfire/mixins/accessors/TextureManagerAccessor.java index a3117285..c3024573 100644 --- a/src/main/java/com/wildfire/mixins/accessors/TextureManagerAccessor.java +++ b/src/main/java/com/wildfire/mixins/accessors/TextureManagerAccessor.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.mixins.accessors; diff --git a/src/main/java/com/wildfire/mixins/accessors/TrimSpriteKeyConstructorAccessor.java b/src/main/java/com/wildfire/mixins/accessors/TrimSpriteKeyConstructorAccessor.java index d663264c..b4fbe3ff 100644 --- a/src/main/java/com/wildfire/mixins/accessors/TrimSpriteKeyConstructorAccessor.java +++ b/src/main/java/com/wildfire/mixins/accessors/TrimSpriteKeyConstructorAccessor.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.mixins.accessors; diff --git a/src/main/java/com/wildfire/mixins/renderstate/LivingEntityRenderStateMixin.java b/src/main/java/com/wildfire/mixins/renderstate/LivingEntityRenderStateMixin.java index eca86a2b..13111715 100644 --- a/src/main/java/com/wildfire/mixins/renderstate/LivingEntityRenderStateMixin.java +++ b/src/main/java/com/wildfire/mixins/renderstate/LivingEntityRenderStateMixin.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.mixins.renderstate; diff --git a/src/main/java/com/wildfire/mixins/renderstate/LivingEntityRendererMixin.java b/src/main/java/com/wildfire/mixins/renderstate/LivingEntityRendererMixin.java index 058e64b5..b9b733e9 100644 --- a/src/main/java/com/wildfire/mixins/renderstate/LivingEntityRendererMixin.java +++ b/src/main/java/com/wildfire/mixins/renderstate/LivingEntityRendererMixin.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.mixins.renderstate; diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index 523227cb..a1bf251a 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.physics; diff --git a/src/main/java/com/wildfire/render/BreastSide.java b/src/main/java/com/wildfire/render/BreastSide.java index b726c258..6e77e082 100644 --- a/src/main/java/com/wildfire/render/BreastSide.java +++ b/src/main/java/com/wildfire/render/BreastSide.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.render; diff --git a/src/main/java/com/wildfire/render/GenderArmorLayer.java b/src/main/java/com/wildfire/render/GenderArmorLayer.java index 07a47605..12cf7413 100644 --- a/src/main/java/com/wildfire/render/GenderArmorLayer.java +++ b/src/main/java/com/wildfire/render/GenderArmorLayer.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.render; diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index 1525e5e3..95f1db7c 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.render; diff --git a/src/main/java/com/wildfire/render/RenderStateEntityCapture.java b/src/main/java/com/wildfire/render/RenderStateEntityCapture.java index 43fc8510..bd85997f 100644 --- a/src/main/java/com/wildfire/render/RenderStateEntityCapture.java +++ b/src/main/java/com/wildfire/render/RenderStateEntityCapture.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.render; diff --git a/src/main/java/com/wildfire/render/WildfireModelRenderer.java b/src/main/java/com/wildfire/render/WildfireModelRenderer.java index 1a81af6a..ae0ce770 100644 --- a/src/main/java/com/wildfire/render/WildfireModelRenderer.java +++ b/src/main/java/com/wildfire/render/WildfireModelRenderer.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.render; diff --git a/src/main/java/com/wildfire/resources/GenderArmorResourceManager.java b/src/main/java/com/wildfire/resources/GenderArmorResourceManager.java index 26a100a1..2b197ed9 100644 --- a/src/main/java/com/wildfire/resources/GenderArmorResourceManager.java +++ b/src/main/java/com/wildfire/resources/GenderArmorResourceManager.java @@ -1,20 +1,20 @@ /* - Wildfire's Female Gender Mod is a female gender mod created for Minecraft. - Copyright (C) 2023 WildfireRomeo - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.wildfire.resources; From f3b896abc95c1b99761cd7ea58115f1121d8dc3a Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Wed, 4 Dec 2024 21:00:06 -0500 Subject: [PATCH 231/238] Breast Physics Changes --- CHANGELOG | 6 ++++++ src/main/java/com/wildfire/main/WildfireEventHandler.java | 8 ++++++++ src/main/java/com/wildfire/physics/BreastPhysics.java | 2 ++ 3 files changed, 16 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 95eddb8e..91aa5f6c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,10 +1,16 @@ Creating this to keep track of most major changes made without having to sift through the commits. +VERSION 4.3.1 +- Added Rotational Horizontal Physics? (if that makes any sense lol) + + + VERSION 4.3 - Added BluelightAmelia to Contributors - Added Keybinding for Breast Rendering - Fixed Removing Chestplates From Armor Stands - Added Keira Emberlyn (???) - Updated French Translation +- Updated Russian Translation - Added Pirate Speak Translation - Removed Unnecessary Code diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index a07c89be..534723ad 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -145,6 +145,14 @@ private static void renderHud(DrawContext context, RenderTickCounter tickCounter return; } + /*if(MinecraftClient.getInstance().player != null) { + PlayerConfig pCfg = WildfireGender.getPlayerById(MinecraftClient.getInstance().player.getUuid()); + if(pCfg != null) { + context.drawText(textRenderer, "Physics Debug", 5, 5, 0xFFFFFF, true); + context.drawText(textRenderer, "Position: " + pCfg.getLeftBreastPhysics().getPositionX() + "," + pCfg.getLeftBreastPhysics().getPositionY(), 5, 15, 0xFFFFFF, true); + context.drawText(textRenderer, "Breast Size: " + pCfg.getLeftBreastPhysics().getBreastSize(tickCounter.getTickDelta(false)), 5, 35, 0xFFFFFF, true); + } + }*/ boolean shouldShow = switch(GlobalConfig.INSTANCE.get(GlobalConfig.ALWAYS_SHOW_LIST)) { case MOD_UI_ONLY -> false; case TAB_LIST_OPEN -> MinecraftClient.getInstance().options.playerListKey.isPressed(); diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index a1bf251a..649f12cd 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -210,6 +210,8 @@ public void update(LivingEntity entity, IGenderArmor armor) { this.targetRotVel = calcRotation(entity, bounceIntensity); this.targetRotVel += (float) motion.y * bounceIntensity * randomB; + this.targetBounceX = calcRotation(entity, bounceIntensity) / 10f; + float f2 = (float) entity.getVelocity().lengthSquared() / 0.2F; f2 = f2 * f2 * f2; if(f2 < 1.0F) f2 = 1.0F; From fa59de76ff68c6900f86cbd9b931132f8d01e52b Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Wed, 4 Dec 2024 21:19:03 -0500 Subject: [PATCH 232/238] Oops --- src/main/java/com/wildfire/physics/BreastPhysics.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index 649f12cd..1e0ca190 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -210,7 +210,7 @@ public void update(LivingEntity entity, IGenderArmor armor) { this.targetRotVel = calcRotation(entity, bounceIntensity); this.targetRotVel += (float) motion.y * bounceIntensity * randomB; - this.targetBounceX = calcRotation(entity, bounceIntensity) / 10f; + this.targetBounceX = -calcRotation(entity, bounceIntensity) / 10f; float f2 = (float) entity.getVelocity().lengthSquared() / 0.2F; f2 = f2 * f2 * f2; From 526cb1d9023efe22483d976ec3c2432873330e43 Mon Sep 17 00:00:00 2001 From: celeste Date: Wed, 4 Dec 2024 20:06:11 -0700 Subject: [PATCH 233/238] fix breast render toggle keybind not hiding the armor layer --- src/main/java/com/wildfire/render/GenderLayer.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index 95f1db7c..f4e27c30 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -119,7 +119,7 @@ public GenderLayer(FeatureRendererContext render) { @Override public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light, S state, float limbAngle, float limbDistance) { MinecraftClient client = MinecraftClient.getInstance(); - if(client.player == null || !WildfireEventHandler.getRenderBreasts()) { + if(client.player == null) { // we're currently in a menu; we won't have any data loaded to begin with, so just give up early return; } @@ -149,6 +149,8 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") protected boolean setupRender(S state, EntityConfig entityConfig) { + if(!WildfireEventHandler.getRenderBreasts()) return false; + float partialTicks = MinecraftClient.getInstance().getRenderTickCounter().getTickDelta(true); LivingEntity entity = Objects.requireNonNull(getEntity(state), "getEntity()"); From 98e65340e19cec3fe67d07aaeae9406db71e21ce Mon Sep 17 00:00:00 2001 From: celeste Date: Wed, 4 Dec 2024 20:16:48 -0700 Subject: [PATCH 234/238] move toggle boolean to GlobalConfig note that this doesn't make this a saved value, but just moves it somewhere that it makes more sense to access it. --- .../java/com/wildfire/main/WildfireEventHandler.java | 11 ++--------- .../java/com/wildfire/main/config/GlobalConfig.java | 3 +++ src/main/java/com/wildfire/render/GenderLayer.java | 3 ++- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index 534723ad..883d62fd 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -51,7 +51,6 @@ import net.minecraft.client.render.entity.EntityRendererFactory; import net.minecraft.client.render.entity.LivingEntityRenderer; import net.minecraft.client.render.entity.PlayerEntityRenderer; -import net.minecraft.client.util.InputUtil; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; @@ -82,11 +81,6 @@ private WildfireEventHandler() { private static final KeyBinding TOGGLE_KEYBIND; private static int timer = 0; - private static boolean RENDER_BREASTS = true; //This is just a toggle to render it in game quickly, I'm not putting this in the config to be saved. - - public static boolean getRenderBreasts() { - return RENDER_BREASTS; - } static { if(FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { // this has to be wrapped in a lambda to ensure that a dedicated server won't crash during startup @@ -97,11 +91,10 @@ public static boolean getRenderBreasts() { return keybind; }); TOGGLE_KEYBIND = Util.make(() -> { - KeyBinding keybind = new KeyBinding("key.wildfire_gender.toggle", InputUtil.UNKNOWN_KEY.getCode(), "category.wildfire_gender.generic"); + KeyBinding keybind = new KeyBinding("key.wildfire_gender.toggle", GLFW.GLFW_KEY_UNKNOWN, "category.wildfire_gender.generic"); KeyBindingHelper.registerKeyBinding(keybind); return keybind; }); - } else { CONFIG_KEYBIND = null; TOGGLE_KEYBIND = null; @@ -213,7 +206,7 @@ private static void onClientTick(MinecraftClient client) { if(TOGGLE_KEYBIND.wasPressed() && client.currentScreen == null) { - RENDER_BREASTS ^= true; + GlobalConfig.RENDER_BREASTS ^= true; } if(CONFIG_KEYBIND.wasPressed() && client.currentScreen == null) { if(GlobalConfig.INSTANCE.get(GlobalConfig.FIRST_TIME_LOAD) && CloudSync.isAvailable()) { diff --git a/src/main/java/com/wildfire/main/config/GlobalConfig.java b/src/main/java/com/wildfire/main/config/GlobalConfig.java index ccdd2ed4..434a770c 100644 --- a/src/main/java/com/wildfire/main/config/GlobalConfig.java +++ b/src/main/java/com/wildfire/main/config/GlobalConfig.java @@ -28,6 +28,9 @@ private GlobalConfig() { super(".", "wildfire_gender"); } + // note: this option is not intended to be saved in any persistent manner + public static boolean RENDER_BREASTS = true; + public static final BooleanConfigKey FIRST_TIME_LOAD = new BooleanConfigKey("firstTimeLoad", true); public static final BooleanConfigKey CLOUD_SYNC_ENABLED = new BooleanConfigKey("cloud_sync", false); public static final BooleanConfigKey AUTOMATIC_CLOUD_SYNC = new BooleanConfigKey("sync_player_data", false); diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index f4e27c30..6a902175 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -20,6 +20,7 @@ import com.wildfire.api.IGenderArmor; import com.wildfire.main.WildfireEventHandler; +import com.wildfire.main.config.GlobalConfig; import com.wildfire.main.entitydata.Breasts; import com.wildfire.main.WildfireGender; import com.wildfire.main.WildfireHelper; @@ -149,7 +150,7 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") protected boolean setupRender(S state, EntityConfig entityConfig) { - if(!WildfireEventHandler.getRenderBreasts()) return false; + if(!GlobalConfig.RENDER_BREASTS) return false; float partialTicks = MinecraftClient.getInstance().getRenderTickCounter().getTickDelta(true); LivingEntity entity = Objects.requireNonNull(getEntity(state), "getEntity()"); From 73f1394d2ceb994a3497aca893926fb7e7573152 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Wed, 4 Dec 2024 23:08:22 -0500 Subject: [PATCH 235/238] Added "+1 Breast Support" to armor type: chestplates. --- .../java/com/wildfire/gui/WildfireToast.java | 110 ++++++++++++++++++ .../wildfire/main/WildfireEventHandler.java | 41 ++++++- .../assets/wildfire_gender/lang/en_us.json | 10 +- 3 files changed, 153 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/wildfire/gui/WildfireToast.java diff --git a/src/main/java/com/wildfire/gui/WildfireToast.java b/src/main/java/com/wildfire/gui/WildfireToast.java new file mode 100644 index 00000000..e20162f4 --- /dev/null +++ b/src/main/java/com/wildfire/gui/WildfireToast.java @@ -0,0 +1,110 @@ +package com.wildfire.gui; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.wildfire.main.WildfireEventHandler; +import com.wildfire.main.WildfireGender; +import com.wildfire.main.WildfireGenderClient; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.toast.Toast; +import net.minecraft.client.toast.Toast.Visibility; +import net.minecraft.client.toast.ToastManager; +import net.minecraft.text.OrderedText; +import net.minecraft.text.Text; +import net.minecraft.util.Colors; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.MathHelper; +import org.jetbrains.annotations.Nullable; + +@Environment(EnvType.CLIENT) +public class WildfireToast implements Toast { + private static final Identifier TEXTURE = Identifier.ofVanilla("toast/tutorial"); + private static final Identifier ICON = Identifier.of(WildfireGender.MODID, "textures/bc_ribbon.png"); + public static final int PROGRESS_BAR_WIDTH = 154; + public static final int PROGRESS_BAR_HEIGHT = 1; + private final List text; + private Visibility visibility = Visibility.SHOW; + private long lastTime; + private float lastProgress; + private float progress; + private final boolean hasProgressBar; + private final int displayDuration; + + public WildfireToast(TextRenderer textRenderer, Text title, @Nullable Text description, boolean hasProgressBar, int i) { + this.text = new ArrayList(2); + this.text.addAll(textRenderer.wrapLines(title.copy().withColor(Colors.PURPLE), 126)); + if (description != null) { + this.text.addAll(textRenderer.wrapLines(description, 126)); + } + + this.hasProgressBar = hasProgressBar; + this.displayDuration = i; + } + + public WildfireToast(TextRenderer textRenderer, Text title, @Nullable Text description, boolean hasProgressBar) { + this(textRenderer, title, description, hasProgressBar, 0); + } + + @Override + public Visibility getVisibility() { + return this.visibility; + } + + @Override + public void update(ToastManager manager, long time) { + if(WildfireEventHandler.getConfigKeybind().isPressed()) { + this.visibility = Visibility.HIDE; + } + //this.visibility = (double)time >= 10000.0 * manager.getNotificationDisplayTimeMultiplier() ? Toast.Visibility.HIDE : Toast.Visibility.SHOW; + } + + @Override + public int getHeight() { + return 7 + this.getTextHeight() + 3; + } + + private int getTextHeight() { + return Math.max(this.text.size(), 2) * 11; + } + + @Override + public void draw(DrawContext context, TextRenderer textRenderer, long startTime) { + int i = this.getHeight(); + context.drawGuiTexture(RenderLayer::getGuiTextured, TEXTURE, 0, 0, this.getWidth(), i); + + context.drawTexture(RenderLayer::getGuiTextured, ICON, 6, 6, 0, 0, 20, 20, 20, 20, 20, 20); + int j = this.text.size() * 11; + int k = 7 + (this.getTextHeight() - j) / 2; + + for (int l = 0; l < this.text.size(); l++) { + context.drawText(textRenderer, (OrderedText)this.text.get(l), 30, k + l * 11, -16777216, false); + } + + if (this.hasProgressBar) { + int l = i - 4; + context.fill(3, l, 157, l + 1, -1); + int m; + if (this.progress >= this.lastProgress) { + m = -16755456; + } else { + m = -11206656; + } + + context.fill(3, l, (int)(3.0F + 154.0F * this.lastProgress), l + 1, m); + } + } + + public void hide() { + this.visibility = Visibility.HIDE; + } + + public void setProgress(float progress) { + this.progress = progress; + } +} diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index 883d62fd..4fe1cba6 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -19,6 +19,7 @@ package com.wildfire.main; import com.wildfire.gui.GuiUtils; +import com.wildfire.gui.WildfireToast; import com.wildfire.gui.screen.WardrobeBrowserScreen; import com.wildfire.gui.screen.WildfireFirstTimeSetupScreen; import com.wildfire.main.cloud.CloudSync; @@ -33,11 +34,13 @@ import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback; import net.fabricmc.fabric.api.client.rendering.v1.LivingEntityFeatureRendererRegistrationCallback; import net.fabricmc.fabric.api.networking.v1.EntityTrackingEvents; +import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.MinecraftClient; @@ -51,10 +54,18 @@ import net.minecraft.client.render.entity.EntityRendererFactory; import net.minecraft.client.render.entity.LivingEntityRenderer; import net.minecraft.client.render.entity.PlayerEntityRenderer; +import net.minecraft.client.toast.SystemToast; +import net.minecraft.client.toast.ToastManager; +import net.minecraft.client.util.InputUtil; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.EquippableComponent; +import net.minecraft.component.type.FoodComponent; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; +import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ArmorItem; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; @@ -81,6 +92,9 @@ private WildfireEventHandler() { private static final KeyBinding TOGGLE_KEYBIND; private static int timer = 0; + public static KeyBinding getConfigKeybind() { + return CONFIG_KEYBIND; + } static { if(FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { // this has to be wrapped in a lambda to ensure that a dedicated server won't crash during startup @@ -117,17 +131,27 @@ public static void registerClientEvents() { ClientEntityEvents.ENTITY_UNLOAD.register(WildfireEventHandler::onEntityUnload); ClientTickEvents.END_CLIENT_TICK.register(WildfireEventHandler::onClientTick); ClientPlayConnectionEvents.DISCONNECT.register(WildfireEventHandler::clientDisconnect); + ClientPlayConnectionEvents.JOIN.register(WildfireEventHandler::clientJoin); LivingEntityFeatureRendererRegistrationCallback.EVENT.register(WildfireEventHandler::registerRenderLayers); HudRenderCallback.EVENT.register(WildfireEventHandler::renderHud); - //ItemTooltipCallback.EVENT.register(WildfireEventHandler::renderTooltip); disabled for now + ItemTooltipCallback.EVENT.register(WildfireEventHandler::renderTooltip); } @Environment(EnvType.CLIENT) private static void renderTooltip(ItemStack stack, Item.TooltipContext tooltipContext, TooltipType type, List lines) { - if (stack.getItem() == Items.LEATHER_CHESTPLATE) { + if(MinecraftClient.getInstance().player == null) return; + PlayerConfig pCfg = WildfireGender.getPlayerById(MinecraftClient.getInstance().player.getUuid()); + if(pCfg == null || pCfg.getGender() == Gender.MALE) return; + - lines.add(1, Text.literal("+1 Breast Support") - .formatted(Formatting.AQUA)); + if (stack.getItem() instanceof ArmorItem armorItem) { + EquippableComponent equippableComponent = armorItem.getComponents().get(DataComponentTypes.EQUIPPABLE); + if(equippableComponent == null) return; + + if(equippableComponent.slot() == EquipmentSlot.CHEST) { + lines.add(3, Text.translatable("wildfire_gender.armor.tooltip") + .formatted(Formatting.LIGHT_PURPLE)); + } } } @@ -225,6 +249,15 @@ private static void clientDisconnect(ClientPlayNetworkHandler networkHandler, Mi WildfireGender.CACHE.invalidateAll(); EntityConfig.CACHE.invalidateAll(); } + @Environment(EnvType.CLIENT) + private static void clientJoin(ClientPlayNetworkHandler var1, PacketSender var2, MinecraftClient client) { + if (client.player == null) return; + /*if (WildfireGender.getPlayerById(client.player.getUuid()) == null) { + var button = WildfireEventHandler.CONFIG_KEYBIND.getBoundKeyLocalizedText(); + ToastManager toastManager = client.getToastManager(); + toastManager.add(new WildfireToast(MinecraftClient.getInstance().textRenderer, Text.translatable("wildfire_gender.player_list.title"), Text.translatable("toast.wildfire_gender.get_started", button), false, 0)); + }*/ + } /** * Removes a disconnecting player from the cache on a server diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index 60f4e218..ae548c68 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -1,8 +1,10 @@ { - "category.wildfire_gender.generic": "Wildfire's Female Gender Mod", + "category.wildfire_gender.generic": "Female Gender Mod", "key.wildfire_gender.gender_menu": "Female Gender Menu", "key.wildfire_gender.toggle": "Toggle Breast Rendering", - "toast.wildfire_gender.get_started": "Press '%s' to get started!", + "toast.wildfire_gender.get_started": "Press %s to get started!", + + "wildfire_gender.armor.tooltip": "+1 Breast Support", "wildfire_gender.player_list.title": "Female Gender Mod", "wildfire_gender.player_list.settings_button": "Settings", @@ -22,7 +24,7 @@ "wildfire_gender.always_show_list.always": "Always", "wildfire_gender.always_show_list.always.tooltip": "The synced player list will always show", - "wildfire_gender.wardrobe.title": "Wildfire's Female Gender Mod", + "wildfire_gender.wardrobe.title": "Female Gender Mod", "wildfire_gender.breast_customization.tab_customization": "Customization", "wildfire_gender.breast_customization.tab_physics": "Breast Physics", "wildfire_gender.breast_customization.tab_miscellaneous": "Miscellaneous", @@ -71,7 +73,7 @@ "wildfire_gender.cancer_awareness.title": "Hey, it's Breast Cancer Awareness Month!", - "wildfire_gender.first_time_setup.title": "Welcome to Wildfire's Female Gender Mod!", + "wildfire_gender.first_time_setup.title": "Welcome to the Female Gender Mod!", "wildfire_gender.first_time_setup.description": "Would you like to enable cloud server syncing for your gender settings? This feature allows other players to view your customized gender appearance, even if the server doesn't have the mod installed.", "wildfire_gender.first_time_setup.notice": "You can always change this setting later in the mod menu.", "wildfire_gender.first_time_setup.enable": "Enable Cloud Syncing", From 8ee999a39b2e4737be959bfd51f3d74698c56c65 Mon Sep 17 00:00:00 2001 From: celeste Date: Wed, 4 Dec 2024 22:33:54 -0700 Subject: [PATCH 236/238] migrate mixins over to being event invokers --- build.gradle | 1 - .../events/ArmorStandInteractEvents.java | 67 +++++++++++ .../events/ArmorStatsTooltipEvent.java | 47 ++++++++ .../wildfire/events/EntityHurtSoundEvent.java | 41 +++++++ .../com/wildfire/events/EntityTickEvent.java | 40 +++++++ .../java/com/wildfire/gui/WildfireToast.java | 20 +++- .../wildfire/main/WildfireEventHandler.java | 105 +++++++++++++----- .../wildfire/main/config/GlobalConfig.java | 4 + .../main/entitydata/BreastDataComponent.java | 1 + .../main/entitydata/EntityConfig.java | 7 ++ .../mixins/ArmorStandEntityMixin.java | 27 +---- ...sicsTickMixin.java => ItemStackMixin.java} | 37 +++--- .../wildfire/mixins/LivingEntityMixin.java | 36 +++--- .../resources/wildfire_gender.mixins.json | 2 +- 14 files changed, 351 insertions(+), 84 deletions(-) create mode 100644 src/main/java/com/wildfire/events/ArmorStandInteractEvents.java create mode 100644 src/main/java/com/wildfire/events/ArmorStatsTooltipEvent.java create mode 100644 src/main/java/com/wildfire/events/EntityHurtSoundEvent.java create mode 100644 src/main/java/com/wildfire/events/EntityTickEvent.java rename src/main/java/com/wildfire/mixins/{BreastPhysicsTickMixin.java => ItemStackMixin.java} (53%) diff --git a/build.gradle b/build.gradle index 0555e967..5e04ee5d 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,6 @@ dependencies { modImplementation fabricApi.module("fabric-networking-api-v1", project.fabric_version) modImplementation fabricApi.module("fabric-key-binding-api-v1", project.fabric_version) modImplementation fabricApi.module("fabric-lifecycle-events-v1", project.fabric_version) - modImplementation fabricApi.module("fabric-item-api-v1", project.fabric_version) modImplementation fabricApi.module("fabric-rendering-v1", project.fabric_version) modImplementation fabricApi.module("fabric-resource-loader-v0", project.fabric_version) modRuntimeOnly fabricApi.module("fabric-registry-sync-v0", project.fabric_version) diff --git a/src/main/java/com/wildfire/events/ArmorStandInteractEvents.java b/src/main/java/com/wildfire/events/ArmorStandInteractEvents.java new file mode 100644 index 00000000..7c852652 --- /dev/null +++ b/src/main/java/com/wildfire/events/ArmorStandInteractEvents.java @@ -0,0 +1,67 @@ +/* + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.wildfire.events; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; + +/** + * Events invoked when a player interacts with the {@link EquipmentSlot#CHEST chest slot} on an armor stand + */ +public final class ArmorStandInteractEvents { + private ArmorStandInteractEvents() { + throw new UnsupportedOperationException(); + } + + /** + * Event invoked when a player equips an item onto an armor stand's {@link EquipmentSlot#CHEST chest slot} + */ + public static final Event EQUIP = EventFactory.createArrayBacked(EquipItem.class, listeners -> (player, item) -> { + for(var listener : listeners) { + listener.onEquip(player, item); + } + }); + + // this doesn't have the same chest slot item guarantee as the above event purely because checking that + // is a lot more work, when all we're using this for is checking if we need to remove our nbt from the item, + // which is already only applicable to chest items. + /** + * Event invoked when an item is removed from an armor stand + * + * @apiNote The provided {@link ItemStack} is not guaranteed to be a {@link EquipmentSlot#CHEST chest slot} item. + */ + public static final Event REMOVE = EventFactory.createArrayBacked(RemoveItem.class, listeners -> item -> { + for(var listener : listeners) { + listener.onRemove(item); + } + }); + + @FunctionalInterface + public interface EquipItem { + void onEquip(PlayerEntity player, ItemStack item); + } + + @FunctionalInterface + public interface RemoveItem { + void onRemove(ItemStack item); + } +} diff --git a/src/main/java/com/wildfire/events/ArmorStatsTooltipEvent.java b/src/main/java/com/wildfire/events/ArmorStatsTooltipEvent.java new file mode 100644 index 00000000..fbd8dc73 --- /dev/null +++ b/src/main/java/com/wildfire/events/ArmorStatsTooltipEvent.java @@ -0,0 +1,47 @@ +/* + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.wildfire.events; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.text.Text; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Consumer; + +/** + * Event invoked when an armor item is appending its stats to a tooltip. + *
+ * This is invoked after all other stats have already been added, but before any other tooltip lines have been added. + */ +@FunctionalInterface +@Environment(EnvType.CLIENT) +public interface ArmorStatsTooltipEvent { + Event EVENT = EventFactory.createArrayBacked(ArmorStatsTooltipEvent.class, listeners -> (item, tooltip, player) -> { + for(var listener : listeners) { + listener.appendTooltips(item, tooltip, player); + } + }); + + void appendTooltips(ItemStack item, Consumer tooltip, @Nullable PlayerEntity player); +} diff --git a/src/main/java/com/wildfire/events/EntityHurtSoundEvent.java b/src/main/java/com/wildfire/events/EntityHurtSoundEvent.java new file mode 100644 index 00000000..808b2455 --- /dev/null +++ b/src/main/java/com/wildfire/events/EntityHurtSoundEvent.java @@ -0,0 +1,41 @@ +/* + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.wildfire.events; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.damage.DamageSource; + +/** + * Event invoked when any {@link LivingEntity} plays a hurt sound. + */ +@FunctionalInterface +@Environment(EnvType.CLIENT) +public interface EntityHurtSoundEvent { + Event EVENT = EventFactory.createArrayBacked(EntityHurtSoundEvent.class, listeners -> (entity, source) -> { + for(var listener : listeners) { + listener.onHurt(entity, source); + } + }); + + void onHurt(LivingEntity entity, DamageSource source); +} diff --git a/src/main/java/com/wildfire/events/EntityTickEvent.java b/src/main/java/com/wildfire/events/EntityTickEvent.java new file mode 100644 index 00000000..97bd1e33 --- /dev/null +++ b/src/main/java/com/wildfire/events/EntityTickEvent.java @@ -0,0 +1,40 @@ +/* + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.wildfire.events; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.entity.LivingEntity; + +/** + * Event invoked when any {@link LivingEntity} ticks on the client. + */ +@FunctionalInterface +@Environment(EnvType.CLIENT) +public interface EntityTickEvent { + Event EVENT = EventFactory.createArrayBacked(EntityTickEvent.class, listeners -> entity -> { + for(var listener : listeners) { + listener.onTick(entity); + } + }); + + void onTick(LivingEntity entity); +} diff --git a/src/main/java/com/wildfire/gui/WildfireToast.java b/src/main/java/com/wildfire/gui/WildfireToast.java index e20162f4..dd5fe5b3 100644 --- a/src/main/java/com/wildfire/gui/WildfireToast.java +++ b/src/main/java/com/wildfire/gui/WildfireToast.java @@ -1,3 +1,21 @@ +/* + * Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + * Copyright (C) 2023-present WildfireRomeo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.wildfire.gui; import java.util.ArrayList; @@ -37,7 +55,7 @@ public class WildfireToast implements Toast { private final int displayDuration; public WildfireToast(TextRenderer textRenderer, Text title, @Nullable Text description, boolean hasProgressBar, int i) { - this.text = new ArrayList(2); + this.text = new ArrayList<>(2); this.text.addAll(textRenderer.wrapLines(title.copy().withColor(Colors.PURPLE), 126)); if (description != null) { this.text.addAll(textRenderer.wrapLines(description, 126)); diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index 4fe1cba6..ef3f1734 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -18,12 +18,16 @@ package com.wildfire.main; +import com.wildfire.events.ArmorStandInteractEvents; +import com.wildfire.events.ArmorStatsTooltipEvent; +import com.wildfire.events.EntityHurtSoundEvent; +import com.wildfire.events.EntityTickEvent; import com.wildfire.gui.GuiUtils; -import com.wildfire.gui.WildfireToast; import com.wildfire.gui.screen.WardrobeBrowserScreen; import com.wildfire.gui.screen.WildfireFirstTimeSetupScreen; import com.wildfire.main.cloud.CloudSync; import com.wildfire.main.config.GlobalConfig; +import com.wildfire.main.entitydata.BreastDataComponent; import com.wildfire.main.entitydata.EntityConfig; import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.networking.ServerboundSyncPacket; @@ -34,7 +38,6 @@ import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; -import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback; @@ -54,34 +57,30 @@ import net.minecraft.client.render.entity.EntityRendererFactory; import net.minecraft.client.render.entity.LivingEntityRenderer; import net.minecraft.client.render.entity.PlayerEntityRenderer; -import net.minecraft.client.toast.SystemToast; -import net.minecraft.client.toast.ToastManager; -import net.minecraft.client.util.InputUtil; import net.minecraft.component.DataComponentTypes; -import net.minecraft.component.type.EquippableComponent; -import net.minecraft.component.type.FoodComponent; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.decoration.ArmorStandEntity; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ArmorItem; -import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.item.Items; -import net.minecraft.item.tooltip.TooltipType; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.sound.SoundEvent; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import net.minecraft.util.Util; import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; import org.lwjgl.glfw.GLFW; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.function.Consumer; public final class WildfireEventHandler { private WildfireEventHandler() { @@ -95,6 +94,7 @@ private WildfireEventHandler() { public static KeyBinding getConfigKeybind() { return CONFIG_KEYBIND; } + static { if(FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { // this has to be wrapped in a lambda to ensure that a dedicated server won't crash during startup @@ -121,6 +121,8 @@ public static KeyBinding getConfigKeybind() { public static void registerCommonEvents() { EntityTrackingEvents.START_TRACKING.register(WildfireEventHandler::onBeginTracking); ServerPlayConnectionEvents.DISCONNECT.register(WildfireEventHandler::playerDisconnected); + ArmorStandInteractEvents.EQUIP.register(WildfireEventHandler::onEquipArmorStand); + ArmorStandInteractEvents.REMOVE.register(BreastDataComponent::removeFromStack); } /** @@ -134,24 +136,20 @@ public static void registerClientEvents() { ClientPlayConnectionEvents.JOIN.register(WildfireEventHandler::clientJoin); LivingEntityFeatureRendererRegistrationCallback.EVENT.register(WildfireEventHandler::registerRenderLayers); HudRenderCallback.EVENT.register(WildfireEventHandler::renderHud); - ItemTooltipCallback.EVENT.register(WildfireEventHandler::renderTooltip); + ArmorStatsTooltipEvent.EVENT.register(WildfireEventHandler::renderTooltip); + EntityHurtSoundEvent.EVENT.register(WildfireEventHandler::onEntityHurt); + EntityTickEvent.EVENT.register(WildfireEventHandler::onEntityTick); } @Environment(EnvType.CLIENT) - private static void renderTooltip(ItemStack stack, Item.TooltipContext tooltipContext, TooltipType type, List lines) { - if(MinecraftClient.getInstance().player == null) return; - PlayerConfig pCfg = WildfireGender.getPlayerById(MinecraftClient.getInstance().player.getUuid()); - if(pCfg == null || pCfg.getGender() == Gender.MALE) return; - + private static void renderTooltip(ItemStack item, Consumer tooltipAppender, @Nullable PlayerEntity player) { + if(player == null || !GlobalConfig.INSTANCE.get(GlobalConfig.ARMOR_STAT)) return; + var playerConfig = WildfireGender.getPlayerById(player.getUuid()); + if(playerConfig == null || !playerConfig.getGender().canHaveBreasts()) return; - if (stack.getItem() instanceof ArmorItem armorItem) { - EquippableComponent equippableComponent = armorItem.getComponents().get(DataComponentTypes.EQUIPPABLE); - if(equippableComponent == null) return; - - if(equippableComponent.slot() == EquipmentSlot.CHEST) { - lines.add(3, Text.translatable("wildfire_gender.armor.tooltip") - .formatted(Formatting.LIGHT_PURPLE)); - } + var equippableComponent = item.get(DataComponentTypes.EQUIPPABLE); + if(equippableComponent != null && equippableComponent.slot() == EquipmentSlot.CHEST) { + tooltipAppender.accept(Text.translatable("wildfire_gender.armor.tooltip").formatted(Formatting.LIGHT_PURPLE)); } } @@ -228,7 +226,6 @@ private static void onClientTick(MinecraftClient client) { if(clientConfig != null) clientConfig.attemptCloudSync(); } - if(TOGGLE_KEYBIND.wasPressed() && client.currentScreen == null) { GlobalConfig.RENDER_BREASTS ^= true; } @@ -249,6 +246,7 @@ private static void clientDisconnect(ClientPlayNetworkHandler networkHandler, Mi WildfireGender.CACHE.invalidateAll(); EntityConfig.CACHE.invalidateAll(); } + @Environment(EnvType.CLIENT) private static void clientJoin(ClientPlayNetworkHandler var1, PacketSender var2, MinecraftClient client) { if (client.player == null) return; @@ -282,6 +280,61 @@ private static void onBeginTracking(Entity tracked, ServerPlayerEntity syncTo) { } } + /** + * Play the relevant mod hurt sound when a player takes damage + */ + @Environment(EnvType.CLIENT) + private static void onEntityHurt(LivingEntity entity, DamageSource damageSource) { + MinecraftClient client = MinecraftClient.getInstance(); + if(client.player == null || client.world == null) return; + if(!(entity instanceof PlayerEntity player) || !player.getWorld().isClient()) return; + + PlayerConfig genderPlayer = WildfireGender.getPlayerById(player.getUuid()); + if(genderPlayer == null || !genderPlayer.hasHurtSounds()) return; + + SoundEvent hurtSound = genderPlayer.getGender().getHurtSound(); + if(hurtSound != null) { + float pitchVariation = (player.getRandom().nextFloat() - player.getRandom().nextFloat()) * 0.2F; + player.playSound(hurtSound, 1f, pitchVariation + genderPlayer.getVoicePitch()); + } + } + + /** + * Tick breast physics on entity tick + */ + @Environment(EnvType.CLIENT) + private static void onEntityTick(LivingEntity entity) { + if(EntityConfig.isSupportedEntity(entity)) { + EntityConfig cfg = EntityConfig.getEntity(entity); + if(entity instanceof ArmorStandEntity) { + cfg.readFromStack(entity.getEquippedStack(EquipmentSlot.CHEST)); + } + cfg.tickBreastPhysics(entity); + } + } + + /** + * Apply player settings to chestplates equipped onto armor stands + */ + private static void onEquipArmorStand(PlayerEntity player, ItemStack item) { + PlayerConfig playerConfig = WildfireGender.getPlayerById(player.getUuid()); + if(playerConfig == null) { + // while we shouldn't have our tag on the stack still, we're still checking to catch any armor + // that may still have the tag from older versions, or from potential cross-mod interactions + // which allow for removing items from armor stands without calling the vanilla + // #equip and/or #onBreak methods + BreastDataComponent.removeFromStack(item); + return; + } + + // Note that we always attach player data to the item stack as a server has no concept of resource packs, + // making it impossible to compare against any armor data that isn't registered through the mod API. + BreastDataComponent component = BreastDataComponent.fromPlayer(player, playerConfig); + if(component != null) { + component.write(player.getWorld().getRegistryManager(), item); + } + } + public static List collectPlayerEntries() { if(MinecraftClient.getInstance().player == null) return new ArrayList<>(); diff --git a/src/main/java/com/wildfire/main/config/GlobalConfig.java b/src/main/java/com/wildfire/main/config/GlobalConfig.java index 434a770c..5ab5471d 100644 --- a/src/main/java/com/wildfire/main/config/GlobalConfig.java +++ b/src/main/java/com/wildfire/main/config/GlobalConfig.java @@ -40,6 +40,9 @@ private GlobalConfig() { public static final EnumConfigKey ALWAYS_SHOW_LIST = new EnumConfigKey<>("alwaysShowList", ShowPlayerListMode.MOD_UI_ONLY, ShowPlayerListMode.BY_ID); + // TODO enable by default? add a ui option? + public static final BooleanConfigKey ARMOR_STAT = new BooleanConfigKey("armor_stat", false); + static { INSTANCE.setDefault(FIRST_TIME_LOAD); INSTANCE.setDefault(CLOUD_SYNC_ENABLED); @@ -47,6 +50,7 @@ private GlobalConfig() { INSTANCE.setDefault(CLOUD_SERVER); INSTANCE.setDefault(SYNC_VERBOSITY); INSTANCE.setDefault(ALWAYS_SHOW_LIST); + INSTANCE.setDefault(ARMOR_STAT); if(!INSTANCE.exists()) { INSTANCE.save(); } diff --git a/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java b/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java index deb3dcef..03a61837 100644 --- a/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java +++ b/src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java @@ -105,6 +105,7 @@ public void write(RegistryWrapper.WrapperLookup lookup, ItemStack stack) { } public static void removeFromStack(ItemStack stack) { + if(stack.isEmpty()) return; NbtComponent component = stack.get(DataComponentTypes.CUSTOM_DATA); if(component != null && component.contains(KEY)) { NbtComponent.set(DataComponentTypes.CUSTOM_DATA, stack, nbt -> nbt.remove(KEY)); diff --git a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java index cf1513c7..25105700 100644 --- a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java +++ b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java @@ -120,6 +120,13 @@ public void readFromStack(@NotNull ItemStack chestplate) { this.jacketLayer = fromComponent.jacket(); } + /** + * @return {@code true} if the mod has support for the provided entity + */ + public static boolean isSupportedEntity(LivingEntity entity) { + return entity instanceof PlayerEntity || entity instanceof ArmorStandEntity; + } + /** * Get the configuration for a given entity * diff --git a/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java b/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java index acff2681..b8967098 100644 --- a/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java +++ b/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java @@ -19,9 +19,7 @@ package com.wildfire.mixins; import com.llamalad7.mixinextras.sugar.Local; -import com.wildfire.main.WildfireGender; -import com.wildfire.main.entitydata.BreastDataComponent; -import com.wildfire.main.entitydata.PlayerConfig; +import com.wildfire.events.ArmorStandInteractEvents; import net.minecraft.entity.EntityType; import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.LivingEntity; @@ -35,7 +33,7 @@ @Mixin(ArmorStandEntity.class) abstract class ArmorStandEntityMixin extends LivingEntity { - protected ArmorStandEntityMixin(EntityType entityType, World world) { + private ArmorStandEntityMixin(EntityType entityType, World world) { super(entityType, world); } @@ -53,22 +51,7 @@ protected ArmorStandEntityMixin(EntityType entityType, W return stack; } - PlayerConfig playerConfig = WildfireGender.getPlayerById(player.getUuid()); - if(playerConfig == null) { - // while we shouldn't have our tag on the stack still, we're still checking to catch any armor - // that may still have the tag from older versions, or from potential cross-mod interactions - // which allow for removing items from armor stands without calling the vanilla - // #equip and/or #onBreak methods - BreastDataComponent.removeFromStack(stack); - return stack; - } - - // Note that we always attach player data to the item stack as a server has no concept of resource packs, - // making it impossible to compare against any armor data that isn't registered through the mod API. - BreastDataComponent component = BreastDataComponent.fromPlayer(player, playerConfig); - if(component != null) { - component.write(player.getWorld().getRegistryManager(), stack); - } + ArmorStandInteractEvents.EQUIP.invoker().onEquip(player, stack); return stack; } @@ -83,7 +66,7 @@ protected ArmorStandEntityMixin(EntityType entityType, W ) public ItemStack wildfiregender$removeBreastDataOnReplace(ItemStack stack, @Local(argsOnly = true) PlayerEntity player) { if(!player.getWorld().isClient()) { - BreastDataComponent.removeFromStack(stack); + ArmorStandInteractEvents.REMOVE.invoker().onRemove(stack); } return stack; } @@ -98,7 +81,7 @@ protected ArmorStandEntityMixin(EntityType entityType, W ) public ItemStack wildfiregender$removeBreastDataOnBreak(ItemStack stack) { if(!getWorld().isClient()) { - BreastDataComponent.removeFromStack(stack); + ArmorStandInteractEvents.REMOVE.invoker().onRemove(stack); } return stack; } diff --git a/src/main/java/com/wildfire/mixins/BreastPhysicsTickMixin.java b/src/main/java/com/wildfire/mixins/ItemStackMixin.java similarity index 53% rename from src/main/java/com/wildfire/mixins/BreastPhysicsTickMixin.java rename to src/main/java/com/wildfire/mixins/ItemStackMixin.java index af9f385a..06e2729f 100644 --- a/src/main/java/com/wildfire/mixins/BreastPhysicsTickMixin.java +++ b/src/main/java/com/wildfire/mixins/ItemStackMixin.java @@ -18,31 +18,38 @@ package com.wildfire.mixins; -import com.wildfire.main.entitydata.EntityConfig; +import com.llamalad7.mixinextras.sugar.Local; +import com.wildfire.events.ArmorStatsTooltipEvent; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; -import net.minecraft.entity.EquipmentSlot; -import net.minecraft.entity.LivingEntity; -import net.minecraft.entity.decoration.ArmorStandEntity; +import net.minecraft.component.type.AttributeModifiersComponent; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ArmorItem; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.text.Text; +import org.jetbrains.annotations.Nullable; 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.CallbackInfo; +import java.util.function.Consumer; + +@Mixin(ItemStack.class) @Environment(EnvType.CLIENT) -@Mixin({ArmorStandEntity.class, PlayerEntity.class}) -abstract class BreastPhysicsTickMixin { - @Inject(at = @At("TAIL"), method = "tick") - public void wildfiregender$tickBreastPhysics(CallbackInfo info) { - LivingEntity entity = (LivingEntity)(Object)this; - // Ignore ticks from the singleplayer integrated server - if(!entity.getWorld().isClient()) return; +abstract class ItemStackMixin { + @Shadow public abstract Item getItem(); - EntityConfig cfg = EntityConfig.getEntity(entity); - if(entity instanceof ArmorStandEntity) { - cfg.readFromStack(entity.getEquippedStack(EquipmentSlot.CHEST)); + @Inject( + method = "appendAttributeModifiersTooltip", + at = @At("TAIL") + ) + public void wildfiregender$armorStats(Consumer textConsumer, @Nullable PlayerEntity player, CallbackInfo ci, @Local AttributeModifiersComponent attributeModifiersComponent) { + if(!attributeModifiersComponent.showInTooltip() || attributeModifiersComponent.modifiers().isEmpty()) return; + if(this.getItem() instanceof ArmorItem) { + ArmorStatsTooltipEvent.EVENT.invoker().appendTooltips((ItemStack)(Object)this, textConsumer, player); } - cfg.tickBreastPhysics(entity); } } diff --git a/src/main/java/com/wildfire/mixins/LivingEntityMixin.java b/src/main/java/com/wildfire/mixins/LivingEntityMixin.java index 84b99643..1b1498e1 100644 --- a/src/main/java/com/wildfire/mixins/LivingEntityMixin.java +++ b/src/main/java/com/wildfire/mixins/LivingEntityMixin.java @@ -18,15 +18,15 @@ package com.wildfire.mixins; -import com.wildfire.main.WildfireGender; -import com.wildfire.main.entitydata.PlayerConfig; +import com.wildfire.events.EntityHurtSoundEvent; +import com.wildfire.events.EntityTickEvent; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; -import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.damage.DamageSource; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.sound.SoundEvent; +import net.minecraft.world.World; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -34,7 +34,13 @@ @Mixin(LivingEntity.class) @Environment(EnvType.CLIENT) -abstract class LivingEntityMixin { +abstract class LivingEntityMixin extends Entity { + private LivingEntityMixin(EntityType type, World world) { + super(type, world); + } + + // TODO would it be worth adding an extra @Inject to #animateDamage(float) to account for servers (namely hypixel) + // using DamageTiltS2CPacket instead of the standard entity damage packet? @Inject( method = "onDamaged", at = @At( @@ -43,18 +49,12 @@ abstract class LivingEntityMixin { ) ) public void wildfiregender$playGenderHurtSound(DamageSource damageSource, CallbackInfo ci) { - MinecraftClient client = MinecraftClient.getInstance(); - if(client.player == null || client.world == null) return; - - if((LivingEntity)(Object)this instanceof PlayerEntity player && player.getWorld().isClient()) { - PlayerConfig genderPlayer = WildfireGender.getPlayerById(player.getUuid()); - if(genderPlayer == null || !genderPlayer.hasHurtSounds()) return; + EntityHurtSoundEvent.EVENT.invoker().onHurt((LivingEntity)(Object)this, damageSource); + } - SoundEvent hurtSound = genderPlayer.getGender().getHurtSound(); - if(hurtSound != null) { - float pitch = (player.getRandom().nextFloat() - player.getRandom().nextFloat()) * 0.2F /*+ 1.0F*/; // +1 is from getVoicePitch() - player.playSound(hurtSound, 1f, pitch + genderPlayer.getVoicePitch()); - } - } + @Inject(method = "tick", at = @At("TAIL")) + public void wildfiregender$onTick(CallbackInfo ci) { + if(!getWorld().isClient()) return; // ignore ticks from the singleplayer integrated server + EntityTickEvent.EVENT.invoker().onTick((LivingEntity)(Object)this); } } diff --git a/src/main/resources/wildfire_gender.mixins.json b/src/main/resources/wildfire_gender.mixins.json index a1e24d18..bbf867d2 100644 --- a/src/main/resources/wildfire_gender.mixins.json +++ b/src/main/resources/wildfire_gender.mixins.json @@ -7,7 +7,7 @@ "ArmorStandEntityMixin" ], "client": [ - "BreastPhysicsTickMixin", + "ItemStackMixin", "LivingEntityMixin", "accessors.EquipmentRendererAccessor", "accessors.TextureManagerAccessor", From 91b6fa85622c35173bfaa00da09a382afee391b8 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Thu, 5 Dec 2024 17:57:04 -0500 Subject: [PATCH 237/238] Changes to UI - Remove unused GUI containers from older mod versions. - Change armor stat default to true - Added "Show Armor Tooltip" button to Miscellaneous tab. TODO: Make a GUI settings menu? (or just make the Miscellaneous tab larger...) --- .../WildfireBreastCustomizationScreen.java | 15 +++++++++++++-- .../wildfire/main/config/GlobalConfig.java | 2 +- .../assets/wildfire_gender/lang/en_us.json | 1 + .../textures/gui/settings_bg.png | Bin 670 -> 0 bytes .../textures/gui/tabs/miscellaneous_tab.png | Bin 1539 -> 1555 bytes .../textures/gui/wardrobe_bg_default.png | Bin 719 -> 0 bytes 6 files changed, 15 insertions(+), 3 deletions(-) delete mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/settings_bg.png delete mode 100644 src/main/resources/assets/wildfire_gender/textures/gui/wardrobe_bg_default.png diff --git a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java index d9efc603..896687ee 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java @@ -24,6 +24,7 @@ import com.wildfire.gui.WildfireSlider; import com.wildfire.main.Gender; import com.wildfire.main.WildfireGender; +import com.wildfire.main.config.GlobalConfig; import com.wildfire.main.entitydata.Breasts; import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.config.Configuration; @@ -64,7 +65,7 @@ public class WildfireBreastCustomizationScreen extends BaseWildfireScreen { //Breast Physics Tab private WildfireSlider bounceSlider, floppySlider; - private WildfireButton btnHideInArmor, btnOverrideArmorPhys, btnBreastPhysics; + private WildfireButton btnHideInArmor, btnOverrideArmorPhys, btnBreastPhysics, btnShowTooltips; //Miscellaneous Tab private WildfireSlider voicePitchSlider; @@ -245,6 +246,15 @@ public void init() { } })); + var config = GlobalConfig.INSTANCE; + + this.addDrawableChild(btnShowTooltips = new WildfireButton(this.width / 2 - 36, tabOffsetY + 70, 166, 20, + Text.translatable("wildfire_gender.char_settings.show_armor_stat", config.get(GlobalConfig.ARMOR_STAT) ? ENABLED : DISABLED), button -> { + config.set(GlobalConfig.ARMOR_STAT, !config.get(GlobalConfig.ARMOR_STAT)); + config.save(); + button.setMessage(Text.translatable("wildfire_gender.char_settings.show_armor_stat", config.get(GlobalConfig.ARMOR_STAT) ? ENABLED : DISABLED)); + })); + //Preset Tab Below PRESET_LIST = new WildfireBreastPresetList(this, 156, (j - 48)); PRESET_LIST.setX(this.width / 2 + 30); @@ -277,6 +287,7 @@ private void updateTabs() { this.btnHideInArmor.visible = currentTab == 2; this.btnHurtSounds.visible = currentTab == 2; this.voicePitchSlider.visible = currentTab == 2; + this.btnShowTooltips.visible = currentTab == 2; } @@ -329,7 +340,7 @@ public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delt } else if(currentTab == 1) { ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND_PHYSICS, (this.width) / 2 - 42, (this.height) / 2 - 43, 0, 0, 178, 104, 512, 512); } else if(currentTab == 2) { - ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND_MISC, (this.width) / 2 - 42, (this.height) / 2 - 43, 0, 0, 178, 80, 512, 512); + ctx.drawTexture(RenderLayer::getGuiTextured, BACKGROUND_MISC, (this.width) / 2 - 42, (this.height) / 2 - 43, 0, 0, 178, 104, 512, 512); } int x = this.width / 2; diff --git a/src/main/java/com/wildfire/main/config/GlobalConfig.java b/src/main/java/com/wildfire/main/config/GlobalConfig.java index 5ab5471d..c7404874 100644 --- a/src/main/java/com/wildfire/main/config/GlobalConfig.java +++ b/src/main/java/com/wildfire/main/config/GlobalConfig.java @@ -41,7 +41,7 @@ private GlobalConfig() { public static final EnumConfigKey ALWAYS_SHOW_LIST = new EnumConfigKey<>("alwaysShowList", ShowPlayerListMode.MOD_UI_ONLY, ShowPlayerListMode.BY_ID); // TODO enable by default? add a ui option? - public static final BooleanConfigKey ARMOR_STAT = new BooleanConfigKey("armor_stat", false); + public static final BooleanConfigKey ARMOR_STAT = new BooleanConfigKey("armor_stat", true); static { INSTANCE.setDefault(FIRST_TIME_LOAD); diff --git a/src/main/resources/assets/wildfire_gender/lang/en_us.json b/src/main/resources/assets/wildfire_gender/lang/en_us.json index ae548c68..9c2483a3 100644 --- a/src/main/resources/assets/wildfire_gender/lang/en_us.json +++ b/src/main/resources/assets/wildfire_gender/lang/en_us.json @@ -48,6 +48,7 @@ "wildfire_gender.tooltip.override_armor_physics.line2": "This is intended for use with resource packs that hide armor, or any similar minimal armor packs", "wildfire_gender.char_settings.hide_in_armor": "Hide In Armor: %s", + "wildfire_gender.char_settings.show_armor_stat": "Show Armor Tooltip: %s", "wildfire_gender.char_settings.hurt_sounds": "Female Hurt Sounds: %s", "wildfire_gender.tooltip.hurt_sounds": "Your character will play a female hurt sound when taking damage if your gender is set to either Female or Other", diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/settings_bg.png b/src/main/resources/assets/wildfire_gender/textures/gui/settings_bg.png deleted file mode 100644 index ca83893c90e53c91348f549f55a512f7f2b68412..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 670 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911MRQ8&P5D>38$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&eBzI1?3h%1o(|NsB7W5+^6L(R?2!@|P2xVW;jvw`B%3a{P)Qk*40e!)OB zV89UUUm*k%EbxddW?{?7RI&Z$5Q4 z@he}r)o+~tHlz5?#XFa0JT7#f&2mil|Nn}2f)(c^ii7$ z#yK(D9prN`n9v_D2xB|kv(bXF8*2V^!PpF+o&|y&0|rkXhqJ)g6U_TT0S^WW^81xw z?1nv+pm2u(s87J0CubLfT+wis;r-&<`~|>x0QuyFRKs<>21~p9`xf~!6hB$$eEZXX z`3B2(8uRxp^PRs}oxySot3vxWh9?)q8f0!UO>mB8D9Ye=;LBw^u~2t#NPcD5KQro> UoU>IWC|xjky85}Sb4q9e0MyIJUjP6A diff --git a/src/main/resources/assets/wildfire_gender/textures/gui/tabs/miscellaneous_tab.png b/src/main/resources/assets/wildfire_gender/textures/gui/tabs/miscellaneous_tab.png index a5c45650d8872502499b37e83f6b4be81c2ac16a..af4e4b316342138664019cc92e87bf391ab6d1d2 100644 GIT binary patch delta 175 zcmV;g08syf43i9yk|k_OL_t(|UhUn%Z6h%d072S1+nayHCwUpd2r$uA5ePF4kPj}A zx6BL$%zM`*@GYJf3jqw1aRMzHw*%OZ`TsfrjJpFkhX95NAZ{N4XYgIXJeR{u? zJAnP}{;w0jxI2Jz2w>P@0XzgSY+dU$)q@CN)N`C`PxcJkju9MuK%@hNTvjGG80<-V}1pydo*YzKWl0LNsS3six000038$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&eBzI1?3h%1o(|NsB7W5+^6L(R?2!@|O{v$MIlK&EnZZq)`-oFzei!9X=& zz!2S7*-C+qaL?=h*M~dvQ$s4d&6;Uk4|!K8OuuWD#Ka z&acqGz{Jr|?)sOl;{Nj6RaWc$Z#@?L&+wr7(Xn^@4gJR7D~^B^{AL$$U;yd>5eIn9 z#Gss-E0(|c8-A~}mP=@0;!t3C_nu{k9QOv9`hR>Z4}{O%{rs<;;luHnkH2}hGZcIi zV1*k}(A*2>%&~QWnhcb_laLJKFr@Pb!t4}aFuxH2;~co@EP~B7j4&&7{A(XCzqZXx z=RhB;0{f4T#=guZXD|Ocv)fGKfX&A{)uvbehBrLDpKyI2pWM=ea^?S-61K7)$l`9e xD#ox{mmw^gVa+y%h+M`Ew}z^O8vBgr{*E4{2g9cQS`AD{44$rjF6*2UngAM%^fCYd From 234ce08472ba86fbc6f9d6cdb498baedf331f14e Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Thu, 5 Dec 2024 18:03:08 -0500 Subject: [PATCH 238/238] Push to 4.3.1 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 8cf924fd..cc6838b9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ yarn_build=1 loader_version=0.16.9 # Mod Properties -mod_version = 4.3 +mod_version = 4.3.1 maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod

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±k&%g8e5C6R%q625oX1DG3 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 h>=9o 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�WXS{`FghgOT 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 literal 0 HcmV?d00001 From b9cc8a48e8209810e24bf494e694c7c79b049324 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Wed, 29 Nov 2023 23:49:01 -0500 Subject: [PATCH 043/238] Breast Physics Improvements (was using a separate branch for this as I was going to completely rewrite it but I decided to just modify the existing code instead) Adjusted underboob texture grabbing slightly (need feedback) --- src/main/java/com/wildfire/physics/BreastPhysics.java | 10 +++++++++- src/main/java/com/wildfire/render/GenderLayer.java | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index 3ca02c6e..eb22a3fd 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -241,7 +241,7 @@ public void update(LivingEntity entity, IGenderArmor armor) { float delta = 2.25f - bounceAmount; //if(plr.isInWater()) delta = 0.75f - (1f * bounceAmount); //water resistance - float distanceFromMin = Math.abs(bounceVel + 0.5f) * 0.5f; + float distanceFromMin = Math.abs(bounceVel + 1.5f) * 0.5f; float distanceFromMax = Math.abs(bounceVel - 2.65f) * 0.5f; if(bounceVel < -0.5f) { @@ -269,6 +269,14 @@ public void update(LivingEntity entity, IGenderArmor armor) { this.wfg_bounceRotation = this.bounceRotVel; this.wfg_femaleBreastX = this.bounceVelX; this.wfg_femaleBreast = this.bounceVel; + + if(this.wfg_femaleBreast < -0.5f) this.wfg_femaleBreast = -0.5f; + if(this.wfg_femaleBreast > 1.5f) { + this.wfg_femaleBreast = 1.5f; + this.velocity = 0; + } + System.out.println(this.wfg_femaleBreast ); + } public float getBreastSize(float partialTicks) { diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index 966c6559..da83d713 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -172,7 +172,7 @@ protected boolean setupRender(T entity, EntityConfig entityConfig, float partial outwardAngle = (Math.round(breasts.getCleavage() * 100f) / 100f) * 100f; outwardAngle = Math.min(outwardAngle, 10); - float reducer = 0; + float reducer = -1; if(bSize < 0.84f) reducer++; if(bSize < 0.72f) reducer++; From 606eaca551a451884460a63b50e36d9eb5789906 Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 30 Nov 2023 13:21:37 -0700 Subject: [PATCH 044/238] 1.20.3-rc1 --- gradle.properties | 10 +++---- .../gui/WildfireBreastPresetList.java | 29 +++++-------------- .../java/com/wildfire/gui/WildfireButton.java | 2 +- .../java/com/wildfire/gui/WildfireSlider.java | 2 +- .../WildfireBreastCustomizationScreen.java | 7 +++-- src/main/resources/fabric.mod.json | 2 +- 6 files changed, 19 insertions(+), 33 deletions(-) diff --git a/gradle.properties b/gradle.properties index 8ac6f724..ba49db87 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,14 +3,14 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://fabricmc.net/develop -minecraft_version=1.20.2 -yarn_mappings=1.20.2+build.1 -loader_version=0.14.22 +minecraft_version=1.20.3-rc1 +yarn_mappings=1.20.3-rc1+build.2 +loader_version=0.15.0 # Mod Properties -mod_version = fabric-1.20.2-3.2 +mod_version = fabric-1.20.3-3.2 maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod # Dependencies -fabric_version=0.89.0+1.20.2 +fabric_version=0.91.1+1.20.3 diff --git a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java index df0ea1a0..a072c983 100644 --- a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java +++ b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java @@ -43,8 +43,8 @@ public BreastPresetListEntry(String name, BreastPresetConfiguration data) { private final int listWidth; private final WildfireBreastCustomizationScreen parent; - public WildfireBreastPresetList(WildfireBreastCustomizationScreen parent, int listWidth, int top, int bottom) { - super(MinecraftClient.getInstance(), 156, parent.height, top, bottom, 32); + public WildfireBreastPresetList(WildfireBreastCustomizationScreen parent, int listWidth, int top) { + super(MinecraftClient.getInstance(), 156, parent.height, top, 32); this.setRenderHeader(false, 0); this.setRenderBackground(false); this.parent = parent; @@ -69,16 +69,15 @@ protected void renderList(DrawContext context, int mouseX, int mouseY, float del for(int m = 0; m < l; ++m) { int n = this.getRowTop(m); int o = this.getRowBottom(m); - if (o >= this.top && n <= this.bottom) { + if (o >= this.getY() && n <= this.getBottom()) { this.renderEntry(context, mouseX, mouseY, delta, m, i, n, j, k); } } - } @Override protected int getRowTop(int index) { - return this.top - (int)this.getScrollAmount() + index * this.itemHeight + this.headerHeight; + return this.getY() - (int)this.getScrollAmount() + index * this.itemHeight + this.headerHeight; } @Override protected int getScrollbarPositionX() { @@ -104,13 +103,13 @@ public void refreshList() { if(this.client.world == null || this.client.player == null) return; - for(int i = 0; i < BREAST_PRESETS.length; i++) { - addEntry(new Entry(BREAST_PRESETS[i])); + for(BreastPresetListEntry breastPreset : BREAST_PRESETS) { + addEntry(new Entry(breastPreset)); } } @Override - public void appendNarrations(NarrationMessageBuilder builder) {} + protected void appendClickableNarrations(NarrationMessageBuilder builder) {} @Environment(EnvType.CLIENT) public class Entry extends EntryListWidget.Entry { @@ -159,18 +158,4 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { return false; } } - - - public int getLeft() { - return left; - } - public int getRight() { - return right; - } - public int getTop() { - return top; - } - public int getBottom() { - return bottom; - } } diff --git a/src/main/java/com/wildfire/gui/WildfireButton.java b/src/main/java/com/wildfire/gui/WildfireButton.java index d207c06c..851798f4 100644 --- a/src/main/java/com/wildfire/gui/WildfireButton.java +++ b/src/main/java/com/wildfire/gui/WildfireButton.java @@ -44,7 +44,7 @@ public WildfireButton(int x, int y, int w, int h, Text text, ButtonWidget.PressA } @Override - public void renderButton(DrawContext ctx, int mouseX, int mouseY, float partialTicks) { + protected void renderWidget(DrawContext ctx, int mouseX, int mouseY, float partialTicks) { MinecraftClient minecraft = MinecraftClient.getInstance(); TextRenderer font = minecraft.textRenderer; int clr = 0x444444 + (84 << 24); diff --git a/src/main/java/com/wildfire/gui/WildfireSlider.java b/src/main/java/com/wildfire/gui/WildfireSlider.java index 7fdf9959..5746f9cf 100644 --- a/src/main/java/com/wildfire/gui/WildfireSlider.java +++ b/src/main/java/com/wildfire/gui/WildfireSlider.java @@ -104,7 +104,7 @@ protected MutableText getNarrationMessage() { } @Override - public void renderButton(DrawContext ctx, int mouseX, int mouseY, float delta) { + protected void renderWidget(DrawContext ctx, int mouseX, int mouseY, float delta) { if (this.visible) { RenderSystem.disableDepthTest(); this.hovered = mouseX >= this.getX() && mouseY >= this.getY() && mouseX < this.getX() + this.width && mouseY < this.getY() + this.height; diff --git a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java index 95d28604..2eb6c19c 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java @@ -130,8 +130,9 @@ public void init() { //Preset Tab Below - PRESET_LIST = new WildfireBreastPresetList(this, 156, (j - 48), (j + 77)); - PRESET_LIST.setLeftPos(this.width / 2 + 30); + PRESET_LIST = new WildfireBreastPresetList(this, 156, (j - 48)); + PRESET_LIST.setX(this.width / 2 + 30); + PRESET_LIST.setHeight(125); this.addSelectableChild(this.PRESET_LIST); @@ -174,7 +175,7 @@ public void renderBackground(DrawContext ctx, int mouseX, int mouseY, float delt ctx.fill(x + 29, y - 63 - 21, x + 189, y - 60, 0x55000000); ctx.drawText(textRenderer, getTitle(), x + 32, y - 60 - 21, 0xFFFFFF, false); if(currentTab == 1) { - ctx.fill(PRESET_LIST.getLeft(), PRESET_LIST.getTop(), PRESET_LIST.getRight(), PRESET_LIST.getBottom(), 0x55000000); + ctx.fill(PRESET_LIST.getX(), PRESET_LIST.getY(), PRESET_LIST.getRight(), PRESET_LIST.getBottom(), 0x55000000); } } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 19c1b9cb..3eaf1e43 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -39,7 +39,7 @@ "depends": { "fabricloader": "*", "fabric-api": "*", - "minecraft": "~1.20.2", + "minecraft": "~1.20.3-rc.1", "java": ">=17" }, "conflicts": { From 6daa84d6eb4aa74854eee1760116e59921d20844 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Thu, 30 Nov 2023 22:27:16 -0500 Subject: [PATCH 045/238] Removed println spam --- src/main/java/com/wildfire/physics/BreastPhysics.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index eb22a3fd..2d3417ce 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -275,7 +275,6 @@ public void update(LivingEntity entity, IGenderArmor armor) { this.wfg_femaleBreast = 1.5f; this.velocity = 0; } - System.out.println(this.wfg_femaleBreast ); } From 4746745431c5d8c6f88203e3b9428583f11987ce Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 30 Nov 2023 13:29:55 -0700 Subject: [PATCH 046/238] Mark relevant classes as client-side only --- .../java/com/wildfire/gui/WildfireBreastPresetList.java | 1 + src/main/java/com/wildfire/gui/WildfireButton.java | 3 +++ src/main/java/com/wildfire/gui/WildfireSlider.java | 3 +++ .../java/com/wildfire/gui/screen/BaseWildfireScreen.java | 3 +++ .../com/wildfire/gui/screen/WardrobeBrowserScreen.java | 3 +++ .../gui/screen/WildfireBreastCustomizationScreen.java | 3 +++ .../gui/screen/WildfireCharacterSettingsScreen.java | 3 +++ src/main/java/com/wildfire/physics/BreastPhysics.java | 7 +++++-- src/main/java/com/wildfire/render/BreastSide.java | 4 ++++ src/main/java/com/wildfire/render/GenderArmorLayer.java | 3 +++ src/main/java/com/wildfire/render/GenderLayer.java | 5 ++++- .../java/com/wildfire/render/WildfireModelRenderer.java | 3 +++ 12 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java index a072c983..f94c1896 100644 --- a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java +++ b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java @@ -15,6 +15,7 @@ import java.util.ArrayList; +@Environment(EnvType.CLIENT) public class WildfireBreastPresetList extends EntryListWidget { public boolean active = true; diff --git a/src/main/java/com/wildfire/gui/WildfireButton.java b/src/main/java/com/wildfire/gui/WildfireButton.java index 851798f4..2fca842e 100644 --- a/src/main/java/com/wildfire/gui/WildfireButton.java +++ b/src/main/java/com/wildfire/gui/WildfireButton.java @@ -20,6 +20,8 @@ import com.mojang.blaze3d.systems.RenderSystem; import com.wildfire.main.WildfireHelper; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; @@ -27,6 +29,7 @@ import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.text.Text; +@Environment(EnvType.CLIENT) public class WildfireButton extends ButtonWidget { public boolean transparent = false; diff --git a/src/main/java/com/wildfire/gui/WildfireSlider.java b/src/main/java/com/wildfire/gui/WildfireSlider.java index 5746f9cf..76cb1128 100644 --- a/src/main/java/com/wildfire/gui/WildfireSlider.java +++ b/src/main/java/com/wildfire/gui/WildfireSlider.java @@ -23,6 +23,8 @@ import com.wildfire.main.config.FloatConfigKey; import it.unimi.dsi.fastutil.floats.Float2ObjectFunction; import it.unimi.dsi.fastutil.floats.FloatConsumer; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; @@ -33,6 +35,7 @@ import net.minecraft.util.math.MathHelper; import org.lwjgl.glfw.GLFW; +@Environment(EnvType.CLIENT) public class WildfireSlider extends ClickableWidget { private double value; private final double minValue; diff --git a/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java b/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java index ec0a7f56..20003519 100644 --- a/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java +++ b/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java @@ -22,9 +22,12 @@ import com.wildfire.main.WildfireGender; import java.util.UUID; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; import net.minecraft.client.gui.screen.Screen; import net.minecraft.text.Text; +@Environment(EnvType.CLIENT) public abstract class BaseWildfireScreen extends Screen { protected final UUID playerUUID; diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index b4ccad68..150aa188 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -28,6 +28,8 @@ import com.wildfire.main.entitydata.PlayerConfig; import com.mojang.blaze3d.systems.RenderSystem; import com.wildfire.main.WildfireHelper; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; @@ -42,6 +44,7 @@ import net.minecraft.util.Identifier; import org.joml.Quaternionf; +@Environment(EnvType.CLIENT) public class WardrobeBrowserScreen extends BaseWildfireScreen { private static final Identifier BACKGROUND_FEMALE = new Identifier(WildfireGender.MODID, "textures/gui/wardrobe_bg2.png"); private static final Identifier BACKGROUND = new Identifier(WildfireGender.MODID, "textures/gui/wardrobe_bg3.png"); diff --git a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java index 2eb6c19c..c9ff2916 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java @@ -27,6 +27,8 @@ import com.wildfire.main.config.Configuration; import com.wildfire.main.config.BreastPresetConfiguration; import it.unimi.dsi.fastutil.floats.FloatConsumer; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; @@ -37,6 +39,7 @@ import java.util.UUID; +@Environment(EnvType.CLIENT) public class WildfireBreastCustomizationScreen extends BaseWildfireScreen { private WildfireSlider breastSlider, xOffsetBoobSlider, yOffsetBoobSlider, zOffsetBoobSlider, cleavageSlider; diff --git a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java index dd0524ee..cc4025f1 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireCharacterSettingsScreen.java @@ -26,6 +26,8 @@ import com.wildfire.gui.WildfireButton; import com.wildfire.main.entitydata.PlayerConfig; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; @@ -35,6 +37,7 @@ import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; +@Environment(EnvType.CLIENT) public class WildfireCharacterSettingsScreen extends BaseWildfireScreen { private static final Text ENABLED = Text.translatable("wildfire_gender.label.enabled").formatted(Formatting.GREEN); diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index 3ca02c6e..ea32f9e4 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -21,9 +21,10 @@ import com.wildfire.api.IGenderArmor; import com.wildfire.main.entitydata.EntityConfig; import com.wildfire.main.WildfireHelper; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.entity.EntityPose; -import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.decoration.ArmorStandEntity; import net.minecraft.entity.passive.HorseEntity; @@ -31,7 +32,6 @@ import net.minecraft.entity.passive.StriderEntity; import net.minecraft.entity.vehicle.BoatEntity; import net.minecraft.entity.vehicle.MinecartEntity; -import net.minecraft.item.ItemStack; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; @@ -55,6 +55,9 @@ public BreastPhysics(EntityConfig entityConfig) { private int randomB = 1; private boolean alreadyFalling = false; + // this class cannot be blanket marked as client-side only, as this is referenced in the constructor for EntityConfig; + // as such, the best we can get here is marking this method as such. + @Environment(EnvType.CLIENT) public void update(LivingEntity entity, IGenderArmor armor) { if(entity instanceof ArmorStandEntity && !armor.armorStandsCopySettings()) { // optimization: skip physics on armor stands that either don't have a chestplate, diff --git a/src/main/java/com/wildfire/render/BreastSide.java b/src/main/java/com/wildfire/render/BreastSide.java index 3d094a7f..5827b3db 100644 --- a/src/main/java/com/wildfire/render/BreastSide.java +++ b/src/main/java/com/wildfire/render/BreastSide.java @@ -1,5 +1,9 @@ package com.wildfire.render; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +@Environment(EnvType.CLIENT) public enum BreastSide { LEFT, RIGHT } diff --git a/src/main/java/com/wildfire/render/GenderArmorLayer.java b/src/main/java/com/wildfire/render/GenderArmorLayer.java index a4ccac30..be030bcd 100644 --- a/src/main/java/com/wildfire/render/GenderArmorLayer.java +++ b/src/main/java/com/wildfire/render/GenderArmorLayer.java @@ -21,6 +21,8 @@ import com.wildfire.main.WildfireGender; import com.wildfire.main.entitydata.EntityConfig; import com.wildfire.render.WildfireModelRenderer.BreastModelBox; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; import net.minecraft.client.MinecraftClient; import net.minecraft.client.model.ModelPart; import net.minecraft.client.network.AbstractClientPlayerEntity; @@ -45,6 +47,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +@Environment(EnvType.CLIENT) public class GenderArmorLayer> extends GenderLayer { private final SpriteAtlasTexture armorTrimsAtlas; diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index 966c6559..f7d62687 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -33,6 +33,8 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; import net.minecraft.block.Blocks; import net.minecraft.client.MinecraftClient; import net.minecraft.client.model.ModelPart; @@ -52,6 +54,7 @@ import net.minecraft.util.math.*; import org.joml.*; +@Environment(EnvType.CLIENT) public class GenderLayer> extends FeatureRenderer { private BreastModelBox lBreast, rBreast; @@ -286,7 +289,7 @@ private void renderBreast(T entity, MatrixStack matrixStack, VertexConsumerProvi } protected static void renderBox(WildfireModelRenderer.ModelBox model, MatrixStack matrixStack, VertexConsumer bufferIn, int packedLightIn, int packedOverlayIn, - float red, float green, float blue, float alpha) { + float red, float green, float blue, float alpha) { Matrix4f matrix4f = matrixStack.peek().getPositionMatrix(); Matrix3f matrix3f = matrixStack.peek().getNormalMatrix(); for (WildfireModelRenderer.TexturedQuad quad : model.quads) { diff --git a/src/main/java/com/wildfire/render/WildfireModelRenderer.java b/src/main/java/com/wildfire/render/WildfireModelRenderer.java index 05683e16..63f6388b 100644 --- a/src/main/java/com/wildfire/render/WildfireModelRenderer.java +++ b/src/main/java/com/wildfire/render/WildfireModelRenderer.java @@ -18,9 +18,12 @@ package com.wildfire.render; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; import net.minecraft.util.math.Direction; import org.joml.Vector3f; +@Environment(EnvType.CLIENT) public class WildfireModelRenderer { public static class ModelBox { From 1fb3ea3396c587212ca9352e600a0f0d218fe853 Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 30 Nov 2023 13:34:23 -0700 Subject: [PATCH 047/238] Fix incorrect see also reference in API javadocs --- src/main/java/com/wildfire/api/WildfireAPI.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/wildfire/api/WildfireAPI.java b/src/main/java/com/wildfire/api/WildfireAPI.java index 660fb91c..f3bb0f2d 100644 --- a/src/main/java/com/wildfire/api/WildfireAPI.java +++ b/src/main/java/com/wildfire/api/WildfireAPI.java @@ -53,7 +53,7 @@ public static void addGenderArmor(Item item, IGenderArmor genderArmor) { * Get the config for a {@link PlayerEntity} * * @param uuid the uuid of the target {@link PlayerEntity} - * @see IGenderArmor + * @see PlayerConfig */ public static @Nullable PlayerConfig getPlayerById(UUID uuid) { return WildfireGender.getPlayerById(uuid); From a687cd853c97a37fec0d2b9ef6964395387cb87a Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 30 Nov 2023 13:37:54 -0700 Subject: [PATCH 048/238] Define mod version from Gradle properties, remove unused repositories --- build.gradle | 37 ++++-------------------------- gradle.properties | 3 ++- src/main/resources/fabric.mod.json | 2 +- 3 files changed, 7 insertions(+), 35 deletions(-) diff --git a/build.gradle b/build.gradle index 4665c8ef..025dd78a 100644 --- a/build.gradle +++ b/build.gradle @@ -7,38 +7,10 @@ sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 archivesBaseName = project.archives_base_name -version = project.mod_version +version = "fabric-${project.minecraft_version}-${project.mod_version}" group = project.maven_group repositories { - maven { - name = "Jitpack" - url 'https://jitpack.io' - } - maven { - url "https://maven.terraformersmc.com/" - } - maven { - url "https://maven.jamieswhiteshirt.com/libs-release/" - } - maven { - url "https://maven.shedaniel.me/" - } - maven { - url = 'https://maven.cafeteria.dev' - content { - includeGroup 'net.adriantodt.fabricmc' - } - } - maven { - name = "Ladysnake Libs" - url = 'https://ladysnake.jfrog.io/artifactory/mods' - } - maven { - url = 'https://repo.minelittlepony-mod.com/maven/release' - } - mavenCentral() - } dependencies { @@ -46,16 +18,15 @@ dependencies { minecraft "com.mojang:minecraft:${project.minecraft_version}" mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" - - // Fabric API. This is technically optional, but you probably want it anyway. modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" } processResources { - inputs.property "version", project.version + inputs.property "version", project.mod_version + inputs.property "minecraft_version", project.minecraft_version filesMatching("fabric.mod.json") { - expand "version": project.version + expand "version": project.mod_version, "minecraft_version": project.minecraft_version } } diff --git a/gradle.properties b/gradle.properties index ba49db87..e298d0c4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,12 +3,13 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://fabricmc.net/develop +# Note that this variable does *not* define the minimum required version in fabric.mod.json! minecraft_version=1.20.3-rc1 yarn_mappings=1.20.3-rc1+build.2 loader_version=0.15.0 # Mod Properties -mod_version = fabric-1.20.3-3.2 +mod_version = 3.2 maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 3eaf1e43..25bd88e3 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -1,7 +1,7 @@ { "schemaVersion": 1, "id": "wildfire_gender", - "version": "1.20-3.2", + "version": "${minecraft_version}-${version}", "name": "Wildfire's Female Gender Mod", "description": "Allows players to choose what gender they want to be.", "authors": [ From d6f70da6169621176957599cb3bb61af665ae747 Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 30 Nov 2023 13:51:19 -0700 Subject: [PATCH 049/238] Skip rendering armor that has a custom renderer Fixes #86, #42 Closes #136 Note that this will only apply to armor that have their renderer registered with Fabric API, which ideally is every mod that has a custom renderer for their armor. If a mod doesn't do so; well, that's on them to figure out. --- src/main/java/com/wildfire/render/GenderArmorLayer.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/com/wildfire/render/GenderArmorLayer.java b/src/main/java/com/wildfire/render/GenderArmorLayer.java index be030bcd..01f88b93 100644 --- a/src/main/java/com/wildfire/render/GenderArmorLayer.java +++ b/src/main/java/com/wildfire/render/GenderArmorLayer.java @@ -23,6 +23,7 @@ import com.wildfire.render.WildfireModelRenderer.BreastModelBox; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.fabricmc.fabric.impl.client.rendering.ArmorRendererRegistryImpl; import net.minecraft.client.MinecraftClient; import net.minecraft.client.model.ModelPart; import net.minecraft.client.network.AbstractClientPlayerEntity; @@ -133,6 +134,13 @@ protected void setupTransformations(T entity, ModelPart body, MatrixStack matrix protected void renderBreastArmor(T entity, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, BreastSide side) { if(armorStack.isEmpty() || !(armorStack.getItem() instanceof ArmorItem armorItem)) return; + // If the armor uses its own custom renderer, just give up rendering entirely, as the only thing we'd + // actually be able to do here is simply render a pink box. + // Note that we fail this far in to allow for mods to override this through means like a mixin, + // until any sort of official compatibility API is added. + //noinspection UnstableApiUsage + if(ArmorRendererRegistryImpl.get(armorStack.getItem()) != null) return; + Identifier armorTexture = getArmorResource(armorItem, false, null); Identifier overlayTexture = null; boolean hasGlint = armorStack.hasGlint(); From 73c81670c3b08705b3fbe72fc7578f86fdff3363 Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 30 Nov 2023 20:40:28 -0700 Subject: [PATCH 050/238] Rename drawScrollableText to clarify that it doesn't draw text shadow --- src/main/java/com/wildfire/gui/WildfireButton.java | 2 +- src/main/java/com/wildfire/gui/WildfireSlider.java | 2 +- src/main/java/com/wildfire/main/WildfireHelper.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/wildfire/gui/WildfireButton.java b/src/main/java/com/wildfire/gui/WildfireButton.java index 2fca842e..93480c5b 100644 --- a/src/main/java/com/wildfire/gui/WildfireButton.java +++ b/src/main/java/com/wildfire/gui/WildfireButton.java @@ -58,7 +58,7 @@ protected void renderWidget(DrawContext ctx, int mouseX, int mouseY, float parti int textColor = active ? 0xFFFFFF : 0x666666; int i = this.getX() + 2; int j = this.getX() + this.getWidth() - 2; - WildfireHelper.drawScrollableText(ctx, font, this.getMessage(), i, this.getY(), j, this.getY() + this.getHeight(), textColor); + WildfireHelper.drawScrollableTextWithoutShadow(ctx, font, this.getMessage(), i, this.getY(), j, this.getY() + this.getHeight(), textColor); RenderSystem.setShaderColor(1f, 1f, 1f, 1f); } diff --git a/src/main/java/com/wildfire/gui/WildfireSlider.java b/src/main/java/com/wildfire/gui/WildfireSlider.java index 76cb1128..74398a32 100644 --- a/src/main/java/com/wildfire/gui/WildfireSlider.java +++ b/src/main/java/com/wildfire/gui/WildfireSlider.java @@ -123,7 +123,7 @@ protected void renderWidget(DrawContext ctx, int mouseX, int mouseY, float delta TextRenderer font = MinecraftClient.getInstance().textRenderer; int i = this.getX() + 2; int j = this.getX() + this.getWidth() - 2; - WildfireHelper.drawScrollableText(ctx, font, this.getMessage(), i, this.getY(), j, this.getY() + this.getHeight(), this.hovered || changed ? 0xFFFF55 : 0xFFFFFF); + WildfireHelper.drawScrollableTextWithoutShadow(ctx, font, this.getMessage(), i, this.getY(), j, this.getY() + this.getHeight(), this.hovered || changed ? 0xFFFF55 : 0xFFFFFF); } } diff --git a/src/main/java/com/wildfire/main/WildfireHelper.java b/src/main/java/com/wildfire/main/WildfireHelper.java index 4e1fcbce..3b7d966a 100644 --- a/src/main/java/com/wildfire/main/WildfireHelper.java +++ b/src/main/java/com/wildfire/main/WildfireHelper.java @@ -95,7 +95,7 @@ public static void drawCenteredText(DrawContext ctx, TextRenderer textRenderer, } @Environment(EnvType.CLIENT) - public static void drawScrollableText(DrawContext context, TextRenderer textRenderer, Text text, int left, int top, int right, int bottom, int color) { + public static void drawScrollableTextWithoutShadow(DrawContext context, TextRenderer textRenderer, Text text, int left, int top, int right, int bottom, int color) { int i = textRenderer.getWidth(text); int var10000 = top + bottom; Objects.requireNonNull(textRenderer); From 28d04b84c66d10d06aed0b3beb4dafee016b3372 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Thu, 30 Nov 2023 23:13:38 -0500 Subject: [PATCH 051/238] Renamed some methods and cleaned up some code in BreastPhysics.java --- .../com/wildfire/physics/BreastPhysics.java | 119 ++++++++---------- .../java/com/wildfire/render/GenderLayer.java | 30 ++--- 2 files changed, 69 insertions(+), 80 deletions(-) diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index 2d3417ce..5ec8527e 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -37,9 +37,12 @@ public class BreastPhysics { - private float bounceVel = 0, targetBounceY = 0, velocity = 0, wfg_femaleBreast, wfg_preBounce; + //X-Axis + private float bounceVelX = 0, targetBounceX = 0, velocityX = 0, positionX, prePositionX; + //Y-Axis + private float bounceVel = 0, targetBounceY = 0, velocity = 0, positionY, prePositionY; + //Rotation private float bounceRotVel = 0, targetRotVel = 0, rotVelocity = 0, wfg_bounceRotation, wfg_preBounceRotation; - private float bounceVelX = 0, targetBounceX = 0, velocityX = 0, wfg_femaleBreastX, wfg_preBounceX; private boolean justSneaking = false, alreadySleeping = false; @@ -62,8 +65,8 @@ public void update(LivingEntity entity, IGenderArmor armor) { return; } - this.wfg_preBounce = this.wfg_femaleBreast; - this.wfg_preBounceX = this.wfg_femaleBreastX; + this.prePositionY = this.positionY; + this.prePositionX = this.positionX; this.wfg_preBounceRotation = this.wfg_bounceRotation; this.preBreastSize = this.breastSize; @@ -72,43 +75,43 @@ public void update(LivingEntity entity, IGenderArmor armor) { return; } - float h = 0; //tickDelta + { + float h = 0; //tickDelta + float i = entity.getLeaningPitch(0); + float j; + float k; - float i = entity.getLeaningPitch(0); - float j; - float k; + float bodyXRotation = 0; + float bodyYRotation = 0; - float bodyXRotation = 0; - float bodyYRotation = 0; - - if (entity.isFallFlying()) { - j = (float) entity.getRoll() + h; - k = MathHelper.clamp(j * j / 100.0F, 0.0F, 1.0F); - if (!entity.isUsingRiptide()) { - bodyXRotation = k * (-90.0F - entity.getPitch()); - } + if (entity.isFallFlying()) { + j = (float) entity.getRoll() + h; + k = MathHelper.clamp(j * j / 100.0F, 0.0F, 1.0F); + if (!entity.isUsingRiptide()) { + bodyXRotation = k * (-90.0F - entity.getPitch()); + } - if(entity instanceof AbstractClientPlayerEntity player) { - Vec3d vec3d = entity.getRotationVec(h); - Vec3d vec3d2 = player.lerpVelocity(h); - double d = vec3d2.horizontalLengthSquared(); - double e = vec3d.horizontalLengthSquared(); - if (d > 0.0 && e > 0.0) { - double l = (vec3d2.x * vec3d.x + vec3d2.z * vec3d.z) / Math.sqrt(d * e); - double m = vec3d2.x * vec3d.z - vec3d2.z * vec3d.x; - bodyYRotation = (float) (Math.signum(m) * Math.acos(l)); + if (entity instanceof AbstractClientPlayerEntity player) { + Vec3d vec3d = entity.getRotationVec(h); + Vec3d vec3d2 = player.lerpVelocity(h); + double d = vec3d2.horizontalLengthSquared(); + double e = vec3d.horizontalLengthSquared(); + if (d > 0.0 && e > 0.0) { + double l = (vec3d2.x * vec3d.x + vec3d2.z * vec3d.z) / Math.sqrt(d * e); + double m = vec3d2.x * vec3d.z - vec3d2.z * vec3d.x; + bodyYRotation = (float) (Math.signum(m) * Math.acos(l)); + } } + } else if (i > 0.0F) { + j = entity.isTouchingWater() ? -90.0F - entity.getPitch() : -90.0F; + k = MathHelper.lerp(i, 0.0F, j); + bodyXRotation = k; + } else if (entity.isSleeping()) { + bodyXRotation = 90f; + } else if (entity.getPose() == EntityPose.CROUCHING) { + bodyXRotation = -15f; } - } else if (i > 0.0F) { - j = entity.isTouchingWater() ? -90.0F - entity.getPitch() : -90.0F; - k = MathHelper.lerp(i, 0.0F, j); - bodyXRotation = k; - } else if(entity.isSleeping()) { - bodyXRotation = 90f; - } else if(entity.getPose() == EntityPose.CROUCHING) { - bodyXRotation = -15f; - } - + } //unused currently, might be later float breastWeight = entityConfig.getBustSize() * 1.25f; float targetBreastSize = entityConfig.getBustSize(); @@ -118,21 +121,14 @@ public void update(LivingEntity entity, IGenderArmor armor) { } else { float tightness = MathHelper.clamp(armor.tightness(), 0, 1); if(entityConfig.getArmorPhysicsOverride()) tightness = 0; //override resistance - //Scale breast size by how tight the armor is, clamping at a max adjustment of shrinking by 0.15 targetBreastSize *= 1 - 0.15F * tightness; } - if(breastSize < targetBreastSize) { - breastSize += Math.abs(breastSize - targetBreastSize) / 2f; - } else { - breastSize -= Math.abs(breastSize - targetBreastSize) / 2f; - } - + breastSize += (breastSize < targetBreastSize) ? Math.abs(breastSize - targetBreastSize) / 2f : -Math.abs(breastSize - targetBreastSize) / 2f; Vec3d motion = entity.getPos().subtract(this.prePos); this.prePos = entity.getPos(); - //System.out.println(motion); float bounceIntensity = (targetBreastSize * 3f) * Math.round((entityConfig.getBounceMultiplier() * 3) * 100) / 100f; float resistance = MathHelper.clamp(armor.physicsResistance(), 0, 1); @@ -154,17 +150,13 @@ public void update(LivingEntity entity, IGenderArmor armor) { this.targetBounceY = (float) motion.y * bounceIntensity; this.targetBounceY += breastWeight; float horizVel = (float) Math.sqrt(Math.pow(motion.x, 2) + Math.pow(motion.z, 2)) * (bounceIntensity); - //float horizLocal = -horizVel * ((plr.getRotationYawHead()-plr.renderYawOffset)<0?-1:1); this.targetRotVel = -((entity.bodyYaw - entity.prevBodyYaw) / 15f) * bounceIntensity; - //System.out.println("Body Rotation: " + (bodyXRotation) / 90); - float f2 = (float) entity.getVelocity().lengthSquared() / 0.2F; f2 = f2 * f2 * f2; if(f2 < 1.0F) f2 = 1.0F; this.targetBounceY += MathHelper.cos(entity.limbAnimator.getPos() * 0.6662F + (float)Math.PI) * 0.5F * entity.limbAnimator.getSpeed() * 0.5F / f2; - //System.out.println(plr.rotationYaw); this.targetRotVel += (float) motion.y * bounceIntensity * randomB; @@ -178,7 +170,6 @@ public void update(LivingEntity entity, IGenderArmor armor) { this.targetBounceY += bounceIntensity; } - //button option for extra entities if(entity.getVehicle() != null) { if(entity.getVehicle() instanceof BoatEntity boat) { @@ -231,8 +222,7 @@ public void update(LivingEntity entity, IGenderArmor armor) { /*if(plr.getPose() == EntityPose.SWIMMING) { //System.out.println(1 - plr.getRotationVec(tickDelta).getY()); rotationMultiplier = 1 - (float) plr.getRotationVec(tickDelta).getY(); - } - */ + }*/ float percent = entityConfig.getFloppiness(); @@ -256,7 +246,6 @@ public void update(LivingEntity entity, IGenderArmor armor) { if(targetRotVel > 25f) targetRotVel = 25f; this.velocity = MathHelper.lerp(bounceAmount, this.velocity, (this.targetBounceY - this.bounceVel) * delta); - //this.preY = MathHelper.lerp(0.5f, this.preY, (this.targetBounce - this.bounceVel) * 1.25f); this.bounceVel += this.velocity * percent * 1.1625f; //X @@ -267,12 +256,12 @@ public void update(LivingEntity entity, IGenderArmor armor) { this.bounceRotVel += this.rotVelocity * percent; this.wfg_bounceRotation = this.bounceRotVel; - this.wfg_femaleBreastX = this.bounceVelX; - this.wfg_femaleBreast = this.bounceVel; + this.positionX = this.bounceVelX; + this.positionY = this.bounceVel; - if(this.wfg_femaleBreast < -0.5f) this.wfg_femaleBreast = -0.5f; - if(this.wfg_femaleBreast > 1.5f) { - this.wfg_femaleBreast = 1.5f; + if(this.positionY < -0.5f) this.positionY = -0.5f; + if(this.positionY > 1.5f) { + this.positionY = 1.5f; this.velocity = 0; } @@ -282,18 +271,18 @@ public float getBreastSize(float partialTicks) { return MathHelper.lerp(partialTicks, preBreastSize, breastSize); } - public float getPreBounceY() { - return this.wfg_preBounce; + public float getPrePositionY() { + return this.prePositionY; } - public float getBounceY() { - return this.wfg_femaleBreast; + public float getPositionY() { + return this.positionY; } - public float getPreBounceX() { - return this.wfg_preBounceX; + public float getPrePositionX() { + return this.prePositionX; } - public float getBounceX() { - return this.wfg_femaleBreastX; + public float getPositionX() { + return this.positionX; } public float getBounceRotation() { diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index da83d713..05b5e1ca 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -62,8 +62,8 @@ public class GenderLayer> protected ItemStack armorStack; protected IGenderArmor genderArmor; protected boolean isChestplateOccupied, bounceEnabled, breathingAnimation; - protected float breastOffsetX, breastOffsetY, breastOffsetZ, lTotal, lTotalX, rTotal, rTotalX, - leftBounceRotation, rightBounceRotation, breastSize, zOffset, outwardAngle; + protected float breastOffsetX, breastOffsetY, breastOffsetZ, lPhysPositionY, lPhysPositionX, rPhysPositionY, rTotalX, + lPhysBounceRotation, rPhysBounceRotation, breastSize, zOffset, outwardAngle; public GenderLayer(FeatureRendererContext render) { super(render); @@ -182,18 +182,18 @@ protected boolean setupRender(T entity, EntityConfig entityConfig, float partial preBreastSize = bSize; } - lTotal = MathHelper.lerp(partialTicks, leftBreastPhysics.getPreBounceY(), leftBreastPhysics.getBounceY()); - lTotalX = MathHelper.lerp(partialTicks, leftBreastPhysics.getPreBounceX(), leftBreastPhysics.getBounceX()); - leftBounceRotation = MathHelper.lerp(partialTicks, leftBreastPhysics.getPreBounceRotation(), leftBreastPhysics.getBounceRotation()); + lPhysPositionY = MathHelper.lerp(partialTicks, leftBreastPhysics.getPrePositionY(), leftBreastPhysics.getPositionY()); + lPhysPositionX = MathHelper.lerp(partialTicks, leftBreastPhysics.getPrePositionX(), leftBreastPhysics.getPositionX()); + lPhysBounceRotation = MathHelper.lerp(partialTicks, leftBreastPhysics.getPreBounceRotation(), leftBreastPhysics.getBounceRotation()); if(breasts.isUniboob()) { - rTotal = lTotal; - rTotalX = lTotalX; - rightBounceRotation = leftBounceRotation; + rPhysPositionY = lPhysPositionY; + rTotalX = lPhysPositionX; + rPhysBounceRotation = lPhysBounceRotation; } else { BreastPhysics rightBreastPhysics = entityConfig.getRightBreastPhysics(); - rTotal = MathHelper.lerp(partialTicks, rightBreastPhysics.getPreBounceY(), rightBreastPhysics.getBounceY()); - rTotalX = MathHelper.lerp(partialTicks, rightBreastPhysics.getPreBounceX(), rightBreastPhysics.getBounceX()); - rightBounceRotation = MathHelper.lerp(partialTicks, rightBreastPhysics.getPreBounceRotation(), rightBreastPhysics.getBounceRotation()); + rPhysPositionY = MathHelper.lerp(partialTicks, rightBreastPhysics.getPrePositionY(), rightBreastPhysics.getPositionY()); + rTotalX = MathHelper.lerp(partialTicks, rightBreastPhysics.getPrePositionX(), rightBreastPhysics.getPositionX()); + rPhysBounceRotation = MathHelper.lerp(partialTicks, rightBreastPhysics.getPreBounceRotation(), rightBreastPhysics.getBounceRotation()); } breastSize = bSize * 1.5f; if(breastSize > 0.7f) breastSize = 0.7f; @@ -227,8 +227,8 @@ protected void setupTransformations(T entity, ModelPart body, MatrixStack matrix } if(bounceEnabled) { - matrixStack.translate((left ? lTotalX : rTotalX) / 32f, 0, 0); - matrixStack.translate(0, (left ? lTotal : rTotal) / 32f, 0); + matrixStack.translate((left ? lPhysPositionX : rTotalX) / 32f, 0, 0); + matrixStack.translate(0, (left ? lPhysPositionY : rPhysPositionY) / 32f, 0); } matrixStack.translate((left ? breastOffsetX : -breastOffsetX) * 0.0625f, 0.05625f + (breastOffsetY * 0.0625f), zOffset - 0.0625f * 2f + (breastOffsetZ * 0.0625f)); //shift down to correct position @@ -237,7 +237,7 @@ protected void setupTransformations(T entity, ModelPart body, MatrixStack matrix matrixStack.translate(-0.0625f * 2 * (left ? 1 : -1), 0, 0); } if(bounceEnabled) { - matrixStack.multiply(new Quaternionf().rotationXYZ(0, (float)((left ? leftBounceRotation : rightBounceRotation) * (Math.PI / 180f)), 0)); + matrixStack.multiply(new Quaternionf().rotationXYZ(0, (float)((left ? lPhysBounceRotation : rPhysBounceRotation) * (Math.PI / 180f)), 0)); } if(!breasts.isUniboob()) { matrixStack.translate(0.0625f * 2 * (left ? 1 : -1), 0, 0); @@ -246,7 +246,7 @@ protected void setupTransformations(T entity, ModelPart body, MatrixStack matrix float rotationMultiplier = 0; if(bounceEnabled) { matrixStack.translate(0, -0.035f * breastSize, 0); //shift down to correct position - rotationMultiplier = -(left ? lTotal : rTotal) / 12f; + rotationMultiplier = -(left ? lPhysPositionY : rPhysPositionY) / 12f; } float totalRotation = breastSize + rotationMultiplier; if(!bounceEnabled) { From da1c1d3e40708c57a8929f92fe0c0e35e961f7ee Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 4 Dec 2023 12:34:17 -0700 Subject: [PATCH 052/238] Fix #164 --- .../com/wildfire/main/networking/WildfireSync.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/wildfire/main/networking/WildfireSync.java b/src/main/java/com/wildfire/main/networking/WildfireSync.java index 34d7aefa..7a9d5631 100644 --- a/src/main/java/com/wildfire/main/networking/WildfireSync.java +++ b/src/main/java/com/wildfire/main/networking/WildfireSync.java @@ -68,13 +68,14 @@ public static void handle(MinecraftServer server, ServerPlayerEntity player, Ser * @param playerConfig The {@link PlayerConfig configuration} for the target player */ public static void sendToAllClients(ServerPlayerEntity toSync, PlayerConfig playerConfig) { - if(playerConfig == null || toSync.getServer() == null) return; + if(playerConfig == null || toSync.getServer() == null) return; - PacketByteBuf packet = new SyncPacket(playerConfig).getPacket(); - PlayerLookup.tracking(toSync).forEach((sendTo) -> { + SyncPacket syncPacket = new SyncPacket(playerConfig); + PlayerLookup.tracking(toSync).forEach((sendTo) -> { if(sendTo.getUuid().equals(toSync.getUuid())) return; - if(ServerPlayNetworking.canSend(sendTo, SYNC_IDENTIFIER)) { - ServerPlayNetworking.send(sendTo, SYNC_IDENTIFIER, packet); + if(ServerPlayNetworking.canSend(sendTo, SYNC_IDENTIFIER)) { + // encode a packet for each player we send this sync to (GH-164) + ServerPlayNetworking.send(sendTo, SYNC_IDENTIFIER, syncPacket.getPacket()); } }); } @@ -86,8 +87,8 @@ public static void sendToAllClients(ServerPlayerEntity toSync, PlayerConfig play * @param toSync The {@link PlayerConfig configuration} for the player being synced */ public static void sendToClient(ServerPlayerEntity sendTo, PlayerConfig toSync) { - PacketByteBuf packet = new SyncPacket(toSync).getPacket(); if(ServerPlayNetworking.canSend(sendTo, SYNC_IDENTIFIER)) { + PacketByteBuf packet = new SyncPacket(toSync).getPacket(); ServerPlayNetworking.send(sendTo, SYNC_IDENTIFIER, packet); } } From 43ad61271117479ea2e53d83e7e0e2f88fa32785 Mon Sep 17 00:00:00 2001 From: WildfireRomeo Date: Mon, 4 Dec 2023 19:28:57 -0500 Subject: [PATCH 053/238] Update version to 3.2.1 --- gradle.properties | 2 +- src/main/resources/fabric.mod.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 8ac6f724..2551647f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ yarn_mappings=1.20.2+build.1 loader_version=0.14.22 # Mod Properties -mod_version = fabric-1.20.2-3.2 +mod_version = fabric-1.20.2-3.2.1 maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 19c1b9cb..d9fd778d 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -1,7 +1,7 @@ { "schemaVersion": 1, "id": "wildfire_gender", - "version": "1.20-3.2", + "version": "1.20.2-3.2.1", "name": "Wildfire's Female Gender Mod", "description": "Allows players to choose what gender they want to be.", "authors": [ From e0e695d83d1f476ff854f0abf99b2c8a319cf2a9 Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 5 Dec 2023 12:40:50 -0700 Subject: [PATCH 054/238] Bump version requirement to full release --- gradle.properties | 4 ++-- src/main/resources/fabric.mod.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 82397f37..d3dcc24e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,8 +4,8 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://fabricmc.net/develop # Note that this variable does *not* define the minimum required version in fabric.mod.json! -minecraft_version=1.20.3-rc1 -yarn_mappings=1.20.3-rc1+build.2 +minecraft_version=1.20.3 +yarn_mappings=1.20.3+build.1 loader_version=0.15.0 # Mod Properties diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 25bd88e3..634115ae 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -39,7 +39,7 @@ "depends": { "fabricloader": "*", "fabric-api": "*", - "minecraft": "~1.20.3-rc.1", + "minecraft": "~1.20.3", "java": ">=17" }, "conflicts": { From 8f890cc04f8eff0b449ea64e3f80382b2dc3feab Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 5 Dec 2023 12:41:48 -0700 Subject: [PATCH 055/238] Add missing license header, simplify render side check --- .../java/com/wildfire/render/BreastSide.java | 26 ++++++++++++++++++- .../com/wildfire/render/GenderArmorLayer.java | 6 ++--- .../java/com/wildfire/render/GenderLayer.java | 21 +++++++-------- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/wildfire/render/BreastSide.java b/src/main/java/com/wildfire/render/BreastSide.java index 5827b3db..b726c258 100644 --- a/src/main/java/com/wildfire/render/BreastSide.java +++ b/src/main/java/com/wildfire/render/BreastSide.java @@ -1,3 +1,21 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + package com.wildfire.render; import net.fabricmc.api.EnvType; @@ -5,5 +23,11 @@ @Environment(EnvType.CLIENT) public enum BreastSide { - LEFT, RIGHT + LEFT(true), RIGHT(false); + + public final boolean isLeft; + + BreastSide(boolean isLeft) { + this.isLeft = isLeft; + } } diff --git a/src/main/java/com/wildfire/render/GenderArmorLayer.java b/src/main/java/com/wildfire/render/GenderArmorLayer.java index 01f88b93..58f91476 100644 --- a/src/main/java/com/wildfire/render/GenderArmorLayer.java +++ b/src/main/java/com/wildfire/render/GenderArmorLayer.java @@ -154,9 +154,9 @@ protected void renderBreastArmor(T entity, MatrixStack matrixStack, VertexConsum } matrixStack.push(); try { - matrixStack.translate(side == BreastSide.LEFT ? 0.001f : -0.001f, 0.015f, -0.015f); + matrixStack.translate(side.isLeft ? 0.001f : -0.001f, 0.015f, -0.015f); matrixStack.scale(1.05f, 1, 1); - BreastModelBox armor = side == BreastSide.LEFT ? lBoobArmor : rBoobArmor; + BreastModelBox armor = side.isLeft ? lBoobArmor : rBoobArmor; RenderLayer armorType = RenderLayer.getArmorCutoutNoCull(armorTexture); VertexConsumer armorVertexConsumer = ItemRenderer.getArmorGlintConsumer(vertexConsumerProvider, armorType, false, hasGlint); renderBox(armor, matrixStack, armorVertexConsumer, packedLightIn, OverlayTexture.DEFAULT_UV, armorR, armorG, armorB, 1); @@ -177,7 +177,7 @@ protected void renderBreastArmor(T entity, MatrixStack matrixStack, VertexConsum protected void renderArmorTrim(ArmorMaterial material, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, ArmorTrim trim, boolean hasGlint, BreastSide side) { - BreastModelBox trimModelBox = side == BreastSide.LEFT ? lTrim : rTrim; + BreastModelBox trimModelBox = side.isLeft ? lTrim : rTrim; Sprite sprite = this.armorTrimsAtlas.getSprite(trim.getGenericModelId(material)); VertexConsumer vertexConsumer = sprite.getTextureSpecificVertexConsumer( vertexConsumerProvider.getBuffer(TexturedRenderLayers.getArmorTrims(trim.getPattern().value().decal()))); diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index 58702c7d..3d680df7 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -217,7 +217,6 @@ protected boolean setupRender(T entity, EntityConfig entityConfig, float partial } protected void setupTransformations(T entity, ModelPart body, MatrixStack matrixStack, BreastSide side) { - boolean left = side == BreastSide.LEFT; matrixStack.translate(body.pivotX * 0.0625f, body.pivotY * 0.0625f, body.pivotZ * 0.0625f); if(body.roll != 0.0F) { matrixStack.multiply(new Quaternionf().rotationXYZ(0f, 0f, body.roll)); @@ -230,26 +229,26 @@ protected void setupTransformations(T entity, ModelPart body, MatrixStack matrix } if(bounceEnabled) { - matrixStack.translate((left ? lPhysPositionX : rTotalX) / 32f, 0, 0); - matrixStack.translate(0, (left ? lPhysPositionY : rPhysPositionY) / 32f, 0); + matrixStack.translate((side.isLeft ? lPhysPositionX : rTotalX) / 32f, 0, 0); + matrixStack.translate(0, (side.isLeft ? lPhysPositionY : rPhysPositionY) / 32f, 0); } - matrixStack.translate((left ? breastOffsetX : -breastOffsetX) * 0.0625f, 0.05625f + (breastOffsetY * 0.0625f), zOffset - 0.0625f * 2f + (breastOffsetZ * 0.0625f)); //shift down to correct position + matrixStack.translate((side.isLeft ? breastOffsetX : -breastOffsetX) * 0.0625f, 0.05625f + (breastOffsetY * 0.0625f), zOffset - 0.0625f * 2f + (breastOffsetZ * 0.0625f)); //shift down to correct position if(!breasts.isUniboob()) { - matrixStack.translate(-0.0625f * 2 * (left ? 1 : -1), 0, 0); + matrixStack.translate(-0.0625f * 2 * (side.isLeft ? 1 : -1), 0, 0); } if(bounceEnabled) { - matrixStack.multiply(new Quaternionf().rotationXYZ(0, (float)((left ? lPhysBounceRotation : rPhysBounceRotation) * (Math.PI / 180f)), 0)); + matrixStack.multiply(new Quaternionf().rotationXYZ(0, (float)((side.isLeft ? lPhysBounceRotation : rPhysBounceRotation) * (Math.PI / 180f)), 0)); } if(!breasts.isUniboob()) { - matrixStack.translate(0.0625f * 2 * (left ? 1 : -1), 0, 0); + matrixStack.translate(0.0625f * 2 * (side.isLeft ? 1 : -1), 0, 0); } float rotationMultiplier = 0; if(bounceEnabled) { matrixStack.translate(0, -0.035f * breastSize, 0); //shift down to correct position - rotationMultiplier = -(left ? lPhysPositionY : rPhysPositionY) / 12f; + rotationMultiplier = -(side.isLeft ? lPhysPositionY : rPhysPositionY) / 12f; } float totalRotation = breastSize + rotationMultiplier; if(!bounceEnabled) { @@ -264,7 +263,7 @@ protected void setupTransformations(T entity, ModelPart body, MatrixStack matrix matrixStack.translate(0, 0, 0.01f); } - matrixStack.multiply(new Quaternionf().rotationXYZ(0, (float)((left ? outwardAngle : -outwardAngle) * (Math.PI / 180f)), 0)); + matrixStack.multiply(new Quaternionf().rotationXYZ(0, (float)((side.isLeft ? outwardAngle : -outwardAngle) * (Math.PI / 180f)), 0)); matrixStack.multiply(new Quaternionf().rotationXYZ((float)(-35f * totalRotation * (Math.PI / 180f)), 0, 0)); if(breathingAnimation) { @@ -280,11 +279,11 @@ private void renderBreast(T entity, MatrixStack matrixStack, VertexConsumerProvi if(breastRenderType == null) return; // only render if the player is visible in some capacity float alpha = entity.isInvisible() ? 0.15F : 1; VertexConsumer vertexConsumer = vertexConsumerProvider.getBuffer(breastRenderType); - renderBox(side == BreastSide.LEFT ? lBreast : rBreast, matrixStack, vertexConsumer, packedLightIn, packedOverlayIn, 1f, 1f, 1f, alpha); + renderBox(side.isLeft ? lBreast : rBreast, matrixStack, vertexConsumer, packedLightIn, packedOverlayIn, 1f, 1f, 1f, alpha); if(entity instanceof AbstractClientPlayerEntity player && player.isPartVisible(PlayerModelPart.JACKET)) { matrixStack.translate(0, 0, -0.015f); matrixStack.scale(1.05f, 1.05f, 1.05f); - renderBox(side == BreastSide.LEFT ? lBreastWear : rBreastWear, matrixStack, vertexConsumer, packedLightIn, packedOverlayIn, 1f, 1f, 1f, alpha); + renderBox(side.isLeft ? lBreastWear : rBreastWear, matrixStack, vertexConsumer, packedLightIn, packedOverlayIn, 1f, 1f, 1f, alpha); } } From 94a89c91daee20fbfa2955dc65b73ce8ed907648 Mon Sep 17 00:00:00 2001 From: RacoonDog <32882447+RacoonDog@users.noreply.github.com> Date: Thu, 28 Dec 2023 03:50:47 -0500 Subject: [PATCH 056/238] raise numbers --- build.gradle | 2 +- gradle.properties | 8 ++++---- gradle/wrapper/gradle-wrapper.properties | 2 +- src/main/resources/fabric.mod.json | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index 025dd78a..40ba8cce 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '0.12-SNAPSHOT' + id 'fabric-loom' version '1.4-SNAPSHOT' id 'maven-publish' } diff --git a/gradle.properties b/gradle.properties index d3dcc24e..f0b346da 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,9 +4,9 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://fabricmc.net/develop # Note that this variable does *not* define the minimum required version in fabric.mod.json! -minecraft_version=1.20.3 -yarn_mappings=1.20.3+build.1 -loader_version=0.15.0 +minecraft_version=1.20.4 +yarn_mappings=1.20.4+build.3 +loader_version=0.15.3 # Mod Properties mod_version = 3.2.1 @@ -14,4 +14,4 @@ maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod # Dependencies -fabric_version=0.91.1+1.20.3 +fabric_version=0.92.0+1.20.4 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 81b8f923..c30b486a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 634115ae..e95413ed 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -39,7 +39,7 @@ "depends": { "fabricloader": "*", "fabric-api": "*", - "minecraft": "~1.20.3", + "minecraft": ">=1.20.3 <=1.20.4", "java": ">=17" }, "conflicts": { From c490e10ca5850946c3488d05f7aa17eefb42ee3b Mon Sep 17 00:00:00 2001 From: RacoonDog <32882447+RacoonDog@users.noreply.github.com> Date: Thu, 28 Dec 2023 03:53:57 -0500 Subject: [PATCH 057/238] migrate annotations --- .../java/com/wildfire/api/WildfireAPI.java | 6 +++--- src/main/java/com/wildfire/main/Gender.java | 3 +-- .../java/com/wildfire/main/WildfireGender.java | 6 +++--- .../java/com/wildfire/main/WildfireHelper.java | 4 ++-- .../wildfire/main/config/NumberConfigKey.java | 2 +- .../wildfire/main/entitydata/EntityConfig.java | 18 +++++++++--------- .../wildfire/main/entitydata/PlayerConfig.java | 4 ++-- .../com/wildfire/render/GenderArmorLayer.java | 9 ++++----- .../java/com/wildfire/render/GenderLayer.java | 6 +++--- 9 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/wildfire/api/WildfireAPI.java b/src/main/java/com/wildfire/api/WildfireAPI.java index f3bb0f2d..51f75106 100644 --- a/src/main/java/com/wildfire/api/WildfireAPI.java +++ b/src/main/java/com/wildfire/api/WildfireAPI.java @@ -24,9 +24,9 @@ import com.wildfire.main.Gender; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -65,7 +65,7 @@ public static void addGenderArmor(Item item, IGenderArmor genderArmor) { * @param uuid the uuid of the target {@link PlayerEntity}. * @see Gender */ - public static @Nonnull Gender getPlayerGender(UUID uuid) { + public static @NotNull Gender getPlayerGender(UUID uuid) { PlayerConfig cfg = WildfireGender.getPlayerById(uuid); if(cfg == null) return Configuration.GENDER.getDefault(); return cfg.getGender(); diff --git a/src/main/java/com/wildfire/main/Gender.java b/src/main/java/com/wildfire/main/Gender.java index d508f911..56a43f49 100644 --- a/src/main/java/com/wildfire/main/Gender.java +++ b/src/main/java/com/wildfire/main/Gender.java @@ -3,8 +3,7 @@ import net.minecraft.sound.SoundEvent; import net.minecraft.text.Text; import net.minecraft.util.Formatting; - -import javax.annotation.Nullable; +import org.jetbrains.annotations.Nullable; public enum Gender { FEMALE(Text.translatable("wildfire_gender.label.female").formatted(Formatting.LIGHT_PURPLE), true, WildfireSounds.FEMALE_HURT), diff --git a/src/main/java/com/wildfire/main/WildfireGender.java b/src/main/java/com/wildfire/main/WildfireGender.java index bf6c8749..eb2e7e0b 100644 --- a/src/main/java/com/wildfire/main/WildfireGender.java +++ b/src/main/java/com/wildfire/main/WildfireGender.java @@ -23,13 +23,13 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.Future; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import com.mojang.logging.LogUtils; import com.wildfire.main.entitydata.PlayerConfig; import net.fabricmc.api.ClientModInitializer; import net.minecraft.util.Util; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; public class WildfireGender implements ClientModInitializer { @@ -47,7 +47,7 @@ public void onInitializeClient() { return PLAYER_CACHE.get(id); } - public static @Nonnull PlayerConfig getOrAddPlayerById(UUID id) { + public static @NotNull PlayerConfig getOrAddPlayerById(UUID id) { return PLAYER_CACHE.computeIfAbsent(id, PlayerConfig::new); } diff --git a/src/main/java/com/wildfire/main/WildfireHelper.java b/src/main/java/com/wildfire/main/WildfireHelper.java index 3b7d966a..52d98e3e 100644 --- a/src/main/java/com/wildfire/main/WildfireHelper.java +++ b/src/main/java/com/wildfire/main/WildfireHelper.java @@ -37,8 +37,8 @@ import net.minecraft.text.Text; import net.minecraft.util.Util; import net.minecraft.util.math.MathHelper; +import org.jetbrains.annotations.NotNull; -import javax.annotation.Nonnull; import java.util.Objects; import java.util.concurrent.ThreadLocalRandom; @@ -129,7 +129,7 @@ public static void drawScrollableTextWithoutShadow(DrawContext context, TextRend * * @see EntityConfig#readFromStack */ - public static void writeToNbt(@Nonnull PlayerEntity player, @Nonnull PlayerConfig config, @Nonnull ItemStack armor) { + public static void writeToNbt(@NotNull PlayerEntity player, @NotNull PlayerConfig config, @NotNull ItemStack armor) { NbtCompound nbt = new NbtCompound(); nbt.putFloat("BreastSize", config.getGender().canHaveBreasts() && config.showBreastsInArmor() ? config.getBustSize() : 0f); nbt.putFloat("Cleavage", config.getBreasts().getCleavage()); diff --git a/src/main/java/com/wildfire/main/config/NumberConfigKey.java b/src/main/java/com/wildfire/main/config/NumberConfigKey.java index b3cf9de2..9c4a9cde 100644 --- a/src/main/java/com/wildfire/main/config/NumberConfigKey.java +++ b/src/main/java/com/wildfire/main/config/NumberConfigKey.java @@ -21,7 +21,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; -import javax.annotation.Nullable; +import org.jetbrains.annotations.Nullable; public abstract class NumberConfigKey> extends ConfigKey { diff --git a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java index 637319c1..a15367e3 100644 --- a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java +++ b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java @@ -32,9 +32,9 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.HashMap; import java.util.UUID; @@ -74,7 +74,7 @@ public class EntityConfig { * * @see WildfireHelper#writeToNbt */ - public void readFromStack(@Nonnull ItemStack chestplate) { + public void readFromStack(@NotNull ItemStack chestplate) { NbtCompound nbt = !chestplate.isEmpty() ? chestplate.getSubNbt("WildfireGender") : null; if(nbt == null) { this.gender = Gender.MALE; @@ -96,7 +96,7 @@ public void readFromStack(@Nonnull ItemStack chestplate) { * @return {@link EntityConfig}, {@link PlayerConfig} if given a {@link PlayerEntity player}, * or {@code null} if given a baby entity */ - public static @Nullable EntityConfig getEntity(@Nonnull LivingEntity entity) { + public static @Nullable EntityConfig getEntity(@NotNull LivingEntity entity) { if(entity instanceof PlayerEntity) { return WildfireGender.getPlayerById(entity.getUuid()); } @@ -107,11 +107,11 @@ public void readFromStack(@Nonnull ItemStack chestplate) { return ENTITY_CACHE.computeIfAbsent(entity.getUuid(), EntityConfig::new); } - public @Nonnull Gender getGender() { + public @NotNull Gender getGender() { return gender; } - public @Nonnull Breasts getBreasts() { + public @NotNull Breasts getBreasts() { return breasts; } @@ -139,10 +139,10 @@ public float getFloppiness() { return this.floppyMultiplier; } - public @Nonnull BreastPhysics getLeftBreastPhysics() { + public @NotNull BreastPhysics getLeftBreastPhysics() { return lBreastPhysics; } - public @Nonnull BreastPhysics getRightBreastPhysics() { + public @NotNull BreastPhysics getRightBreastPhysics() { return rBreastPhysics; } @@ -155,7 +155,7 @@ public boolean hasJacketLayer() { } @Environment(EnvType.CLIENT) - public void tickBreastPhysics(@Nonnull LivingEntity entity) { + public void tickBreastPhysics(@NotNull LivingEntity entity) { IGenderArmor armor = WildfireHelper.getArmorConfig(entity.getEquippedStack(EquipmentSlot.CHEST)); getLeftBreastPhysics().update(entity, armor); diff --git a/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java b/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java index 58ce2983..ffac95ed 100644 --- a/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java +++ b/src/main/java/com/wildfire/main/entitydata/PlayerConfig.java @@ -24,8 +24,8 @@ import com.wildfire.main.config.Configuration; import com.wildfire.main.Gender; import net.minecraft.item.ItemStack; +import org.jetbrains.annotations.NotNull; -import javax.annotation.Nonnull; import java.util.UUID; import java.util.function.Consumer; @@ -70,7 +70,7 @@ public PlayerConfig(UUID uuid, Gender gender) { // this shouldn't ever be called on players, but just to be safe, override with a noop. @Override - public void readFromStack(@Nonnull ItemStack chestplate) {} + public void readFromStack(@NotNull ItemStack chestplate) {} public Configuration getConfig() { return cfg; diff --git a/src/main/java/com/wildfire/render/GenderArmorLayer.java b/src/main/java/com/wildfire/render/GenderArmorLayer.java index 58f91476..78d04faa 100644 --- a/src/main/java/com/wildfire/render/GenderArmorLayer.java +++ b/src/main/java/com/wildfire/render/GenderArmorLayer.java @@ -44,9 +44,8 @@ import net.minecraft.item.DyeableArmorItem; import net.minecraft.item.trim.ArmorTrim; import net.minecraft.util.Identifier; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; @Environment(EnvType.CLIENT) public class GenderArmorLayer> extends GenderLayer { @@ -67,7 +66,7 @@ public GenderArmorLayer(FeatureRendererContext render, BakedModelManager b rTrim = new BreastModelBox(64, 32, 20, 17, 0, 0.0F, 0F, 4, 5, 4, 0.001F, false); } - public Identifier getArmorResource(@Nonnull ArmorItem item, boolean legs, @Nullable String overlay) { + public Identifier getArmorResource(@NotNull ArmorItem item, boolean legs, @Nullable String overlay) { String material = item.getMaterial().getName(); String namespace = "minecraft"; int namespaceDelim = material.indexOf(":"); @@ -79,7 +78,7 @@ public Identifier getArmorResource(@Nonnull ArmorItem item, boolean legs, @Nulla } @Override - public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, @Nonnull T ent, float limbAngle, float limbDistance, float partialTicks, float animationProgress, float headYaw, float headPitch) { + public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, @NotNull T ent, float limbAngle, float limbDistance, float partialTicks, float animationProgress, float headYaw, float headPitch) { MinecraftClient client = MinecraftClient.getInstance(); if(client.player == null) { // we're currently in a menu, give up rendering before we crash the game diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index 3d680df7..5b8c81fc 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -30,8 +30,6 @@ import java.lang.Math; import java.util.ConcurrentModificationException; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -52,6 +50,8 @@ import net.minecraft.item.ItemStack; import net.minecraft.util.Identifier; import net.minecraft.util.math.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.joml.*; @Environment(EnvType.CLIENT) @@ -100,7 +100,7 @@ public GenderLayer(FeatureRendererContext render) { } @Override - public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, @Nonnull T ent, float limbAngle, + public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, @NotNull T ent, float limbAngle, float limbDistance, float partialTicks, float animationProgress, float headYaw, float headPitch) { MinecraftClient client = MinecraftClient.getInstance(); if(client.player == null) { From d8d20a86c5c39c467a89294cbf639ff249a5d822 Mon Sep 17 00:00:00 2001 From: RacoonDog <32882447+RacoonDog@users.noreply.github.com> Date: Thu, 28 Dec 2023 03:57:55 -0500 Subject: [PATCH 058/238] depend on individual fapi modules --- build.gradle | 7 ++++++- src/main/resources/fabric.mod.json | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 40ba8cce..07e79b02 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,12 @@ dependencies { minecraft "com.mojang:minecraft:${project.minecraft_version}" mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" - modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + modImplementation fabricApi.module("fabric-networking-api-v1", project.fabric_version) + modImplementation fabricApi.module("fabric-key-binding-api-v1", project.fabric_version) + modImplementation fabricApi.module("fabric-lifecycle-events-v1", project.fabric_version) + modImplementation fabricApi.module("fabric-rendering-v1", project.fabric_version) + modRuntimeOnly fabricApi.module("fabric-resource-loader-v0", project.fabric_version) + modRuntimeOnly fabricApi.module("fabric-registry-sync-v0", project.fabric_version) } processResources { diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index e95413ed..ff945c81 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -38,7 +38,12 @@ ], "depends": { "fabricloader": "*", - "fabric-api": "*", + "fabric-networking-api-v1": "*", + "fabric-key-binding-api-v1": "*", + "fabric-lifecycle-events-v1": "*", + "fabric-rendering-v1": "*", + "fabric-resource-loader-v0": "*", + "fabric-registry-sync-v0": "*", "minecraft": ">=1.20.3 <=1.20.4", "java": ">=17" }, From e3f888d58de5b6a97166a2cef512753125c066f7 Mon Sep 17 00:00:00 2001 From: RacoonDog <32882447+RacoonDog@users.noreply.github.com> Date: Thu, 28 Dec 2023 04:03:51 -0500 Subject: [PATCH 059/238] Update build.gradle --- build.gradle | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 07e79b02..f913afa4 100644 --- a/build.gradle +++ b/build.gradle @@ -6,10 +6,13 @@ plugins { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 -archivesBaseName = project.archives_base_name version = "fabric-${project.minecraft_version}-${project.mod_version}" group = project.maven_group +base { + archivesName = project.archives_base_name +} + repositories { } From 16efefb63315c1557f755dd28523391cb1784e0f Mon Sep 17 00:00:00 2001 From: Amirhan-Taipovjan-Greatest-I <51203385+Amirhan-Taipovjan-Greatest-I@users.noreply.github.com> Date: Tue, 16 Jan 2024 16:31:38 +0300 Subject: [PATCH 060/238] Actualized Vanilla-ish Tatar Translation! --- .../assets/wildfire_gender/lang/tt_ru.json | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/main/resources/assets/wildfire_gender/lang/tt_ru.json diff --git a/src/main/resources/assets/wildfire_gender/lang/tt_ru.json b/src/main/resources/assets/wildfire_gender/lang/tt_ru.json new file mode 100644 index 00000000..1e24cee3 --- /dev/null +++ b/src/main/resources/assets/wildfire_gender/lang/tt_ru.json @@ -0,0 +1,63 @@ +{ + "modmenu.descriptionTranslation.wildfire_gender": "Уенчыларга теләгән җенесне сайларга рөхсәт итә.", + + "category.wildfire_gender.generic": "Wildfire's Female Gender Mod", + "key.wildfire_gender.gender_menu": "Хатын җенесе менюсы", + "toast.wildfire_gender.get_started": "Башлау өчен «%s» төймәсенә басыгыз!", + + "wildfire_gender.player_list.title": "Хатын җенесенең моды", + "wildfire_gender.player_list.settings_button": "Көйләүләр", + "wildfire_gender.player_list.sync_status": "Синхронлаштыру халәте", + "wildfire_gender.player_list.state.loading": "Мәгълүмат йөкләү...", + "wildfire_gender.player_list.state.synced": "Синхронлаштырылган уенчы", + "wildfire_gender.player_list.bounce_multiplier": "Сикерү тапкырлаучысы: %s×", + "wildfire_gender.player_list.breast_momentum": "Имчәк моменты: %s%%", + "wildfire_gender.player_list.female_sounds": "Хатын тавышлары: %s", + + "wildfire_gender.wardrobe.title": "Wildfire's Female Gender Mod", + "wildfire_gender.breast_customization.tab_customization": "Көйләү", + "wildfire_gender.breast_customization.tab_presets": "Әзерләмә", + + "wildfire_gender.breast_customization.presets.add_new": "Өстәү...", + "wildfire_gender.breast_customization.presets.delete": "Бетерү", + + "wildfire_gender.wardrobe.slider.breast_size": "Имчәк зурлыгы: %s%%", + "wildfire_gender.wardrobe.slider.separation": "Уем: %s", + "wildfire_gender.wardrobe.slider.height": "Биеклек: %s", + "wildfire_gender.wardrobe.slider.depth": "Тирәнлек: %s", + "wildfire_gender.wardrobe.slider.rotation": "Бору: %s градус", + + "wildfire_gender.appearance_settings.title": "Тышкы кыяфәт көйләүләре", + "wildfire_gender.char_settings.title": "Персонаж көйләүләре", + "wildfire_gender.char_settings.physics": "Имчәк физикасы: %s", + + "wildfire_gender.char_settings.override_armor_physics": "Көбә физикасы: %s", + "wildfire_gender.tooltip.override_armor_physics.line1": "Кушылганда имчәк физикасы киенгән көбәгез тарафыннан кечерәймәячәк һәм бастырылмаячак", + "wildfire_gender.tooltip.override_armor_physics.line2": "Бу көйләү яшерүче ресурс җыелмалары яки шундый ук минималь көбә җыелмалары белән куллану өчен эшләнгән", + + "wildfire_gender.char_settings.hide_in_armor": "Көбәдә яшерү: %s", + "wildfire_gender.char_settings.hurt_sounds": "Яралану тавышлары: %s", + "wildfire_gender.tooltip.hurt_sounds": "Әгәр Сезнең җенес «Хатын» яки «Башка» дип урнаштырылса, зыян алынганда персонажыгыз хатынның яралану тавышын уйнатачак.", + + "wildfire_gender.breast_customization.dual_physics": "Ике-физик: %s", + + "wildfire_gender.label.gender": "Җенес", + "wildfire_gender.label.female": "Хатын", + "wildfire_gender.label.male": "Ир", + "wildfire_gender.label.other": "Башка", + + "wildfire_gender.label.enabled": "Кушык", + "wildfire_gender.label.disabled": "Сүнек", + "wildfire_gender.label.yes": "Әйе", + "wildfire_gender.label.no": "Юк", + "wildfire_gender.label.with_creator": "Сез бу модның ясаучы белән бер серверда уйныйсыз!", + + "wildfire_gender.slider.bounce": "Сикерү интенсивлыгы: ×%s", + "wildfire_gender.slider.floppy": "Имчәк моменты: %s%%", + "wildfire_gender.slider.min_bounce": "Нигә физика гомумән кушылды?", + "wildfire_gender.slider.max_bounce": "Анимедагы имчәлек физикасы!!!", + "wildfire_gender.tooltip.bounce_warning": "«Сикерү интенсивлыгы» югары кыйммәтендә табигый булмаган рәвештә күренәчәк!", + + "wildfire_gender.cancer_awareness.title": "Әй, бу имчәкнең ракы турында хәбәрдарлыкның ае!", + "wildfire_gender.coming_soon": "Тиздән" +} From e34f6e92e8598212698e2ca122402dc7adc32e47 Mon Sep 17 00:00:00 2001 From: celeste Date: Wed, 10 Apr 2024 16:30:35 -0600 Subject: [PATCH 061/238] 1.20.5-pre1 note that while 1.20.5 introduces data components, we're opting to not use them for armor data and instead using the custom NBT data component to ensure that we remain compatible with vanilla clients on servers, which would simply disconnect upon recieving an armor item with a custom component that it doesn't recognize. --- .github/workflows/fabric.yml | 2 +- build.gradle | 15 ++- gradle.properties | 8 +- gradle/wrapper/gradle-wrapper.properties | 2 +- .../gui/WildfireBreastPresetList.java | 28 ++-- .../gui/screen/BaseWildfireScreen.java | 34 +++++ .../gui/screen/WardrobeBrowserScreen.java | 50 +------ .../WildfireBreastCustomizationScreen.java | 4 +- src/main/java/com/wildfire/main/Gender.java | 20 +++ .../wildfire/main/WildfireEventHandler.java | 6 +- .../wildfire/main/WildfireGenderServer.java | 2 +- .../com/wildfire/main/WildfireHelper.java | 7 +- .../com/wildfire/main/entitydata/Breasts.java | 14 +- .../main/entitydata/EntityConfig.java | 13 +- ...yncPacket.java => AbstractSyncPacket.java} | 66 ++++----- .../networking/ClientboundSyncPacket.java | 66 +++++++++ .../networking/ServerboundSyncPacket.java | 62 +++++++++ .../main/networking/WildfireSync.java | 76 +++++------ .../mixins/ArmorStandEntityMixin.java | 15 ++- .../com/wildfire/physics/BreastPhysics.java | 4 +- .../com/wildfire/render/GenderArmorLayer.java | 127 +++++++----------- .../java/com/wildfire/render/GenderLayer.java | 40 +++--- src/main/resources/fabric.mod.json | 4 +- 23 files changed, 397 insertions(+), 268 deletions(-) rename src/main/java/com/wildfire/main/networking/{SyncPacket.java => AbstractSyncPacket.java} (63%) create mode 100644 src/main/java/com/wildfire/main/networking/ClientboundSyncPacket.java create mode 100644 src/main/java/com/wildfire/main/networking/ServerboundSyncPacket.java diff --git a/.github/workflows/fabric.yml b/.github/workflows/fabric.yml index 0f0c5249..7b5d5da2 100644 --- a/.github/workflows/fabric.yml +++ b/.github/workflows/fabric.yml @@ -13,7 +13,7 @@ jobs: uses: actions/setup-java@v3 with: distribution: 'temurin' - java-version: 17 + java-version: 21 - name: Attempt to compile using Gradle run: | chmod +x gradlew diff --git a/build.gradle b/build.gradle index f913afa4..866c4c24 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,8 @@ plugins { - id 'fabric-loom' version '1.4-SNAPSHOT' + id 'fabric-loom' version '1.6-SNAPSHOT' id 'maven-publish' } -sourceCompatibility = JavaVersion.VERSION_17 -targetCompatibility = JavaVersion.VERSION_17 - version = "fabric-${project.minecraft_version}-${project.mod_version}" group = project.maven_group @@ -21,6 +18,7 @@ dependencies { minecraft "com.mojang:minecraft:${project.minecraft_version}" mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" + modImplementation fabricApi.module("fabric-networking-api-v1", project.fabric_version) modImplementation fabricApi.module("fabric-key-binding-api-v1", project.fabric_version) modImplementation fabricApi.module("fabric-lifecycle-events-v1", project.fabric_version) @@ -39,8 +37,13 @@ processResources { } tasks.withType(JavaCompile).configureEach { - // Minecraft 1.18 (1.18-pre2) upwards uses Java 17. - it.options.release = 17 + // Minecraft 1.20.5 (24w14a) upwards uses Java 21. + it.options.release = 21 +} + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } jar { diff --git a/gradle.properties b/gradle.properties index f0b346da..45b50b62 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,9 +4,9 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://fabricmc.net/develop # Note that this variable does *not* define the minimum required version in fabric.mod.json! -minecraft_version=1.20.4 -yarn_mappings=1.20.4+build.3 -loader_version=0.15.3 +minecraft_version=1.20.5-pre1 +yarn_mappings=1.20.5-pre1+build.3 +loader_version=0.15.9 # Mod Properties mod_version = 3.2.1 @@ -14,4 +14,4 @@ maven_group = com.wildfiregender.main archives_base_name = Female-Gender-Mod # Dependencies -fabric_version=0.92.0+1.20.4 +fabric_version=0.96.15+1.20.5 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c30b486a..20db9ad5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java index f94c1896..deb822ee 100644 --- a/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java +++ b/src/main/java/com/wildfire/gui/WildfireBreastPresetList.java @@ -47,7 +47,6 @@ public BreastPresetListEntry(String name, BreastPresetConfiguration data) { public WildfireBreastPresetList(WildfireBreastCustomizationScreen parent, int listWidth, int top) { super(MinecraftClient.getInstance(), 156, parent.height, top, 32); this.setRenderHeader(false, 0); - this.setRenderBackground(false); this.parent = parent; this.listWidth = listWidth; this.refreshList(); @@ -60,18 +59,21 @@ public BreastPresetListEntry[] getPresetList() { @Override protected void drawSelectionHighlight(DrawContext context, int y, int entryWidth, int entryHeight, int borderColor, int fillColor) {} + @Override + protected void drawMenuListBackground(DrawContext context) {} + + // copy of EntryListWidget#renderList without the added margin between entries @Override protected void renderList(DrawContext context, int mouseX, int mouseY, float delta) { - int i = this.getRowLeft(); - int j = this.getRowWidth(); - int k = this.itemHeight; - int l = this.getEntryCount(); - - for(int m = 0; m < l; ++m) { - int n = this.getRowTop(m); - int o = this.getRowBottom(m); - if (o >= this.getY() && n <= this.getBottom()) { - this.renderEntry(context, mouseX, mouseY, delta, m, i, n, j, k); + int left = this.getRowLeft(); + int width = this.getRowWidth(); + int count = this.getEntryCount(); + + for(int index = 0; index < count; ++index) { + int top = this.getRowTop(index); + int bottom = this.getRowBottom(index); + if(bottom >= this.getY() && top <= this.getBottom()) { + this.renderEntry(context, mouseX, mouseY, delta, index, left, top, width, itemHeight); } } } @@ -80,8 +82,9 @@ protected void renderList(DrawContext context, int mouseX, int mouseY, float del protected int getRowTop(int index) { return this.getY() - (int)this.getScrollAmount() + index * this.itemHeight + this.headerHeight; } + @Override - protected int getScrollbarPositionX() { + protected int getScrollbarX() { return parent.width / 2 + 181; } @@ -135,6 +138,7 @@ private Entry(final BreastPresetListEntry nInfo) { public void render(DrawContext ctx, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float partialTicks) { if(!visible) return; + btnOpenGUI.active = WildfireBreastPresetList.this.active; TextRenderer font = MinecraftClient.getInstance().textRenderer; //ctx.fill(x, y, x + entryWidth, y + entryHeight, 0x55005555); diff --git a/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java b/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java index 20003519..90013469 100644 --- a/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java +++ b/src/main/java/com/wildfire/gui/screen/BaseWildfireScreen.java @@ -24,8 +24,13 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.InventoryScreen; +import net.minecraft.entity.LivingEntity; import net.minecraft.text.Text; +import org.joml.Quaternionf; +import org.joml.Vector3f; @Environment(EnvType.CLIENT) public abstract class BaseWildfireScreen extends Screen { @@ -47,4 +52,33 @@ public PlayerConfig getPlayer() { public boolean shouldPause() { return false; } + + // this is adapted from InventoryScreen#drawEntity to allow for applying our own scissor calls, and instead + // simply accept an origin point to render from + public static void drawEntityOnScreen(DrawContext ctx, int x, int y, int size, float mouseX, float mouseY, LivingEntity entity) { + float i = (float) Math.atan(mouseX / 40.0F); + float j = (float) Math.atan(mouseY / 40.0F); + Quaternionf quaternionf = new Quaternionf().rotateZ((float) Math.PI); + Quaternionf quaternionf2 = new Quaternionf().rotateX(j * 20.0F * (float) (Math.PI / 180.0)); + quaternionf.mul(quaternionf2); + float k = entity.bodyYaw; + float l = entity.getYaw(); + float m = entity.getPitch(); + float n = entity.prevHeadYaw; + float o = entity.headYaw; + entity.bodyYaw = 180.0F + i * 20.0F; + entity.setYaw(180.0F + i * 40.0F); + entity.setPitch(-j * 20.0F); + entity.headYaw = entity.getYaw(); + entity.prevHeadYaw = entity.getYaw(); + // divide by entity scale to ensure that we always draw the entity at a constant size, avoiding the entity + // being either too small or far too large for the gui + float renderSize = size / entity.getScale(); + InventoryScreen.drawEntity(ctx, x, y, renderSize, new Vector3f(0f), quaternionf, quaternionf2, entity); + entity.bodyYaw = k; + entity.setYaw(l); + entity.setPitch(m); + entity.prevHeadYaw = n; + entity.headYaw = o; + } } \ No newline at end of file diff --git a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java index 150aa188..f5852c74 100644 --- a/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WardrobeBrowserScreen.java @@ -109,7 +109,9 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { int xP = this.width / 2 - 82; int yP = this.height / 2 + 40; PlayerEntity ent = client.world.getPlayerByUuid(this.playerUUID); - if(ent != null) drawEntityOnScreen(xP, yP, 45, (xP - mouseX), (yP - 76 - mouseY), ent); + if(ent != null) { + drawEntityOnScreen(ctx, xP, yP, 45, (xP - mouseX), (yP - 76 - mouseY), ent); + } } if(client != null && client.player != null) { @@ -130,50 +132,4 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { ctx.drawTexture(TXTR_RIBBON, x + 130, bcaY + 109, 26, 26, 0, 0, 20, 20, 20, 20); } } - - public static void drawEntityOnScreen(int x, int y, int size, float mouseX, float mouseY, LivingEntity entity) { - float f = (float)Math.atan(mouseX / 40.0F); - float g = (float)Math.atan(mouseY / 40.0F); - MatrixStack matrixStack = RenderSystem.getModelViewStack(); - matrixStack.push(); - matrixStack.translate((float)x, (float)y, 1050.0F); - matrixStack.scale(1.0F, 1.0F, -1.0F); - RenderSystem.applyModelViewMatrix(); - MatrixStack matrixStack2 = new MatrixStack(); - matrixStack2.translate(0.0F, 0.0F, 1000.0F); - matrixStack2.scale((float)size, (float)size, (float)size); - Quaternionf quaternionf = (new Quaternionf()).rotateZ(3.1415927F); - Quaternionf quaternionf2 = (new Quaternionf()).rotateX(g * 20.0F * 0.017453292F); - quaternionf.mul(quaternionf2); - matrixStack2.multiply(quaternionf); - float h = entity.bodyYaw; - float i = entity.getYaw(); - float j = entity.getPitch(); - float k = entity.prevHeadYaw; - float l = entity.headYaw; - entity.bodyYaw = 180.0F + f * 20.0F; - entity.setYaw(180.0F + f * 40.0F); - entity.setPitch(-g * 20.0F); - entity.headYaw = entity.getYaw(); - entity.prevHeadYaw = entity.getYaw(); - DiffuseLighting.method_34742(); - EntityRenderDispatcher entityRenderDispatcher = MinecraftClient.getInstance().getEntityRenderDispatcher(); - quaternionf2.conjugate(); - entityRenderDispatcher.setRotation(quaternionf2); - entityRenderDispatcher.setRenderShadows(false); - VertexConsumerProvider.Immediate immediate = MinecraftClient.getInstance().getBufferBuilders().getEntityVertexConsumers(); - RenderSystem.runAsFancy(() -> { - entityRenderDispatcher.render(entity, 0.0, 0.0, 0.0, 0.0F, 1.0F, matrixStack2, immediate, 15728880); - }); - immediate.draw(); - entityRenderDispatcher.setRenderShadows(true); - entity.bodyYaw = h; - entity.setYaw(i); - entity.setPitch(j); - entity.prevHeadYaw = k; - entity.headYaw = l; - matrixStack.pop(); - RenderSystem.applyModelViewMatrix(); - DiffuseLighting.enableGuiDepthLighting(); - } } \ No newline at end of file diff --git a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java index c9ff2916..e70dc9ce 100644 --- a/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java +++ b/src/main/java/com/wildfire/gui/screen/WildfireBreastCustomizationScreen.java @@ -193,7 +193,9 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { int xP = this.width / 2 - 102; int yP = this.height / 2 + 275; PlayerEntity ent = client.world.getPlayerByUuid(this.playerUUID); - if(ent != null) WardrobeBrowserScreen.drawEntityOnScreen(xP, yP, 200, -20, -20, ent); + if(ent != null) { + drawEntityOnScreen(ctx, xP, yP, 200, -20, -20, ent); + } int x = this.width / 2; int y = this.height / 2; diff --git a/src/main/java/com/wildfire/main/Gender.java b/src/main/java/com/wildfire/main/Gender.java index 56a43f49..72565c7d 100644 --- a/src/main/java/com/wildfire/main/Gender.java +++ b/src/main/java/com/wildfire/main/Gender.java @@ -1,3 +1,21 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + package com.wildfire.main; import net.minecraft.sound.SoundEvent; @@ -6,6 +24,8 @@ import org.jetbrains.annotations.Nullable; public enum Gender { + + // NOTE: The order of these should remain unchanged! Changing these WILL modify player configs! FEMALE(Text.translatable("wildfire_gender.label.female").formatted(Formatting.LIGHT_PURPLE), true, WildfireSounds.FEMALE_HURT), MALE(Text.translatable("wildfire_gender.label.male").formatted(Formatting.BLUE), false, null), OTHER(Text.translatable("wildfire_gender.label.other").formatted(Formatting.GREEN), true, WildfireSounds.FEMALE_HURT); diff --git a/src/main/java/com/wildfire/main/WildfireEventHandler.java b/src/main/java/com/wildfire/main/WildfireEventHandler.java index 56f584b7..72b8c160 100644 --- a/src/main/java/com/wildfire/main/WildfireEventHandler.java +++ b/src/main/java/com/wildfire/main/WildfireEventHandler.java @@ -21,6 +21,7 @@ import com.wildfire.gui.screen.WardrobeBrowserScreen; import com.wildfire.main.entitydata.EntityConfig; import com.wildfire.main.entitydata.PlayerConfig; +import com.wildfire.main.networking.ServerboundSyncPacket; import com.wildfire.main.networking.WildfireSync; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -28,7 +29,6 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; -import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.client.network.ClientPlayNetworkHandler; @@ -51,7 +51,7 @@ public static void registerClientEvents() { ClientEntityEvents.ENTITY_UNLOAD.register(WildfireEventHandler::onEntityUnload); ClientTickEvents.END_CLIENT_TICK.register(WildfireEventHandler::onClientTick); ClientPlayConnectionEvents.DISCONNECT.register(WildfireEventHandler::disconnect); - ClientPlayNetworking.registerGlobalReceiver(WildfireSync.SYNC_IDENTIFIER, WildfireSync::handle); + WildfireSync.registerClient(); } private static void onEntityLoad(Entity entity, World world) { @@ -76,7 +76,7 @@ private static void onClientTick(MinecraftClient client) { if(client.world == null || client.player == null) return; // Only attempt to sync if the server will accept the packet, and only once every 5 ticks, or around 4 times a second - if(ClientPlayNetworking.canSend(WildfireSync.SEND_GENDER_IDENTIFIER) && timer++ % 5 == 0) { + if(ServerboundSyncPacket.canSend() && timer++ % 5 == 0) { PlayerConfig aPlr = WildfireGender.getPlayerById(client.player.getUuid()); // sendToServer will only actually send a packet if any changes have been made that need to be synced, // or if we haven't synced before. diff --git a/src/main/java/com/wildfire/main/WildfireGenderServer.java b/src/main/java/com/wildfire/main/WildfireGenderServer.java index e14d9b39..d9b27031 100644 --- a/src/main/java/com/wildfire/main/WildfireGenderServer.java +++ b/src/main/java/com/wildfire/main/WildfireGenderServer.java @@ -32,7 +32,7 @@ public void onInitialize() { // while this class is named 'Server', this is actually a common code path, // so we can safely register here for both sides. WildfireSounds.register(); - ServerPlayNetworking.registerGlobalReceiver(WildfireSync.SEND_GENDER_IDENTIFIER, WildfireSync::handle); + WildfireSync.register(); EntityTrackingEvents.START_TRACKING.register(this::onBeginTracking); } diff --git a/src/main/java/com/wildfire/main/WildfireHelper.java b/src/main/java/com/wildfire/main/WildfireHelper.java index 52d98e3e..3ebb88f6 100644 --- a/src/main/java/com/wildfire/main/WildfireHelper.java +++ b/src/main/java/com/wildfire/main/WildfireHelper.java @@ -30,10 +30,13 @@ import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.render.entity.PlayerModelPart; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.NbtComponent; import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.*; import net.minecraft.nbt.NbtCompound; +import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.text.Text; import net.minecraft.util.Util; import net.minecraft.util.math.MathHelper; @@ -63,7 +66,7 @@ public static IGenderArmor getArmorConfig(ItemStack stack) { //Start by checking if it is a vanilla chestplate as we have custom configurations for those we check against // the armor material instead of the item instance in case any mods define custom armor items using vanilla // materials as then we can make a better guess at what we want the default implementation to be - ArmorMaterial material = armorItem.getMaterial(); + RegistryEntry material = armorItem.getMaterial(); if (material == ArmorMaterials.LEATHER) { return SimpleGenderArmor.LEATHER; } else if (material == ArmorMaterials.CHAIN) { @@ -140,6 +143,6 @@ public static void writeToNbt(@NotNull PlayerEntity player, @NotNull PlayerConfi // note that we also copy this to properly copy the exact size, as the player model will push the breast armor // layer out a bit if they have a visible jacket layer nbt.putBoolean("Jacket", player.isPartVisible(PlayerModelPart.JACKET)); - armor.setSubNbt("WildfireGender", nbt); + NbtComponent.set(DataComponentTypes.CUSTOM_DATA, armor, armorNbt -> armorNbt.put("WildfireGender", nbt)); } } \ No newline at end of file diff --git a/src/main/java/com/wildfire/main/entitydata/Breasts.java b/src/main/java/com/wildfire/main/entitydata/Breasts.java index 39b64710..b849221b 100644 --- a/src/main/java/com/wildfire/main/entitydata/Breasts.java +++ b/src/main/java/com/wildfire/main/entitydata/Breasts.java @@ -20,13 +20,15 @@ import com.wildfire.main.config.ConfigKey; import com.wildfire.main.config.Configuration; +import org.joml.Vector3f; + import java.util.function.Consumer; /** * Data class representing an entity's breast appearance settings */ @SuppressWarnings("UnusedReturnValue") -public class Breasts { +public final class Breasts { private float xOffset = Configuration.BREASTS_OFFSET_X.getDefault(), yOffset = Configuration.BREASTS_OFFSET_Y.getDefault(), zOffset = Configuration.BREASTS_OFFSET_Z.getDefault(); private float cleavage = Configuration.BREASTS_CLEAVAGE.getDefault(); @@ -40,6 +42,16 @@ private boolean updateValue(ConfigKey key, VALUE value, Consumer< return false; } + public Vector3f getOffsets() { + return new Vector3f(xOffset, yOffset, zOffset); + } + + public void updateOffsets(Vector3f offsets) { + updateXOffset(offsets.x); + updateYOffset(offsets.y); + updateZOffset(offsets.z); + } + /** * How far apart the player's breasts should be rendered from each other, also referred to as Separation in the UI * diff --git a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java index a15367e3..2bd49921 100644 --- a/src/main/java/com/wildfire/main/entitydata/EntityConfig.java +++ b/src/main/java/com/wildfire/main/entitydata/EntityConfig.java @@ -26,6 +26,8 @@ import com.wildfire.physics.BreastPhysics; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.NbtComponent; import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.decoration.ArmorStandEntity; @@ -75,7 +77,16 @@ public class EntityConfig { * @see WildfireHelper#writeToNbt */ public void readFromStack(@NotNull ItemStack chestplate) { - NbtCompound nbt = !chestplate.isEmpty() ? chestplate.getSubNbt("WildfireGender") : null; + NbtComponent component = chestplate.get(DataComponentTypes.CUSTOM_DATA); + + // #getNbt() is already marked as deprecated, despite the only other option (#copyNbt()) + // being a less performant option for what we need, which is simply a read-only view of + // the underlying nbt compound. + @SuppressWarnings("deprecation") + NbtCompound nbt = component != null && component.contains("WildfireGender") + ? component.getNbt().getCompound("WildfireGender") + : null; + if(nbt == null) { this.gender = Gender.MALE; return; diff --git a/src/main/java/com/wildfire/main/networking/SyncPacket.java b/src/main/java/com/wildfire/main/networking/AbstractSyncPacket.java similarity index 63% rename from src/main/java/com/wildfire/main/networking/SyncPacket.java rename to src/main/java/com/wildfire/main/networking/AbstractSyncPacket.java index d13ce4fd..5a260dcf 100644 --- a/src/main/java/com/wildfire/main/networking/SyncPacket.java +++ b/src/main/java/com/wildfire/main/networking/AbstractSyncPacket.java @@ -21,64 +21,59 @@ import com.wildfire.main.entitydata.Breasts; import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.Gender; -import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.minecraft.network.PacketByteBuf; +import org.joml.Vector3f; import java.util.UUID; -class SyncPacket { +abstract class AbstractSyncPacket { protected final UUID uuid; private final Gender gender; - private final float bust_size; + private final float bustSize; //physics variables - private final boolean breast_physics; - private final boolean show_in_armor; + private final boolean breastPhysics; + private final boolean showInArmor; private final float bounceMultiplier; private final float floppyMultiplier; - private final float xOffset, yOffset, zOffset; + private final Vector3f offsets; private final boolean uniboob; private final float cleavage; private final boolean hurtSounds; - protected SyncPacket(PlayerConfig plr) { + protected AbstractSyncPacket(PlayerConfig plr) { this.uuid = plr.uuid; this.gender = plr.getGender(); - this.bust_size = plr.getBustSize(); + this.bustSize = plr.getBustSize(); this.hurtSounds = plr.hasHurtSounds(); //physics variables - this.breast_physics = plr.hasBreastPhysics(); - this.show_in_armor = plr.showBreastsInArmor(); + this.breastPhysics = plr.hasBreastPhysics(); + this.showInArmor = plr.showBreastsInArmor(); this.bounceMultiplier = plr.getBounceMultiplier(); this.floppyMultiplier = plr.getFloppiness(); Breasts breasts = plr.getBreasts(); - this.xOffset = breasts.getXOffset(); - this.yOffset = breasts.getYOffset(); - this.zOffset = breasts.getZOffset(); - + this.offsets = breasts.getOffsets(); this.uniboob = breasts.isUniboob(); this.cleavage = breasts.getCleavage(); } - protected SyncPacket(PacketByteBuf buffer) { + protected AbstractSyncPacket(PacketByteBuf buffer) { this.uuid = buffer.readUuid(); this.gender = buffer.readEnumConstant(Gender.class); - this.bust_size = buffer.readFloat(); + this.bustSize = buffer.readFloat(); this.hurtSounds = buffer.readBoolean(); //physics variables - this.breast_physics = buffer.readBoolean(); - this.show_in_armor = buffer.readBoolean(); + this.breastPhysics = buffer.readBoolean(); + this.showInArmor = buffer.readBoolean(); this.bounceMultiplier = buffer.readFloat(); this.floppyMultiplier = buffer.readFloat(); - this.xOffset = buffer.readFloat(); - this.yOffset = buffer.readFloat(); - this.zOffset = buffer.readFloat(); + this.offsets = buffer.readVector3f(); this.uniboob = buffer.readBoolean(); this.cleavage = buffer.readFloat(); } @@ -86,46 +81,33 @@ protected SyncPacket(PacketByteBuf buffer) { protected void encode(PacketByteBuf buffer) { buffer.writeUuid(this.uuid); buffer.writeEnumConstant(this.gender); - buffer.writeFloat(this.bust_size); + buffer.writeFloat(this.bustSize); buffer.writeBoolean(this.hurtSounds); - buffer.writeBoolean(this.breast_physics); - buffer.writeBoolean(this.show_in_armor); + buffer.writeBoolean(this.breastPhysics); + buffer.writeBoolean(this.showInArmor); buffer.writeFloat(this.bounceMultiplier); buffer.writeFloat(this.floppyMultiplier); - buffer.writeFloat(this.xOffset); - buffer.writeFloat(this.yOffset); - buffer.writeFloat(this.zOffset); + buffer.writeVector3f(offsets); buffer.writeBoolean(this.uniboob); buffer.writeFloat(this.cleavage); } protected void updatePlayerFromPacket(PlayerConfig plr) { plr.updateGender(gender); - plr.updateBustSize(bust_size); + plr.updateBustSize(bustSize); plr.updateHurtSounds(hurtSounds); //physics - plr.updateBreastPhysics(breast_physics); - plr.updateShowBreastsInArmor(show_in_armor); + plr.updateBreastPhysics(breastPhysics); + plr.updateShowBreastsInArmor(showInArmor); plr.updateBounceMultiplier(bounceMultiplier); plr.updateFloppiness(floppyMultiplier); //System.out.println(plr.username + " - " + plr.gender); Breasts breasts = plr.getBreasts(); - breasts.updateXOffset(xOffset); - breasts.updateYOffset(yOffset); - breasts.updateZOffset(zOffset); + breasts.updateOffsets(offsets); breasts.updateUniboob(uniboob); breasts.updateCleavage(cleavage); } - - /** - * Convenience method for creating a sync packet to send over the network - */ - protected PacketByteBuf getPacket() { - PacketByteBuf packet = PacketByteBufs.create(); - this.encode(packet); - return packet; - } } diff --git a/src/main/java/com/wildfire/main/networking/ClientboundSyncPacket.java b/src/main/java/com/wildfire/main/networking/ClientboundSyncPacket.java new file mode 100644 index 00000000..3137a435 --- /dev/null +++ b/src/main/java/com/wildfire/main/networking/ClientboundSyncPacket.java @@ -0,0 +1,66 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.main.networking; + +import com.wildfire.main.WildfireGender; +import com.wildfire.main.entitydata.PlayerConfig; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.RegistryByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.network.packet.CustomPayload; +import net.minecraft.server.network.ServerPlayerEntity; + +public final class ClientboundSyncPacket extends AbstractSyncPacket implements CustomPayload { + + public static final CustomPayload.Id ID = CustomPayload.id("wildfire_gender:sync"); + public static final PacketCodec CODEC = PacketCodec.of(ClientboundSyncPacket::encode, ClientboundSyncPacket::new); + + ClientboundSyncPacket(PlayerConfig plr) { + super(plr); + } + + ClientboundSyncPacket(PacketByteBuf buffer) { + super(buffer); + } + + @Override + public Id getId() { + return ID; + } + + public static boolean canSend(ServerPlayerEntity player) { + return ServerPlayNetworking.canSend(player, ID); + } + + @Environment(EnvType.CLIENT) + public void handle(ClientPlayNetworking.Context context) { + if(context.player().getUuid().equals(uuid)) { + WildfireGender.LOGGER.warn("Ignoring sync packet referring to the client player"); + return; + } + + PlayerConfig plr = WildfireGender.getOrAddPlayerById(uuid); + updatePlayerFromPacket(plr); + plr.syncStatus = PlayerConfig.SyncStatus.SYNCED; + } +} diff --git a/src/main/java/com/wildfire/main/networking/ServerboundSyncPacket.java b/src/main/java/com/wildfire/main/networking/ServerboundSyncPacket.java new file mode 100644 index 00000000..18148d7d --- /dev/null +++ b/src/main/java/com/wildfire/main/networking/ServerboundSyncPacket.java @@ -0,0 +1,62 @@ +/* + Wildfire's Female Gender Mod is a female gender mod created for Minecraft. + Copyright (C) 2023 WildfireRomeo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package com.wildfire.main.networking; + +import com.wildfire.main.WildfireGender; +import com.wildfire.main.entitydata.PlayerConfig; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.RegistryByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.network.packet.CustomPayload; +import net.minecraft.server.network.ServerPlayerEntity; + +public final class ServerboundSyncPacket extends AbstractSyncPacket implements CustomPayload { + + public static final Id ID = CustomPayload.id("wildfire_gender:send_gender_info"); + public static final PacketCodec CODEC = PacketCodec.of(ServerboundSyncPacket::encode, ServerboundSyncPacket::new); + + ServerboundSyncPacket(PlayerConfig plr) { + super(plr); + } + + ServerboundSyncPacket(PacketByteBuf buffer) { + super(buffer); + } + + @Override + public Id getId() { + return ID; + } + + @Environment(EnvType.CLIENT) + public static boolean canSend() { + return ClientPlayNetworking.canSend(ID); + } + + public void handle(ServerPlayNetworking.Context context) { + ServerPlayerEntity player = context.player(); + PlayerConfig plr = WildfireGender.getOrAddPlayerById(player.getUuid()); + updatePlayerFromPacket(plr); + WildfireSync.sendToAllClients(player, plr); + } +} diff --git a/src/main/java/com/wildfire/main/networking/WildfireSync.java b/src/main/java/com/wildfire/main/networking/WildfireSync.java index 7a9d5631..828658b1 100644 --- a/src/main/java/com/wildfire/main/networking/WildfireSync.java +++ b/src/main/java/com/wildfire/main/networking/WildfireSync.java @@ -19,46 +19,39 @@ package com.wildfire.main.networking; import com.wildfire.main.entitydata.PlayerConfig; -import com.wildfire.main.WildfireGender; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; -import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; import net.fabricmc.fabric.api.networking.v1.PlayerLookup; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.ClientPlayNetworkHandler; -import net.minecraft.network.PacketByteBuf; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.util.Identifier; +import org.jetbrains.annotations.ApiStatus; public class WildfireSync { - // While these two identifiers could be combined into one `sync` identifier, this is kept as-is for the sake of compatibility - // with older versions, and servers that may implement syncing on other platforms (such as Spigot or any of its forks). - public static final Identifier SEND_GENDER_IDENTIFIER = new Identifier(WildfireGender.MODID, "send_gender_info"); - public static final Identifier SYNC_IDENTIFIER = new Identifier(WildfireGender.MODID, "sync"); + @ApiStatus.Internal + public static void register() { + // note that each packet has to be registered on both sides for receiving and sending, regardless + // of if the current side is actually supposed to be doing either action for a given packet. + PayloadTypeRegistry.playC2S().register(ClientboundSyncPacket.ID, ClientboundSyncPacket.CODEC); + PayloadTypeRegistry.playS2C().register(ClientboundSyncPacket.ID, ClientboundSyncPacket.CODEC); - @SuppressWarnings("unused") - @Environment(EnvType.CLIENT) - public static void handle(MinecraftClient client, ClientPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender) { - SyncPacket packet = new SyncPacket(buf); - if(client.player == null || packet.uuid.equals(client.player.getUuid())) return; + PayloadTypeRegistry.playC2S().register(ServerboundSyncPacket.ID, ServerboundSyncPacket.CODEC); + PayloadTypeRegistry.playS2C().register(ServerboundSyncPacket.ID, ServerboundSyncPacket.CODEC); - PlayerConfig plr = WildfireGender.getOrAddPlayerById(packet.uuid); - packet.updatePlayerFromPacket(plr); - plr.syncStatus = PlayerConfig.SyncStatus.SYNCED; + ServerPlayConnectionEvents.INIT.register((handler, server) -> { + ServerPlayNetworking.registerReceiver(handler, ServerboundSyncPacket.ID, ServerboundSyncPacket::handle); + }); } - @SuppressWarnings("unused") - public static void handle(MinecraftServer server, ServerPlayerEntity player, ServerPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender) { - SyncPacket packet = new SyncPacket(buf); - if(player == null || !player.getUuid().equals(packet.uuid)) return; - - PlayerConfig plr = WildfireGender.getOrAddPlayerById(packet.uuid); - packet.updatePlayerFromPacket(plr); - sendToAllClients(player, plr); + @ApiStatus.Internal + @Environment(EnvType.CLIENT) + public static void registerClient() { + ClientPlayConnectionEvents.INIT.register((handler, client) -> { + ClientPlayNetworking.registerReceiver(ClientboundSyncPacket.ID, ClientboundSyncPacket::handle); + }); } /** @@ -70,14 +63,10 @@ public static void handle(MinecraftServer server, ServerPlayerEntity player, Ser public static void sendToAllClients(ServerPlayerEntity toSync, PlayerConfig playerConfig) { if(playerConfig == null || toSync.getServer() == null) return; - SyncPacket syncPacket = new SyncPacket(playerConfig); - PlayerLookup.tracking(toSync).forEach((sendTo) -> { - if(sendTo.getUuid().equals(toSync.getUuid())) return; - if(ServerPlayNetworking.canSend(sendTo, SYNC_IDENTIFIER)) { - // encode a packet for each player we send this sync to (GH-164) - ServerPlayNetworking.send(sendTo, SYNC_IDENTIFIER, syncPacket.getPacket()); - } - }); + PlayerLookup.tracking(toSync).stream() + .filter(player -> !player.equals(toSync)) + .filter(ClientboundSyncPacket::canSend) + .forEach(player -> ServerPlayNetworking.send(player, new ClientboundSyncPacket(playerConfig))); } /** @@ -87,9 +76,8 @@ public static void sendToAllClients(ServerPlayerEntity toSync, PlayerConfig play * @param toSync The {@link PlayerConfig configuration} for the player being synced */ public static void sendToClient(ServerPlayerEntity sendTo, PlayerConfig toSync) { - if(ServerPlayNetworking.canSend(sendTo, SYNC_IDENTIFIER)) { - PacketByteBuf packet = new SyncPacket(toSync).getPacket(); - ServerPlayNetworking.send(sendTo, SYNC_IDENTIFIER, packet); + if(ClientboundSyncPacket.canSend(sendTo)) { + ServerPlayNetworking.send(sendTo, new ClientboundSyncPacket(toSync)); } } @@ -100,9 +88,11 @@ public static void sendToClient(ServerPlayerEntity sendTo, PlayerConfig toSync) */ @Environment(EnvType.CLIENT) public static void sendToServer(PlayerConfig plr) { - if(plr == null || !plr.needsSync) return; - PacketByteBuf buffer = new SyncPacket(plr).getPacket(); - ClientPlayNetworking.send(SEND_GENDER_IDENTIFIER, buffer); - plr.needsSync = false; + if(plr == null || !plr.needsSync || !ServerboundSyncPacket.canSend()) { + return; + } + + ClientPlayNetworking.send(new ServerboundSyncPacket(plr)); + plr.needsSync = false; } } diff --git a/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java b/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java index cd4560c0..a6337caa 100644 --- a/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java +++ b/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java @@ -22,6 +22,8 @@ import com.wildfire.main.WildfireGender; import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.WildfireHelper; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.NbtComponent; import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.decoration.ArmorStandEntity; import net.minecraft.entity.player.PlayerEntity; @@ -30,12 +32,21 @@ import net.minecraft.item.ItemStack; import net.minecraft.util.Hand; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(ArmorStandEntity.class) public abstract class ArmorStandEntityMixin { + @Unique + private void wildfiregender$removeBreastDataFromStack(ItemStack stack) { + NbtComponent component = stack.get(DataComponentTypes.CUSTOM_DATA); + if(component != null && component.contains("WildfireGender")) { + NbtComponent.set(DataComponentTypes.CUSTOM_DATA, stack, nbt -> nbt.remove("WildfireGender")); + } + } + @Inject( method = "equip", at = @At( @@ -53,9 +64,7 @@ public abstract class ArmorStandEntityMixin { PlayerConfig playerConfig = WildfireGender.getPlayerById(player.getUuid()); if(playerConfig == null) { - if(stack.getSubNbt("WildfireGender") != null) { - stack.removeSubNbt("WildfireGender"); - } + wildfiregender$removeBreastDataFromStack(stack); return; } diff --git a/src/main/java/com/wildfire/physics/BreastPhysics.java b/src/main/java/com/wildfire/physics/BreastPhysics.java index 276ba0d6..09d540d6 100644 --- a/src/main/java/com/wildfire/physics/BreastPhysics.java +++ b/src/main/java/com/wildfire/physics/BreastPhysics.java @@ -78,7 +78,7 @@ public void update(LivingEntity entity, IGenderArmor armor) { return; } - { + /*{ float h = 0; //tickDelta float i = entity.getLeaningPitch(0); float j; @@ -114,7 +114,7 @@ public void update(LivingEntity entity, IGenderArmor armor) { } else if (entity.getPose() == EntityPose.CROUCHING) { bodyXRotation = -15f; } - } //unused currently, might be later + }*/ //unused currently, might be later float breastWeight = entityConfig.getBustSize() * 1.25f; float targetBreastSize = entityConfig.getBustSize(); diff --git a/src/main/java/com/wildfire/render/GenderArmorLayer.java b/src/main/java/com/wildfire/render/GenderArmorLayer.java index 78d04faa..75879341 100644 --- a/src/main/java/com/wildfire/render/GenderArmorLayer.java +++ b/src/main/java/com/wildfire/render/GenderArmorLayer.java @@ -36,16 +36,21 @@ import net.minecraft.client.texture.Sprite; import net.minecraft.client.texture.SpriteAtlasTexture; import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.DyedColorComponent; import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.decoration.ArmorStandEntity; import net.minecraft.item.ArmorItem; import net.minecraft.item.ArmorMaterial; -import net.minecraft.item.DyeableArmorItem; +import net.minecraft.item.ItemStack; import net.minecraft.item.trim.ArmorTrim; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.registry.tag.ItemTags; +import net.minecraft.util.Colors; import net.minecraft.util.Identifier; +import net.minecraft.util.math.ColorHelper; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; @Environment(EnvType.CLIENT) public class GenderArmorLayer> extends GenderLayer { @@ -66,17 +71,6 @@ public GenderArmorLayer(FeatureRendererContext render, BakedModelManager b rTrim = new BreastModelBox(64, 32, 20, 17, 0, 0.0F, 0F, 4, 5, 4, 0.001F, false); } - public Identifier getArmorResource(@NotNull ArmorItem item, boolean legs, @Nullable String overlay) { - String material = item.getMaterial().getName(); - String namespace = "minecraft"; - int namespaceDelim = material.indexOf(":"); - if(namespaceDelim >= 0) { - namespace = material.substring(0, namespaceDelim); - material = material.substring(namespaceDelim + 1); - } - return new Identifier(namespace, "textures/models/armor/" + material + "_layer_" + (legs ? 2 : 1) + (overlay == null ? "" : "_" + overlay) + ".png"); - } - @Override public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, @NotNull T ent, float limbAngle, float limbDistance, float partialTicks, float animationProgress, float headYaw, float headPitch) { MinecraftClient client = MinecraftClient.getInstance(); @@ -85,9 +79,16 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume return; } + final ItemStack chestplate = ent.getEquippedStack(EquipmentSlot.CHEST); // If the entity has no armor to render, just immediately give up // Note that we have to be very fast at abandoning rendering here, as this class is also attached to armor stands - if(ent.getEquippedStack(EquipmentSlot.CHEST).isEmpty()) return; + if(chestplate.isEmpty() || !(chestplate.getItem() instanceof ArmorItem)) return; + // And similarly just entirely give up if the item has a renderer registered with Fabric API + // This will likely result in the player's breasts sticking out through the armor layer unless the mod in question + // implements an IGenderArmor to prevent them from rendering entirely, but oh well; at least we won't be + // rendering a pink box. + //noinspection UnstableApiUsage + if(ArmorRendererRegistryImpl.get(chestplate.getItem()) != null) return; try { entityConfig = getConfig(ent); @@ -95,26 +96,30 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume if(!setupRender(ent, entityConfig, partialTicks)) return; if(ent instanceof ArmorStandEntity && !genderArmor.armorStandsCopySettings()) return; - BipedEntityModel model = getContextModel(); - - // Render left - matrixStack.push(); - try { - setupTransformations(ent, model.body, matrixStack, BreastSide.LEFT); - renderBreastArmor(ent, matrixStack, vertexConsumerProvider, packedLightIn, BreastSide.LEFT); - } finally { - matrixStack.pop(); - } - - matrixStack.push(); - // Render right - try { - setupTransformations(ent, model.body, matrixStack, BreastSide.RIGHT); - renderBreastArmor(ent, matrixStack, vertexConsumerProvider, packedLightIn, BreastSide.RIGHT); - } finally { - matrixStack.pop(); - } - } catch (Exception e) { + + final RegistryEntry material = ((ArmorItem) chestplate.getItem()).getMaterial(); + final int color = armorStack.isIn(ItemTags.DYEABLE) ? DyedColorComponent.getColor(armorStack, -6265536) : Colors.WHITE; + final boolean glint = armorStack.hasGlint(); + + renderSides(ent, getContextModel(), matrixStack, side -> { + material.value().layers().forEach(layer -> { + float r, g, b; + if(layer.isDyeable() && color != Colors.WHITE) { + r = (float)ColorHelper.Argb.getRed(color) / 255f; + g = (float)ColorHelper.Argb.getGreen(color) / 255f; + b = (float)ColorHelper.Argb.getBlue(color) / 255f; + } else { + r = g = b = 1f; + } + renderBreastArmor(layer.getTexture(false), matrixStack, vertexConsumerProvider, packedLightIn, side, r, g, b, glint); + }); + + ArmorTrim trim = armorStack.get(DataComponentTypes.TRIM); + if(trim != null) { + renderArmorTrim(material, matrixStack, vertexConsumerProvider, packedLightIn, trim, glint, side); + } + }); + } catch(Exception e) { WildfireGender.LOGGER.error("Failed to render breast armor", e); } } @@ -127,55 +132,21 @@ protected void setupTransformations(T entity, ModelPart body, MatrixStack matrix matrixStack.translate(0, 0, -0.015f); matrixStack.scale(1.05f, 1.05f, 1.05f); } + matrixStack.translate(side.isLeft ? 0.001f : -0.001f, 0.015f, -0.015f); + matrixStack.scale(1.05f, 1, 1); } // TODO eventually expose some way for mods to override this, maybe through a default impl in IGenderArmor or similar - protected void renderBreastArmor(T entity, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, BreastSide side) { - if(armorStack.isEmpty() || !(armorStack.getItem() instanceof ArmorItem armorItem)) return; - - // If the armor uses its own custom renderer, just give up rendering entirely, as the only thing we'd - // actually be able to do here is simply render a pink box. - // Note that we fail this far in to allow for mods to override this through means like a mixin, - // until any sort of official compatibility API is added. - //noinspection UnstableApiUsage - if(ArmorRendererRegistryImpl.get(armorStack.getItem()) != null) return; - - Identifier armorTexture = getArmorResource(armorItem, false, null); - Identifier overlayTexture = null; - boolean hasGlint = armorStack.hasGlint(); - float armorR = 1f, armorG = 1f, armorB = 1f; - if(armorItem instanceof DyeableArmorItem dyeableItem) { - //overlayTexture = getArmorResource(entity, armorStack, EquipmentSlot.CHEST, "overlay"); - int color = dyeableItem.getColor(armorStack); - armorR = (float) (color >> 16 & 255) / 255.0F; - armorG = (float) (color >> 8 & 255) / 255.0F; - armorB = (float) (color & 255) / 255.0F; - } - matrixStack.push(); - try { - matrixStack.translate(side.isLeft ? 0.001f : -0.001f, 0.015f, -0.015f); - matrixStack.scale(1.05f, 1, 1); - BreastModelBox armor = side.isLeft ? lBoobArmor : rBoobArmor; - RenderLayer armorType = RenderLayer.getArmorCutoutNoCull(armorTexture); - VertexConsumer armorVertexConsumer = ItemRenderer.getArmorGlintConsumer(vertexConsumerProvider, armorType, false, hasGlint); - renderBox(armor, matrixStack, armorVertexConsumer, packedLightIn, OverlayTexture.DEFAULT_UV, armorR, armorG, armorB, 1); - //noinspection ConstantValue - if(overlayTexture != null) { - RenderLayer overlayType = RenderLayer.getArmorCutoutNoCull(overlayTexture); - VertexConsumer overlayVertexConsumer = ItemRenderer.getArmorGlintConsumer(vertexConsumerProvider, overlayType, false, hasGlint); - renderBox(armor, matrixStack, overlayVertexConsumer, packedLightIn, OverlayTexture.DEFAULT_UV, 1, 1, 1, 1); - } - - ArmorTrim.getTrim(entity.getWorld().getRegistryManager(), armorStack, true).ifPresent((trim) -> { - renderArmorTrim(armorItem.getMaterial(), matrixStack, vertexConsumerProvider, packedLightIn, trim, hasGlint, side); - }); - } finally { - matrixStack.pop(); - } + protected void renderBreastArmor(Identifier texture, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, + int light, BreastSide side, float r, float g, float b, boolean glint) { + BreastModelBox armor = side.isLeft ? lBoobArmor : rBoobArmor; + RenderLayer armorType = RenderLayer.getArmorCutoutNoCull(texture); + VertexConsumer armorVertexConsumer = ItemRenderer.getArmorGlintConsumer(vertexConsumerProvider, armorType, false, glint); + renderBox(armor, matrixStack, armorVertexConsumer, light, OverlayTexture.DEFAULT_UV, r, g, b, 1); } - protected void renderArmorTrim(ArmorMaterial material, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int packedLightIn, - ArmorTrim trim, boolean hasGlint, BreastSide side) { + protected void renderArmorTrim(RegistryEntry material, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, + int packedLightIn, ArmorTrim trim, boolean hasGlint, BreastSide side) { BreastModelBox trimModelBox = side.isLeft ? lTrim : rTrim; Sprite sprite = this.armorTrimsAtlas.getSprite(trim.getGenericModelId(material)); VertexConsumer vertexConsumer = sprite.getTextureSpecificVertexConsumer( diff --git a/src/main/java/com/wildfire/render/GenderLayer.java b/src/main/java/com/wildfire/render/GenderLayer.java index 5b8c81fc..c3cdb6c4 100644 --- a/src/main/java/com/wildfire/render/GenderLayer.java +++ b/src/main/java/com/wildfire/render/GenderLayer.java @@ -30,6 +30,7 @@ import java.lang.Math; import java.util.ConcurrentModificationException; +import java.util.function.Consumer; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -114,25 +115,10 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume try { if(!setupRender(ent, entityConfig, partialTicks)) return; int combineTex = LivingEntityRenderer.getOverlay(ent, 0); - BipedEntityModel model = getContextModel(); - - // Render left - matrixStack.push(); - try { - setupTransformations(ent, model.body, matrixStack, BreastSide.LEFT); - renderBreast(ent, matrixStack, vertexConsumerProvider, packedLightIn, combineTex, BreastSide.LEFT); - } finally { - matrixStack.pop(); - } - // Render right - matrixStack.push(); - try { - setupTransformations(ent, model.body, matrixStack, BreastSide.RIGHT); - renderBreast(ent, matrixStack, vertexConsumerProvider, packedLightIn, combineTex, BreastSide.RIGHT); - } finally { - matrixStack.pop(); - } + renderSides(ent, getContextModel(), matrixStack, side -> { + renderBreast(ent, matrixStack, vertexConsumerProvider, packedLightIn, combineTex, side); + }); } catch(Exception e) { WildfireGender.LOGGER.error("Failed to render breast layer", e); } @@ -287,6 +273,24 @@ private void renderBreast(T entity, MatrixStack matrixStack, VertexConsumerProvi } } + protected void renderSides(T entity, M model, MatrixStack matrixStack, Consumer renderer) { + matrixStack.push(); + try { + setupTransformations(entity, model.body, matrixStack, BreastSide.LEFT); + renderer.accept(BreastSide.LEFT); + } finally { + matrixStack.pop(); + } + + matrixStack.push(); + try { + setupTransformations(entity, model.body, matrixStack, BreastSide.RIGHT); + renderer.accept(BreastSide.RIGHT); + } finally { + matrixStack.pop(); + } + } + protected static void renderBox(WildfireModelRenderer.ModelBox model, MatrixStack matrixStack, VertexConsumer bufferIn, int packedLightIn, int packedOverlayIn, float red, float green, float blue, float alpha) { Matrix4f matrix4f = matrixStack.peek().getPositionMatrix(); diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index ff945c81..8467e339 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -44,8 +44,8 @@ "fabric-rendering-v1": "*", "fabric-resource-loader-v0": "*", "fabric-registry-sync-v0": "*", - "minecraft": ">=1.20.3 <=1.20.4", - "java": ">=17" + "minecraft": ">=1.20.5-alpha.24.14.a <=1.20.5", + "java": ">=21" }, "conflicts": { "skinlayers": "*", From 1b643a3574924f854d79558b5476f491e6a6b661 Mon Sep 17 00:00:00 2001 From: celeste Date: Wed, 10 Apr 2024 16:43:15 -0600 Subject: [PATCH 062/238] switch to using '$mod+$minecraft' versioning instead of '$minecraft-$mod' the way this was done previously led fabric to infer that the mod version was the minecraft version, and not the (intended) other way around. this also provides some level of parity with the neoforge version change made in aa55cbe5284b26bc7d62d7f9111bf7f9ccb5757d, which simply drops the minecraft version from the mod version string entirely. --- build.gradle | 2 +- src/main/resources/fabric.mod.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 866c4c24..a9e722ef 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { id 'maven-publish' } -version = "fabric-${project.minecraft_version}-${project.mod_version}" +version = "fabric-${project.mod_version}+${project.minecraft_version}" group = project.maven_group base { diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 8467e339..07d3cfca 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -1,7 +1,7 @@ { "schemaVersion": 1, "id": "wildfire_gender", - "version": "${minecraft_version}-${version}", + "version": "${version}+${minecraft_version}", "name": "Wildfire's Female Gender Mod", "description": "Allows players to choose what gender they want to be.", "authors": [ From aa5b2cd5f36c81f5a7f280ac24ae1b59b62fcc4a Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 10 Mar 2024 17:44:47 -0600 Subject: [PATCH 063/238] update action versions, cache downloaded libraries also include a job summary of the target minecraft and fabric version --- .github/extract_refs.sh | 24 ++++++++++++++++++++++++ .github/workflows/fabric.yml | 36 +++++++++++++++++++++++------------- gradlew | 0 3 files changed, 47 insertions(+), 13 deletions(-) create mode 100755 .github/extract_refs.sh mode change 100644 => 100755 gradlew diff --git a/.github/extract_refs.sh b/.github/extract_refs.sh new file mode 100755 index 00000000..20c560fd --- /dev/null +++ b/.github/extract_refs.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +# Extract the branch name from $GITHUB_REF, replacing any slash characters with dashes +# https://github.com/CaffeineMC/sodium-fabric/blob/435a6bd7ecfa58499b64f1a73b61309070929d86/.github/workflows/build-commit.yml#L15 +ref="${GITHUB_REF#refs/heads/}" && echo "branch=${ref////-}" >> $GITHUB_OUTPUT + +# Extract the Minecraft version from gradle.properties +minecraft_version=$(grep 'minecraft_version=' gradle.properties --color=never) && echo $minecraft_version >> $GITHUB_OUTPUT + +### Build version summary + +fabric_loader=$(grep 'loader_version=' gradle.properties --color=never) +fabric_loader="${fabric_loader#loader_version=}" + +fabric_api=$(grep 'fabric_version=' gradle.properties --color=never) +fabric_api="${fabric_api#fabric_version=}" +# Strip the trailing '+version' syntax +fabric_api="${fabric_api//+[0-9]*/}" + +echo "## Version summary" >> $GITHUB_STEP_SUMMARY +echo "" >> $GITHUB_STEP_SUMMARY +echo "- Targeting Minecraft version ${minecraft_version#minecraft_version=}" >> $GITHUB_STEP_SUMMARY +echo "- Using Fabric loader $fabric_loader" >> $GITHUB_STEP_SUMMARY +echo "- Using Fabric API version $fabric_api" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/fabric.yml b/.github/workflows/fabric.yml index 7b5d5da2..00b23907 100644 --- a/.github/workflows/fabric.yml +++ b/.github/workflows/fabric.yml @@ -1,26 +1,36 @@ -name: Build Fabric mod with Gradle +name: Build Fabric on: push: jobs: build: runs-on: ubuntu-22.04 steps: - - name: Get the repository contents. - uses: actions/checkout@v3 + - name: Checkout repository + uses: actions/checkout@v4 with: clean: false + - name: Extract build version information + id: ref + run: .github/extract_refs.sh - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: 21 - - name: Attempt to compile using Gradle - run: | - chmod +x gradlew - ./gradlew build - # we do not want -sources and -dev files, also this needs a github account to be downloaded via summary page. - - name: Upload compiled mod jars. - uses: actions/upload-artifact@v3 + - name: Initialize caches + uses: actions/cache@v4 with: - name: WFGM-artifacts - path: build/libs/*[0-9].jar + path: | + ~/.gradle/caches + ~/.gradle/loom-cache + ~/.gradle/wrapper + key: ${{ runner.os }}-build-fabric-${{ steps.ref.outputs.minecraft_version }} + restore-keys: | + ${{ runner.os }}-build-fabric- + - name: Compile + run: ./gradlew build + - name: Upload compiled artifacts + uses: actions/upload-artifact@v4 + with: + name: WFGM-artifacts-${{ steps.ref.outputs.branch }} + path: build/libs/*.jar diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 From 11cd7eee13bbf631c5d4a8a5cb75a5491ba72d31 Mon Sep 17 00:00:00 2001 From: celeste Date: Wed, 10 Apr 2024 17:51:54 -0600 Subject: [PATCH 064/238] add devauth to allow logging into a minecraft account in a dev env --- build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.gradle b/build.gradle index a9e722ef..217acd9d 100644 --- a/build.gradle +++ b/build.gradle @@ -11,6 +11,7 @@ base { } repositories { + maven { url = "https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1" } } dependencies { @@ -25,6 +26,10 @@ dependencies { modImplementation fabricApi.module("fabric-rendering-v1", project.fabric_version) modRuntimeOnly fabricApi.module("fabric-resource-loader-v0", project.fabric_version) modRuntimeOnly fabricApi.module("fabric-registry-sync-v0", project.fabric_version) + + // Allow logging into an actual Minecraft account in a dev env + // See https://github.com/DJtheRedstoner/DevAuth + modLocalRuntime "me.djtheredstoner:DevAuth-fabric:1.2.0" } processResources { From 4251f6be9a6abcf16474c2bd98f44a440efa703f Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 10 Mar 2024 17:42:08 -0600 Subject: [PATCH 065/238] refactor armor stand mixins switch from using an Inject with a shift to ModifyArg to make it clear where our stack modification is intended to be performed also remove our tag from armor removed from armor stands --- .../mixins/ArmorStandEntityMixin.java | 73 ++++++++++++++----- src/main/resources/fabric.mod.json | 2 +- 2 files changed, 57 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java b/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java index a6337caa..2bdcfb60 100644 --- a/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java +++ b/src/main/java/com/wildfire/mixins/ArmorStandEntityMixin.java @@ -18,27 +18,31 @@ package com.wildfire.mixins; +import com.llamalad7.mixinextras.sugar.Local; import com.wildfire.api.IGenderArmor; import com.wildfire.main.WildfireGender; import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.main.WildfireHelper; import net.minecraft.component.DataComponentTypes; import net.minecraft.component.type.NbtComponent; +import net.minecraft.entity.EntityType; import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.LivingEntity; import net.minecraft.entity.decoration.ArmorStandEntity; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ArmorItem; -import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.util.Hand; +import net.minecraft.world.World; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.ModifyArg; @Mixin(ArmorStandEntity.class) -public abstract class ArmorStandEntityMixin { +public abstract class ArmorStandEntityMixin extends LivingEntity { + protected ArmorStandEntityMixin(EntityType entityType, World world) { + super(entityType, world); + } + @Unique private void wildfiregender$removeBreastDataFromStack(ItemStack stack) { NbtComponent component = stack.get(DataComponentTypes.CUSTOM_DATA); @@ -47,30 +51,65 @@ public abstract class ArmorStandEntityMixin { } } - @Inject( + @ModifyArg( method = "equip", at = @At( value = "INVOKE", - target = "Lnet/minecraft/entity/decoration/ArmorStandEntity;equipStack(Lnet/minecraft/entity/EquipmentSlot;Lnet/minecraft/item/ItemStack;)V", - shift = At.Shift.BEFORE - ) + target = "Lnet/minecraft/entity/decoration/ArmorStandEntity;equipStack(Lnet/minecraft/entity/EquipmentSlot;Lnet/minecraft/item/ItemStack;)V" + ), + index = 1 ) - public void wildfiregender$equipArmorStandChestplate(PlayerEntity player, EquipmentSlot slot, ItemStack stack, Hand hand, CallbackInfoReturnable cir) { - if(player == null || player.getWorld().isClient()) return; - - Item item = stack.getItem(); - // Only apply to chestplates - if(!(item instanceof ArmorItem armorItem) || armorItem.getSlotType() != EquipmentSlot.CHEST) return; + public ItemStack wildfiregender$attachBreastData(ItemStack stack, @Local(argsOnly = true) EquipmentSlot slot, + @Local(argsOnly = true) PlayerEntity player) { + if(player == null || player.getWorld().isClient() || slot != EquipmentSlot.CHEST) { + return stack; + } PlayerConfig playerConfig = WildfireGender.getPlayerById(player.getUuid()); if(playerConfig == null) { + // while we shouldn't have our tag on the stack still, we're still checking to catch any armor + // that may still have the tag from older versions, or from potential cross-mod interactions + // which allow for removing items from armor stands without calling the vanilla + // #equip and/or #onBreak methods wildfiregender$removeBreastDataFromStack(stack); - return; + return stack; } IGenderArmor armorConfig = WildfireHelper.getArmorConfig(stack); if(armorConfig.armorStandsCopySettings()) { WildfireHelper.writeToNbt(player, playerConfig, stack); } + + return stack; + } + + @ModifyArg( + method = "equip", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/entity/player/PlayerEntity;setStackInHand(Lnet/minecraft/util/Hand;Lnet/minecraft/item/ItemStack;)V" + ), + index = 1 + ) + public ItemStack wildfiregender$removeBreastDataOnReplace(ItemStack stack, @Local(argsOnly = true) PlayerEntity player) { + if(!player.getWorld().isClient()) { + wildfiregender$removeBreastDataFromStack(stack); + } + return stack; + } + + @ModifyArg( + method = "onBreak", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/block/Block;dropStack(Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/item/ItemStack;)V" + ), + index = 2 + ) + public ItemStack wildfiregender$removeBreastDataOnBreak(ItemStack stack) { + if(!getWorld().isClient()) { + wildfiregender$removeBreastDataFromStack(stack); + } + return stack; } } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 07d3cfca..7bb535b6 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -37,7 +37,7 @@ "wildfire_gender.mixins.json" ], "depends": { - "fabricloader": "*", + "fabricloader": ">=0.15", "fabric-networking-api-v1": "*", "fabric-key-binding-api-v1": "*", "fabric-lifecycle-events-v1": "*", From 0629782bca2e95f7856ede8a4668f886269eb930 Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 1 Apr 2024 15:25:14 -0600 Subject: [PATCH 066/238] switch to using a data component-like record for armor --- .../com/wildfire/main/WildfireHelper.java | 51 +++++--------- .../main/entitydata/BreastDataComponent.java | 70 +++++++++++++++++++ .../main/entitydata/EntityConfig.java | 41 ++++++----- .../mixins/ArmorStandEntityMixin.java | 6 +- 4 files changed, 115 insertions(+), 53 deletions(-) create mode 100644 src/main/java/com/wildfire/main/entitydata/BreastDataComponent.java diff --git a/src/main/java/com/wildfire/main/WildfireHelper.java b/src/main/java/com/wildfire/main/WildfireHelper.java index 3ebb88f6..a942c439 100644 --- a/src/main/java/com/wildfire/main/WildfireHelper.java +++ b/src/main/java/com/wildfire/main/WildfireHelper.java @@ -20,30 +20,25 @@ import com.wildfire.api.IGenderArmor; import com.wildfire.api.WildfireAPI; -import com.wildfire.main.entitydata.Breasts; -import com.wildfire.main.entitydata.EntityConfig; -import com.wildfire.main.entitydata.PlayerConfig; import com.wildfire.render.armor.SimpleGenderArmor; import com.wildfire.render.armor.EmptyGenderArmor; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.render.entity.PlayerModelPart; -import net.minecraft.component.DataComponentTypes; -import net.minecraft.component.type.NbtComponent; +import com.wildfire.main.config.FloatConfigKey; import net.minecraft.entity.EquipmentSlot; -import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.*; import net.minecraft.nbt.NbtCompound; import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.text.Text; import net.minecraft.util.Util; import net.minecraft.util.math.MathHelper; -import org.jetbrains.annotations.NotNull; import java.util.Objects; +import java.util.Optional; import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Function; public class WildfireHelper { public static int randInt(int min, int max) { @@ -119,30 +114,20 @@ public static void drawScrollableTextWithoutShadow(DrawContext context, TextRend } /** - *