├── .gitignore ├── README.md ├── pom.xml └── src └── main ├── java └── thorny │ └── grasscutters │ └── AttackModifier │ ├── AddAttack.java │ ├── AttackModifier.java │ ├── EventListener.java │ ├── commands │ └── AttackModifierCommand.java │ ├── objects │ └── PluginConfig.java │ └── utils │ ├── CharacterAvatar.java │ ├── Config.java │ ├── ConfigParser.java │ └── OldConfig.java └── resources ├── config.json └── plugin.json /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Attack Modifier Basics 2 | 3 | ### This plugin is basically a cosmetic-only simple implementation of how this could work. 4 | 5 | You may get hit by your own attacks. 6 | 7 | Preview by Moistcrafter: 8 | 9 | https://user-images.githubusercontent.com/107363768/245295660-291fa040-4a0a-4c7b-8914-e18eb7da08df.mp4 10 | 11 | ## Installation 12 | **Prebuilt JAR (Recommended):** 13 | - Get latest AttackModifier.jar release from [releases](https://github.com/NotThorny/AttackModifier/releases) and place it in your `\grasscutter\plugins` folder. 14 | - Restart Grasscutter if it was already running. 15 | - See [usage](https://github.com/NotThorny/AttackModifier#usage) for how to use the plugin in-game. 16 | 17 | **Building from source (For advanced users):** 18 | 19 | Building from source is for if you wish to build the plugin from source code yourself. If you are a normal user, please follow the prebuilt jar instruction above. 20 | - Run these commands: 21 | ``` 22 | cd AttackModifier 23 | mvn clean install 24 | ``` 25 | - Place built jar into `\grasscutter\plugins\` folder. 26 | 27 | Restart the server if it was already running. 28 | 29 | ## Usage 30 | 31 | `/at remove` to clear all gadgets 32 | 33 | `/at reload` to reload the config 34 | 35 | `/at set n|e|q [gadgetId]` to set the gadget for the current character's normal attack (n), elemental skill (e), or burst (q) 36 | 37 | `/at off|on` to toggle off/on the effects. Effects are enabled by default. 38 | 39 | Gadget ids can be found from a list such as [Jie's GrasscutterCommandGenerator](https://github.com/jie65535/GrasscutterCommandGenerator/blob/main/Source/GrasscutterTools/Resources/en-us/Gadget.txt), or in your Grasscutter resources folder: GadgetExcelConfigData.json 40 | 41 | ## Modifying 42 | 43 | Now with `config.json` yay 44 | 45 | ```json 46 | "ayaka": { 47 | "skill": { 48 | "normalAtk": 0, 49 | "elementalSkill": 0, 50 | "elementalBurst": 0 51 | } 52 | } 53 | ``` 54 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | NotThorny 8 | AttackModifier 9 | 2.0.0 10 | 11 | 12 | 17 13 | 17 14 | 15 | 16 | 17 | 21 | 22 | sonatype 23 | https://s01.oss.sonatype.org/content/groups/public/ 24 | 25 | 26 | 27 | 28 | 29 | xyz.grasscutters 30 | grasscutter 31 | 1.7.4-53 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/main/java/thorny/grasscutters/AttackModifier/AddAttack.java: -------------------------------------------------------------------------------- 1 | package thorny.grasscutters.AttackModifier; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import emu.grasscutter.Grasscutter; 7 | import emu.grasscutter.command.CommandHandler; 8 | import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData; 9 | import emu.grasscutter.game.avatar.Avatar; 10 | import emu.grasscutter.game.entity.EntityGadget; 11 | import emu.grasscutter.game.player.Player; 12 | import emu.grasscutter.game.world.Position; 13 | import emu.grasscutter.game.world.Scene; 14 | import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType; 15 | import emu.grasscutter.server.game.GameSession; 16 | import thorny.grasscutters.AttackModifier.commands.AttackModifierCommand; 17 | import thorny.grasscutters.AttackModifier.utils.CharacterAvatar; 18 | import thorny.grasscutters.AttackModifier.utils.CharacterAvatar.SkillIds; 19 | import thorny.grasscutters.AttackModifier.utils.Config; 20 | 21 | public class AddAttack { 22 | static ArrayList blacklistUIDs = AttackModifier.getInstance().getBlackList(); 23 | static List activeGadgets = new ArrayList<>(); // Current gadgets 24 | static List removeGadgets = new ArrayList<>(); // To be removed gadgets 25 | public static int x = 0; 26 | public static int y = 0; 27 | public static int z = 0; 28 | 29 | public static void setXYZ(int x, int y, int z) { 30 | AddAttack.x = x; 31 | AddAttack.y = y; 32 | AddAttack.z = z; 33 | } 34 | 35 | public static void addAttack(GameSession session, int skillId, int uid) { 36 | 37 | if (!(blacklistUIDs.contains(uid))) { 38 | int fileUid = AttackModifier.getInstance().getConfigUID(); 39 | 40 | if (!(fileUid == uid)) { 41 | Grasscutter.getLogger().debug("[AttackModifier] Loaded player " + uid + " config"); 42 | AttackModifier.getInstance().reloadConfig(uid); 43 | } 44 | 45 | int addedAttack; // Gadget to add 46 | int usedAttack = -1; // Default of no attack 47 | 48 | // Get avatar info 49 | Avatar avatar = session.getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar(); 50 | AvatarSkillDepotData skillDepot = avatar.getSkillDepot(); 51 | 52 | if (skillDepot == null) { 53 | Grasscutter.getLogger().debug("[AttackModifier] Attempted to get null skill data, skipping."); 54 | return; 55 | } 56 | 57 | // Check what skill type was used 58 | if (skillId == (skillDepot.getSkills().get(0))) { 59 | usedAttack = 0; 60 | } 61 | if (skillId == (skillDepot.getSkills().get(1))) { 62 | usedAttack = 1; 63 | } 64 | if (skillId == (skillDepot.getEnergySkill())) { 65 | usedAttack = 2; 66 | } 67 | 68 | // Get current avatar name 69 | String curName = avatar.getAvatarData().getName().toLowerCase(); 70 | 71 | AttackModifier.getInstance().reloadConfig(uid); 72 | 73 | var currentAvatar = AttackModifier.getInstance().getConfig().getCharacters().get(curName); 74 | 75 | // Create new entry if it does not exist 76 | if (currentAvatar == null) { 77 | CharacterAvatar temp = new CharacterAvatar(0, 0, 0); 78 | currentAvatar = temp; 79 | 80 | // Add the new entry 81 | AttackModifier.getInstance().getConfig().getCharacters().put(curName, currentAvatar); 82 | AttackModifier.getInstance().saveGadgetConfig(AttackModifier.getInstance().getConfig(), uid); 83 | } 84 | 85 | if (currentAvatar.getSkills() == null) { 86 | Grasscutter.getLogger().debug("Skills are null!" + AttackModifier.getInstance().getConfig().toCleanString()); 87 | } 88 | 89 | // Universal switch 90 | switch (usedAttack) { 91 | default -> addedAttack = 0; 92 | case 0 -> addedAttack = currentAvatar.getSkills().normalAtk(); // Normal attack 93 | case 1 -> addedAttack = currentAvatar.getSkills().elementalSkill(); // Elemental skill 94 | case 2 -> addedAttack = currentAvatar.getSkills().elementalBurst(); // Burst 95 | } 96 | 97 | // Get position 98 | var scene = session.getPlayer().getScene(); 99 | Position pos = new Position(session.getPlayer().getPosition()); 100 | Position rot = new Position(session.getPlayer().getRotation()); 101 | var r = 3; 102 | 103 | // Try to set position in front of player to not get hit 104 | double angle = rot.getY(); 105 | Position target = new Position(pos); 106 | 107 | // Only change gadget pos for basic attack 108 | if (usedAttack == 0) { 109 | target.addX((float) (r * Math.sin(Math.PI / 180 * angle))); 110 | target.addZ((float) (r * Math.cos(Math.PI / 180 * angle))); 111 | } 112 | 113 | // Optional xyz args 114 | if (x != 0 || y != 0 || z != 0) { 115 | target.addX(x); 116 | target.addY(y); 117 | target.addZ(z); 118 | } 119 | 120 | // Only spawn on match 121 | if (addedAttack != 0) { 122 | EntityGadget att = new EntityGadget(scene, addedAttack, target, rot); 123 | 124 | // Silly way to track gadget alive time 125 | int currTime = (int) (System.currentTimeMillis() - 1665393100); 126 | att.setGroupId(currTime); 127 | 128 | activeGadgets.add(att); 129 | 130 | scene.addEntity(att); 131 | 132 | // For future use, this may be helpful in preventing self-damage 133 | // att.setFightProperty(2001, 0); 134 | // att.setFightProperty(1, 0); 135 | 136 | } 137 | // Remove all gadgets when list not empty 138 | if (!activeGadgets.isEmpty()) { 139 | removeGadgets(scene); 140 | } // if 141 | } // if toAdd 142 | } // addAttack 143 | 144 | public static void removeGadgets(Scene scene) { 145 | for (EntityGadget gadget : activeGadgets) { 146 | 147 | // When gadgets have lived for 15 sec 148 | if (AttackModifierCommand.userCalled 149 | || (int) (System.currentTimeMillis() - 1665393100) > (gadget.getGroupId() + 15000)) { 150 | // Add to removal list 151 | removeGadgets.add(gadget); 152 | 153 | // Remove entity 154 | scene.removeEntity(gadget, VisionType.VISION_TYPE_REMOVE); 155 | } // if 156 | } // for 157 | // Remove gadgets and clean list 158 | activeGadgets.removeAll(removeGadgets); 159 | removeGadgets.clear(); 160 | AttackModifierCommand.userCalled = false; 161 | } // removeGadgets 162 | 163 | public static void setGadget(Player targetPlayer, String avatarName, int uid, String attackType, int newGadget) { 164 | // Get config 165 | //AttackModifier.getInstance().config.loadGadgetConfig(uid, false); 166 | Config gadgetConfig = AttackModifier.getInstance().getConfig(); 167 | 168 | // Get character 169 | var changedChar = gadgetConfig.getCharacters().get(avatarName); 170 | 171 | SkillIds charSkills; 172 | 173 | // If it is a new character 174 | if (changedChar == null) { 175 | changedChar = new CharacterAvatar(0, 0, 0); 176 | } 177 | 178 | charSkills = changedChar.getSkills(); 179 | 180 | switch (attackType) { 181 | default -> CommandHandler.sendMessage(targetPlayer, "/at set n|e|q [gadgetId]"); 182 | case "n" -> charSkills.setNormalAtk(newGadget); // Normal attack 183 | case "e" -> charSkills.setElementalSkill(newGadget); // Elemental skill 184 | case "q" -> charSkills.setElementalBurst(newGadget); // Burst 185 | } 186 | 187 | // Set new skills 188 | changedChar.setSkills(charSkills); 189 | 190 | // Add new values 191 | gadgetConfig.getCharacters().put(avatarName, changedChar); 192 | 193 | // Save changes 194 | AttackModifier.getInstance().saveGadgetConfig(gadgetConfig, uid); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/main/java/thorny/grasscutters/AttackModifier/AttackModifier.java: -------------------------------------------------------------------------------- 1 | package thorny.grasscutters.AttackModifier; 2 | 3 | import java.util.ArrayList; 4 | 5 | import emu.grasscutter.net.packet.PacketOpcodes; 6 | import emu.grasscutter.plugin.Plugin; 7 | import emu.grasscutter.server.event.EventHandler; 8 | import emu.grasscutter.server.event.HandlerPriority; 9 | import emu.grasscutter.server.event.game.ReceivePacketEvent; 10 | import thorny.grasscutters.AttackModifier.utils.Config; 11 | import thorny.grasscutters.AttackModifier.utils.ConfigParser; 12 | 13 | public final class AttackModifier extends Plugin { 14 | private static AttackModifier instance; 15 | private ConfigParser config; 16 | private int skillSuccPacketId; 17 | 18 | public static AttackModifier getInstance() { 19 | return instance; 20 | } 21 | 22 | @Override 23 | public void onLoad() { 24 | // Set the plugin instance. 25 | instance = this; 26 | this.config = new ConfigParser(); 27 | setSkillSuccPacketId(); 28 | this.getLogger().info("Loaded yay"); 29 | } 30 | 31 | @Override 32 | public void onEnable() { 33 | new EventHandler<>(ReceivePacketEvent.class) 34 | .priority(HandlerPriority.NORMAL) 35 | .listener(EventListener::onPacket) 36 | .register(this); 37 | 38 | // Register commands. 39 | this.getHandle().registerCommand(new thorny.grasscutters.AttackModifier.commands.AttackModifierCommand()); 40 | 41 | // Log a plugin status message. 42 | this.getLogger().info("The Attack Modifier plugin has been enabled."); 43 | } 44 | 45 | @Override 46 | public void onDisable() { 47 | // Log a plugin status message. 48 | this.getLogger().info("Attack Modifier has been disabled."); 49 | } 50 | 51 | // Modified from PacketOpcodeUtils 52 | private void setSkillSuccPacketId() { 53 | var fields = PacketOpcodes.class.getFields(); 54 | for (var f : fields) { 55 | if (f.getType().equals(int.class)) { 56 | if (f.getName().equals("EvtDoSkillSuccNotify")) { 57 | try { 58 | this.skillSuccPacketId = f.getInt(null); 59 | } catch (Exception exception) { 60 | exception.printStackTrace(); 61 | } 62 | 63 | // Found packet, stop checking 64 | return; 65 | } 66 | } 67 | } 68 | } 69 | 70 | public int getSkillSuccPacketId() { 71 | return this.skillSuccPacketId; 72 | } 73 | 74 | public Config getConfig() { 75 | return this.config.getGadgetConfig(); 76 | } 77 | 78 | public void setConfig(ConfigParser config) { 79 | this.config = config; 80 | } 81 | 82 | public void reloadConfig(Config updated) { 83 | this.config.setConfig(updated); 84 | this.config.loadConfig(); 85 | instance = this; 86 | } 87 | 88 | public void reloadConfig(int uid) { 89 | this.config.loadGadgetConfig(uid); 90 | instance = this; 91 | } 92 | 93 | public int getConfigUID() { 94 | return this.config.getGadgetConfigUid(); 95 | } 96 | 97 | public void saveGadgetConfig(Config updated, int uid) { 98 | this.config.saveGadgetList(updated, uid); 99 | this.config.loadGadgetConfig(uid); 100 | } 101 | 102 | public ArrayList getBlackList() { 103 | return this.config.getBlacklist(); 104 | } 105 | 106 | public void saveBlacklist(ArrayList blacklistUIDs) { 107 | this.config.saveBlacklist(blacklistUIDs); 108 | this.config.loadBlacklist(); 109 | } 110 | 111 | } -------------------------------------------------------------------------------- /src/main/java/thorny/grasscutters/AttackModifier/EventListener.java: -------------------------------------------------------------------------------- 1 | package thorny.grasscutters.AttackModifier; 2 | 3 | import com.google.protobuf.InvalidProtocolBufferException; 4 | 5 | import emu.grasscutter.Grasscutter; 6 | import emu.grasscutter.net.proto.EvtDoSkillSuccNotifyOuterClass.EvtDoSkillSuccNotify; 7 | import emu.grasscutter.server.event.EventHandler; 8 | import emu.grasscutter.server.event.HandlerPriority; 9 | import emu.grasscutter.server.event.game.ReceivePacketEvent; 10 | 11 | /** 12 | * A class containing all event handlers. 13 | * Syntax in event handler methods are similar to CraftBukkit. 14 | * To register an event handler, create a new instance of {@link EventHandler}. 15 | * Pass through the event class you want to handle. (ex. `new 16 | * EventHandler<>(PlayerJoinEvent.class);`) 17 | * You can change the point at which the handler method is invoked with 18 | * {@link EventHandler#priority(HandlerPriority)}. 19 | * You can set whether the handler method should be invoked when another plugin 20 | * cancels the event with {@link EventHandler#ignore(boolean)}. 21 | */ 22 | public final class EventListener { 23 | public static void onPacket(ReceivePacketEvent event) { 24 | if (event.getPacketId() == AttackModifier.getInstance().getSkillSuccPacketId()) { 25 | EvtDoSkillSuccNotify notify = null; 26 | try { 27 | notify = EvtDoSkillSuccNotify.parseFrom(event.getPacketData()); 28 | } catch (InvalidProtocolBufferException e) { 29 | Grasscutter.getLogger().error("Failed to parse packet data for EvtDoSkillSuccNotify"); 30 | } 31 | 32 | // Sanity check 33 | if (notify == null) { 34 | return; 35 | } 36 | 37 | // Get packet info 38 | var session = event.getGameSession(); 39 | int skillId = notify.getSkillId(); 40 | int uuid = session.getPlayer().getUid(); 41 | 42 | // Send to addAttack 43 | AddAttack.addAttack(session, skillId, uuid); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/thorny/grasscutters/AttackModifier/commands/AttackModifierCommand.java: -------------------------------------------------------------------------------- 1 | package thorny.grasscutters.AttackModifier.commands; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import emu.grasscutter.command.Command; 7 | import emu.grasscutter.command.Command.TargetRequirement; 8 | import emu.grasscutter.command.CommandHandler; 9 | import emu.grasscutter.game.entity.EntityGadget; 10 | import emu.grasscutter.game.player.Player; 11 | import thorny.grasscutters.AttackModifier.AddAttack; 12 | import thorny.grasscutters.AttackModifier.AttackModifier; 13 | import thorny.grasscutters.AttackModifier.utils.Config; 14 | 15 | // Command usage 16 | @Command(label = "attack", aliases = "at", usage = "on|off|remove \n set n|e|q [gadgetId]", targetRequirement = TargetRequirement.PLAYER) 17 | public class AttackModifierCommand implements CommandHandler { 18 | static ArrayList blacklistUIDs = AttackModifier.getInstance().getBlackList(); 19 | public static final Config gadgetConfig = AttackModifier.getInstance().getConfig(); 20 | 21 | public static boolean toAdd = true; // Default state to add attacks 22 | public static boolean userCalled = false; // Whether removeGadget was called by the user 23 | 24 | @Override 25 | public void execute(Player sender, Player targetPlayer, List args) { 26 | 27 | /* 28 | * Command usage available to check the gadgets before adding them 29 | * Just spawns the gadget where the player is standing, given the id 30 | * Also allows turning on/off added attacks and removing all active gadgets 31 | */ 32 | 33 | // Spawn a gadget at the players location and in the direction faced with /at gadgetId 34 | var scene = targetPlayer.getScene(); 35 | var pos = targetPlayer.getPosition(); 36 | var rot = targetPlayer.getRotation(); 37 | int thing = 0; 38 | int newGadget; 39 | String state; 40 | String avatarName = targetPlayer.getTeamManager().getCurrentAvatarEntity().getAvatar().getAvatarData().getName() 41 | .toLowerCase(); 42 | int uid = targetPlayer.getUid(); 43 | int x = 0; 44 | int y = 0; 45 | int z = 0; 46 | 47 | state = args.get(0); 48 | try { 49 | thing = Integer.parseInt(args.get(0)); 50 | } catch (NumberFormatException e) { 51 | } 52 | 53 | // Change whether added attacks should be on or not 54 | if (state.equals("off")) { 55 | if (blacklistUIDs.contains(uid)) { 56 | CommandHandler.sendMessage(targetPlayer, "Added attacks already disabled!"); 57 | } else { 58 | blacklistUIDs.add(uid); 59 | AttackModifier.getInstance().saveBlacklist(blacklistUIDs); 60 | CommandHandler.sendMessage(targetPlayer, "Disabled added attacks!"); 61 | } 62 | } 63 | 64 | if (state.equals("on")) { 65 | if (blacklistUIDs.contains(uid)) { 66 | blacklistUIDs.remove(Integer.valueOf(uid)); 67 | AttackModifier.getInstance().saveBlacklist(blacklistUIDs); 68 | CommandHandler.sendMessage(targetPlayer, "Enabled added attacks!"); 69 | } else { 70 | CommandHandler.sendMessage(targetPlayer, "Added attacks already enabled!!"); 71 | } 72 | } 73 | 74 | if (state.equals("remove")) { 75 | userCalled = true; 76 | AddAttack.removeGadgets(scene); 77 | CommandHandler.sendMessage(targetPlayer, "Removed all active gadgets!"); 78 | } 79 | if (state.equals("set")) { 80 | var attackType = args.get(1).toLowerCase(); 81 | try { 82 | newGadget = Integer.parseInt(args.get(2)); 83 | } catch (NumberFormatException e) { 84 | sendUsageMessage(targetPlayer); 85 | return; 86 | } 87 | try { 88 | if (args.size() > 3) { 89 | for (var a : args) { 90 | if (a.startsWith("x")) { 91 | x = Integer.parseInt(a.substring(1)); 92 | } 93 | if (a.startsWith("y")) { 94 | y = Integer.parseInt(a.substring(1)); 95 | } 96 | if (a.startsWith("z")) { 97 | z = Integer.parseInt(a.substring(1)); 98 | } 99 | } 100 | AddAttack.setXYZ(x, y, z); 101 | CommandHandler.sendMessage(targetPlayer, "Set spawn coordinates of: x" + x + ", y" + y + ", z" + z); 102 | } 103 | } catch (NumberFormatException e) { 104 | CommandHandler.sendMessage(targetPlayer, 105 | "Coordinates may be invalid. Ensure they match the format of x123 y123 z123. Only the desired x, y, or z is required.\n If you only want to change y, include just y123."); 106 | } 107 | 108 | AddAttack.setGadget(targetPlayer, avatarName, uid, attackType, newGadget); 109 | CommandHandler.sendMessage(targetPlayer, "Set new gadget!"); 110 | return; 111 | } 112 | 113 | EntityGadget entity = new EntityGadget(scene, thing, pos, rot); 114 | scene.addEntity(entity); 115 | 116 | } 117 | } // AttackModifierCommand -------------------------------------------------------------------------------- /src/main/java/thorny/grasscutters/AttackModifier/objects/PluginConfig.java: -------------------------------------------------------------------------------- 1 | package thorny.grasscutters.AttackModifier.objects; 2 | 3 | import com.google.gson.Gson; 4 | 5 | import java.io.Reader; 6 | import java.lang.reflect.Type; 7 | 8 | /** 9 | * A data container for the plugin configuration. 10 | * 11 | * This class is used in conjunction with {@link Gson#toJson(Object)} and {@link Gson#fromJson(Reader, Type)}. 12 | * With {@link Gson}, it is possible to save and load configuration values from a JSON file. 13 | * 14 | * You can set property defaults using `public Object property = (default value);`. 15 | * Use {@link Gson#fromJson(Reader, Type)} to load the values set from a reader/string into a new instance of this class. 16 | */ 17 | public final class PluginConfig { 18 | public boolean sendJoinMessage = true; 19 | public String joinMessage = "Welcome to the server!"; 20 | 21 | /** 22 | * When saved with {@link Gson#toJson(Object)}, it produces: 23 | * { 24 | * "sendJoinMessage": true, 25 | * "joinMessage": "Welcome to the server!" 26 | * } 27 | */ 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/thorny/grasscutters/AttackModifier/utils/CharacterAvatar.java: -------------------------------------------------------------------------------- 1 | package thorny.grasscutters.AttackModifier.utils; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | public class CharacterAvatar { 6 | @SerializedName("skill") 7 | public SkillIds skill; 8 | 9 | // Constructor for fully provided skill data 10 | public CharacterAvatar(int a, int b, int c) { 11 | this.skill = new SkillIds(a, b, c); 12 | } 13 | 14 | public SkillIds getSkills() { 15 | return this.skill; 16 | } 17 | 18 | public void setSkills(SkillIds skills) { 19 | this.skill = skills; 20 | } 21 | 22 | @Override 23 | public String toString() { 24 | return this.skill.toString(); 25 | } 26 | 27 | // Ids for the skills to be used by the character 28 | public static class SkillIds { 29 | @SerializedName("normalAtk") 30 | private int normalAtk; 31 | @SerializedName("elementalSkill") 32 | private int elementalSkill; 33 | @SerializedName("elementalBurst") 34 | private int elementalBurst; 35 | 36 | public SkillIds(int i, int j, int k) { 37 | this.normalAtk = i; 38 | this.elementalSkill = j; 39 | this.elementalBurst = k; 40 | } 41 | 42 | public SkillIds() { 43 | this.normalAtk = 0; 44 | this.elementalSkill = 0; 45 | this.elementalBurst = 0; 46 | } 47 | 48 | public void setNormalAtk(int normalAtk) { 49 | this.normalAtk = normalAtk; 50 | } 51 | 52 | public void setElementalSkill(int elementalSkill) { 53 | this.elementalSkill = elementalSkill; 54 | } 55 | 56 | public void setElementalBurst(int elementalBurst) { 57 | this.elementalBurst = elementalBurst; 58 | } 59 | 60 | public int normalAtk() { 61 | return normalAtk; 62 | } 63 | 64 | public int elementalSkill() { 65 | return elementalSkill; 66 | } 67 | 68 | public int elementalBurst() { 69 | return elementalBurst; 70 | } 71 | 72 | @Override 73 | public String toString() { 74 | return "{ " + this.normalAtk + ", " + this.elementalSkill + ", " + this.elementalBurst + " }"; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/thorny/grasscutters/AttackModifier/utils/Config.java: -------------------------------------------------------------------------------- 1 | package thorny.grasscutters.AttackModifier.utils; 2 | 3 | import java.util.HashMap; 4 | 5 | import com.google.gson.annotations.SerializedName; 6 | 7 | /* 8 | * A class that contains data for characters. 9 | * Character data is stored in a HashMap of String, CharacterAvatar, 10 | * where String is the name of the avatar, and the CharacterAvatar contains 11 | * the saved skill data. 12 | */ 13 | public final class Config { 14 | 15 | @SerializedName("characters") 16 | private HashMap characters; 17 | 18 | public HashMap getCharacters() { 19 | return this.characters; 20 | } 21 | 22 | public void setCharacters(HashMap a) { 23 | characters = a; 24 | } 25 | 26 | public String toCleanString() { 27 | return this.characters.toString(); 28 | } 29 | 30 | public void setDefaults() { 31 | // Create a new character 32 | characters = new HashMap<>(); 33 | 34 | // Create the "showcase" skills 35 | CharacterAvatar raiden = new CharacterAvatar(42906105, 42906108, 42906119); 36 | 37 | // Add to the list 38 | characters.put("shougun", raiden); 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/java/thorny/grasscutters/AttackModifier/utils/ConfigParser.java: -------------------------------------------------------------------------------- 1 | package thorny.grasscutters.AttackModifier.utils; 2 | 3 | import java.io.File; 4 | import java.io.FileReader; 5 | import java.io.FileWriter; 6 | import java.io.IOException; 7 | import java.lang.reflect.Type; 8 | import java.util.ArrayList; 9 | 10 | import com.google.gson.Gson; 11 | import com.google.gson.GsonBuilder; 12 | import com.google.gson.JsonIOException; 13 | import com.google.gson.JsonSyntaxException; 14 | import com.google.gson.reflect.TypeToken; 15 | 16 | import emu.grasscutter.Grasscutter; 17 | import emu.grasscutter.utils.JsonUtils; 18 | import thorny.grasscutters.AttackModifier.AttackModifier; 19 | 20 | public final class ConfigParser { 21 | 22 | private Config gadgetConfig; 23 | private int gadgetConfigUid; 24 | private ArrayList blacklistUID = new ArrayList<>(); 25 | private final String configPath = Grasscutter.getConfig().folderStructure.plugins + "AttackModifier"; 26 | private final File blacklistFile = new File(this.configPath + "/blacklist.json"); 27 | private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); 28 | Type listType = new TypeToken>() {}.getType(); 29 | 30 | public ConfigParser() { 31 | // Load config 32 | this.loadConfig(); 33 | this.loadBlacklist(); 34 | } 35 | 36 | public Config getGadgetConfig() { 37 | return this.gadgetConfig; 38 | } 39 | 40 | public ArrayList getBlacklist() { 41 | return this.blacklistUID; 42 | } 43 | 44 | public int getGadgetConfigUid() { 45 | return this.gadgetConfigUid; 46 | } 47 | 48 | public void setConfig(Config config) { 49 | this.gadgetConfig = config; 50 | } 51 | 52 | public void reloadInstance() { 53 | AttackModifier.getInstance().onLoad(); 54 | } 55 | 56 | public void loadConfig() { 57 | loadGadgetConfig(0, false); 58 | Grasscutter.getLogger().info("[AttackModifier] Config Loaded!"); 59 | 60 | if (!saveGadgetList(gadgetConfig, 0)) { 61 | Grasscutter.getLogger().error("[AttackModifier] Unable to save config file."); 62 | } 63 | 64 | Grasscutter.getLogger().info("[AttackModifier] Plugin loaded successfully!"); 65 | } 66 | 67 | public void loadBlacklist() { 68 | try (FileReader file = new FileReader(this.blacklistFile)) { 69 | this.blacklistUID = gson.fromJson(file, listType); 70 | } catch (Exception e) { 71 | saveBlacklist(null); 72 | } 73 | } 74 | 75 | public void loadGadgetConfig(int uid) { 76 | File gadgetFile = new File(this.configPath, "/" + uid + ".json"); 77 | try (FileReader file = new FileReader(gadgetFile)) { 78 | this.gadgetConfig = gson.fromJson(file, Config.class); 79 | 80 | this.gadgetConfigUid = uid; 81 | } catch (Exception e) { 82 | this.gadgetConfig = new Config(); 83 | this.gadgetConfig.setDefaults(); 84 | saveGadgetList(gadgetConfig, uid); 85 | } 86 | } 87 | 88 | public void loadGadgetConfig(int uid, boolean looping) { 89 | File gadgetFile = new File(this.configPath, "/" + uid + ".json"); 90 | try (FileReader file = new FileReader(gadgetFile)) { 91 | this.gadgetConfig = gson.fromJson(file, Config.class); 92 | 93 | // For configs that slip through 94 | if (JsonUtils.encode(gadgetConfig).isEmpty() || JsonUtils.encode(gadgetConfig).equals("{}")) { 95 | throw new JsonSyntaxException("Invalid syntax"); 96 | } 97 | 98 | this.gadgetConfigUid = uid; 99 | } catch (JsonSyntaxException e) { // Old configs 100 | Grasscutter.getLogger().info("Old config detected, fixing!"); 101 | 102 | try (FileReader file2 = new FileReader(gadgetFile)) { 103 | File dir = new File(this.configPath); 104 | File[] dirList = dir.listFiles(); 105 | 106 | // Load with old config class 107 | OldConfig old = gson.fromJson(file2, OldConfig.class); 108 | // Why use many word when few do trick 109 | var changed = """ 110 | { 111 | "characters": """ + JsonUtils.encode(old) + "\n}"; 112 | // Change names to match current 113 | changed = changed.replaceAll("Ids", ""); 114 | // Write changes 115 | try (FileWriter filew = new FileWriter(gadgetFile)) { 116 | filew.write(changed); 117 | } catch (Exception ear) { 118 | Grasscutter.getLogger().error("Unable to save fixed config! It will be reset."); 119 | return; 120 | } 121 | 122 | // Don't go into this while already looping 123 | if (!looping) { 124 | if (dirList != null) { 125 | for (File child : dirList) { 126 | if (child.getName().contains("blacklist") || child.getName().contains("config")) { 127 | continue; 128 | } 129 | 130 | loadGadgetConfig(Integer.parseInt(child.getName().replace(".json", "")), true); 131 | } 132 | } 133 | } 134 | 135 | if (this.gadgetConfig == null) { 136 | // Worst case, reset the config to avoid locking instance as null 137 | Grasscutter.getLogger().info("AttackModifier config unable to be fixed or loaded properly, resetting it!"); 138 | reloadInstance(); 139 | } 140 | } catch (Exception er) { 141 | // This should never happen 142 | Grasscutter.getLogger().warn( 143 | "Your AttackModifier configs have failed during updating, and may experience major problems such as a full reset!"); 144 | } 145 | } catch (JsonIOException | IOException e) { // Missing configs 146 | this.gadgetConfig = new Config(); 147 | this.gadgetConfig.setDefaults(); 148 | saveGadgetList(gadgetConfig, uid); 149 | } 150 | } 151 | 152 | public boolean saveGadgetList(Config updated, int uid) { 153 | File dir = new File(this.configPath); 154 | File gadgetFile = new File(this.configPath, "/" + uid + ".json"); 155 | 156 | if (!dir.exists() || !dir.isDirectory()) { 157 | if (!new java.io.File(String.valueOf(dir)).mkdirs()) { 158 | return false; 159 | } 160 | } 161 | 162 | try (FileWriter file = new FileWriter(gadgetFile)) { 163 | if (updated == null) { 164 | file.write(JsonUtils.encode(this.gadgetConfig)); 165 | } else { 166 | file.write(JsonUtils.encode(updated)); 167 | } 168 | } catch (Exception e) { 169 | return false; 170 | } 171 | 172 | return true; 173 | } 174 | 175 | public boolean saveBlacklist(ArrayList updated) { 176 | File dir = new File(this.configPath); 177 | 178 | if (!dir.exists() || !dir.isDirectory()) { 179 | if (!new java.io.File(String.valueOf(dir)).mkdirs()) { 180 | return false; 181 | } 182 | } 183 | 184 | try (FileWriter file = new FileWriter(this.blacklistFile)) { 185 | if (updated == null) { 186 | file.write(JsonUtils.encode(this.blacklistUID)); 187 | } else { 188 | file.write(JsonUtils.encode(updated)); 189 | } 190 | } catch (Exception e) { 191 | return false; 192 | } 193 | 194 | return true; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/main/java/thorny/grasscutters/AttackModifier/utils/OldConfig.java: -------------------------------------------------------------------------------- 1 | package thorny.grasscutters.AttackModifier.utils; 2 | 3 | @Deprecated 4 | public class OldConfig { 5 | 6 | /* 7 | * This class is exclusively for updating old configuration files. 8 | * Do not use. 9 | */ 10 | 11 | public characters ayakaIds = new characters(); 12 | public characters qinIds = new characters(); 13 | public characters playerboyIds = new characters(); 14 | public characters lisaIds = new characters(); 15 | public characters playergirlIds = new characters(); 16 | public characters barbaraIds = new characters(); 17 | public characters kaeyaIds = new characters(); 18 | public characters dilucIds = new characters(); 19 | public characters razorIds = new characters(); 20 | public characters amborIds = new characters(); 21 | public characters ventiIds = new characters(); 22 | public characters xianglingIds = new characters(); 23 | public characters beidouIds = new characters(); 24 | public characters xingqiuIds = new characters(); 25 | public characters xiaoIds = new characters(); 26 | public characters ningguangIds = new characters(); 27 | public characters kleeIds = new characters(); 28 | public characters zhongliIds = new characters(); 29 | public characters fischlIds = new characters(); 30 | public characters bennettIds = new characters(); 31 | public characters tartagliaIds = new characters(); 32 | public characters noelIds = new characters(); 33 | public characters qiqiIds = new characters(); 34 | public characters chongyunIds = new characters(); 35 | public characters ganyuIds = new characters(); 36 | public characters albedoIds = new characters(); 37 | public characters dionaIds = new characters(); 38 | public characters monaIds = new characters(); 39 | public characters keqingIds = new characters(); 40 | public characters sucroseIds = new characters(); 41 | public characters xinyanIds = new characters(); 42 | public characters rosariaIds = new characters(); 43 | public characters hutaoIds = new characters(); 44 | public characters kazuhaIds = new characters(); 45 | public characters feiyanIds = new characters(); 46 | public characters yoimiyaIds = new characters(); 47 | public characters tohmaIds = new characters(); 48 | public characters eulaIds = new characters(); 49 | public characters shougunIds = new characters(); 50 | public characters sayuIds = new characters(); 51 | public characters kokomiIds = new characters(); 52 | public characters gorouIds = new characters(); 53 | public characters saraIds = new characters(); 54 | public characters ittoIds = new characters(); 55 | public characters yaeIds = new characters(); 56 | public characters heizoIds = new characters(); 57 | public characters yelanIds = new characters(); 58 | public characters aloyIds = new characters(); 59 | public characters shenheIds = new characters(); 60 | public characters yunjinIds = new characters(); 61 | public characters shinobuIds = new characters(); 62 | public characters ayatoIds = new characters(); 63 | public characters colleiIds = new characters(); 64 | public characters doriIds = new characters(); 65 | public characters tighnariIds = new characters(); 66 | public characters nilouIds = new characters(); 67 | public characters cynoIds = new characters(); 68 | public characters candaceIds = new characters(); 69 | public characters nahidaIds = new characters(); 70 | public characters laylaIds = new characters(); 71 | public characters wandererIds = new characters(); 72 | public characters faruzanIds = new characters(); 73 | public characters yaoyaoIds = new characters(); 74 | public characters alhathamIds = new characters(); 75 | public characters dehyaIds = new characters(); 76 | public characters mikaIds = new characters(); 77 | public characters kavehIds = new characters(); 78 | public characters baizhuIds = new characters(); 79 | public characters kiraraIds = new characters(); 80 | public characters linetteIds = new characters(); 81 | public characters lineyIds = new characters(); 82 | public characters freminetIds = new characters(); 83 | public characters wriothesleyIds = new characters(); 84 | public characters neuvilletteIds = new characters(); 85 | public characters charlotteIds = new characters(); 86 | public characters furinaIds = new characters(); 87 | public characters chevreuseIds = new characters(); 88 | public characters naviaIds = new characters(); 89 | public characters gamingIds = new characters(); 90 | public characters xianyunIds = new characters(); 91 | public characters chioriIds = new characters(); 92 | public characters sigewinneIds = new characters(); 93 | public characters arlecchinoIds = new characters(); 94 | public characters sethosIds = new characters(); 95 | public characters clorindeIds = new characters(); 96 | public characters emileIds = new characters(); 97 | public characters kachinaIds = new characters(); 98 | public characters kinichIds = new characters(); 99 | public characters mualaniIds = new characters(); 100 | 101 | public static class characters { 102 | 103 | public skillIds skill = new skillIds(); 104 | 105 | public static class skillIds { 106 | 107 | public int normalAtk = 0; 108 | public int elementalSkill = 0; 109 | public int elementalBurst = 0; 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/resources/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ayakaIds": { 3 | "skill": { 4 | "normalAtk": 0, 5 | "elementalSkill": 0, 6 | "elementalBurst": 0 7 | } 8 | }, 9 | "jeanIds": { 10 | "skill": { 11 | "normalAtk": 0, 12 | "elementalSkill": 0, 13 | "elementalBurst": 0 14 | } 15 | }, 16 | "playerboyIds": { 17 | "skill": { 18 | "normalAtk": 0, 19 | "elementalSkill": 0, 20 | "elementalBurst": 0 21 | } 22 | }, 23 | "lisaIds": { 24 | "skill": { 25 | "normalAtk": 0, 26 | "elementalSkill": 0, 27 | "elementalBurst": 0 28 | } 29 | }, 30 | "playergirlIds": { 31 | "skill": { 32 | "normalAtk": 0, 33 | "elementalSkill": 0, 34 | "elementalBurst": 0 35 | } 36 | }, 37 | "barbaraIds": { 38 | "skill": { 39 | "normalAtk": 0, 40 | "elementalSkill": 0, 41 | "elementalBurst": 0 42 | } 43 | }, 44 | "kaeyaIds": { 45 | "skill": { 46 | "normalAtk": 0, 47 | "elementalSkill": 0, 48 | "elementalBurst": 0 49 | } 50 | }, 51 | "dilucIds": { 52 | "skill": { 53 | "normalAtk": 0, 54 | "elementalSkill": 0, 55 | "elementalBurst": 0 56 | } 57 | }, 58 | "razorIds": { 59 | "skill": { 60 | "normalAtk": 0, 61 | "elementalSkill": 0, 62 | "elementalBurst": 0 63 | } 64 | }, 65 | "amberIds": { 66 | "skill": { 67 | "normalAtk": 0, 68 | "elementalSkill": 0, 69 | "elementalBurst": 0 70 | } 71 | }, 72 | "ventiIds": { 73 | "skill": { 74 | "normalAtk": 0, 75 | "elementalSkill": 0, 76 | "elementalBurst": 0 77 | } 78 | }, 79 | "xianglingIds": { 80 | "skill": { 81 | "normalAtk": 0, 82 | "elementalSkill": 0, 83 | "elementalBurst": 0 84 | } 85 | }, 86 | "beidouIds": { 87 | "skill": { 88 | "normalAtk": 0, 89 | "elementalSkill": 0, 90 | "elementalBurst": 0 91 | } 92 | }, 93 | "xingqiuIds": { 94 | "skill": { 95 | "normalAtk": 0, 96 | "elementalSkill": 0, 97 | "elementalBurst": 0 98 | } 99 | }, 100 | "xiaoIds": { 101 | "skill": { 102 | "normalAtk": 0, 103 | "elementalSkill": 0, 104 | "elementalBurst": 0 105 | } 106 | }, 107 | "ningguangIds": { 108 | "skill": { 109 | "normalAtk": 0, 110 | "elementalSkill": 0, 111 | "elementalBurst": 0 112 | } 113 | }, 114 | "kleeIds": { 115 | "skill": { 116 | "normalAtk": 0, 117 | "elementalSkill": 0, 118 | "elementalBurst": 0 119 | } 120 | }, 121 | "zhongliIds": { 122 | "skill": { 123 | "normalAtk": 0, 124 | "elementalSkill": 0, 125 | "elementalBurst": 0 126 | } 127 | }, 128 | "fischlIds": { 129 | "skill": { 130 | "normalAtk": 0, 131 | "elementalSkill": 0, 132 | "elementalBurst": 0 133 | } 134 | }, 135 | "bennettIds": { 136 | "skill": { 137 | "normalAtk": 0, 138 | "elementalSkill": 0, 139 | "elementalBurst": 0 140 | } 141 | }, 142 | "tartagliaIds": { 143 | "skill": { 144 | "normalAtk": 0, 145 | "elementalSkill": 0, 146 | "elementalBurst": 0 147 | } 148 | }, 149 | "noelleIds": { 150 | "skill": { 151 | "normalAtk": 0, 152 | "elementalSkill": 0, 153 | "elementalBurst": 0 154 | } 155 | }, 156 | "qiqiIds": { 157 | "skill": { 158 | "normalAtk": 0, 159 | "elementalSkill": 0, 160 | "elementalBurst": 0 161 | } 162 | }, 163 | "chongyunIds": { 164 | "skill": { 165 | "normalAtk": 0, 166 | "elementalSkill": 0, 167 | "elementalBurst": 0 168 | } 169 | }, 170 | "ganyuIds": { 171 | "skill": { 172 | "normalAtk": 0, 173 | "elementalSkill": 0, 174 | "elementalBurst": 0 175 | } 176 | }, 177 | "albedoIds": { 178 | "skill": { 179 | "normalAtk": 0, 180 | "elementalSkill": 0, 181 | "elementalBurst": 0 182 | } 183 | }, 184 | "dionaIds": { 185 | "skill": { 186 | "normalAtk": 0, 187 | "elementalSkill": 0, 188 | "elementalBurst": 0 189 | } 190 | }, 191 | "monaIds": { 192 | "skill": { 193 | "normalAtk": 0, 194 | "elementalSkill": 0, 195 | "elementalBurst": 0 196 | } 197 | }, 198 | "keqingIds": { 199 | "skill": { 200 | "normalAtk": 42906009, 201 | "elementalSkill": 42509009, 202 | "elementalBurst": 0 203 | } 204 | }, 205 | "sucroseIds": { 206 | "skill": { 207 | "normalAtk": 0, 208 | "elementalSkill": 0, 209 | "elementalBurst": 0 210 | } 211 | }, 212 | "xinyanIds": { 213 | "skill": { 214 | "normalAtk": 0, 215 | "elementalSkill": 0, 216 | "elementalBurst": 0 217 | } 218 | }, 219 | "rosariaIds": { 220 | "skill": { 221 | "normalAtk": 0, 222 | "elementalSkill": 0, 223 | "elementalBurst": 0 224 | } 225 | }, 226 | "hutaoIds": { 227 | "skill": { 228 | "normalAtk": 0, 229 | "elementalSkill": 0, 230 | "elementalBurst": 0 231 | } 232 | }, 233 | "kazuhaIds": { 234 | "skill": { 235 | "normalAtk": 0, 236 | "elementalSkill": 0, 237 | "elementalBurst": 0 238 | } 239 | }, 240 | "feiyanIds": { 241 | "skill": { 242 | "normalAtk": 0, 243 | "elementalSkill": 0, 244 | "elementalBurst": 0 245 | } 246 | }, 247 | "yoimiyaIds": { 248 | "skill": { 249 | "normalAtk": 0, 250 | "elementalSkill": 0, 251 | "elementalBurst": 0 252 | } 253 | }, 254 | "tohmaIds": { 255 | "skill": { 256 | "normalAtk": 0, 257 | "elementalSkill": 0, 258 | "elementalBurst": 0 259 | } 260 | }, 261 | "eulaIds": { 262 | "skill": { 263 | "normalAtk": 0, 264 | "elementalSkill": 0, 265 | "elementalBurst": 0 266 | } 267 | }, 268 | "shougunIds": { 269 | "skill": { 270 | "normalAtk": 0, 271 | "elementalSkill": 0, 272 | "elementalBurst": 0 273 | } 274 | }, 275 | "sayuIds": { 276 | "skill": { 277 | "normalAtk": 0, 278 | "elementalSkill": 0, 279 | "elementalBurst": 0 280 | } 281 | }, 282 | "kokomiIds": { 283 | "skill": { 284 | "normalAtk": 0, 285 | "elementalSkill": 0, 286 | "elementalBurst": 0 287 | } 288 | }, 289 | "gorouIds": { 290 | "skill": { 291 | "normalAtk": 0, 292 | "elementalSkill": 0, 293 | "elementalBurst": 0 294 | } 295 | }, 296 | "saraIds": { 297 | "skill": { 298 | "normalAtk": 0, 299 | "elementalSkill": 0, 300 | "elementalBurst": 0 301 | } 302 | }, 303 | "ittoIds": { 304 | "skill": { 305 | "normalAtk": 0, 306 | "elementalSkill": 0, 307 | "elementalBurst": 0 308 | } 309 | }, 310 | "yaeIds": { 311 | "skill": { 312 | "normalAtk": 0, 313 | "elementalSkill": 0, 314 | "elementalBurst": 0 315 | } 316 | }, 317 | "heizoIds": { 318 | "skill": { 319 | "normalAtk": 0, 320 | "elementalSkill": 0, 321 | "elementalBurst": 0 322 | } 323 | }, 324 | "yelanIds": { 325 | "skill": { 326 | "normalAtk": 0, 327 | "elementalSkill": 0, 328 | "elementalBurst": 0 329 | } 330 | }, 331 | "aloyIds": { 332 | "skill": { 333 | "normalAtk": 0, 334 | "elementalSkill": 0, 335 | "elementalBurst": 0 336 | } 337 | }, 338 | "shenheIds": { 339 | "skill": { 340 | "normalAtk": 0, 341 | "elementalSkill": 0, 342 | "elementalBurst": 0 343 | } 344 | }, 345 | "yunjinIds": { 346 | "skill": { 347 | "normalAtk": 0, 348 | "elementalSkill": 0, 349 | "elementalBurst": 0 350 | } 351 | }, 352 | "shinobuIds": { 353 | "skill": { 354 | "normalAtk": 0, 355 | "elementalSkill": 0, 356 | "elementalBurst": 0 357 | } 358 | }, 359 | "ayatoIds": { 360 | "skill": { 361 | "normalAtk": 0, 362 | "elementalSkill": 0, 363 | "elementalBurst": 0 364 | } 365 | }, 366 | "colleiIds": { 367 | "skill": { 368 | "normalAtk": 0, 369 | "elementalSkill": 0, 370 | "elementalBurst": 0 371 | } 372 | }, 373 | "doriIds": { 374 | "skill": { 375 | "normalAtk": 0, 376 | "elementalSkill": 0, 377 | "elementalBurst": 0 378 | } 379 | }, 380 | "tighnariIds": { 381 | "skill": { 382 | "normalAtk": 0, 383 | "elementalSkill": 0, 384 | "elementalBurst": 0 385 | } 386 | }, 387 | "nilouIds": { 388 | "skill": { 389 | "normalAtk": 0, 390 | "elementalSkill": 0, 391 | "elementalBurst": 0 392 | } 393 | }, 394 | "cynoIds": { 395 | "skill": { 396 | "normalAtk": 0, 397 | "elementalSkill": 0, 398 | "elementalBurst": 0 399 | } 400 | }, 401 | "candaceIds": { 402 | "skill": { 403 | "normalAtk": 0, 404 | "elementalSkill": 0, 405 | "elementalBurst": 0 406 | } 407 | }, 408 | "nahidaIds": { 409 | "skill": { 410 | "normalAtk": 0, 411 | "elementalSkill": 0, 412 | "elementalBurst": 0 413 | } 414 | }, 415 | "laylaIds": { 416 | "skill": { 417 | "normalAtk": 0, 418 | "elementalSkill": 0, 419 | "elementalBurst": 0 420 | } 421 | }, 422 | "alhathamIds": { 423 | "skill": { 424 | "normalAtk": 0, 425 | "elementalSkill": 0, 426 | "elementalBurst": 0 427 | } 428 | }, 429 | "yaoyaoIds": { 430 | "skill": { 431 | "normalAtk": 0, 432 | "elementalSkill": 0, 433 | "elementalBurst": 0 434 | } 435 | } 436 | } -------------------------------------------------------------------------------- /src/main/resources/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AttackModifier", 3 | "description": "Make attacks do extra things", 4 | "version": "1.3.0-dev", 5 | "authors": [ "NotThorny" ], 6 | "api": 2, 7 | 8 | "mainClass": "thorny.grasscutters.AttackModifier.AttackModifier", 9 | 10 | "comments": [ 11 | "Don't bug test this because it will break" 12 | ] 13 | } 14 | --------------------------------------------------------------------------------