Skip to content

Commit

Permalink
Release 1.8
Browse files Browse the repository at this point in the history
-Added Legacy4J Settings Screen, a way to search all the settings, accessible via Mods screen.
-Added World Host Friends screen to Main menu.
-Added `Advanced Options Mode` slider to Legacy4J Settings screen Advanced User Interface section.
-Added `Save Cache` option to Game Options section.
-Added `Inverted Front Camera Pitch` option to Game Options section.
-Added `Auto Save Countdown` option to User Interface section.
-Added `Head Follows the Camera` option to Graphics section.
-Added `Fast Leaves When Blocked` option to Graphics section.
-Added `Fast Leaves Custom Models` option to Graphics section.
-Added `Override Terrain Fog End` option to `Graphics` section.
-Added `Legacy Sky Shape` option to `Graphics` section.
-Added back `Virtual Cursor` option to Controller section.
-Added Legacy Common Config (`.minecraft/config/legacy/common.json`).
-Added Legacy Common Mixin Config (`.minecraft/config/legacy/common_mixin.json`).
-Added Legacy Mixin Options (`.minecraft/config/legacy/client_mixin.json`).
-Added Legacy4J Settings key mapping, with `Y` as default key.
-Added `Display System Messages as Overlay` option in Advanced User Interface section.
-Added `Global Resource Packs` to Graphics section.
-Added `Classic Trading`, `Classic Stonecutting`, `Classic Loom` and `Mixed Crafting` options to User Interface section.
-Added missing `Force Smooth Movement` Portuguese translations.
-Added `put_scrollable_renderer`, `put_renderable_vertical_list`, `modify_renderable_vertical_list`, `draw_outlined_string` and `render_enchanted_book` UI Definition Element Types.
-Added `Random Block Rotations` and `Nearest Mipmap Scaling` Factory API options to Graphics section.
-Added manual mipmaps for water.
-Added a Patch Notes screen, which appears when the game/Legacy4J is updated or launched for the first time.
-Added common options to Game Options section (or Host Options when playing in a world): Legacy Combat`, `Legacy Sword Blocking` and `Squared View Distance`.
-Added client mixin options to Graphics section:
 -Screens: `Legacy Inventory Screen`, `Legacy Classic Crafting Screen`,  `Legacy Classic Stonecutter Screen`, `Legacy Classic Loom Screen`, `Legacy Classic Merchant Screen`, `Legacy Container Screen`, `Legacy Crafter Screen` (in versions >=1.20.4), `Legacy Furnace Screen`, `Legacy Anvil Screen`, `Legacy Smithing Screen`, `Legacy Gridstone Screen`, `Legacy Cartography Screen`, `Legacy Enchantment Screen`, `Legacy Beacon Screen`, `Legacy Brewing Stand Screen`, `Legacy Book Screen`, `Legacy Create World Screen` and `Legacy Title Screen`.
 -Miscellaneous: `Legacy Gui`, `Legacy Chat`, `Legacy Bosshealth`, `Legacy Witches` and `Legacy Drowned`.
-Now, the leaves when using Fast graphics will use an alternative model instead of removing the cutout effect, similar to LE.
-Now, Legacy Waters has a new logo.
-Now, the Mixed Crafting Interface will be used when Legacy4J isn't present on server side.
-Now, the Alphabetical sorting will be used in the Mods screen by default.
-Now, the Graphics section will show the resource selectors tooltip box.
-Now, the `Interface Resolution` slider will be nominally 50 to 150%, equivalent to the real value used.
-Now, the enchantment tooltips will be similar to LE.
-Now, the options are completely unlinked from vanilla, having a new directory `.minecraft/config/legacy/client_options.json`.
-Now, the How To Play will be similar to LE (thanks to Moustard for providing the text) and almost completely data-driven with UI Definitions.
-Now, it'll be possible to access the vanilla Options screen via Mods screen.
-Now, the sugar cane won't have tint when using Programmer Art or Console Aspects, being similar to LE.
-Now, multiline edit boxes will have a LE-style and allow using the keyboard.
-Now, the Add Album screen will allow description changes.
-Now, any resolution next to 720p will use the mojangles 11 font.
-Now, the Brewing Stand Interface panel will be positioned similar to LE.
-Now, the Controller Mapping and Keybinds screens are more similar to LE, having tooltips for cancel and conflicts.
-Now, the Loading and Load Save screen are more similar to LE.
-Now, the text in tabs is positioned more similarly to LE.
-Now, there will be a message when trying to spawn the wither in peaceful mode, like in LE.
-Now, the loading background is a texture `legacy:textures/gui/loading_background.png`.
-Now, hints and autosave icon will no longer appear when the gui is hidden (with F1)
-Now, the `Terrain Fog End` option will be in chunks, being more similar to LE.
-Improved JEI controller compatibility.
-Renamed the Resource Assorts to Resource Albums (thanks to cloud54 for suggesting this).
-Removed blockstate definitions that disabled the random block rotations.
-Removed all the deprecated listing syntax.
-Fixed incompatibility with Hold My Items mod.
-Fixed Controller Manager stopping working when using more than one controller on Linux and macOS.
-Fixed a lot of confirmation screens having incorrect background panel height.
-Fixed Player Tab List with wrong background height.
-Fixed Programmer Art and Console Aspects not having item definitions in 1.21.2+, resulting in broken models.
-Fixed Interface Opacity being different in versions 1.21.2+.
-Fixed crash when loading a World Template with a non-game path.
-Fixed Loading Overlay screen not rendering correctly in versions +1.21.2.
-Fixed Firework with incorrect texture format in versions +1.21.2.
-Fixed mixin crash in versions forge/neoforge <=1.20.4.
-Fixed items with custom names based on the item stack not having a default tip based on their name, such as potions.
-Fixed `put_legacy_slot` UI Element Type missing the warning icon when it's a real slot, and not updating its display when it's a fake slot.
-Fixed Merchant Interface with weird scale when the villager can't level up.
-Fixed scroll out of the panel in the Host Options Interface.
-Fixed chat line spacing not being at max by default in versions >=1.21.2.
-Fixed Creative Mode screen not allowing to drop items in versions >=1.21.2.
-Fixed JEI having wrong positions for recipe viewers.
-Fixed loading background not being black in versions >=1.21.4.
-Fixed incompatibility with Iris shaders caused by `Flying View Rolling`.
-Fixed a lot of mixin incompatibilities by using mixin extras.
-Fixed items and blocks being invisible in the hotbar when using Fabulous graphics in 1.21.1.
-Updated Mod Credits.
-MIT License is being used again.
  • Loading branch information
Wilyicaro committed Mar 2, 2025
1 parent 42b05a8 commit 484ca2d
Show file tree
Hide file tree
Showing 436 changed files with 9,135 additions and 3,007 deletions.
47 changes: 36 additions & 11 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ group = rootProject.maven_group
def loader = property("loom.platform")

repositories {
mavenCentral()
maven { url "https://maven.neoforged.net/releases/" }

maven {
Expand Down Expand Up @@ -49,6 +50,19 @@ repositories {
}

maven{url = "https://maven.su5ed.dev/releases"}


maven {
// location of the maven that hosts JEI files since January 2023
name = "Jared's maven"
url = "https://maven.blamejared.com/"
}

maven {
// location of a maven mirror for JEI files, as a fallback
name = "ModMaven"
url = "https://modmaven.dev"
}
}
configurations.configureEach {
resolutionStrategy {
Expand All @@ -62,32 +76,48 @@ dependencies {
modApi "wily.factory_api:factory_api-${loader}:${stonecutter.current.version}-${factory_api_version}"
modCompileOnly("maven.modrinth:world-host:${world_host_version}")
modCompileOnly("maven.modrinth:vivecraft:${vivecraft_version}")
if (stonecutter.eval(stonecutter.current.version, "<=1.21.1")) {
modCompileOnlyApi("mezz.jei:jei-${stonecutter.current.version}-${loader}-api:${jei_version}")
modCompileOnly("mezz.jei:jei-${stonecutter.current.version}-${loader}:${jei_version}")
}

if (loader == "fabric" || stonecutter.eval(stonecutter.current.version, ">=1.21") && loader == "neoforge"){
modCompileOnly("maven.modrinth:sodium:${sodium_version}")
modImplementation("maven.modrinth:sodium:${sodium_version}")
modCompileOnly("maven.modrinth:iris:${iris_version}")
}

if (loader == "fabric") {
modImplementation "net.fabricmc:fabric-loader:${fabric_loader_version}"
modApi "net.fabricmc.fabric-api:fabric-api:${fabric_api_version}"
modCompileOnly("com.terraformersmc:modmenu:${modmenu_version}")
modImplementation("com.terraformersmc:modmenu:${modmenu_version}")
if (stonecutter.eval(stonecutter.current.version, ">=1.21") || stonecutter.eval(stonecutter.current.version, "1.20.1")) {
modCompileOnly("maven.modrinth:nostalgic-tweaks:${nt_version}")
modCompileOnly("dev.architectury:architectury-fabric:${architectury_version}")
}

} else if (loader == "forge") {
forge "net.minecraftforge:forge:${stonecutter.current.version}-${forge_version}"
if (stonecutter.eval(stonecutter.current.version, "1.20.1")){
modCompileOnly("maven.modrinth:nostalgic-tweaks:${nt_version}")
modCompileOnly("dev.architectury:architectury-forge:${architectury_version}")
}
compileOnly(annotationProcessor("io.github.llamalad7:mixinextras-common:${mixin_extras_version}"))
} else if (loader == "neoforge") {
neoForge "net.neoforged:neoforge:${neoforge_version}"
}

if (loader == "neoforge" && stonecutter.eval(stonecutter.current.version, ">=1.21")) {
modCompileOnly("org.sinytra.forgified-fabric-api:fabric-rendering-data-attachment-v1:0.3.48+73761d2e19")
modCompileOnly("org.sinytra.forgified-fabric-api:fabric-block-view-api-v2:1.0.10+9afaaf8c19")
modCompileOnly("maven.modrinth:nostalgic-tweaks:${nt_version}")
modCompileOnly("dev.architectury:architectury-neoforge:${architectury_version}")
}

if (loader == "forge" || loader == "neoforge") {
forgeRuntimeLibrary sdl_dependency
}


}

def aw = "${mod_id}-${stonecutter.eval(stonecutter.current.version, ">=1.21.2") ? 1212 : 120}.accesswidener"
Expand Down Expand Up @@ -148,13 +178,8 @@ ext {
def changes = new StringBuilder()
changes << "## " + stage.capitalize() + " $version \nUpdated at **$time**. \n**Changelog** "
def proc = "git log --max-count=4 --pretty=format:%s".execute()
proc.in.eachLine { line ->
def processedLine = line.toString()
def lines = processedLine.split(" -")
changes << "\n**${lines[0]}** "
lines.each {s-> if (s != lines[0]) changes << "\n- " + s + " "}
}
changes << "\n[Click here for complete changelog]($mod_source/commits/$branch)"
getRootProject().file("src/main/resources/assets/legacy/changelog/en_us.txt").readLines().forEach {s-> changes << "\n" + s}
changes << "\n\n[Click here for complete changelog]($mod_source/commits/$branch)"
proc.waitFor()
return changes.toString()
}
Expand All @@ -180,6 +205,7 @@ processResources {
"mod_issues" : mod_issues,
"neoforge_version" : neoforge_version,
"forge_version" : forge_version,
"fabric_api_version" : fabric_api_version,
"mc_version_range" : version_range,
"mc_version" : stonecutter.current.version,
"aw": aw,
Expand All @@ -189,11 +215,10 @@ processResources {
if (loader != "fabric") {
exclude "fabric.mod.json"
exclude "${mod_id}-fabric.mixins.json"
} else exclude "${mod_id}-forge_like.mixins.json"
}
if (loader != "forge") exclude "META-INF/mods.toml"
if (loader != "neoforge") {
exclude "META-INF/neoforge.mods.toml"
exclude "${mod_id}-neoforge.mixins.json"
}
filesMatching(loader == "fabric" ? "fabric.mod.json" : loader == "forge" ? "META-INF/mods.toml" : "META-INF/neoforge.mods.toml") {
expand properties
Expand Down
12 changes: 8 additions & 4 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ org.gradle.jvmargs=-Xmx3072M
enabled_platforms=fabric,forge,neoforge

archives_base_name=Legacy4J
mod_version=1.7.10.2502.0
mod_version=1.8
mod_id=legacy
mod_license=MIT
mod_name=Legacy4J
mod_description=This mod adapts all the features from old Minecraft Legacy Console edition
mod_description=This mod adapts all the features from old Minecraft Legacy Console Edition
mod_source=https://github.com/Wilyicaro/Legacy-Minecraft
mod_issues=https://github.com/Wilyicaro/Legacy-Minecraft/Issues
mod_authors=Wilyicaro
mod_credits=4J Studios and Mojang
mod_credits=4J Studios, Mojang, AgentMindStorm, cloud54, RedRain0o0, Cjnator38, Steeve, Jab125, NicSonic, ItzJustAPlayer, Permdog99
maven_group=wily.legacy

mc_version_range=[VERSIONED]
Expand All @@ -21,13 +21,17 @@ fabric_api_version=[VERSIONED]
forge_version=[VERSIONED]
neoforge_version=[VERSIONED]

factory_api_version=2.2.2501.24
factory_api_version=2.2.3-pre

sodium_version=mc1.21-0.6.0-beta.2-fabric
iris_version=1.8.0-beta.4+1.21-fabric
world_host_version=0.5.1+1.21.1-fabric
vivecraft_version=1.21.1-1.1.14-b1-fabric
modmenu_version=11.0.1
nt_version=dQ41WNcA
architectury_version=13.0.6
jei_version=19.21.0.247
mixin_extras_version=0.4.1

sdl_dependency=dev.isxander:libsdl4j:3.preview-3.1.3-46

Expand Down
83 changes: 56 additions & 27 deletions src/main/java/wily/legacy/Legacy4J.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,21 @@
import net.minecraft.sounds.SoundSource;
import net.minecraft.stats.Stats;
import net.minecraft.tags.ItemTags;
import net.minecraft.world.InteractionResult;
//? if >=1.20.5 && <1.21.2 {
import net.minecraft.world.ItemInteractionResult;
//?}
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.alchemy.Potion;
//? if >=1.20.5 {
import net.minecraft.world.level.block.entity.BannerPatternLayers;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.core.component.DataComponents;
import net.minecraft.world.item.alchemy.PotionContents;
import net.minecraft.world.item.component.DyedItemColor;
//?} else {
/*import net.minecraft.world.item.alchemy.PotionUtils;
import wily.factoryapi.util.ColorUtil;
import wily.factoryapi.util.FactoryScreenUtil;
import wily.legacy.util.ItemAccessor;
*///?}
Expand All @@ -60,9 +60,12 @@
import wily.factoryapi.FactoryAPI;
import wily.factoryapi.FactoryAPIPlatform;
import wily.factoryapi.FactoryEvent;
import wily.factoryapi.base.config.FactoryConfig;
import wily.factoryapi.base.network.CommonNetwork;
import wily.legacy.block.entity.WaterCauldronBlockEntity;
import wily.legacy.config.LegacyConfig;
import wily.legacy.config.LegacyCommonOptions;
import wily.legacy.config.LegacyMixinToggles;
import wily.legacy.config.LegacyWorldOptions;
import wily.legacy.init.*;
import wily.legacy.network.*;
import wily.legacy.entity.LegacyPlayerInfo;
Expand All @@ -83,7 +86,6 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.*;
import java.util.List;
import java.util.function.Function;
Expand All @@ -100,10 +102,8 @@ public class Legacy4J {

public static final String MOD_ID = "legacy";
public static final Supplier<String> VERSION = ()-> FactoryAPIPlatform.getModInfo(MOD_ID).getVersion();

public static final Logger LOGGER = LogManager.getLogger(MOD_ID);

public static MinecraftServer currentServer;
public static final FactoryConfig.StorageHandler MIXIN_CONFIGS_STORAGE = FactoryConfig.StorageHandler.fromMixin(LegacyMixinToggles.COMMON_STORAGE, true);

private static Collection<CommonNetwork.Payload> playerInitialPayloads = Collections.emptySet();

Expand All @@ -114,7 +114,39 @@ public Legacy4J(){
Legacy4JClient.init();
*///?}
}

public static List<Integer> getParsedVersion(String version){
List<Integer> parsedVersion = new ArrayList<>();
String[] versions = version.split("\\.");
for (String s : versions) {
int value;
try {
value = Integer.parseInt(s);
} catch (NumberFormatException e) {
value = 0;
}
parsedVersion.add(value);
}
return parsedVersion;
}

public static boolean isNewerVersion(String actualVersion, String previous){
return isNewerVersion(actualVersion, previous, 2);
}

public static boolean isNewerVersion(String actualVersion, String previous, int limitCount){
List<Integer> v = getParsedVersion(actualVersion);
List<Integer> v1 = getParsedVersion(previous);
int size = limitCount <= 0 ? v.size() : Math.min(limitCount, v.size());
for (int i = 0; i < size; i++) {
if (v.get(i) > (v1.size() <= i ? 0 : v1.get(i))) return true;
}
return false;
}

public static void init(){
FactoryConfig.registerCommonStorage(createModLocation("common"), LegacyCommonOptions.COMMON_STORAGE);
FactoryConfig.registerCommonStorage(createModLocation("mixin_common"), MIXIN_CONFIGS_STORAGE);
LegacyRegistries.register();
LegacyGameRules.init();
FactoryEvent.registerPayload(r->{
Expand All @@ -132,10 +164,6 @@ public static void init(){
r.register(false, TipCommand.Payload.ID);
r.register(false, TipCommand.EntityPayload.ID);
r.register(false, TopMessage.Payload.ID);
r.register(false, CommonConfigSyncPayload.ID);
//? if >=1.21.2 {
/*r.register(false, CommonRecipeManager.ClientPayload.ID);
*///?}
});
ArmorStandPose.init();
//? if >=1.20.5 {
Expand All @@ -146,7 +174,6 @@ public static void init(){
*///?}
FactoryEvent.registerCommands((dispatcher,context,selection)->{
TipCommand.register(dispatcher,context,selection);
Legacy4JCommand.register(dispatcher,context);
});
FactoryEvent.setup(Legacy4J::setup);
FactoryEvent.tagsLoaded(Legacy4J::tagsLoaded);
Expand All @@ -171,8 +198,8 @@ public static ResourceLocation createModLocation(String path){
}

public static void setup(){
//LegacyConfig.commonLoadAll();
if (!LegacyConfig.legacyCauldrons.get()) return;
LegacyCommonOptions.COMMON_STORAGE.load();
if (!LegacyMixinToggles.legacyCauldrons.get()) return;
Map<Item, CauldronInteraction> emptyCauldron = CauldronInteraction.EMPTY/*? if >1.20.1 {*/.map()/*?}*/;
Map<Item, CauldronInteraction> waterCauldron = CauldronInteraction.WATER/*? if >1.20.1 {*/.map()/*?}*/;
CauldronInteraction emptyCauldronPotion = (blockState, level, blockPos, player, interactionHand, itemStack) ->{
Expand Down Expand Up @@ -329,9 +356,17 @@ public ItemStack execute(BlockSource blockSource, ItemStack itemStack) {
*///?}
}

public static boolean isChunkPosVisibleInSquare(int centerX, int centerZ, int viewDistance, int x, int z, boolean offset){
int n = Math.max(0, Math.abs(x - centerX) - 1);
int o = Math.max(0, Math.abs(z - centerZ) - 1);
long p = Math.max(0, Math.max(n, o) - (offset ? 1 : 0));
long q = Math.min(n, o);
return Math.max(p, q) < viewDistance;
}

public static ItemStack setItemStackPotion(ItemStack stack, Holder<Potion> potion){
//? if <1.20.5 {
/*return PotionUtils.setPotion(stack,potion.value());
/*return PotionUtils.setPotion(stack, potion.value());
*///?} else {
stack.set(DataComponents.POTION_CONTENTS, new PotionContents(potion));
return stack;
Expand All @@ -347,17 +382,17 @@ public static void addPotionTooltip(Holder<Potion> potion, List<Component> toolt
}

public static int getDyeColor(DyeColor dyeColor){
return /*? if <1.20.5 {*//*FactoryScreenUtil.colorFromFloat(dyeColor.getTextureDiffuseColors())*//*?} else {*/dyeColor.getTextureDiffuseColor()/*?}*/;
return /*? if <1.20.5 {*//*ColorUtil.colorFromFloat(dyeColor.getTextureDiffuseColors())*//*?} else {*/dyeColor.getTextureDiffuseColor()/*?}*/;
}

public static float getItemDamageModifier(ItemStack stack){
if (LegacyConfig.hasCommonConfigEnabled(LegacyConfig.legacyCombat)){
if (FactoryConfig.hasCommonConfigEnabled(LegacyCommonOptions.legacyCombat)){
if (stack.getItem() instanceof SwordItem) return 1;
else if (stack.getItem() instanceof ShovelItem) return -0.5f;
else if (stack.getItem() instanceof PickaxeItem) return 1;
else if (stack.getItem() instanceof AxeItem) {
if (stack.is(Items.STONE_AXE)) return -4;
else if (stack.is(Items.DIAMOND_AXE)) return - 2;
else if (stack.is(Items.DIAMOND_AXE) || stack.is(Items.NETHERITE_AXE)) return -2;
else return -3;
}
}
Expand All @@ -371,7 +406,7 @@ public static void tagsLoaded(){
}

public static void registerDyedWaterCauldronInteraction(Map<Item, CauldronInteraction> waterCauldron){
if (!LegacyConfig.legacyCauldrons.get()) return;
if (!LegacyMixinToggles.legacyCauldrons.get()) return;
BuiltInRegistries.ITEM.asHolderIdMap().forEach(i-> {
if (!isDyeableItem(i)) return;
waterCauldron.put(i.value(),(blockState, level, blockPos, player, interactionHand, itemStack) -> {
Expand Down Expand Up @@ -503,30 +538,24 @@ public static void onServerPlayerJoin(ServerPlayer p){
((LegacyPlayerInfo)p).setIdentifierIndex(pos);
CommonNetwork.forceEnabledPlayer(p, ()-> {
CommonNetwork.sendToPlayer(p, new PlayerInfoSync.All(Map.of(p.getUUID(),(LegacyPlayerInfo)p), Collections.emptyMap(),p.server.getDefaultGameType(),PlayerInfoSync.All.ID_S2C));
CommonNetwork.sendToPlayer(p, CommonConfigSyncPayload.of(LegacyConfig.COMMON_STORAGE));
playerInitialPayloads.forEach(payload->CommonNetwork.sendToPlayer(p, payload));
});
if (!p.server.isDedicatedServer()) Legacy4JClient.serverPlayerJoin(p);
}

public static void onServerStart(MinecraftServer server){
//? if >=1.21.2 {
/*CommonRecipeManager.recipesByType = server.getRecipeManager().getRecipes().stream().collect(Collectors.groupingBy(h->h.value().getType(),Collectors.toMap(h->h.id().location(), Function.identity())));
*///?}
playerInitialPayloads = createPlayerInitialPayloads(server);
LegacyWorldOptions.WORLD_STORAGE.withServerFile(server, "legacy_data.json").load();
}

public static void onResourcesReload(PlayerList playerList){
onServerStart(playerList.getServer());
playerList.getPlayers().forEach(sp-> playerInitialPayloads.forEach(payload->CommonNetwork.sendToPlayer(sp, payload)));
playerInitialPayloads.forEach(payload->CommonNetwork.sendToPlayers(playerList.getPlayers(), payload));
}

public static Collection<CommonNetwork.Payload> createPlayerInitialPayloads(MinecraftServer server){
HashSet<CommonNetwork.Payload> payloads = new HashSet<>();
payloads.add(new ClientAdvancementsPayload(/*? if >1.20.1 {*/List.copyOf(server.getAdvancements().getAllAdvancements())/*?} else {*//*server.getAdvancements().getAllAdvancements().stream().collect(Collectors.toMap(Advancement::getId, Advancement::deconstruct))*//*?}*/));
//? if >=1.21.2 {
/*payloads.add(new CommonRecipeManager.ClientPayload(CommonRecipeManager.recipesByType));
*///?}
return payloads;
}

Expand Down
Loading

0 comments on commit 484ca2d

Please sign in to comment.