From b84ebee2892f08863db274618e00a885c50db0ef Mon Sep 17 00:00:00 2001 From: Jkibbels Date: Sat, 24 Aug 2024 22:21:57 -0400 Subject: [PATCH] [5] Initial implementation of some attribute stuff. Nothing in this commit actually works but the game still launches! Will need to move item and armor stuff to a more generic file as well to make it less unique so it can be used broadly. Directory structure extended to add items and resources [5] **WORKING** add custom item and item group to game. primarily metaljacket items not added yet - but example item works. [5] initial add of custom blocks [5] Made adding blocks more dynamic - tested to work [5] Fixed metal jacket armor & created correct texture files to not break game. TEXTURES ARE RANDOM ON WEARING AS I DONT HAVE AN ACTUAL TEXTURE YET [5] Started attribute tree stuff [5] end of day commit. **NOT WORKING** [5] First successful attribute write using test commandsgit br [5] one directory for you, and one for you, and one for... [5] Loot tables, dropping blocks, experience block, example ore block changes [5] Documentation. Small command patch to make command treegit br --- src/main/java/jesse/CommonServerUtils.java | 2 +- .../keeblarcraft/Armor/MetalJacketArmor.java | 81 +++++ .../AttributeMgr/AttributeMgr.java | 115 +++++++ .../AttributeNodes/AbstractNode.java | 52 +++ .../AttributeNodes/AttributeFlight.java | 68 ++++ .../AttributeNodes/AttributeMetalJacket.java | 56 +++ .../AttributeMgr/AttributeTree.java | 322 ++++++++++++++++++ .../keeblarcraft/BankMgr/BankManager.java | 4 +- .../Commands/AttributeCommands.java | 115 +++++++ .../Commands/CustomCommandManager.java | 4 +- .../Commands/ShortcutCommands.java | 2 + .../keeblarcraft/ConfigMgr/ConfigManager.java | 9 - .../CustomBlocks/BlockManager.java | 79 +++++ .../CustomItems/CustomItemGroups.java | 41 +++ .../keeblarcraft/CustomItems/ItemManager.java | 70 ++++ .../keeblarcraft/EventMgr/EventManager.java | 3 +- .../{ => EventMgr}/ServerTickListener.java | 11 +- .../java/jesse/keeblarcraft/Keeblarcraft.java | 49 ++- .../java/jesse/keeblarcraft/Utils/Setup.java | 28 +- .../blockstates/example_block.json | 7 + .../blockstates/example_block_ore.json | 7 + .../assets/keeblarcraft/lang/en_us.json | 11 + .../models/block/example_block.json | 6 + .../models/block/example_block_ore.json | 6 + .../models/item/example_block.json | 3 + .../models/item/example_block_ore.json | 3 + .../models/item/metaljacket_boots.json | 6 + .../models/item/metaljacket_chestplate.json | 6 + .../models/item/metaljacket_helmet.json | 6 + .../models/item/metaljacket_leggings.json | 6 + .../textures/block/example_block.png | Bin 0 -> 1081 bytes .../textures/block/example_block_ore.png | Bin 0 -> 1093 bytes .../textures/item/example_block_ore.png | Bin 0 -> 1093 bytes .../textures/item/metaljacket_boots.png | Bin 0 -> 269 bytes .../textures/item/metaljacket_chestplate.png | Bin 0 -> 287 bytes .../textures/item/metaljacket_helmet.png | Bin 0 -> 263 bytes .../textures/item/metaljacket_leggings.png | Bin 0 -> 267 bytes .../models/armor/metaljacket_layer_1.png | 0 .../models/armor/metaljacket_layer_2.png | 0 .../tags/blocks/needs_tool_level_4.json | 6 + .../loot_tables/blocks/example_block.json | 20 ++ .../loot_tables/blocks/example_block_ore.json | 20 ++ .../example_block_from_ore_shaped.json | 18 + .../recipes/example_block_shaped_recipe.json | 21 ++ .../example_block_shapeless_recipe.json | 13 + .../tags/blocks/mineable/pickaxe.json | 7 + .../tags/blocks/needs_diamond_tool.json | 6 + 47 files changed, 1242 insertions(+), 47 deletions(-) create mode 100644 src/main/java/jesse/keeblarcraft/Armor/MetalJacketArmor.java create mode 100644 src/main/java/jesse/keeblarcraft/AttributeMgr/AttributeMgr.java create mode 100644 src/main/java/jesse/keeblarcraft/AttributeMgr/AttributeNodes/AbstractNode.java create mode 100644 src/main/java/jesse/keeblarcraft/AttributeMgr/AttributeNodes/AttributeFlight.java create mode 100644 src/main/java/jesse/keeblarcraft/AttributeMgr/AttributeNodes/AttributeMetalJacket.java create mode 100644 src/main/java/jesse/keeblarcraft/AttributeMgr/AttributeTree.java create mode 100644 src/main/java/jesse/keeblarcraft/Commands/AttributeCommands.java create mode 100644 src/main/java/jesse/keeblarcraft/CustomBlocks/BlockManager.java create mode 100644 src/main/java/jesse/keeblarcraft/CustomItems/CustomItemGroups.java create mode 100644 src/main/java/jesse/keeblarcraft/CustomItems/ItemManager.java rename src/main/java/jesse/keeblarcraft/{ => EventMgr}/ServerTickListener.java (62%) create mode 100644 src/main/resources/assets/keeblarcraft/blockstates/example_block.json create mode 100644 src/main/resources/assets/keeblarcraft/blockstates/example_block_ore.json create mode 100644 src/main/resources/assets/keeblarcraft/lang/en_us.json create mode 100644 src/main/resources/assets/keeblarcraft/models/block/example_block.json create mode 100644 src/main/resources/assets/keeblarcraft/models/block/example_block_ore.json create mode 100644 src/main/resources/assets/keeblarcraft/models/item/example_block.json create mode 100644 src/main/resources/assets/keeblarcraft/models/item/example_block_ore.json create mode 100644 src/main/resources/assets/keeblarcraft/models/item/metaljacket_boots.json create mode 100644 src/main/resources/assets/keeblarcraft/models/item/metaljacket_chestplate.json create mode 100644 src/main/resources/assets/keeblarcraft/models/item/metaljacket_helmet.json create mode 100644 src/main/resources/assets/keeblarcraft/models/item/metaljacket_leggings.json create mode 100644 src/main/resources/assets/keeblarcraft/textures/block/example_block.png create mode 100644 src/main/resources/assets/keeblarcraft/textures/block/example_block_ore.png create mode 100644 src/main/resources/assets/keeblarcraft/textures/item/example_block_ore.png create mode 100644 src/main/resources/assets/keeblarcraft/textures/item/metaljacket_boots.png create mode 100644 src/main/resources/assets/keeblarcraft/textures/item/metaljacket_chestplate.png create mode 100644 src/main/resources/assets/keeblarcraft/textures/item/metaljacket_helmet.png create mode 100644 src/main/resources/assets/keeblarcraft/textures/item/metaljacket_leggings.png create mode 100644 src/main/resources/assets/keeblarcraft/textures/models/armor/metaljacket_layer_1.png create mode 100644 src/main/resources/assets/keeblarcraft/textures/models/armor/metaljacket_layer_2.png create mode 100644 src/main/resources/data/fabric/tags/blocks/needs_tool_level_4.json create mode 100644 src/main/resources/data/keeblarcraft/loot_tables/blocks/example_block.json create mode 100644 src/main/resources/data/keeblarcraft/loot_tables/blocks/example_block_ore.json create mode 100644 src/main/resources/data/keeblarcraft/recipes/example_block_from_ore_shaped.json create mode 100644 src/main/resources/data/keeblarcraft/recipes/example_block_shaped_recipe.json create mode 100644 src/main/resources/data/keeblarcraft/recipes/example_block_shapeless_recipe.json create mode 100644 src/main/resources/data/minecraft/tags/blocks/mineable/pickaxe.json create mode 100644 src/main/resources/data/minecraft/tags/blocks/needs_diamond_tool.json diff --git a/src/main/java/jesse/CommonServerUtils.java b/src/main/java/jesse/CommonServerUtils.java index b241354..0de60aa 100644 --- a/src/main/java/jesse/CommonServerUtils.java +++ b/src/main/java/jesse/CommonServerUtils.java @@ -13,5 +13,5 @@ public void SetServerInstance(MinecraftServer inputServer) { public static MinecraftServer GetServerInstance() { return server; } - + } diff --git a/src/main/java/jesse/keeblarcraft/Armor/MetalJacketArmor.java b/src/main/java/jesse/keeblarcraft/Armor/MetalJacketArmor.java new file mode 100644 index 0000000..d6a12e0 --- /dev/null +++ b/src/main/java/jesse/keeblarcraft/Armor/MetalJacketArmor.java @@ -0,0 +1,81 @@ +package jesse.keeblarcraft.Armor; + +import jesse.keeblarcraft.Keeblarcraft; +import net.minecraft.item.ArmorItem; +import net.minecraft.item.ArmorMaterial; +import net.minecraft.item.Items; +import net.minecraft.recipe.Ingredient; +import net.minecraft.sound.SoundEvent; +import net.minecraft.sound.SoundEvents; + +public class MetalJacketArmor implements ArmorMaterial { + // All references to this class must refer to this + public static final MetalJacketArmor INSTANCE = new MetalJacketArmor(); + + int defaultArmorModifier = 3; + + /// FUNCTIONS BELOW + + public MetalJacketArmor() { + System.out.println("Constructor for armor called"); + } + + @Override + public int getDurability(ArmorItem.Type type) { + System.out.println("durability for armor called"); + // Replace this multiplier by a constant value for the durability of the armor. + // For reference, diamond uses 33 for all armor pieces, whilst leather uses 5. + return switch (type) { + case ArmorItem.Type.BOOTS -> Integer.MAX_VALUE; + case ArmorItem.Type.LEGGINGS -> Integer.MAX_VALUE; + case ArmorItem.Type.CHESTPLATE -> Integer.MAX_VALUE; + case ArmorItem.Type.HELMET -> Integer.MAX_VALUE; + }; + } + + @Override + public int getProtection(ArmorItem.Type type) { + System.out.println("Protection called"); + // Protection values for all the slots. + // For reference, diamond uses 3 for boots, 6 for leggings, 8 for chestplate, and 3 for helmet, + // whilst leather uses 1, 2, 3 and 1 respectively. + return switch (type) { + case ArmorItem.Type.BOOTS -> defaultArmorModifier; + case ArmorItem.Type.HELMET -> defaultArmorModifier; + case ArmorItem.Type.LEGGINGS -> defaultArmorModifier; + case ArmorItem.Type.CHESTPLATE -> defaultArmorModifier; + }; + } + + @Override + public int getEnchantability() { + return Integer.MAX_VALUE; + } + + @Override + public SoundEvent getEquipSound() { + // Example for Iron Armor + return SoundEvents.ITEM_ARMOR_EQUIP_IRON; + } + + @Override + public Ingredient getRepairIngredient() { + return Ingredient.ofItems(Items.BEDROCK); // prayfully impossible repair or just not worth it + } + + @Override + public String getName() { + return Keeblarcraft.MOD_ID + ":" + "metaljacket"; + } + + @Override + public float getToughness() { + // Toughness is the actual "resistance" the armor provides to HIGH damage attacks + return (float) defaultArmorModifier; + } + + @Override + public float getKnockbackResistance() { + return 0; + } +} \ No newline at end of file diff --git a/src/main/java/jesse/keeblarcraft/AttributeMgr/AttributeMgr.java b/src/main/java/jesse/keeblarcraft/AttributeMgr/AttributeMgr.java new file mode 100644 index 0000000..c58ff64 --- /dev/null +++ b/src/main/java/jesse/keeblarcraft/AttributeMgr/AttributeMgr.java @@ -0,0 +1,115 @@ +/* + * + * AttributeMgr + * + * Central point for the attribute skill system in the mod + * + * +*/ + +package jesse.keeblarcraft.AttributeMgr; + +import java.util.HashMap; + +import jesse.keeblarcraft.Keeblarcraft; +import jesse.keeblarcraft.AttributeMgr.AttributeNodes.AbstractNode; +import jesse.keeblarcraft.AttributeMgr.AttributeNodes.AttributeFlight; +import jesse.keeblarcraft.AttributeMgr.AttributeNodes.AttributeMetalJacket; +import jesse.keeblarcraft.ConfigMgr.ConfigManager; +import jesse.keeblarcraft.Utils.ChatUtil; + +public class AttributeMgr { + ConfigManager config; + + // Global list of attributes + public static final HashMap> attributes = new HashMap>(); + + // This is a list of all logged in player tree's. These are kept in memory until either the mod is torn down or + // players log out for optimization reasons + public static final HashMap activeTrees = new HashMap(); + + ///////////////////////////////////////////////////////////////////////////// + /// @fn RegisterAttributeClass + /// + /// @brief All generic class objects for attributes should be + /// registered via this function + ///////////////////////////////////////////////////////////////////////////// + @SuppressWarnings("deprecation") + public static void RegisterAttributeClass(Class classObj) { + AbstractNode verifyNode = null; + try { + // We spin up an instance of this node; but it will be dead after this function. We need this for name + verifyNode = classObj.newInstance(); + } catch (Exception e) { + Keeblarcraft.LOGGER.error("Attempted to assign AbstractNode class type when registering object but could not call .newInstance()! Constructs must be empty"); + e.printStackTrace(); + } + ChatUtil.LoggerColored("Registring attribute called", ChatUtil.CONSOLE_COLOR.CYAN, Keeblarcraft.LOGGER); + + try { + if (attributes.containsKey(verifyNode.GetNodeTitle())) { + Keeblarcraft.LOGGER.warn("Could not register attribute with duplicate name '" + verifyNode.GetNodeTitle() + "'"); + } else { + ChatUtil.LoggerColored("REGISTERING ATTRIBUTE " + verifyNode.GetNodeTitle(), ChatUtil.CONSOLE_COLOR.YELLOW, Keeblarcraft.LOGGER); + attributes.put(verifyNode.GetNodeTitle(), classObj); + } + } catch (Exception e) {} // Left empty since previous try-catch will throw error-message + } + + ///////////////////////////////////////////////////////////////////////////// + /// @fn ApplyAttribute + /// + /// @arg[in] uuid is the players uuid + /// + /// @arg[in] attributeName is the node title of the attribute class object + /// + /// @brief Used to apply an attribute to a player's attribute tree + ///////////////////////////////////////////////////////////////////////////// + @SuppressWarnings("deprecation") + public static String ApplyAttribute(String uuid, String attributeName) { + String msg = ""; + if (attributes.containsKey(attributeName)) { + AttributeTree playerTree = activeTrees.get(uuid); + AbstractNode node = null; + try { + node = attributes.get(attributeName).newInstance(); + } catch (Exception e) { + Keeblarcraft.LOGGER.error("Could not successfully apply attribute. String of attribute name could not be successfully cast to actual java object"); + } + + if (playerTree != null && node != null) { + /////////// + // debug testing + String isNull = (node == null ? "NULL" : "NOT NULL"); + System.out.println("Node is " + isNull); + if (isNull.equals("NOT NULL")) { System.out.println("Node name: " + node.GetNodeTitle()); } + // end debug testing + /////////// + + playerTree.AddNewNode(node, 1, null, null); + msg = "Applied attribute '" + attributeName + "' successfully"; + } else { + msg = "Player tree not found!"; + } + } else { + msg = "Could not apply attribute, attribute name does not exist!"; + } + return msg; + } + + ///////////////////////////////////////////////////////////////////////////// + /// @fn RegisterAttributes + /// + /// @brief This registers all attribute classes to the global attribute + /// class. Individual classes can be added here that call the + /// more braod "RegisterAttributeClass" + /// @see RegisterAttributeClass + ///////////////////////////////////////////////////////////////////////////// + public static void RegisterAttributes() { + // Manually register all attribute node classes here + /// TODO: Find a better way to do this more dynamically in the future + + RegisterAttributeClass(AttributeFlight.class); + RegisterAttributeClass(AttributeMetalJacket.class); + } +} diff --git a/src/main/java/jesse/keeblarcraft/AttributeMgr/AttributeNodes/AbstractNode.java b/src/main/java/jesse/keeblarcraft/AttributeMgr/AttributeNodes/AbstractNode.java new file mode 100644 index 0000000..03550dd --- /dev/null +++ b/src/main/java/jesse/keeblarcraft/AttributeMgr/AttributeNodes/AbstractNode.java @@ -0,0 +1,52 @@ +/* + * + * AbstractNode + * + * This is the general definition of everything that is allowed inside a node and is called by our system + * + * +*/ + +package jesse.keeblarcraft.AttributeMgr.AttributeNodes; + +import java.util.HashMap; +import java.util.List; + +abstract public class AbstractNode { + + ///////////////////////////////////////////////////////////////////////////// + /// @fn GetNodeTitle + /// + /// @brief The title of your node/skill! + ///////////////////////////////////////////////////////////////////////////// + public abstract String GetNodeTitle(); + + ///////////////////////////////////////////////////////////////////////////// + /// @fn GetNodeDescription + /// + /// @brief This will become the hover-over text display of a skill in + /// the skill tree + ///////////////////////////////////////////////////////////////////////////// + public abstract String GetNodeDescription(); + + ///////////////////////////////////////////////////////////////////////////// + /// @fn GetDetails + /// + /// @brief This is the general details that may be displayed inside the + /// GUI when the skill tree becomes available to a player. The + /// object is suggested to be treated as such: + /// + /// KEY (String) -> The title of an effect/attribute + /// VAL (List) -> Treated as a list of description + /// attributes. 1 string per description + ///////////////////////////////////////////////////////////////////////////// + public abstract HashMap> GetDetails(); + + ///////////////////////////////////////////////////////////////////////////// + /// @fn RegisterCallbacks + /// + /// @brief If your node has responsive callbacks; then this is the area + /// you will register your callbacks for everything + ///////////////////////////////////////////////////////////////////////////// + public abstract void RegisterCallbacks(); +} diff --git a/src/main/java/jesse/keeblarcraft/AttributeMgr/AttributeNodes/AttributeFlight.java b/src/main/java/jesse/keeblarcraft/AttributeMgr/AttributeNodes/AttributeFlight.java new file mode 100644 index 0000000..4122b1f --- /dev/null +++ b/src/main/java/jesse/keeblarcraft/AttributeMgr/AttributeNodes/AttributeFlight.java @@ -0,0 +1,68 @@ +/* + * + * AttributeFlight + * + * The flight attribute + * + * +*/ + +package jesse.keeblarcraft.AttributeMgr.AttributeNodes; + +import java.util.HashMap; +import java.util.List; + +import net.fabricmc.fabric.api.event.player.AttackBlockCallback; +import net.minecraft.block.BlockState; +import net.minecraft.util.ActionResult; + +public class AttributeFlight extends AbstractNode { + + Boolean registeredHitCallback = false; + + public AttributeFlight() { + + } + + @Override + public String GetNodeTitle() { + return "attribute_low_health_flight"; + } + + @Override + public String GetNodeDescription() { + return "Gives player flight with low health"; + } + + @Override + public HashMap> GetDetails() { + HashMap> ret = new HashMap>(); + + // Filling out description item stuff here + ret.put("Flight", List.of ( + "Gives a player natural flight if they have less than or equal to two hearts remaining" + )); + + return ret; + } + + @Override + public void RegisterCallbacks() { + // Register events here + if (registeredHitCallback == false) { + AttackBlockCallback.EVENT.register((player, world, hand, pos, direction) -> { + BlockState state = world.getBlockState(pos); + + // Manual spectator check is necessary because AttackBlockCallbacks fire before the spectator check + if (!player.isSpectator() && player.getMainHandStack().isEmpty() && state.isToolRequired()) { + player.damage(world.getDamageSources().generic(), 1.0F); + } + + return ActionResult.PASS; + }); + + } + + registeredHitCallback = true; + } +} diff --git a/src/main/java/jesse/keeblarcraft/AttributeMgr/AttributeNodes/AttributeMetalJacket.java b/src/main/java/jesse/keeblarcraft/AttributeMgr/AttributeNodes/AttributeMetalJacket.java new file mode 100644 index 0000000..599f304 --- /dev/null +++ b/src/main/java/jesse/keeblarcraft/AttributeMgr/AttributeNodes/AttributeMetalJacket.java @@ -0,0 +1,56 @@ +package jesse.keeblarcraft.AttributeMgr.AttributeNodes; + +import java.util.HashMap; +import java.util.List; + +import jesse.keeblarcraft.Keeblarcraft; +import jesse.keeblarcraft.Armor.MetalJacketArmor; +import jesse.keeblarcraft.CustomItems.ItemManager; +import net.minecraft.item.ArmorItem; +import net.minecraft.item.Item; + +public final class AttributeMetalJacket extends AbstractNode { + // This is the custom armor set that players will receive if no armor is on & attribute is equipped + public final Item jacketHelm = new ArmorItem(MetalJacketArmor.INSTANCE, ArmorItem.Type.HELMET, new Item.Settings()); + public final Item jacketChest = new ArmorItem(MetalJacketArmor.INSTANCE, ArmorItem.Type.CHESTPLATE, new Item.Settings()); + public final Item jacketLegs = new ArmorItem(MetalJacketArmor.INSTANCE, ArmorItem.Type.LEGGINGS, new Item.Settings()); + public final Item jacketBoots = new ArmorItem(MetalJacketArmor.INSTANCE, ArmorItem.Type.BOOTS, new Item.Settings()); + + public AttributeMetalJacket() { + // Finally register items with game + Keeblarcraft.LOGGER.debug("Registering metaljackets"); + ItemManager.RegisterItem("metaljacket_helmet", jacketHelm); + ItemManager.RegisterItem("metaljacket_chestplate", jacketChest); + ItemManager.RegisterItem("metaljacket_leggings", jacketLegs); + ItemManager.RegisterItem("metaljacket_boots", jacketBoots); + } + + @Override + public String GetNodeTitle() { + return "MetalJacket"; + } + + // Short description of node on hover-event in GUI + @Override + public String GetNodeDescription() { + return "MetalJacket affects armor value modifiers or gives player base armor when none is worn"; + } + + // Detailed description of node on click-event in GUI + @Override + public HashMap> GetDetails() { + HashMap> ret = new HashMap>(); + + // Filling out description item stuff here + ret.put("durability", List.of ( + "Gives player a base armor set with 3 protection and resistance", + "If armor is being worn, this attribute multiplies each pieces armor value by 120% (rounding up to nearest integer)" + )); + + return ret; + } + + @Override + public void RegisterCallbacks() { + } +} diff --git a/src/main/java/jesse/keeblarcraft/AttributeMgr/AttributeTree.java b/src/main/java/jesse/keeblarcraft/AttributeMgr/AttributeTree.java new file mode 100644 index 0000000..d7a8c25 --- /dev/null +++ b/src/main/java/jesse/keeblarcraft/AttributeMgr/AttributeTree.java @@ -0,0 +1,322 @@ +/* + * + * AttributeTree + * + * Handles a players individual attribute tree + * + * +*/ + +package jesse.keeblarcraft.AttributeMgr; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; +import java.util.HashMap; + +import jesse.keeblarcraft.Keeblarcraft; +import jesse.keeblarcraft.AttributeMgr.AttributeNodes.AbstractNode; +import jesse.keeblarcraft.ConfigMgr.ConfigManager; +import jesse.keeblarcraft.Utils.ChatUtil; +import jesse.keeblarcraft.Utils.ChatUtil.CONSOLE_COLOR; +import jesse.keeblarcraft.Utils.CustomExceptions.FILE_WRITE_EXCEPTION; + +public class AttributeTree { + + PlayerTree playerAttributeTree = new PlayerTree(); + ConfigManager config = new ConfigManager(); + private AbstractNode root = new RootNode(); + + ///////////////////////////////////////////////////////////////////////////// + /// @class TreeNode + /// + /// @brief This is an individual node that goes within the larger + /// PlayerTree class object + ///////////////////////////////////////////////////////////////////////////// + private class TreeNode { + public TreeNode(AbstractNode node, Integer maxLevel, List parents, List children) { + thisNode = node; + parentNodes = parents; + childrenNodes = children; + maxNodeLevel = maxLevel; + currentNodeLevel = 1; + } + + AbstractNode thisNode; + Integer currentNodeLevel; + Integer maxNodeLevel; + + // Store the names of the parent and children nodes; this lets a node have any number of parents and children + // NOTE TO DEVELOPERS: Be aware! The more nodes the harrier the tree will look in the GUI!!! Always test your + // code before just adding stuff willy-nilly! + List parentNodes; + List childrenNodes; + } + + ///////////////////////////////////////////////////////////////////////////// + /// @class PlayerTree + /// + /// @brief This is the tree object of a given player. It contains all + /// valid attribute information for a player that is statically + /// stored inside the AttributeMgr class + ///////////////////////////////////////////////////////////////////////////// + private class PlayerTree { + String uuid; + // Key = name of AbstractNode + // Val = The attribute itself + HashMap tree = new HashMap(); + } + + ///////////////////////////////////////////////////////////////////////////// + /// @class RootNode + /// + /// @brief This is the default root node of a tree. It is mostly left + /// blank and used to code around as an "anchor" object + ///////////////////////////////////////////////////////////////////////////// + private class RootNode extends AbstractNode { + @Override + public String GetNodeTitle() { + return "root"; + } + + @Override + public String GetNodeDescription() { + return "This is the players tree root! All attributes extend from here"; + } + + @Override + public HashMap> GetDetails() { + HashMap> ret = new HashMap>(); + + ret.put("First Attribute", List.of("This is your skill tree", "All attributes grow from this root! Unlocking nodes requires all nodes connected to that previously to be unlocked first")); + + return ret; + } + + @Override + public void RegisterCallbacks() { + } + } + + ///////////////////////////////////////////////////////////////////////////// + /// @fn RegisterActiveCallbacks + /// + /// @brief This will register all the active callbacks on all nodes that + /// are unlocked within a players tree instance + ///////////////////////////////////////////////////////////////////////////// + public void RegisterActiveCallbacks() { + for(Entry tree : playerAttributeTree.tree.entrySet()) { + if (tree.getValue().currentNodeLevel != 0) { + + // We need to construct the node object if it's null. If it's not null; we assume + // the callback registration process has already happened + if (tree.getValue().thisNode == null) { + System.out.println("REGISTERACTIVECALLBACKS - NULL NODE"); + tree.getValue().thisNode.RegisterCallbacks(); + } else { + System.out.println("REGISTERACTIVECALLBACKS - NOT NULL NODE"); + } + } + } + } + + ///////////////////////////////////////////////////////////////////////////// + /// @fn AddNewNode + /// + /// @arg[in] newNode is the attribute node to be added to the player tree + /// + /// @arg[in] maxNodeLevel is the max level of the node + /// + /// @arg[in] parents is the list of parent attributes. If empty, + /// root is default + /// + /// @arg[in] children is the list of children attributes. + /// Root cannot be child + /// + /// @brief Add a new node to the tree arbitrarily. Root can never be a + /// child node parents can never be empty or null (null or empty + /// would imply you are adding root) + /// + /// @note developer warning: you must personally verify your nodes are + /// in the tree. anywhere is fine, and the tree is very dynamic + /// (which is why self verification is required). When the tree + /// is drawn in the GUI, it starts from the root node. As long as + /// your node is attached to this node anywhere in the indirected + /// graph; then your attribute will be drawn in the gui + ///////////////////////////////////////////////////////////////////////////// + public Boolean AddNewNode(AbstractNode newNode, Integer maxNodeLevel, List parents, List children) { + Boolean ret = false; + TreeNode nodeReference = playerAttributeTree.tree.get(newNode.GetNodeTitle()); + + System.out.println("Is node reference null? -> " + (nodeReference == null ? "YES":"NO")); + System.out.println("Is root reference null? -> " + (root == null ? "YES":"NO")); + System.out.println("Is parents null? -> " + (parents == null ? "YES":"NO")); + + // Some special handling is required on checking child list in case it is null + Boolean validChildren = true; + if (children != null) { + validChildren = !children.contains(root.GetNodeTitle()); + } + + if (nodeReference == null && validChildren) { + // Since these values can be left as null, we want to guarentee they are at least initialized here + maxNodeLevel = (maxNodeLevel == null ? 1 : maxNodeLevel); + parents = (parents == null ? new ArrayList() : parents); + children = (children == null ? new ArrayList() : children); + + if (parents.size() == 0) { + parents.add(root.GetNodeTitle()); // No disconnected nodes allowed! All parentless nodes are adopted by root by default + } + + // Node implementation here + playerAttributeTree.tree.put(newNode.GetNodeTitle(), new TreeNode(newNode, maxNodeLevel, parents, children)); + + // if the new nodes level is not 0 we need to manually register the callbacks + if (playerAttributeTree.tree.get(newNode.GetNodeTitle()).currentNodeLevel != 0) { + System.out.println("Registering new callback for new node"); + playerAttributeTree.tree.get(newNode.GetNodeTitle()).thisNode.RegisterCallbacks(); + } + } else { + // Some fancy error handling for console log + if (nodeReference != null) { + Keeblarcraft.LOGGER.error("Attribute with name " + newNode.GetNodeTitle() + " was already found within the tree! Rename your node to add it"); + } else { + Keeblarcraft.LOGGER.error("The attribute you attempted to add (name: " + newNode.GetNodeTitle() + "), has root in the children"); + } + ret = false; + } + + FlashConfig(); + return ret; + } + + ///////////////////////////////////////////////////////////////////////////// + /// @fn ChangeNodeLevel + /// + /// @arg[in] nodeName is the attribute node title + /// + /// @arg[in] newLevel is the new level you wish to grant the node + /// + /// @brief If you wish to change the level of an attribute, you can use + /// this function to do so + ///////////////////////////////////////////////////////////////////////////// + public void ChangeNodeLevel(String nodeName, Integer newLevel) { + TreeNode nodeReference = playerAttributeTree.tree.get(nodeName); + + if (nodeReference != null) { + if (newLevel <= nodeReference.maxNodeLevel) { + nodeReference.currentNodeLevel = newLevel; + } + } + + FlashConfig(); + } + + ///////////////////////////////////////////////////////////////////////////// + /// @fn DeleteNode + /// + /// @arg[in] nodeName is the attribute node title + /// + /// @brief Delete a node via the name + ///////////////////////////////////////////////////////////////////////////// + public void DeleteNode(String nodeName) { + // Do not delete root! + if (nodeName != root.GetNodeTitle()) { + playerAttributeTree.tree.remove(nodeName); + } + + FlashConfig(); + } + + ///////////////////////////////////////////////////////////////////////////// + /// @fn GetNodeDetails + /// + /// @arg[in] nodeName is the attribute node title + /// + /// @brief Returns the detail map from the attribute if it has any + ///////////////////////////////////////////////////////////////////////////// + public HashMap> GetNodeDetails(String nodeName) { + HashMap> ret = null; + + TreeNode nodeReference = playerAttributeTree.tree.get(nodeName); + + if (nodeReference != null) { + ret = nodeReference.thisNode.GetDetails(); + } + + return ret; + } + + ///////////////////////////////////////////////////////////////////////////// + /// @fn GetPlayerTree + /// + /// @brief Returns the player tree. Will be deprecated in use of the + /// static active user map list instead for performance reasons + ///////////////////////////////////////////////////////////////////////////// + @Deprecated + public PlayerTree GetPlayerTree() { + return playerAttributeTree; + } + + ///////////////////////////////////////////////////////////////////////////// + /// @fn AttributeTree + /// + /// @arg[in] uuid is the player uuid + /// + /// @brief Constructor for class + ///////////////////////////////////////////////////////////////////////////// + public AttributeTree(String uuid) { + // DEVELOPER NOTE: + // If you are testing this part of the code, please be reminded that anonymous testing starts a new + // player instance everytime you launch. This means the UUID CAN CHANGE when you launch the + // game! This is not an issue with proper registered accounts in production + Boolean existingFile = false; + try { + playerAttributeTree = config.GetJsonObjectFromFile("attributes/" + uuid + ".json", PlayerTree.class); + existingFile = true; + } catch (Exception e) { + // Do nothing. This means the file does not exist + } + + // In the event the above code failed out, this means a new file has to be created for the player's uuid + if (!existingFile) + { + System.out.println(ChatUtil.ColoredString("Trying to create new file", CONSOLE_COLOR.BLUE)); + try { + playerAttributeTree.uuid = uuid; + FlashConfig(); + } catch (Exception e) { + System.out.println(ChatUtil.ColoredString("Could not write to file", CONSOLE_COLOR.RED)); + } + } else { + System.out.println(ChatUtil.ColoredString("Moving on", CONSOLE_COLOR.BLUE)); + } + + // It's possible the above code will return a blank class if a file doesn't exist. This will make + // a new file with this players uuid + if ("".equals(playerAttributeTree.uuid)) { + System.out.println(ChatUtil.ColoredString("Assigning new config file for this uuid. No previous existing", CONSOLE_COLOR.BLUE)); + playerAttributeTree.uuid = uuid; + } + + // If the tree is empty or missing root, add it! + if (playerAttributeTree.tree.size() == 0 || playerAttributeTree.tree.containsKey(root.GetNodeTitle()) == false) { + playerAttributeTree.tree.put(root.GetNodeTitle(), new TreeNode(root, 1, null, null)); + } + + RegisterActiveCallbacks(); + } + + ///////////////////////////////////////////////////////////////////////////// + /// @fn FlashConfig + /// + /// @brief Flashes the config to the disk + ///////////////////////////////////////////////////////////////////////////// + public void FlashConfig() { + try { + config.WriteToJsonFile("attributes/" + playerAttributeTree.uuid + ".json", playerAttributeTree); + } catch (FILE_WRITE_EXCEPTION e) { + System.out.println(ChatUtil.ColoredString("Could not flash notes configuration file", CONSOLE_COLOR.RED)); + } + } +} diff --git a/src/main/java/jesse/keeblarcraft/BankMgr/BankManager.java b/src/main/java/jesse/keeblarcraft/BankMgr/BankManager.java index 01e6e06..ccf6a31 100644 --- a/src/main/java/jesse/keeblarcraft/BankMgr/BankManager.java +++ b/src/main/java/jesse/keeblarcraft/BankMgr/BankManager.java @@ -165,7 +165,7 @@ public class BankManager { if (config.GetFile("bank/" + GetUuidByName(server, otherParty) + ".json").size() < 1) { BankManagerFile newBankInfo = new BankManagerFile(); BankManagerMetaData newBank = new BankManagerMetaData(0, "Account Created", 0, "Server", 0); - newBankInfo.bank.put(GetUuidByName(server, otherParty).toString(), newBank); + newBankInfo.bank.put(GetUuidByName(server, otherParty).toString(), newBank); try { config.WriteToJsonFile("bank/" + newBankInfo.uuid + ".json", newBankInfo); } catch (FILE_WRITE_EXCEPTION e) { @@ -193,7 +193,7 @@ public class BankManager { } catch (FILE_WRITE_EXCEPTION e) { System.out.println(ChatUtil.ColoredString("Could not flash notes configuration file", CONSOLE_COLOR.RED)); } - + } else { System.out.println(ChatUtil.ColoredString("Player Not Found: " + otherParty, CONSOLE_COLOR.RED)); return; diff --git a/src/main/java/jesse/keeblarcraft/Commands/AttributeCommands.java b/src/main/java/jesse/keeblarcraft/Commands/AttributeCommands.java new file mode 100644 index 0000000..2c0a3e8 --- /dev/null +++ b/src/main/java/jesse/keeblarcraft/Commands/AttributeCommands.java @@ -0,0 +1,115 @@ +/* + * + * AttributeCommands + * + * This class handles all the possible in-game commands for the attribute system + * + * +*/ + +package jesse.keeblarcraft.Commands; + +import java.util.Map.Entry; + +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; + +import jesse.keeblarcraft.Keeblarcraft; +import jesse.keeblarcraft.AttributeMgr.AttributeMgr; +import jesse.keeblarcraft.AttributeMgr.AttributeNodes.AbstractNode; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.minecraft.command.argument.EntityArgumentType; +import net.minecraft.server.command.CommandManager; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; + +public class AttributeCommands { + public void RegisterCommands() { + + // Command Root: "/attributes" + // Subcommands: "apply ", "remove " + CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { + var attributeNode = CommandManager.literal("attributes").build(); + + var applyNode = CommandManager.literal("apply").build(); + var removeNode = CommandManager.literal("delete").build(); + var listNode = CommandManager.literal("list").executes(context -> ListAttributes(context)).build(); + + var playerArgAdd = CommandManager.argument("targetPlayer", EntityArgumentType.player()).build(); + var playerArgRemove = CommandManager.argument("targetPlayer", EntityArgumentType.player()).build(); + + var attributeNameAdd = CommandManager.argument("attributeName", StringArgumentType.greedyString()) + .executes(context -> ApplyAttribute + ( + EntityArgumentType.getPlayer(context, "targetPlayer"), + StringArgumentType.getString(context, "attributeName"), + context) + ) + .build(); + + var attributeNameDelete = CommandManager.argument("attributeName", StringArgumentType.greedyString()) + .executes(context -> DeleteAttribute + ( + EntityArgumentType.getPlayer(context, "targetPlayer"), + StringArgumentType.getString(context, "attributeName"), + context) + ) + .build(); + + // Build out the argument tree here + dispatcher.getRoot().addChild(attributeNode); + attributeNode.addChild(applyNode); + attributeNode.addChild(listNode); + attributeNode.addChild(removeNode); + + // Subcommands "/apply playerArg", "/remove playerArg" + applyNode.addChild(playerArgAdd); + removeNode.addChild(playerArgRemove); + + // name argument + playerArgAdd.addChild(attributeNameAdd); + playerArgRemove.addChild(attributeNameDelete); + }); + + } + + public int ApplyAttribute(ServerPlayerEntity targetPlayer, String attributeName, CommandContext context) { + int ret = -1; + + System.out.println("Applying attribute"); + if (context.getSource().isExecutedByPlayer()) { + System.out.println("Executed by player"); + // String result = AttributeMgr.ApplyAttribute(targetPlayer.getUuidAsString(), attributeName); + String result = AttributeMgr.ApplyAttribute(context.getSource().getPlayer().getUuidAsString(), attributeName); + Keeblarcraft.LOGGER.info("[ApplyAttribute] -> " + result); + context.getSource().getPlayer().sendMessage(Text.of(result)); + ret = 0; + } + + return ret; + } + + public int DeleteAttribute(ServerPlayerEntity username, String attributeName, CommandContext context) { + int ret = -1; + + + + return ret; + } + + public int ListAttributes(CommandContext context) { + int ret = -1; + + if (context.getSource().isExecutedByPlayer()) { + ServerPlayerEntity player = context.getSource().getPlayer(); + + for (Entry> entry : AttributeMgr.attributes.entrySet()) { + Keeblarcraft.LOGGER.debug("ATTR-LIST: " + entry.getKey() + " LINKS " + entry.getValue()); + player.sendMessage(Text.of(entry.getKey())); + } + } + + return ret; + } +} diff --git a/src/main/java/jesse/keeblarcraft/Commands/CustomCommandManager.java b/src/main/java/jesse/keeblarcraft/Commands/CustomCommandManager.java index 39736c4..0605bdd 100644 --- a/src/main/java/jesse/keeblarcraft/Commands/CustomCommandManager.java +++ b/src/main/java/jesse/keeblarcraft/Commands/CustomCommandManager.java @@ -21,12 +21,14 @@ public class CustomCommandManager { ShortcutCommands shortcuts = new ShortcutCommands(); NoteCommands noteCommands = new NoteCommands(); BankCommands bankCommands = new BankCommands(); + AttributeCommands attributeCommands = new AttributeCommands(); // REGISTER COMMANDS BELOW System.out.println(ChatUtil.ColoredString("REGISTERING CUSTOM COMMAND EXTENSIONS BELOW", CONSOLE_COLOR.BLUE)); shortcuts.RegisterShortcutCommands(); noteCommands.RegisterNoteCommands(); bankCommands.RegisterCommands(); - + attributeCommands.RegisterCommands(); + } } diff --git a/src/main/java/jesse/keeblarcraft/Commands/ShortcutCommands.java b/src/main/java/jesse/keeblarcraft/Commands/ShortcutCommands.java index 09400aa..692a744 100644 --- a/src/main/java/jesse/keeblarcraft/Commands/ShortcutCommands.java +++ b/src/main/java/jesse/keeblarcraft/Commands/ShortcutCommands.java @@ -4,6 +4,8 @@ * * A class that simplifies some of the current vanilla commands with shortcut commands in the game. You may remember an old plugin * called "Essentials" (Or EssentialsEx in a later version) -> This file is re-creating a portion of that plugin but in mod-format + * + * */ package jesse.keeblarcraft.Commands; diff --git a/src/main/java/jesse/keeblarcraft/ConfigMgr/ConfigManager.java b/src/main/java/jesse/keeblarcraft/ConfigMgr/ConfigManager.java index dd8110a..029c609 100644 --- a/src/main/java/jesse/keeblarcraft/ConfigMgr/ConfigManager.java +++ b/src/main/java/jesse/keeblarcraft/ConfigMgr/ConfigManager.java @@ -12,7 +12,6 @@ package jesse.keeblarcraft.ConfigMgr; import java.io.FileWriter; -import java.io.FileReader; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -21,27 +20,19 @@ import com.google.common.base.Charsets; import com.google.common.io.Files; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import com.google.gson.JsonArray; import com.google.gson.JsonIOException; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; import java.util.List; import org.apache.commons.io.FileUtils; -import org.spongepowered.asm.mixin.injection.struct.InjectorGroupInfo.Map; import java.util.ArrayList; import jesse.keeblarcraft.Utils.ChatUtil; import jesse.keeblarcraft.Utils.ChatUtil.CONSOLE_COLOR; -// Import all custom exceptions import jesse.keeblarcraft.Utils.CustomExceptions.*; -//minecraft server instance -import net.minecraft.server.MinecraftServer; - public class ConfigManager { // Pedantic empty constructor diff --git a/src/main/java/jesse/keeblarcraft/CustomBlocks/BlockManager.java b/src/main/java/jesse/keeblarcraft/CustomBlocks/BlockManager.java new file mode 100644 index 0000000..90eaa36 --- /dev/null +++ b/src/main/java/jesse/keeblarcraft/CustomBlocks/BlockManager.java @@ -0,0 +1,79 @@ +/* + * + * BlockManager + * + * The mod block manager + * + * +*/ + +package jesse.keeblarcraft.CustomBlocks; + +import java.util.ArrayList; +import java.util.List; + +import jesse.keeblarcraft.Keeblarcraft; +import net.fabricmc.fabric.api.item.v1.FabricItemSettings; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; +import net.minecraft.block.Block; +import net.minecraft.block.Blocks; +import net.minecraft.block.ExperienceDroppingBlock; +import net.minecraft.item.BlockItem; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.sound.BlockSoundGroup; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.intprovider.UniformIntProvider; + +public class BlockManager { + + /// The block list. DO NOT ADD TO THE LIST YOURSELF. USE + /// THE RegisterBlock(...) FUNCTION OTHERWISE YOUR BLOCK WILL + /// NOT BE REGISTERED PROPERLY AND MAY BREAK THE GAME + public static final List blockList = new ArrayList(); + + ///////////////////////////////////////////////////////////////////////////// + /// @fn RegisterBlock + /// + /// @arg[in] name is the block name. IMPORTANT: Name must adhere to rules: + /// 1. The name provided here must match these names: + /// * This blocks models/block name + /// * This blocks textures/block name + /// 2 Name must be lowercase & no special characters besides '_' + /// + /// @arg[in] block is the block to be added to the block list + /// + /// @brief This is the call to register your block to the game! Please + /// do not forget to update the models/block json file, the + /// textures/block png name, and finally the lang/en_us.json file + ///////////////////////////////////////////////////////////////////////////// + public static void RegisterBlock(String name, Block block) { + + // This call registers the block as an item in inventories + Registry.register(Registries.ITEM, new Identifier(Keeblarcraft.MOD_ID, name), new BlockItem(block, new FabricItemSettings())); + + // This call registers the block as placed + Block newBlock = Registry.register(Registries.BLOCK, new Identifier(Keeblarcraft.MOD_ID, name), block); + + // Add the block to the block list + blockList.add(newBlock); + } + + ///////////////////////////////////////////////////////////////////////////// + /// @fn RegisterBlocks + /// + /// @brief This function is only meant to be called by the main mod + /// execution path and should only be called once. This is a + /// glorified print statement - but also serves to add the + /// example block in the game + ///////////////////////////////////////////////////////////////////////////// + public static void RegisterBlocks() { + Keeblarcraft.LOGGER.info("Registering modded blocks for " + Keeblarcraft.MOD_ID); + + // Register example block to the mod + Block exampleBlock = new Block(FabricBlockSettings.copyOf(Blocks.IRON_BLOCK).sounds(BlockSoundGroup.AMETHYST_BLOCK).requiresTool().breakInstantly()); + Block exampleBlockOre = new ExperienceDroppingBlock(FabricBlockSettings.copyOf(Blocks.IRON_BLOCK).sounds(BlockSoundGroup.ANCIENT_DEBRIS).requiresTool(), UniformIntProvider.create(4, 20)); + RegisterBlock("example_block_ore", exampleBlockOre); + RegisterBlock("example_block", exampleBlock); + } +} diff --git a/src/main/java/jesse/keeblarcraft/CustomItems/CustomItemGroups.java b/src/main/java/jesse/keeblarcraft/CustomItems/CustomItemGroups.java new file mode 100644 index 0000000..5b71ea8 --- /dev/null +++ b/src/main/java/jesse/keeblarcraft/CustomItems/CustomItemGroups.java @@ -0,0 +1,41 @@ +package jesse.keeblarcraft.CustomItems; + +import jesse.keeblarcraft.Keeblarcraft; +import jesse.keeblarcraft.CustomBlocks.BlockManager; +import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup; +import net.minecraft.item.ItemGroup; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; + +public class CustomItemGroups { + + // This is the custom mod group for the mod + public static final ItemGroup MOD_GROUP = Registry.register(Registries.ITEM_GROUP, + new Identifier(Keeblarcraft.MOD_ID, "keeblarcraft"), + FabricItemGroup.builder().displayName(Text.translatable("itemgroup.keeblarcraft")) + .icon(() -> new ItemStack(Items.ANVIL)).entries((displayContext, entries) -> { + // We iterate through all the modded items and add them to the custom item group here + for (int i = 0; i < ItemManager.itemList.size(); i++) { + entries.add(ItemManager.itemList.get(i)); + } + + // We iterate through all the modded blocks and add them to the custom block group here + for (int i = 0; i < BlockManager.blockList.size(); i++) { + entries.add(BlockManager.blockList.get(i)); + } + }).build()); + + ///////////////////////////////////////////////////////////////////////////// + /// @fn RegisterGroups + /// + /// @brief Glorified print statement that the class has initialized + /// basically - meaning that all item groups have registered + ///////////////////////////////////////////////////////////////////////////// + public static void RegisterGroups() { + Keeblarcraft.LOGGER.info("Registering item groups for " + Keeblarcraft.MOD_ID); + } +} diff --git a/src/main/java/jesse/keeblarcraft/CustomItems/ItemManager.java b/src/main/java/jesse/keeblarcraft/CustomItems/ItemManager.java new file mode 100644 index 0000000..e687f57 --- /dev/null +++ b/src/main/java/jesse/keeblarcraft/CustomItems/ItemManager.java @@ -0,0 +1,70 @@ +/* + * + * ItemManager + * + * This is the general purpose item manager for the mod. All items will be registered through + * this class in order to make custom items a lot cleaner to make! + * + * +*/ + +package jesse.keeblarcraft.CustomItems; + +import java.util.ArrayList; +import java.util.List; + +import jesse.keeblarcraft.Keeblarcraft; +import net.fabricmc.fabric.api.item.v1.FabricItemSettings; +import net.minecraft.item.Item; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.util.Identifier; + +public class ItemManager { + + /// The item list. DO NOT ADD TO THE LIST YOURSELF. USE + /// THE RegisterItem(...) FUNCTION OTHERWISE YOUR ITEM WILL + /// NOT BE REGISTERED PROPERLY AND MAY BREAK THE GAME + public static final List itemList = new ArrayList(); + + + ///////////////////////////////////////////////////////////////////////////// + /// @fn RegisterItem + /// + /// @arg[in] name is the item name. IMPORTANT: Name must adhere to rules: + /// 1. The name provided here must match these names: + /// * This items models/item name + /// * This items textures/item name + /// 2 Name must be lowercase & no special characters besides '_' + /// + /// @arg[in] item is the item to be added to the item list + /// + /// @brief This is the call to register your item to the game! Please + /// do not forget to update the models/item json file, the + /// textures/item png name, and finally the lang/en_us.json file + ///////////////////////////////////////////////////////////////////////////// + public static void RegisterItem(String name, Item item) { + Item newItem = Registry.register(Registries.ITEM, new Identifier(Keeblarcraft.MOD_ID, name), item); + itemList.add(newItem); + } + + ///////////////////////////////////////////////////////////////////////////// + /// @fn RegisterAllItems + /// + /// @brief This function is only meant to be called by the main mod + /// execution path and should only be called once. This is a + /// glorified print statement - but also serves to add the + /// example item in the game + ///////////////////////////////////////////////////////////////////////////// + public static void RegisterAllItems() { + Keeblarcraft.LOGGER.info("Registering mod items for " + Keeblarcraft.MOD_ID); + + // Call the item group register function first + CustomItemGroups.RegisterGroups(); + + // The example item provides a demo of how you could make an item in your class + // Item exampleItem = new Item(new FabricItemSettings()); + // RegisterItem("metaljacket_helmet", exampleItem); + } + +} diff --git a/src/main/java/jesse/keeblarcraft/EventMgr/EventManager.java b/src/main/java/jesse/keeblarcraft/EventMgr/EventManager.java index 57a06e4..e0b41c1 100644 --- a/src/main/java/jesse/keeblarcraft/EventMgr/EventManager.java +++ b/src/main/java/jesse/keeblarcraft/EventMgr/EventManager.java @@ -10,6 +10,7 @@ package jesse.keeblarcraft.EventMgr; + public class EventManager { -} +} \ No newline at end of file diff --git a/src/main/java/jesse/keeblarcraft/ServerTickListener.java b/src/main/java/jesse/keeblarcraft/EventMgr/ServerTickListener.java similarity index 62% rename from src/main/java/jesse/keeblarcraft/ServerTickListener.java rename to src/main/java/jesse/keeblarcraft/EventMgr/ServerTickListener.java index b31258a..25c4d3b 100644 --- a/src/main/java/jesse/keeblarcraft/ServerTickListener.java +++ b/src/main/java/jesse/keeblarcraft/EventMgr/ServerTickListener.java @@ -1,20 +1,21 @@ -package jesse.keeblarcraft; +package jesse.keeblarcraft.EventMgr; import jesse.CommonServerUtils; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.minecraft.server.MinecraftServer; -//this lets get the MinecraftServer instance and also do stuff on the end of the tick if we wanted to +// This interface is responsible for the end tick of a server world event tick public class ServerTickListener implements ServerTickEvents.EndTick { CommonServerUtils config = new CommonServerUtils(); @Override public void onEndTick(MinecraftServer server) { - //Code that runs on end of each tick yes this actually works and tested - config.SetServerInstance(server); + if (server != null) { + config.SetServerInstance(server); + } } // Static method to register the server tick listener - public static void initialize() { + public static void InitializeServerTicks() { ServerTickEvents.END_SERVER_TICK.register(new ServerTickListener()); } } diff --git a/src/main/java/jesse/keeblarcraft/Keeblarcraft.java b/src/main/java/jesse/keeblarcraft/Keeblarcraft.java index b8c6ab4..e55d3ba 100644 --- a/src/main/java/jesse/keeblarcraft/Keeblarcraft.java +++ b/src/main/java/jesse/keeblarcraft/Keeblarcraft.java @@ -12,23 +12,25 @@ package jesse.keeblarcraft; import net.fabricmc.api.ModInitializer; +// import net.minecraft.server.command.ServerCommandSource; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import jesse.keeblarcraft.AttributeMgr.AttributeMgr; +import jesse.keeblarcraft.AttributeMgr.AttributeTree; import jesse.keeblarcraft.Commands.CustomCommandManager; +import jesse.keeblarcraft.CustomBlocks.BlockManager; +import jesse.keeblarcraft.CustomItems.ItemManager; +import jesse.keeblarcraft.EventMgr.ServerTickListener; import jesse.keeblarcraft.Utils.CustomExceptions.SETUP_FAILED_EXCEPTION; import jesse.keeblarcraft.Utils.ChatUtil; import jesse.keeblarcraft.Utils.Setup; import jesse.keeblarcraft.Utils.ChatUtil.CONSOLE_COLOR; -// import com.mojang.brigadier.Command; - - public class Keeblarcraft implements ModInitializer { - // This logger is used to write text to the console and the log file. - // It is considered best practice to use your mod id as the logger's name. - // That way, it's clear which mod wrote info, warnings, and errors. + public static String MOD_ID = "keeblarcraft"; public static final Logger LOGGER = LoggerFactory.getLogger("keeblarcraft"); CustomCommandManager cmdMgr = new CustomCommandManager(); @@ -47,10 +49,45 @@ public class Keeblarcraft implements ModInitializer { LOGGER.info("\033[34m Running setup stage \033[0m"); setup.RunSetup(); + // This is a very special case where this must be in this classes' initializer + // method + ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { + var player = handler.player; + Keeblarcraft.LOGGER.info("Player " + player.getName() + " has logged in. Creating tree..."); + + if (AttributeMgr.activeTrees.containsKey(player.getUuidAsString()) == false) { + AttributeMgr.activeTrees.put(player.getUuidAsString(), new AttributeTree(player.getUuidAsString())); + } + }); + + ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> { + var player = handler.player; + Keeblarcraft.LOGGER.info("Player " + player.getName() + " has logged out. Deleting tree..."); + + if (AttributeMgr.activeTrees.containsKey(player.getUuidAsString()) == true) { + AttributeMgr.activeTrees.remove(player.getUuidAsString()); + } + }); + + // Initialize our ticks!! + ServerTickListener.InitializeServerTicks(); + // Run command registrations from the command manager LOGGER.info("\033[34m Running command registration \033[0m"); cmdMgr.RegisterCustomCommands(); + // Register attributes + AttributeMgr.RegisterAttributes(); + + + /// THE BELOW ITEMS MUST BE DONE LAST IN THE STEPS + // Register items + ItemManager.RegisterAllItems(); + + // Register blocks + BlockManager.RegisterBlocks(); + + } catch (SETUP_FAILED_EXCEPTION e) { System.out.println(ChatUtil.ColoredString("ERROR. Setup failed to initialize environment. Mod likely does not have read/write permissions inside area. Mod will now close out.", CONSOLE_COLOR.RED)); e.printStackTrace(); diff --git a/src/main/java/jesse/keeblarcraft/Utils/Setup.java b/src/main/java/jesse/keeblarcraft/Utils/Setup.java index a824252..1897695 100644 --- a/src/main/java/jesse/keeblarcraft/Utils/Setup.java +++ b/src/main/java/jesse/keeblarcraft/Utils/Setup.java @@ -12,17 +12,12 @@ package jesse.keeblarcraft.Utils; import java.util.List; -import org.apache.commons.io.FileUtils; -import org.spongepowered.include.com.google.common.io.Files; -import java.util.List; import java.util.ArrayList; -import jesse.keeblarcraft.Keeblarcraft; -import jesse.keeblarcraft.ServerTickListener; import jesse.keeblarcraft.ConfigMgr.ConfigManager; +import jesse.keeblarcraft.EventMgr.ServerTickListener; import jesse.keeblarcraft.Utils.ChatUtil.CONSOLE_COLOR; import jesse.keeblarcraft.Utils.CustomExceptions.DIRECTORY_CREATE_EXCEPTION; -import jesse.keeblarcraft.Utils.CustomExceptions.DIRECTORY_DELETE_EXCEPTION; import jesse.keeblarcraft.Utils.CustomExceptions.SETUP_FAILED_EXCEPTION; // Singleton class is designed to help the mod set itself up and create all the important things. It does two things: @@ -62,14 +57,16 @@ public final class Setup { add("commands"); // Expect 1 file per command that's configurable! add("events"); // Expect 1 file per event that is configurable! add("bank"); + add("attributes"); }}; // These will be top-level config files above the directories this mod creates private static final List FILE_LIST = new ArrayList() {{ - add("story/story.json"); // Big config file, determines when players can do certain things at different story levels - add("factions/factions.json"); // General configuration file for factions stuff - add("events/1events.json"); // General configuration file for story events! + add("story/general_story_config.json"); // Big config file, determines when players can do certain things at different story levels + add("factions/general_factions_config.json"); // General configuration file for factions stuff + add("events/general_event_config.json"); // General configuration file for story events! add("general.json"); // The super general configuration file! (May be removed) + add("attributes/general_attribute_config.json"); }}; // RunChecks() @@ -159,21 +156,8 @@ public final class Setup { throw new SETUP_FAILED_EXCEPTION(); } - //testing for this - try { - onInitialize(); - } catch (Exception e){ - System.out.println(ChatUtil.ColoredString("servertick failed to inilize.", CONSOLE_COLOR.RED)); - throw new SETUP_FAILED_EXCEPTION(); - } - System.out.println(ChatUtil.ColoredString("DID SETUP COMPLETE SUCCESSFULLY? ", CONSOLE_COLOR.YELLOW) + (ret ? ChatUtil.ColoredString("YES", CONSOLE_COLOR.YELLOW) : ChatUtil.ColoredString("NO", CONSOLE_COLOR.YELLOW))); return ret; } - - public void onInitialize() { - // Call the setup file to register event listeners and other setup tasks - ServerTickListener.initialize(); - } } diff --git a/src/main/resources/assets/keeblarcraft/blockstates/example_block.json b/src/main/resources/assets/keeblarcraft/blockstates/example_block.json new file mode 100644 index 0000000..5fe6687 --- /dev/null +++ b/src/main/resources/assets/keeblarcraft/blockstates/example_block.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "keeblarcraft:block/example_block" + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/keeblarcraft/blockstates/example_block_ore.json b/src/main/resources/assets/keeblarcraft/blockstates/example_block_ore.json new file mode 100644 index 0000000..001c270 --- /dev/null +++ b/src/main/resources/assets/keeblarcraft/blockstates/example_block_ore.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "keeblarcraft:block/example_block_ore" + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/keeblarcraft/lang/en_us.json b/src/main/resources/assets/keeblarcraft/lang/en_us.json new file mode 100644 index 0000000..67759b7 --- /dev/null +++ b/src/main/resources/assets/keeblarcraft/lang/en_us.json @@ -0,0 +1,11 @@ +{ + "item.keeblarcraft.metaljacket_helmet": "MetalJacket Helmet", + "item.keeblarcraft.metaljacket_chestplate": "MetalJacket Chestplate", + "item.keeblarcraft.metaljacket_leggings": "MetalJacket Leggings", + "item.keeblarcraft.metaljacket_boots": "MetalJacket Booties", + + "itemgroup.keeblarcraft": "Keeblarcraft Modded Items", + + "block.keeblarcraft.example_block": "Keeblarcraft example block", + "block.keeblarcraft.example_block_ore": "Keeblarcraft example block ore" +} \ No newline at end of file diff --git a/src/main/resources/assets/keeblarcraft/models/block/example_block.json b/src/main/resources/assets/keeblarcraft/models/block/example_block.json new file mode 100644 index 0000000..05cacac --- /dev/null +++ b/src/main/resources/assets/keeblarcraft/models/block/example_block.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "keeblarcraft:block/example_block" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/keeblarcraft/models/block/example_block_ore.json b/src/main/resources/assets/keeblarcraft/models/block/example_block_ore.json new file mode 100644 index 0000000..f5f092e --- /dev/null +++ b/src/main/resources/assets/keeblarcraft/models/block/example_block_ore.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "keeblarcraft:block/example_block_ore" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/keeblarcraft/models/item/example_block.json b/src/main/resources/assets/keeblarcraft/models/item/example_block.json new file mode 100644 index 0000000..3b9b562 --- /dev/null +++ b/src/main/resources/assets/keeblarcraft/models/item/example_block.json @@ -0,0 +1,3 @@ +{ + "parent": "keeblarcraft:block/example_block" +} \ No newline at end of file diff --git a/src/main/resources/assets/keeblarcraft/models/item/example_block_ore.json b/src/main/resources/assets/keeblarcraft/models/item/example_block_ore.json new file mode 100644 index 0000000..5a34a72 --- /dev/null +++ b/src/main/resources/assets/keeblarcraft/models/item/example_block_ore.json @@ -0,0 +1,3 @@ +{ + "parent": "keeblarcraft:block/example_block_ore" +} \ No newline at end of file diff --git a/src/main/resources/assets/keeblarcraft/models/item/metaljacket_boots.json b/src/main/resources/assets/keeblarcraft/models/item/metaljacket_boots.json new file mode 100644 index 0000000..6ce6192 --- /dev/null +++ b/src/main/resources/assets/keeblarcraft/models/item/metaljacket_boots.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "keeblarcraft:item/metaljacket_boots" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/keeblarcraft/models/item/metaljacket_chestplate.json b/src/main/resources/assets/keeblarcraft/models/item/metaljacket_chestplate.json new file mode 100644 index 0000000..2950213 --- /dev/null +++ b/src/main/resources/assets/keeblarcraft/models/item/metaljacket_chestplate.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "keeblarcraft:item/metaljacket_chestplate" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/keeblarcraft/models/item/metaljacket_helmet.json b/src/main/resources/assets/keeblarcraft/models/item/metaljacket_helmet.json new file mode 100644 index 0000000..7301828 --- /dev/null +++ b/src/main/resources/assets/keeblarcraft/models/item/metaljacket_helmet.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "keeblarcraft:item/metaljacket_helmet" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/keeblarcraft/models/item/metaljacket_leggings.json b/src/main/resources/assets/keeblarcraft/models/item/metaljacket_leggings.json new file mode 100644 index 0000000..6e82a01 --- /dev/null +++ b/src/main/resources/assets/keeblarcraft/models/item/metaljacket_leggings.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "keeblarcraft:item/metaljacket_leggings" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/keeblarcraft/textures/block/example_block.png b/src/main/resources/assets/keeblarcraft/textures/block/example_block.png new file mode 100644 index 0000000000000000000000000000000000000000..0a7db9ba86a53deb3d00f0e0482909189aa0e2f8 GIT binary patch literal 1081 zcmV-91jhS`P)EX>4Tx04R}tkv&MmKpe$iQ>8^J4t5afkfC+5i;9R-twIqhgj%6h2a`*`ph-)T z;^HW{799LotU9+0Yt2!bCV&JIqBE>hzEl0u6Z503ls?%w0>9U!!-Of&lufTmkU zCY2O(`BgFSiXb8YW)YK_S)Y@nG(5-GJ$!tDJc?d{()o&J6ReSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00I$7L_t(2&t;M^NLx`9hQEYBC#B+qGagiroIf%hNd{4 z4!2Fkp5gM}|D6AvbAK*tYij_Ylu}ChzOR(xZ}`4%PSq*ZW!`{o<+`7kByC`fpWQAtJN%W-`MGr%=6?IKNpCAPkcP5bNOxQk&%%w z412v^9LJBS403&#!b@HQZ>bltmuXxUWXk1otJPXxUk6rJRsds;F}`pAzX?-QQzs`U zLqkKQQmJ0A1O1KvuRb_9$Y`w(4-d21?DX`s<2bf$Un%lD&vBemskFbpzqz@Ywr#uH z?PjxCrPTQNIFKZX)>>=bXf(9e=7$3l6BEF1&4}3_BOlk}00000NkvXXu0mjfx-jW> literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/keeblarcraft/textures/block/example_block_ore.png b/src/main/resources/assets/keeblarcraft/textures/block/example_block_ore.png new file mode 100644 index 0000000000000000000000000000000000000000..169ef56743c59d3ccbefb1bf26761c2a363c7ce8 GIT binary patch literal 1093 zcmV-L1iJf)P)EX>4Tx04R}tkvmAkP!xv$riu?Lf_4yb2w0u$q9Ts93Pq?8YK2xEOm6yuCJjl7 zi=*ILaPYBMb#QUk)xlK|1Ro&I4o-?LQsTKup+$@bF8AZV=l{9)TtKLonPzpw08O{e zR3a{Bva4d(D|!&X00fN6%rfRADGA^Dx~E>MyC~1{@B6d*)SShDfJhu;hG`RT5YKGd z2Iqa^Fe}O`@j3B?Nf#u3C`-Nm{=^dvC_t@XllgM#1U1~DPPEV zta9Gstd*;*c~AbrP)=W2<~pqrB(R7jND!f*iW17O5v5%x#X^eC<39dj*DsMvAy)~E z91EyGgY5dj|KRs*t^CxamlTQvoiC2_F$Q$)0*#vEd>=bb;{*sk16O*>U#SDLpQP7X zTI2}m-v%zOTbi;5Tk8{ps& z7%x!vy3f12+voOgPjh}hKmu}-s${1*00006VoOIv0AK)A090qf-?IP!010qNS#tmY zE+YT{E+YYWr9XB6000McNliru=m`N51ribi#7F=D02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00JFJL_t(2&y|ufZ_`i^$N#Y%J9*%?ZezEpNNM9DR0pIE z1%Xh+z`z7kGlD!Zuplua7Cr%6SF$j5>?c4)s#FQF5T|MzVnRyfIPGiVV%f<{#PCok z6$>}rdw1{d{`Y$cdwm`MV*-G|5*sWrlTwpXEsob34K0p&*rb#&Mi?_Gz-N}A` z|IPPkExu57CW`*jz^!E%R8bbT45cCiV1f|HG9TS?T>y@*0}vlBF3x|HPZfox(*UF& zQi3IB!>~}RovG?_ydgmI|l1v2f$i7<5=RHEYg#uwL z8w8Y)NE87G9wGGy!ay3@rQ*G3HypiVewg1Xf8%nqL2##1;oA!(34pk2S?}NOJbro$ zKzVc9E)_{7rJbmsUma*C>RCtaO-&uC>U0!c_q^7`gj9QdWcis3Yx(k|e*5_f_m5Ml znJBtht@a54APtkFam+3i%bVNWIgjEvm7<1mxz__Abh2HZG=6g>yu`oBc@AF zgw=P;cB#0#Q4R0@rqd)pTdLYpRgVxZ-Ev)dI21=C020|r0dVw=^Rfeg>zRz>uA*4J z4EX>4Tx04R}tkvmAkP!xv$riu?Lf_4yb2w0u$q9Ts93Pq?8YK2xEOm6yuCJjl7 zi=*ILaPYBMb#QUk)xlK|1Ro&I4o-?LQsTKup+$@bF8AZV=l{9)TtKLonPzpw08O{e zR3a{Bva4d(D|!&X00fN6%rfRADGA^Dx~E>MyC~1{@B6d*)SShDfJhu;hG`RT5YKGd z2Iqa^Fe}O`@j3B?Nf#u3C`-Nm{=^dvC_t@XllgM#1U1~DPPEV zta9Gstd*;*c~AbrP)=W2<~pqrB(R7jND!f*iW17O5v5%x#X^eC<39dj*DsMvAy)~E z91EyGgY5dj|KRs*t^CxamlTQvoiC2_F$Q$)0*#vEd>=bb;{*sk16O*>U#SDLpQP7X zTI2}m-v%zOTbi;5Tk8{ps& z7%x!vy3f12+voOgPjh}hKmu}-s${1*00006VoOIv0AK)A090qf-?IP!010qNS#tmY zE+YT{E+YYWr9XB6000McNliru=m`N51ribi#7F=D02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00JFJL_t(2&y|ufZ_`i^$N#Y%J9*%?ZezEpNNM9DR0pIE z1%Xh+z`z7kGlD!Zuplua7Cr%6SF$j5>?c4)s#FQF5T|MzVnRyfIPGiVV%f<{#PCok z6$>}rdw1{d{`Y$cdwm`MV*-G|5*sWrlTwpXEsob34K0p&*rb#&Mi?_Gz-N}A` z|IPPkExu57CW`*jz^!E%R8bbT45cCiV1f|HG9TS?T>y@*0}vlBF3x|HPZfox(*UF& zQi3IB!>~}RovG?_ydgmI|l1v2f$i7<5=RHEYg#uwL z8w8Y)NE87G9wGGy!ay3@rQ*G3HypiVewg1Xf8%nqL2##1;oA!(34pk2S?}NOJbro$ zKzVc9E)_{7rJbmsUma*C>RCtaO-&uC>U0!c_q^7`gj9QdWcis3Yx(k|e*5_f_m5Ml znJBtht@a54APtkFam+3i%bVNWIgjEvm7<1mxz__Abh2HZG=6g>yu`oBc@AF zgw=P;cB#0#Q4R0@rqd)pTdLYpRgVxZ-Ev)dI21=C020|r0dVw=^Rfeg>zRz>uA*4J z4BY|R^1s;*b3=DjSL74G){)!Z!V3wzgV~9j}ZXYAxVFM24%mp9+*WW2#aM|Rn z)3YQ^2an*X3%w(536$LQnmFqWQ`jn@71mq-7JlJR+R4MSin-kRN?T>czDvyR@)e~` z9Je;|B^9!t(-fYv@h;<$#y2mXrOq~8WAx=h{$G|$%)h3xcKoV+b`NMPgQu&X%Q~lo FCIBwJWBmXC literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/keeblarcraft/textures/item/metaljacket_chestplate.png b/src/main/resources/assets/keeblarcraft/textures/item/metaljacket_chestplate.png new file mode 100644 index 0000000000000000000000000000000000000000..996534bfd349e38a7a49754a025d67e9c77bff8e GIT binary patch literal 287 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!6%)r1HuVsG}$YCrFa(7}_cTVOdki(Mh=lBSEK+1Ydl>XLnOjo`xu3o4LO+BD*gL^Ja%$izYmX= zP_o)Gvt^3j(FOvNTv@G7N)rk?V`px)ofW#wG`V_hoy8{E4h=Wq{tN1>rFtu~k3PGq zIO$i9{Os$`8Qpy5e^Qlco2s48qHrBw{%se2r)9-CHue9+Ui%JzvVyv^vyu~7(8A5T-G@yGywp{V_N0_ literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/keeblarcraft/textures/item/metaljacket_leggings.png b/src/main/resources/assets/keeblarcraft/textures/item/metaljacket_leggings.png new file mode 100644 index 0000000000000000000000000000000000000000..4d37c8da95f6b5be88cacb3219c2cbd237e14bb0 GIT binary patch literal 267 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!6%)r1HuVsG}$YCrFa(7}_cTVOdki(Mh=|3I}G