-
Notifications
You must be signed in to change notification settings - Fork 755
/
Modifier.java
432 lines (377 loc) · 13.9 KB
/
Modifier.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
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
package slimeknights.tconstruct.library.modifiers;
import com.google.gson.JsonObject;
import lombok.Getter;
import net.minecraft.ChatFormatting;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.TextColor;
import net.minecraft.server.packs.PackType;
import net.minecraft.tags.FluidTags;
import net.minecraft.tags.TagKey;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffectUtil;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.level.storage.loot.LootContext;
import slimeknights.mantle.client.ResourceColorManager;
import slimeknights.mantle.data.registry.GenericLoaderRegistry.IGenericLoader;
import slimeknights.mantle.data.registry.GenericLoaderRegistry.IHaveLoader;
import slimeknights.mantle.registration.object.IdAwareObject;
import slimeknights.tconstruct.common.TinkerTags;
import slimeknights.tconstruct.library.modifiers.ModifierManager.ModifierRegistrationEvent;
import slimeknights.tconstruct.library.modifiers.util.ModifierLevelDisplay;
import slimeknights.tconstruct.library.module.ModuleHook;
import slimeknights.tconstruct.library.module.ModuleHookMap;
import slimeknights.tconstruct.library.module.ModuleHookMap.Builder;
import slimeknights.tconstruct.library.tools.nbt.IToolStackView;
import slimeknights.tconstruct.library.tools.nbt.ToolStack;
import slimeknights.tconstruct.library.utils.Util;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Random;
/**
* Class representing both modifiers and traits. Acts as a storage container for {@link ModuleHook} modules, which are used to implement various modifier behaviors.
* @see ModifierHooks
* @see #registerHooks(Builder)
*/
@SuppressWarnings("unused")
public class Modifier implements IHaveLoader, IdAwareObject {
/** Default loader instance for a modifier with no properties */
public static final IGenericLoader<Modifier> DEFAULT_LOADER = new IGenericLoader<>() {
@Override
public Modifier deserialize(JsonObject json) {
return new Modifier();
}
@Override
public Modifier fromNetwork(FriendlyByteBuf buffer) {
return new Modifier();
}
@Override
public void serialize(Modifier object, JsonObject json) {
if (object.getClass() != Modifier.class) {
throw new IllegalStateException("Attempting to serialize a subclass of Modifier using the default modifier loader, this likely means the modifier did not override getLoader()");
}
}
@Override
public void toNetwork(Modifier object, FriendlyByteBuf buffer) {}
};
/** Modifier random instance, use for chance based effects */
protected static Random RANDOM = new Random();
/** Priority of modfiers by default */
public static final int DEFAULT_PRIORITY = 100;
/** Registry name of this modifier, null before fully registered */
private ModifierId id;
/** Cached key used for translations */
@Nullable
private String translationKey;
/** Cached text component for display names */
@Nullable
private Component displayName;
/** Cached text component for description */
@Nullable
protected List<Component> descriptionList;
/** Cached text component for description */
@Nullable
private Component description;
/** Map of all modifier hooks registered to this modifier */
@Getter
private final ModuleHookMap hooks;
/** Creates a new modifier using the given hook map */
protected Modifier(ModuleHookMap hooks) {
this.hooks = hooks;
}
/** Creates a new instance using the hook builder */
public Modifier() {
ModuleHookMap.Builder hookBuilder = ModuleHookMap.builder();
registerHooks(hookBuilder);
this.hooks = hookBuilder.build();
}
/**
* Registers a hook to the modifier.
* Note that this is run in the constructor, so you are unable to use any instance fields in this method unless initialized in this method.
* TODO 1.19: consider making abstract as everyone is going to need it in the future.
*/
protected void registerHooks(ModuleHookMap.Builder hookBuilder) {}
@Override
public IGenericLoader<? extends Modifier> getLoader() {
return DEFAULT_LOADER;
}
/**
* Override this method to make your modifier run earlier or later.
* Higher numbers run earlier, 100 is default
* @return Priority
*/
public int getPriority() {
return DEFAULT_PRIORITY;
}
/* Registry methods */
/** Sets the modifiers ID. Internal as ID is set through {@link ModifierRegistrationEvent} or the dynamic loader */
final void setId(ModifierId name) {
if (id != null) {
throw new IllegalStateException("Attempted to set registry name with existing registry name! New: " + name + " Old: " + id);
}
this.id = name;
}
@Override
public ModifierId getId() {
return Objects.requireNonNull(id, "Modifier has null registry name");
}
/** Checks if the modifier is in the given tag */
public final boolean is(TagKey<Modifier> tag) {
return ModifierManager.isInTag(this.getId(), tag);
}
/* Tooltips */
/**
* Called on pack reload to clear caches
* @param packType type of pack being reloaded
*/
public void clearCache(PackType packType) {
if (packType == PackType.CLIENT_RESOURCES) {
displayName = null;
}
}
/** Gets the color for this modifier */
public final TextColor getTextColor() {
return ResourceColorManager.getTextColor(getTranslationKey());
}
/** Gets the color for this modifier */
public final int getColor() {
return getTextColor().getValue();
}
/**
* Overridable method to create a translation key. Will be called once and the result cached
* @return Translation key
*/
protected String makeTranslationKey() {
return Util.makeTranslationKey("modifier", Objects.requireNonNull(id));
}
/**
* Gets the translation key for this modifier
* @return Translation key
*/
public final String getTranslationKey() {
if (translationKey == null) {
translationKey = makeTranslationKey();
}
return translationKey;
}
/**
* Overridable method to create the display name for this modifier, ideal to modify colors.
* TODO: this method does not really seem to do much, is it really needed? I feel like it was supposed to be called in {@link #getDisplayName()}, but it needs to be mutable for that.
* @return Display name
*/
protected Component makeDisplayName() {
return Component.translatable(getTranslationKey());
}
/**
* Applies relevant text styles (typically color) to the modifier text
* @param component Component to modifiy
* @return Resulting component
*/
public MutableComponent applyStyle(MutableComponent component) {
return component.withStyle(style -> style.withColor(getTextColor()));
}
/**
* Gets the display name for this modifier
* @return Display name for this modifier
*/
public Component getDisplayName() {
if (displayName == null) {
displayName = Component.translatable(getTranslationKey()).withStyle(style -> style.withColor(getTextColor()));
}
return displayName;
}
/**
* Gets the display name for the given level of this modifier
* @param level Modifier level
* @return Display name
*/
public Component getDisplayName(int level) {
return ModifierLevelDisplay.DEFAULT.nameForLevel(this, level);
}
/**
* Stack sensitive version of {@link #getDisplayName(int)}. Useful for displaying persistent data such as overslime or redstone amount
* @param tool Tool instance
* @param entry Tool level
* @return Stack sensitive display name
*/
public Component getDisplayName(IToolStackView tool, ModifierEntry entry) {
return entry.getDisplayName();
}
/**
* Gets the description for this modifier
* @return Description for this modifier
*/
public List<Component> getDescriptionList() {
if (descriptionList == null) {
descriptionList = Arrays.asList(
Component.translatable(getTranslationKey() + ".flavor").withStyle(ChatFormatting.ITALIC),
Component.translatable(getTranslationKey() + ".description"));
}
return descriptionList;
}
/**
* Gets the description for this modifier, sensitive to the tool
* @param level Modifier level
* @return Description for this modifier
*/
public List<Component> getDescriptionList(int level) {
return getDescriptionList();
}
/**
* Gets the description for this modifier, sensitive to the tool
* @param tool Tool containing this modifier
* @param entry Modifier level
* @return Description for this modifier
*/
public List<Component> getDescriptionList(IToolStackView tool, ModifierEntry entry) {
return getDescriptionList(entry.getLevel());
}
/** Converts a list of text components to a single text component, newline separated */
private static Component listToComponent(List<Component> list) {
if (list.isEmpty()) {
return Component.empty();
}
MutableComponent textComponent = Component.literal("");
Iterator<Component> iterator = list.iterator();
textComponent.append(iterator.next());
while (iterator.hasNext()) {
textComponent.append("\n");
textComponent.append(iterator.next());
}
return textComponent;
}
/**
* Gets the description for this modifier
* @return Description for this modifier
*/
public final Component getDescription() {
if (description == null) {
description = listToComponent(getDescriptionList());
}
return description;
}
/**
* Gets the description for this modifier
* @return Description for this modifier
*/
public final Component getDescription(int level) {
// if the method is not overridden, use the cached description component
List<Component> extendedDescription = getDescriptionList(level);
if (extendedDescription == getDescriptionList()) {
return getDescription();
}
return listToComponent(extendedDescription);
}
/**
* Gets the description for this modifier
* @return Description for this modifier
*/
public final Component getDescription(IToolStackView tool, ModifierEntry entry) {
// if the method is not overridden, use the cached description component
List<Component> extendedDescription = getDescriptionList(tool, entry);
if (extendedDescription == getDescriptionList()) {
return getDescription();
}
return listToComponent(extendedDescription);
}
/* General hooks */
/**
* Determines if the modifier should display
* @param advanced If true, in an advanced view such as the tinker station. False for tooltips
* @return True if the modifier should show
*/
public boolean shouldDisplay(boolean advanced) {
return true;
}
/* Hooks */
/**
* Called on entity or block loot to allow modifying loot
* @param tool Current tool instance
* @param modifier Modifier level
* @param generatedLoot Current loot list before this modifier
* @param context Full loot context
* TODO: can we ditch this hook in favor of just using GLMs? Just need a loot condition to detect a modifier, and it gives us a lot more flexability
*/
public void processLoot(IToolStackView tool, ModifierEntry modifier, List<ItemStack> generatedLoot, LootContext context) {}
/* Modules */
/**
* Gets a hook of this modifier. To modify the return values, use {@link #registerHooks(Builder)}
*
* @param hook Hook to fetch
* @param <T> Hook return type
* @return Submodule implementing the hook, or default instance if its not implemented
*/
public final <T> T getHook(ModuleHook<T> hook) {
return hooks.getOrDefault(hook);
}
@Override
public String toString() {
return "Modifier{" + id + '}';
}
/* Utils */
/**
* Gets the tool stack from the given entities mainhand. Useful for specialized event handling in modifiers
* @param living Entity instance
* @return Tool stack
*/
@Nullable
public static ToolStack getHeldTool(@Nullable LivingEntity living, InteractionHand hand) {
return getHeldTool(living, Util.getSlotType(hand));
}
/**
* Gets the tool stack from the given entities mainhand. Useful for specialized event handling in modifiers
* @param living Entity instance
* @return Tool stack
*/
@Nullable
public static ToolStack getHeldTool(@Nullable LivingEntity living, EquipmentSlot slot) {
if (living == null) {
return null;
}
ItemStack stack = living.getItemBySlot(slot);
if (stack.isEmpty() || !stack.is(TinkerTags.Items.MODIFIABLE)) {
return null;
}
ToolStack tool = ToolStack.from(stack);
return tool.isBroken() ? null : tool;
}
/**
* Gets the mining speed modifier for the current conditions, notably potions and armor enchants
* @param entity Entity to check
* @return Mining speed modifier
*/
public static float getMiningModifier(LivingEntity entity) {
float modifier = 1.0f;
// haste effect
if (MobEffectUtil.hasDigSpeed(entity)) {
modifier *= 1.0F + (MobEffectUtil.getDigSpeedAmplification(entity) + 1) * 0.2f;
}
// mining fatigue
MobEffectInstance miningFatigue = entity.getEffect(MobEffects.DIG_SLOWDOWN);
if (miningFatigue != null) {
switch (miningFatigue.getAmplifier()) {
case 0 -> modifier *= 0.3F;
case 1 -> modifier *= 0.09F;
case 2 -> modifier *= 0.0027F;
default -> modifier *= 8.1E-4F;
}
}
// water
if (entity.isEyeInFluid(FluidTags.WATER) && !EnchantmentHelper.hasAquaAffinity(entity)) {
modifier /= 5.0F;
}
if (!entity.isOnGround()) {
modifier /= 5.0F;
}
return modifier;
}
}