[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
This commit is contained in:
parent
14ebf3062e
commit
3795875cdf
81
src/main/java/jesse/keeblarcraft/Armor/MetalJacketArmor.java
Normal file
81
src/main/java/jesse/keeblarcraft/Armor/MetalJacketArmor.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
*
|
||||
* AttributeMgr
|
||||
*
|
||||
* Central point for the attribute skill system in the mod
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package jesse.keeblarcraft.AttributeMgr;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import jesse.keeblarcraft.Keeblarcraft;
|
||||
import jesse.keeblarcraft.AttributeMgr.AttributeNodes.AbstractNode;
|
||||
import jesse.keeblarcraft.AttributeMgr.AttributeNodes.AttributeFlight;
|
||||
import jesse.keeblarcraft.ConfigMgr.ConfigManager;
|
||||
import jesse.keeblarcraft.Utils.ChatUtil;
|
||||
|
||||
public class AttributeMgr {
|
||||
ConfigManager config;
|
||||
|
||||
public static final HashMap<String, AbstractNode> attributes = new HashMap<String, AbstractNode>();
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void RegisterAttributeClass(Class<? extends AbstractNode> classObj) {
|
||||
AbstractNode test;
|
||||
ChatUtil.LoggerColored("Registring attribute called", ChatUtil.CONSOLE_COLOR.CYAN, Keeblarcraft.LOGGER);
|
||||
try {
|
||||
test = classObj.newInstance();
|
||||
if (attributes.containsKey(test.GetNodeTitle())) {
|
||||
Keeblarcraft.LOGGER.warn("Could not register attribute with duplicate name '" + test.GetNodeTitle() + "'");
|
||||
} else {
|
||||
ChatUtil.LoggerColored("REGISTERING ATTRIBUTE " + test.GetNodeTitle(), ChatUtil.CONSOLE_COLOR.YELLOW, Keeblarcraft.LOGGER);
|
||||
attributes.put(test.GetNodeTitle(), test);
|
||||
}
|
||||
} catch (InstantiationException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static String ApplyAttribute(String uuid, String attributeName) {
|
||||
String msg = "";
|
||||
if (attributes.containsKey(attributeName)) {
|
||||
AttributeTree playerTree = new AttributeTree(uuid);
|
||||
AbstractNode node = attributes.get(attributeName);
|
||||
|
||||
///////////
|
||||
// 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); ///TODO: Make handling in AttributeTree of NULL parents list to be defaulted to ROOT
|
||||
msg = "Applied attribute '" + attributeName + "' successfully";
|
||||
} else {
|
||||
msg = "Could not apply attribute, attribute name does not exist!";
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
*
|
||||
* 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<String>) -> Treated as a list of description
|
||||
/// attributes. 1 string per description
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
public abstract HashMap<String, List<String>> GetDetails();
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package jesse.keeblarcraft.AttributeMgr.AttributeNodes;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import jesse.keeblarcraft.AttributeMgr.AttributeMgr;
|
||||
|
||||
public class AttributeFlight extends AbstractNode {
|
||||
|
||||
public AttributeFlight() {
|
||||
// AttributeMgr.RegisterAttributeClass(getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String GetNodeTitle() {
|
||||
return "attribute_low_health_flight";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String GetNodeDescription() {
|
||||
return "Gives player flight with low health";
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<String, List<String>> GetDetails() {
|
||||
HashMap<String, List<String>> ret = new HashMap<String, List<String>>();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
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<String, List<String>> GetDetails() {
|
||||
HashMap<String, List<String>> ret = new HashMap<String, List<String>>();
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
218
src/main/java/jesse/keeblarcraft/AttributeMgr/AttributeTree.java
Normal file
218
src/main/java/jesse/keeblarcraft/AttributeMgr/AttributeTree.java
Normal file
@ -0,0 +1,218 @@
|
||||
/*
|
||||
*
|
||||
* AttributeTree
|
||||
*
|
||||
* Handles a players individual attribute tree
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package jesse.keeblarcraft.AttributeMgr;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
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();
|
||||
|
||||
private class TreeNode {
|
||||
|
||||
public TreeNode(AbstractNode node, Integer maxLevel, List<String> parents, List<String> 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<String> parentNodes;
|
||||
List<String> childrenNodes;
|
||||
}
|
||||
|
||||
// Separate structure as this is written to the config file
|
||||
private class PlayerTree {
|
||||
String uuid;
|
||||
// Key = name of AbstractNode
|
||||
// Val = The attribute itself
|
||||
HashMap<String, TreeNode> tree = new HashMap<String, TreeNode>();
|
||||
}
|
||||
|
||||
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<String, List<String>> GetDetails() {
|
||||
HashMap<String, List<String>> ret = new HashMap<String, List<String>>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
//
|
||||
// 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<String> parents, List<String> 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<String>() : parents);
|
||||
children = (children == null ? new ArrayList<String>() : children);
|
||||
|
||||
System.out.println("Parent size: " + parents.size());
|
||||
if (parents.size() == 0) {
|
||||
System.out.println("Root node title: " + root.GetNodeTitle());
|
||||
parents.add(root.GetNodeTitle()); // No disconnected nodes allowed! All parentless nodes are adopted by root by default
|
||||
}
|
||||
System.out.println("Parents size: " + parents.size());
|
||||
|
||||
// Node implementation here
|
||||
playerAttributeTree.tree.put(newNode.GetNodeTitle(), new TreeNode(newNode, maxNodeLevel, parents, children));
|
||||
|
||||
long pSize = playerAttributeTree.tree.get(newNode.GetNodeTitle()).parentNodes.size();
|
||||
System.out.println("parent size is " + pSize);
|
||||
} 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;
|
||||
}
|
||||
|
||||
public void ChangeNodeLevel(String nodeName, Integer newLevel) {
|
||||
TreeNode nodeReference = playerAttributeTree.tree.get(nodeName);
|
||||
|
||||
if (nodeReference != null) {
|
||||
if (newLevel <= nodeReference.maxNodeLevel) {
|
||||
nodeReference.currentNodeLevel = newLevel;
|
||||
}
|
||||
}
|
||||
|
||||
FlashConfig();
|
||||
}
|
||||
|
||||
public void DeleteNode(String nodeName) {
|
||||
// Do not delete root!
|
||||
if (nodeName != root.GetNodeTitle()) {
|
||||
playerAttributeTree.tree.remove(nodeName);
|
||||
}
|
||||
|
||||
FlashConfig();
|
||||
}
|
||||
|
||||
public HashMap<String, List<String>> GetNodeDetails(String nodeName) {
|
||||
HashMap<String, List<String>> ret = null;
|
||||
|
||||
TreeNode nodeReference = playerAttributeTree.tree.get(nodeName);
|
||||
|
||||
if (nodeReference != null) {
|
||||
ret = nodeReference.thisNode.GetDetails();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Returns the player's attribute tree
|
||||
public PlayerTree GetPlayerTree() {
|
||||
return playerAttributeTree;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
109
src/main/java/jesse/keeblarcraft/Commands/AttributeCommands.java
Normal file
109
src/main/java/jesse/keeblarcraft/Commands/AttributeCommands.java
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
*
|
||||
* 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 apply <username> <attribute name>"
|
||||
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
|
||||
dispatcher.register(CommandManager.literal("attributes")
|
||||
.then(CommandManager.literal("apply")
|
||||
.then(CommandManager.argument("username", EntityArgumentType.player()))
|
||||
.then(CommandManager.argument("attributeName", StringArgumentType.string()))
|
||||
.executes(context -> ApplyAttribute(
|
||||
EntityArgumentType.getPlayer(context, "username"),
|
||||
StringArgumentType.getString(context, "attributeName"),
|
||||
context
|
||||
))));
|
||||
});
|
||||
|
||||
// Command Root: "/attributes remove <username> <attribute name>"
|
||||
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
|
||||
dispatcher.register(CommandManager.literal("attributes")
|
||||
.then(CommandManager.literal("delete")
|
||||
.then(CommandManager.argument("username", EntityArgumentType.player()))
|
||||
.then(CommandManager.argument("attributeName", StringArgumentType.greedyString()))
|
||||
.executes(context -> DeleteAttribute(
|
||||
EntityArgumentType.getPlayer(context, "username"),
|
||||
StringArgumentType.getString(context, "attributeName"),
|
||||
context
|
||||
))));
|
||||
});
|
||||
|
||||
// Command Root: "/attribute-test <attribute_name>"
|
||||
/// TODO: Remove this in final version. This is purely debug
|
||||
// CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
|
||||
// dispatcher.register(CommandManager.literal("attribute-test")
|
||||
// .then(CommandManager.argument("value", StringArgumentType.greedyString())
|
||||
// .executes(context -> ApplyAttribute(StringArgumentType.getString(context, "value"), context))));
|
||||
// });
|
||||
|
||||
// Command Root: "/list-attributes"
|
||||
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
|
||||
dispatcher.register(CommandManager.literal("list-attributes")
|
||||
.executes(context -> ListAttributes(context)));
|
||||
});
|
||||
}
|
||||
|
||||
public int ApplyAttribute(ServerPlayerEntity targetPlayer, String attributeName, CommandContext<ServerCommandSource> 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<ServerCommandSource> context) {
|
||||
int ret = -1;
|
||||
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public int ListAttributes(CommandContext<ServerCommandSource> context) {
|
||||
int ret = -1;
|
||||
|
||||
if (context.getSource().isExecutedByPlayer()) {
|
||||
ServerPlayerEntity player = context.getSource().getPlayer();
|
||||
|
||||
for (Entry<String, AbstractNode> entry : AttributeMgr.attributes.entrySet()) {
|
||||
Keeblarcraft.LOGGER.debug("ATTR-LIST: " + entry.getKey() + " LINKS " + entry.getValue().GetNodeTitle());
|
||||
player.sendMessage(Text.of(entry.getKey()));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
@ -34,7 +36,7 @@ public class ShortcutCommands {
|
||||
.executes(context -> GamemodeShortcut(IntegerArgumentType.getInteger(context, "value"), context))));
|
||||
});
|
||||
|
||||
// Fly command ///TODO: Is this just being condensed into the FlightSpeedShortcut fn?
|
||||
// Fly command
|
||||
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
|
||||
dispatcher.register(CommandManager.literal("fly")
|
||||
.executes(context -> { FlightShortcut(context);
|
||||
|
@ -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
|
||||
|
@ -0,0 +1,67 @@
|
||||
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.item.BlockItem;
|
||||
import net.minecraft.registry.Registries;
|
||||
import net.minecraft.registry.Registry;
|
||||
import net.minecraft.sound.BlockSoundGroup;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
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<Block> blockList = new ArrayList<Block>();
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
/// @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));
|
||||
RegisterBlock("example_block", exampleBlock);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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<Item> itemList = new ArrayList<Item>();
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
/// @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);
|
||||
}
|
||||
|
||||
}
|
@ -7,7 +7,6 @@
|
||||
|
||||
package jesse.keeblarcraft.JsonClassObjects;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
|
@ -15,7 +15,11 @@ import net.fabricmc.api.ModInitializer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jesse.keeblarcraft.AttributeMgr.AttributeMgr;
|
||||
import jesse.keeblarcraft.Commands.CustomCommandManager;
|
||||
import jesse.keeblarcraft.CustomBlocks.BlockManager;
|
||||
import jesse.keeblarcraft.CustomItems.CustomItemGroups;
|
||||
import jesse.keeblarcraft.CustomItems.ItemManager;
|
||||
import jesse.keeblarcraft.Utils.CustomExceptions.SETUP_FAILED_EXCEPTION;
|
||||
import jesse.keeblarcraft.Utils.ChatUtil;
|
||||
import jesse.keeblarcraft.Utils.Setup;
|
||||
@ -28,6 +32,7 @@ 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();
|
||||
|
||||
@ -50,6 +55,18 @@ public class Keeblarcraft implements ModInitializer {
|
||||
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();
|
||||
|
@ -1,4 +1,4 @@
|
||||
package jesse.keeblarcraft;
|
||||
package jesse.keeblarcraft.Utils;
|
||||
|
||||
import jesse.CommonServerUtils;
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
|
@ -12,17 +12,11 @@ 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.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 +56,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<String> FILE_LIST = new ArrayList<String>() {{
|
||||
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()
|
||||
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "keeblarcraft:block/example_block"
|
||||
}
|
||||
}
|
||||
}
|
10
src/main/resources/assets/keeblarcraft/lang/en_us.json
Normal file
10
src/main/resources/assets/keeblarcraft/lang/en_us.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"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"
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "block/cube_all",
|
||||
"textures": {
|
||||
"all": "keeblarcraft:block/example_block"
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"parent": "keeblarcraft:block/example_block"
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "item/generated",
|
||||
"textures": {
|
||||
"layer0": "keeblarcraft:item/metaljacket_boots"
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "item/generated",
|
||||
"textures": {
|
||||
"layer0": "keeblarcraft:item/metaljacket_chestplate"
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "item/generated",
|
||||
"textures": {
|
||||
"layer0": "keeblarcraft:item/metaljacket_helmet"
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "item/generated",
|
||||
"textures": {
|
||||
"layer0": "keeblarcraft:item/metaljacket_leggings"
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 269 B |
Binary file not shown.
After Width: | Height: | Size: 287 B |
Binary file not shown.
After Width: | Height: | Size: 263 B |
Binary file not shown.
After Width: | Height: | Size: 267 B |
Loading…
Reference in New Issue
Block a user