├── .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 |
--------------------------------------------------------------------------------