/
EnchantmentScriptContainer.java
313 lines (290 loc) · 15.1 KB
/
EnchantmentScriptContainer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
package com.denizenscript.denizen.scripts.containers.core;
import com.denizenscript.denizen.nms.NMSHandler;
import com.denizenscript.denizen.objects.EntityTag;
import com.denizenscript.denizen.objects.ItemTag;
import com.denizenscript.denizen.tags.BukkitTagContext;
import com.denizenscript.denizen.utilities.FormattedTextHelper;
import com.denizenscript.denizen.utilities.debugging.Debug;
import com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;
import com.denizenscript.denizencore.objects.core.ElementTag;
import com.denizenscript.denizencore.objects.core.ScriptTag;
import com.denizenscript.denizencore.scripts.ScriptEntry;
import com.denizenscript.denizencore.scripts.containers.ScriptContainer;
import com.denizenscript.denizencore.scripts.queues.ContextSource;
import com.denizenscript.denizencore.scripts.queues.core.InstantQueue;
import com.denizenscript.denizencore.tags.TagContext;
import com.denizenscript.denizencore.tags.TagManager;
import com.denizenscript.denizencore.utilities.AsciiMatcher;
import com.denizenscript.denizencore.utilities.CoreUtilities;
import com.denizenscript.denizencore.utilities.YamlConfiguration;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import org.bukkit.Bukkit;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Entity;
import org.bukkit.inventory.ItemStack;
import java.util.HashMap;
import java.util.List;
public class EnchantmentScriptContainer extends ScriptContainer {
// <--[language]
// @name Enchantment Script Containers
// @group Script Container System
// @description
// Enchantment script containers allow you to register custom item enchantments.
// For the most part, they work similarly to vanilla enchantments, albeit with some limitations.
// These can be attached to enchanted books and used in anvils,
// and can be generated by the enchanting table (requires discoverable: true and treasure_only: false).
//
// In current implementation, custom enchantments do not appear in lore on their own, and will need fake lore added in their place.
// This might be fixed in the future.
//
// It may be beneficial in some cases to restart your server after making changes to enchantments, rather than just reloading scripts.
//
// Using these may cause unpredictable compatibility issues with external plugins.
//
// <code>
// Enchantment_Script_Name:
//
// type: enchantment
//
// # The ID is used as the internal registration key, and for lookups with things like the 'enchantments' mechanism.
// # If unspecified, will use the script name.
// # Limited to A-Z or _.
// # | Most enchantment scripts should have this key!
// id: my_id
//
// # A list of slots this enchantment is valid in.
// # | ALL enchantment scripts MUST have this key!
// slots:
// # Can be any of: mainhand, offhand, feet, legs, chest, head
// - mainhand
//
// # The rarity level of this enchantment. Can be any of: COMMON, UNCOMMON, RARE, VERY_RARE
// # If unspecified, will use COMMON.
// # | Most enchantment scripts should have this key!
// rarity: common
//
// # The category/type of this enchantment. Can be any of: ARMOR, ARMOR_FEET, ARMOR_LEGS, ARMOR_CHEST, ARMOR_HEAD,
// # WEAPON, DIGGER, FISHING_ROD, TRIDENT, BREAKABLE, BOW, WEARABLE, CROSSBOW, VANISHABLE
// # If unspecified, will use WEAPON.
// # | Most enchantment scripts should have this key!
// category: weapon
//
// # The per-level full name of this enchantment. Does not appear in lore automatically (but might in the future?).
// # Can make use of "<context.level>" for the applicable level.
// # | Most enchantment scripts should have this key!
// full_name: My Enchantment <context.level>
//
// # The minimum level of this enchantment.
// # If unspecified, will use 1.
// # | Most enchantment scripts can exclude this key.
// min_level: 1
//
// # The maximum level of this enchantment.
// # If unspecified, will use 1.
// # | Some enchantment scripts should have this key.
// max_level: 1
//
// # The per-level minimum XP cost of enchanting.
// # Can make use of "<context.level>" for the applicable level.
// # | Most enchantment scripts should have this key!
// min_cost: <context.level.mul[1]>
//
// # The per-level maximum XP cost of enchanting.
// # Can make use of "<context.level>" for the applicable level.
// # | Most enchantment scripts should have this key!
// max_cost: <context.level.mul[1]>
//
// # Whether this enchantment is only considered to be treasure. Treasure enchantments do not show in the enchanting table.
// # If unspecified, will be false.
// # | Most enchantment scripts can exclude this key.
// treasure_only: false
//
// # Whether this enchantment is only considered to be a curse. (TODO: What difference does this make?)
// # If unspecified, will be false.
// # | Most enchantment scripts can exclude this key.
// is_curse: false
//
// # Whether this enchantment is only considered to be tradable. Villagers won't trade this enchantment if set to false.
// # If unspecified, will be true.
// # | Most enchantment scripts can exclude this key.
// is_tradable: true
//
// # Whether this enchantment is only considered to be discoverable.
// # If true, this will spawn from vanilla sources like the enchanting table. If false, it can only be given directly by script.
// # If unspecified, will be true.
// # | Most enchantment scripts can exclude this key.
// is_discoverable: true
//
// # A tag that returns a boolean indicating whether this enchantment is compatible with another.
// # Can make use of "<context.enchantment_key>" for the applicable enchantment's key, like "minecraft:sharpness".
// # If unspecified, will default to always true.
// # | Most enchantment scripts can exclude this key.
// is_compatible: <context.enchantment_key.advanced_matches[minecraft:lure|minecraft:luck*]>
//
// # A tag that returns a boolean indicating whether this enchantment can enchant a specific item.
// # Can make use of "<context.item>" for the applicable ItemTag.
// # If unspecified, will default to always true.
// # | Most enchantment scripts can exclude this key.
// can_enchant: <context.item.advanced_matches[*_sword|*_axe]>
//
// # A tag that returns a decimal number indicating how much extra damage this item should deal.
// # Can make use of "<context.level>" for the enchantment level,
// # and "<context.type>" for the type of monster being fought: ARTHROPOD, ILLAGER, WATER, UNDEAD, or UNDEFINED
// # If unspecified, will default to 0.0.
// # | Most enchantment scripts can exclude this key.
// damage_bonus: 0.0
//
// # A tag that returns an integer number indicating how much this item should protection against damage.
// # Can make use of "<context.level>" for the enchantment level, and "<context.cause>" for the applicable damage cause, using internal cause names.
// # Internal cause names: inFire, lightningBolt, onFire, lava, hotFloor, inWall, cramming, drown, starve, cactus, fall, flyIntoWall,
// # outOfWorld, generic, magic, wither, anvil, fallingBlock, dragonBreath, dryout, sweetBerryBush, freeze, fallingStalactite, stalagmite
// # If unspecified, will default to 0.
// # | Most enchantment scripts can exclude this key.
// damage_protection: 0
//
// # Triggered after an enchanted weapon is used to attack an entity.
// # Has <context.attacker> (EntityTag), <context.victim> (EntityTag), and <context.level>.
// # May fire rapidly or multiple times per hit. Use a ratelimit to prevent this.
// # | Some enchantment scripts should have this key.
// after attack:
// - narrate "You attacked <context.victim.name> with a <context.level> enchantment!"
//
// # Triggered after an enchanted armor is used to defend against an attack.
// # Has <context.attacker> (EntityTag), <context.victim> (EntityTag), and <context.level>.
// # | Some enchantment scripts should have this key.
// after hurt:
// - narrate "You got hurt by <context.attacker.name> and protected by a level <context.level> enchantment!"
// </code>
//
// -->
public static AsciiMatcher descriptionCharsAllowed = new AsciiMatcher("abcdefghijklmnopqrstuvwxyz" + "_");
public static HashMap<String, EnchantmentReference> registeredEnchantmentContainers = new HashMap<>();
public static class EnchantmentReference {
public EnchantmentScriptContainer script;
}
public EnchantmentScriptContainer(YamlConfiguration configurationSection, String scriptContainerName) {
super(configurationSection, scriptContainerName);
canRunScripts = false;
id = descriptionCharsAllowed.trimToMatches(CoreUtilities.toLowerCase(getString("id", scriptContainerName)));
descriptionId = "enchantment.denizen." + id;
minLevel = Integer.parseInt(getString("min_level", "1"));
maxLevel = Integer.parseInt(getString("max_level", "1"));
isTreasureOnly = CoreUtilities.toLowerCase(getString("treasure_only", "false")).equals("true");
isCurse = CoreUtilities.toLowerCase(getString("is_curse", "false")).equals("true");
isTradable = CoreUtilities.toLowerCase(getString("is_tradable", "true")).equals("true");
isDiscoverable = CoreUtilities.toLowerCase(getString("is_discoverable", "true")).equals("true");
rarity = getString("rarity", "COMMON").toUpperCase();
category = getString("category", "WEAPON").toUpperCase();
slots = getStringList("slots");
fullNameTaggable = getString("full_name", "");
canEnchantTaggable = getString("can_enchant", "true");
isCompatibleTaggable = getString("is_compatible", "true");
minCostTaggle = getString("min_cost", "1");
maxCostTaggable = getString("max_cost", "1");
damageBonusTaggable = getString("damage_bonus", "0.0");
damageProtectionTaggable = getString("damage_protection", "0");
EnchantmentReference ref = registeredEnchantmentContainers.get(id);
boolean isNew = ref == null;
if (isNew) {
ref = new EnchantmentReference();
}
ref.script = this;
registeredEnchantmentContainers.put(id, ref);
if (isNew) {
NMSHandler.getItemHelper().registerFakeEnchantment(ref);
}
}
public int minLevel, maxLevel;
public String id, rarity, category, descriptionId, fullNameTaggable, canEnchantTaggable, isCompatibleTaggable, minCostTaggle, maxCostTaggable, damageBonusTaggable, damageProtectionTaggable;
public boolean isTreasureOnly, isCurse, isTradable, isDiscoverable;
public List<String> slots;
public HashMap<Integer, BaseComponent[]> fullNamePerLevel = new HashMap<>();
public void validateThread() {
if (!Bukkit.isPrimaryThread()) {
try {
throw new RuntimeException("Stack reference");
}
catch (RuntimeException ex) {
Debug.echoError("Warning: enchantment access from wrong thread, errors will result");
Debug.echoError(ex);
}
}
}
public String autoTag(String value, ContextSource src) {
if (value == null) {
return null;
}
validateThread();
TagContext context = new BukkitTagContext(null, new ScriptTag(this));
context.contextSource = src;
return TagManager.tag(value, context);
}
public String autoTagForLevel(String value, int level) {
ContextSource.SimpleMap src = new ContextSource.SimpleMap();
src.contexts = new HashMap<>();
src.contexts.put("level", new ElementTag(level));
return autoTag(value, src);
}
public boolean canEnchant(ItemStack item) {
ContextSource.SimpleMap src = new ContextSource.SimpleMap();
src.contexts = new HashMap<>();
src.contexts.put("item", new ItemTag(item));
String res = autoTag(canEnchantTaggable, src);
return CoreUtilities.toLowerCase(res).equals("true");
}
public boolean isCompatible(Enchantment enchantment) {
ContextSource.SimpleMap src = new ContextSource.SimpleMap();
src.contexts = new HashMap<>();
src.contexts.put("enchantment_key", new ElementTag(enchantment.getKey().toString()));
String res = autoTag(isCompatibleTaggable, src);
return CoreUtilities.toLowerCase(res).equals("true");
}
public BaseComponent[] getFullName(int level) {
BaseComponent[] result = fullNamePerLevel.get(level);
if (result != null) {
return result;
}
String tagged = autoTagForLevel(fullNameTaggable, level);
result = FormattedTextHelper.parse(tagged, ChatColor.WHITE);
fullNamePerLevel.put(level, result);
return result;
}
public int getDamageProtection(int level, String causeName) {
ContextSource.SimpleMap src = new ContextSource.SimpleMap();
src.contexts = new HashMap<>();
src.contexts.put("level", new ElementTag(level));
src.contexts.put("cause", new ElementTag(causeName));
return Integer.parseInt(autoTag(damageProtectionTaggable, src));
}
public float getDamageBonus(int level, String type) {
ContextSource.SimpleMap src = new ContextSource.SimpleMap();
src.contexts = new HashMap<>();
src.contexts.put("level", new ElementTag(level));
src.contexts.put("type", new ElementTag(type));
return Float.parseFloat(autoTag(damageBonusTaggable, src));
}
public void runSubScript(String pathName, Entity attacker, Entity victim, Entity primary, int level) {
validateThread();
List<ScriptEntry> entries = getEntries(new BukkitScriptEntryData(new EntityTag(primary)), pathName);
InstantQueue queue = new InstantQueue(getName());
queue.addEntries(entries);
ContextSource.SimpleMap src = new ContextSource.SimpleMap();
src.contexts = new HashMap<>();
if (attacker != null) {
src.contexts.put("attacker", new EntityTag(attacker));
}
if (victim != null) {
src.contexts.put("victim", new EntityTag(victim));
}
src.contexts.put("level", new ElementTag(level));
queue.contextSource = src;
queue.start();
}
public void doPostAttack(Entity attacker, Entity victim, int level) {
runSubScript("after attack", attacker, victim, attacker, level);
}
public void doPostHurt(Entity victim, Entity attacker, int level) {
runSubScript("after hurt", attacker, victim, victim, level);
}
}