diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index c0e4ff164..6ad2f1c7b 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -2,10 +2,7 @@ package emu.grasscutter.data; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.binout.*; -import emu.grasscutter.data.binout.config.ConfigEntityAvatar; -import emu.grasscutter.data.binout.config.ConfigEntityGadget; -import emu.grasscutter.data.binout.config.ConfigEntityMonster; -import emu.grasscutter.data.binout.config.ConfigLevelEntity; +import emu.grasscutter.data.binout.config.*; import emu.grasscutter.data.binout.routes.Route; import emu.grasscutter.data.custom.TrialAvatarActivityCustomData; import emu.grasscutter.data.custom.TrialAvatarCustomData; @@ -41,6 +38,7 @@ import emu.grasscutter.data.excels.world.WorldAreaData; import emu.grasscutter.data.excels.world.WorldLevelData; import emu.grasscutter.data.server.ActivityCondGroup; import emu.grasscutter.data.server.GadgetMapping; +import emu.grasscutter.data.server.MonsterMapping; import emu.grasscutter.game.dungeons.DungeonDropEntry; import emu.grasscutter.game.quest.QuestEncryptionKey; import emu.grasscutter.game.quest.RewindData; @@ -49,11 +47,13 @@ import emu.grasscutter.game.quest.enums.QuestCond; import emu.grasscutter.game.world.GroupReplacementData; import emu.grasscutter.utils.Utils; import it.unimi.dsi.fastutil.ints.*; +import lombok.Getter; +import lombok.Setter; +import lombok.val; + +import javax.annotation.Nullable; import java.lang.reflect.Field; import java.util.*; -import javax.annotation.Nullable; -import lombok.Getter; -import lombok.val; @SuppressWarnings({"unused", "MismatchedQueryAndUpdateOfCollection"}) public final class GameData { @@ -70,10 +70,7 @@ public final class GameData { @Getter private static final Int2ObjectMap abilityHashes = new Int2ObjectOpenHashMap<>(); - @Deprecated(forRemoval = true) - @Getter - private static final Map abilityModifiers = new HashMap<>(); - + @Getter private static final Map> talents = new HashMap<>(); @Getter private static final Map avatarConfigData = new HashMap<>(); @Getter private static final Map gadgetConfigData = new HashMap<>(); @Getter private static final Map monsterConfigData = new HashMap<>(); @@ -246,6 +243,8 @@ public final class GameData { private static final Int2ObjectMap equipAffixDataMap = new Int2ObjectOpenHashMap<>(); + @Getter private static final Int2ObjectMap monsterAffixDataMap = new Int2ObjectOpenHashMap<>(); + @Getter private static final Int2ObjectMap fetterCharacterCardDataMap = new Int2ObjectOpenHashMap<>(); @@ -440,6 +439,9 @@ public final class GameData { private static final Int2ObjectMap codexWeaponDataMap = new Int2ObjectOpenHashMap<>(); + @Getter @Setter + private static ConfigGlobalCombat configGlobalCombat = null; + // Custom community server resources @Getter private static final Int2ObjectMap> dungeonDropDataMap = @@ -449,6 +451,9 @@ public final class GameData { private static final Int2ObjectMap gadgetMappingMap = new Int2ObjectOpenHashMap<>(); + @Getter private static final Int2ObjectMap monsterMappingMap = + new Int2ObjectOpenHashMap<>(); + @Getter private static final Int2ObjectMap activityCondGroupMap = new Int2ObjectOpenHashMap<>(); diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index 42b1f82ca..78b91f43d 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -1,10 +1,7 @@ package emu.grasscutter.data; -import static emu.grasscutter.utils.FileUtils.getDataPath; -import static emu.grasscutter.utils.FileUtils.getResourcePath; -import static emu.grasscutter.utils.lang.Language.translate; - import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.binout.*; import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; @@ -15,6 +12,7 @@ import emu.grasscutter.data.custom.TrialAvatarCustomData; import emu.grasscutter.data.excels.trial.TrialAvatarActivityDataData; import emu.grasscutter.data.server.ActivityCondGroup; import emu.grasscutter.data.server.GadgetMapping; +import emu.grasscutter.data.server.MonsterMapping; import emu.grasscutter.game.managers.blossom.BlossomConfig; import emu.grasscutter.game.quest.QuestEncryptionKey; import emu.grasscutter.game.quest.RewindData; @@ -29,10 +27,17 @@ import emu.grasscutter.scripts.ScriptLoader; import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.JsonUtils; import emu.grasscutter.utils.TsvUtils; +import emu.grasscutter.utils.Utils; import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntArraySet; +import lombok.SneakyThrows; +import lombok.val; +import org.reflections.Reflections; + +import javax.script.Bindings; +import javax.script.CompiledScript; import java.io.IOException; import java.io.InputStreamReader; import java.nio.file.Files; @@ -44,11 +49,10 @@ import java.util.concurrent.CopyOnWriteArraySet; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.script.Bindings; -import javax.script.CompiledScript; -import lombok.SneakyThrows; -import lombok.val; -import org.reflections.Reflections; + +import static emu.grasscutter.utils.FileUtils.getDataPath; +import static emu.grasscutter.utils.FileUtils.getResourcePath; +import static emu.grasscutter.utils.lang.Language.translate; public final class ResourceLoader { @@ -108,6 +112,7 @@ public final class ResourceLoader { loadConfigData(); // Load ability lists loadAbilityEmbryos(); + loadTalents(); loadOpenConfig(); loadAbilityModifiers(); // Load resources @@ -130,9 +135,11 @@ public final class ResourceLoader { loadConfigLevelEntityData(); loadQuestShareConfig(); loadGadgetMappings(); + loadMonsterMappings(); loadActivityCondGroups(); loadGroupReplacements(); loadTrialAvatarCustomData(); + loadGlobalCombatConfig(); EntityControllerScriptManager.load(); @@ -223,6 +230,14 @@ public final class ResourceLoader { }); } + private static void loadGlobalCombatConfig(){ + try { + GameData.setConfigGlobalCombat(JsonUtils.loadToClass(getResourcePath("BinOutput/Common/ConfigGlobalCombat.json"), ConfigGlobalCombat.class)); + } catch (IOException e) { + Grasscutter.getLogger().error("Cannot load ConfigGlobalCombat.json, this error is important, fix it!"); + } + } + private static void loadScenePoints() { val pattern = Pattern.compile("scene([0-9]+)_point\\.json"); try { @@ -384,12 +399,13 @@ public final class ResourceLoader { private static void loadAbilityData(AbilityData data) { GameData.getAbilityDataMap().put(data.abilityName, data); + GameData.getAbilityHashes().put(Utils.abilityHash(data.abilityName), data.abilityName); - val modifiers = data.modifiers; + var modifiers = data.modifiers; if (modifiers == null || modifiers.size() == 0) return; - String name = data.abilityName; - AbilityModifierEntry modifierEntry = new AbilityModifierEntry(name); + var name = data.abilityName; + var modifierEntry = new AbilityModifierEntry(name); modifiers.forEach( (key, modifier) -> { Stream.ofNullable(modifier.onAdded) @@ -408,8 +424,29 @@ public final class ResourceLoader { .filter(action -> action.type == AbilityModifierAction.Type.HealHP) .forEach(action -> modifierEntry.getOnRemoved().add(action)); }); + } - GameData.getAbilityModifiers().put(name, modifierEntry); + private static void loadTalents() { + // Load from BinOutput + try (var paths = Files.walk(getResourcePath("BinOutput/Talent/AvatarTalents/"))) { + paths.filter(Files::isDirectory).forEach((folderPath) -> { + try (var paths2 = Files.walk(folderPath)) { + paths2.filter(Files::isRegularFile).filter(path -> path.toString().endsWith(".json")).forEach(ResourceLoader::loadTalent); + } catch (IOException e) { + Grasscutter.getLogger().error("Error loading talents: ", e); + } + }); + } catch (IOException e) { + Grasscutter.getLogger().error("Error loading talents: ", e); + } + } + + private static void loadTalent(Path path) { + try { + GameData.getTalents().putAll(JsonUtils.loadToMap(path, String.class, new TypeToken>() {}.getType())); + } catch (IOException e) { + Grasscutter.getLogger().error("Error loading ability modifiers from path " + path.toString() + ": ", e); + } } private static void loadSpawnData() { @@ -773,6 +810,20 @@ public final class ResourceLoader { } } + private static void loadMonsterMappings() { + try { + var monsterMap = GameData.getMonsterMappingMap(); + try { + JsonUtils.loadToList(getResourcePath("Server/MonsterMapping.json"), MonsterMapping.class) + .forEach(entry -> monsterMap.put(entry.getMonsterId(), entry)); + } catch (IOException | NullPointerException ignored) {} + + Grasscutter.getLogger().debug("Loaded {} monster mappings.", monsterMap.size()); + } catch (Exception e) { + Grasscutter.getLogger().error("Unable to load monster mappings.", e); + } + } + private static void loadActivityCondGroups() { try { val gadgetMap = GameData.getActivityCondGroupMap(); @@ -914,7 +965,7 @@ public final class ResourceLoader { public int pointDelta; } - public class ScenePointConfig { // Sadly this doesn't work as a local class in loadScenePoints() + public static class ScenePointConfig { // Sadly this doesn't work as a local class in loadScenePoints() public Map points; } } diff --git a/src/main/java/emu/grasscutter/data/binout/AbilityData.java b/src/main/java/emu/grasscutter/data/binout/AbilityData.java index 9674e3d37..0cdb5f2b3 100644 --- a/src/main/java/emu/grasscutter/data/binout/AbilityData.java +++ b/src/main/java/emu/grasscutter/data/binout/AbilityData.java @@ -1,9 +1,9 @@ package emu.grasscutter.data.binout; -import static emu.grasscutter.game.ability.AbilityLocalIdGenerator.*; - import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; import emu.grasscutter.game.ability.AbilityLocalIdGenerator; +import emu.grasscutter.game.ability.AbilityLocalIdGenerator.ConfigAbilitySubContainerType; + import java.util.HashMap; import java.util.Map; @@ -12,13 +12,26 @@ public class AbilityData { public Map modifiers; public boolean isDynamicAbility; public Map abilitySpecials; + public AbilityModifierAction[] onAdded; + public AbilityModifierAction[] onRemoved; + public AbilityModifierAction[] onAbilityStart; + public AbilityModifierAction[] onKill; + public AbilityModifierAction[] onFieldEnter; + public AbilityModifierAction[] onExit; + public AbilityModifierAction[] onAttach; + public AbilityModifierAction[] onDetach; + public AbilityModifierAction[] onAvatarIn; + public AbilityModifierAction[] onAvatarOut; + public AbilityModifierAction[] onTriggerAvatarRay; + public AbilityModifierAction[] onVehicleIn; + public AbilityModifierAction[] onVehicleOut; // abilityMixins - // onAbilityStart - // onKill + public AbilityMixinData[] abilityMixins; public final Map localIdToAction = new HashMap<>(); + public final Map localIdToMixin = new HashMap<>(); private boolean _initialized = false; @@ -26,48 +39,88 @@ public class AbilityData { if (_initialized) return; _initialized = true; - if (modifiers == null) return; + initializeMixins(); + initializeModifiers(); + initializeActions(); + } + + private void initializeActions() { + AbilityLocalIdGenerator generator = new AbilityLocalIdGenerator(ConfigAbilitySubContainerType.ACTION); + generator.configIndex = 0; + + generator.initializeActionLocalIds(onAdded, localIdToAction); + generator.configIndex++; + generator.initializeActionLocalIds(onRemoved, localIdToAction); + generator.configIndex++; + generator.initializeActionLocalIds(onAbilityStart, localIdToAction); + generator.configIndex++; + generator.initializeActionLocalIds(onKill, localIdToAction); + generator.configIndex++; + generator.initializeActionLocalIds(onFieldEnter, localIdToAction); + generator.configIndex++; + generator.initializeActionLocalIds(onExit, localIdToAction); + generator.configIndex++; + generator.initializeActionLocalIds(onAttach, localIdToAction); + generator.configIndex++; + generator.initializeActionLocalIds(onDetach, localIdToAction); + generator.configIndex++; + generator.initializeActionLocalIds(onAvatarIn, localIdToAction); + generator.configIndex++; + generator.initializeActionLocalIds(onAvatarOut, localIdToAction); + generator.configIndex++; + generator.initializeActionLocalIds(onTriggerAvatarRay, localIdToAction); + generator.configIndex++; + generator.initializeActionLocalIds(onVehicleIn, localIdToAction); + generator.configIndex++; + generator.initializeActionLocalIds(onVehicleOut, localIdToAction); + } + + private void initializeMixins() { + if(abilityMixins != null) { + AbilityLocalIdGenerator generator = new AbilityLocalIdGenerator(ConfigAbilitySubContainerType.MIXIN); + generator.modifierIndex = 0; + generator.configIndex = 0; + + generator.initializeMixinsLocalIds(abilityMixins, localIdToMixin); + } + } + + private void initializeModifiers() { + if(modifiers == null) return; + + var _modifiers = modifiers.entrySet().stream() + .sorted(Map.Entry.comparingByKey()).map(Map.Entry::getValue) + .toList(); - var _modifiers = modifiers.values().toArray(new AbilityModifier[0]); var modifierIndex = 0; - for (var modifier : _modifiers) { + for (AbilityModifier abilityModifier : _modifiers) { long configIndex = 0L; - this.initializeActionSubCategory( - modifierIndex, configIndex++, modifier.onAdded, localIdToAction); - this.initializeActionSubCategory( - modifierIndex, configIndex++, modifier.onRemoved, localIdToAction); - this.initializeActionSubCategory( - modifierIndex, configIndex++, modifier.onBeingHit, localIdToAction); - this.initializeActionSubCategory( - modifierIndex, configIndex++, modifier.onAttackLanded, localIdToAction); - this.initializeActionSubCategory( - modifierIndex, configIndex++, modifier.onHittingOther, localIdToAction); - this.initializeActionSubCategory( - modifierIndex, configIndex++, modifier.onThinkInterval, localIdToAction); - this.initializeActionSubCategory( - modifierIndex, configIndex++, modifier.onKill, localIdToAction); - this.initializeActionSubCategory( - modifierIndex, configIndex++, modifier.onCrash, localIdToAction); - this.initializeActionSubCategory( - modifierIndex, configIndex++, modifier.onAvatarIn, localIdToAction); - this.initializeActionSubCategory( - modifierIndex, configIndex++, modifier.onAvatarOut, localIdToAction); - this.initializeActionSubCategory( - modifierIndex, configIndex++, modifier.onReconnect, localIdToAction); - this.initializeActionSubCategory( - modifierIndex, configIndex++, modifier.onChangeAuthority, localIdToAction); - this.initializeActionSubCategory( - modifierIndex, configIndex++, modifier.onVehicleIn, localIdToAction); - this.initializeActionSubCategory( - modifierIndex, configIndex++, modifier.onVehicleOut, localIdToAction); - this.initializeActionSubCategory( - modifierIndex, configIndex++, modifier.onZoneEnter, localIdToAction); - this.initializeActionSubCategory( - modifierIndex, configIndex++, modifier.onZoneExit, localIdToAction); - this.initializeActionSubCategory( - modifierIndex, configIndex++, modifier.onHeal, localIdToAction); - this.initializeActionSubCategory( - modifierIndex, configIndex++, modifier.onBeingHealed, localIdToAction); + this.initializeActionSubCategory(modifierIndex, configIndex++, abilityModifier.onAdded, localIdToAction); + this.initializeActionSubCategory(modifierIndex, configIndex++, abilityModifier.onRemoved, localIdToAction); + this.initializeActionSubCategory(modifierIndex, configIndex++, abilityModifier.onBeingHit, localIdToAction); + this.initializeActionSubCategory(modifierIndex, configIndex++, abilityModifier.onAttackLanded, localIdToAction); + this.initializeActionSubCategory(modifierIndex, configIndex++, abilityModifier.onHittingOther, localIdToAction); + this.initializeActionSubCategory(modifierIndex, configIndex++, abilityModifier.onThinkInterval, localIdToAction); + this.initializeActionSubCategory(modifierIndex, configIndex++, abilityModifier.onKill, localIdToAction); + this.initializeActionSubCategory(modifierIndex, configIndex++, abilityModifier.onCrash, localIdToAction); + this.initializeActionSubCategory(modifierIndex, configIndex++, abilityModifier.onAvatarIn, localIdToAction); + this.initializeActionSubCategory(modifierIndex, configIndex++, abilityModifier.onAvatarOut, localIdToAction); + this.initializeActionSubCategory(modifierIndex, configIndex++, abilityModifier.onReconnect, localIdToAction); + this.initializeActionSubCategory(modifierIndex, configIndex++, abilityModifier.onChangeAuthority, localIdToAction); + this.initializeActionSubCategory(modifierIndex, configIndex++, abilityModifier.onVehicleIn, localIdToAction); + this.initializeActionSubCategory(modifierIndex, configIndex++, abilityModifier.onVehicleOut, localIdToAction); + this.initializeActionSubCategory(modifierIndex, configIndex++, abilityModifier.onZoneEnter, localIdToAction); + this.initializeActionSubCategory(modifierIndex, configIndex++, abilityModifier.onZoneExit, localIdToAction); + this.initializeActionSubCategory(modifierIndex, configIndex++, abilityModifier.onHeal, localIdToAction); + this.initializeActionSubCategory(modifierIndex, configIndex++, abilityModifier.onBeingHealed, localIdToAction); + + if (abilityModifier.modifierMixins != null) { + var generator = new AbilityLocalIdGenerator(ConfigAbilitySubContainerType.MODIFIER_MIXIN); + generator.modifierIndex = modifierIndex; + generator.configIndex = 0; + + generator.initializeMixinsLocalIds(abilityModifier.modifierMixins, localIdToMixin); + } modifierIndex++; } diff --git a/src/main/java/emu/grasscutter/data/binout/AbilityMixinData.java b/src/main/java/emu/grasscutter/data/binout/AbilityMixinData.java new file mode 100644 index 000000000..6eb290979 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/binout/AbilityMixinData.java @@ -0,0 +1,41 @@ +package emu.grasscutter.data.binout; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.google.gson.Gson; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; + +public class AbilityMixinData implements Serializable { + private static final long serialVersionUID = -2001232313615923575L; + + public enum Type { + AttachToGadgetStateMixin, AttachToStateIDMixin, ShieldBarMixin, TileAttackManagerMixin; + } + @SerializedName("$type") + public Type type; + + private JsonElement modifierName; + + public List getModifierNames() { + if(modifierName.isJsonArray()) { + java.lang.reflect.Type listType = (new TypeToken>() { + }).getType(); + List list = (new Gson()).fromJson(modifierName, listType); + return list; + } else { + return Arrays.asList(modifierName.getAsString()); + } + } + +} diff --git a/src/main/java/emu/grasscutter/data/binout/AbilityModifier.java b/src/main/java/emu/grasscutter/data/binout/AbilityModifier.java index 39a6b2507..53de7490b 100644 --- a/src/main/java/emu/grasscutter/data/binout/AbilityModifier.java +++ b/src/main/java/emu/grasscutter/data/binout/AbilityModifier.java @@ -35,8 +35,11 @@ public class AbilityModifier implements Serializable { public AbilityModifierAction[] onHeal; public AbilityModifierAction[] onBeingHealed; public DynamicFloat duration = DynamicFloat.ZERO; + public DynamicFloat thinkInterval = DynamicFloat.ZERO; public String stacking; + public AbilityMixinData[] modifierMixins; + public ElementType elementType; public DynamicFloat elementDurability = DynamicFloat.ZERO; @@ -264,21 +267,52 @@ public class AbilityModifier implements Serializable { public String target; - @SerializedName(value = "amount", alternate = "PDLLIFICICJ") + @SerializedName(value = "amount", alternate = {"PDLLIFICICJ", "cdRatio"}) public DynamicFloat amount = DynamicFloat.ZERO; + @SerializedName(value = "amountByTargetCurrentHPRatio") public DynamicFloat amountByCasterAttackRatio = DynamicFloat.ZERO; + + @SerializedName(value = "unused") public DynamicFloat amountByCasterCurrentHPRatio = DynamicFloat.ZERO; + + @SerializedName(value = "unknown", alternate = {"HFNJHOGGFKB", "GEJGGCIOLKN"}) public DynamicFloat amountByCasterMaxHPRatio = DynamicFloat.ZERO; + public DynamicFloat amountByGetDamage = DynamicFloat.ZERO; + + @SerializedName(value = "amountByTargetMaxHPRatio") public DynamicFloat amountByTargetCurrentHPRatio = DynamicFloat.ZERO; + + @SerializedName(value = "amountByCasterMaxHPRatio") public DynamicFloat amountByTargetMaxHPRatio = DynamicFloat.ZERO; + public DynamicFloat limboByTargetMaxHPRatio = DynamicFloat.ZERO; + + public DynamicFloat healRatio = DynamicFloat.ONE; + @SerializedName(value = "ignoreAbilityProperty", alternate = "HHFGADCJJDI") public boolean ignoreAbilityProperty; public String modifierName; + public boolean enableLockHP; + public boolean disableWhenLoading; + public boolean lethal = true; + + public boolean muteHealEffect = false; + + public boolean byServer; + public boolean lifeByOwnerIsAlive; + public String campTargetType; + public int campID; + public int gadgetID; + public boolean ownerIsTarget; + + public boolean isFromOwner; + public String globalValueKey; + public String abilityFormula; + public int param1; public int param2; public int param3; diff --git a/src/main/java/emu/grasscutter/data/binout/TalentData.java b/src/main/java/emu/grasscutter/data/binout/TalentData.java new file mode 100644 index 000000000..f5514a688 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/binout/TalentData.java @@ -0,0 +1,23 @@ +package emu.grasscutter.data.binout; + +import java.io.Serializable; + +import com.google.gson.annotations.SerializedName; + +import emu.grasscutter.data.common.DynamicFloat; + +public class TalentData implements Serializable { + public enum Type { + AddAbility, ModifySkillCD, UnlockTalentParam, AddTalentExtraLevel, ModifyAbility; + } + @SerializedName("$type") + public Type type; + public String abilityName; + public String talentParam; + public int talentIndex; + public int extraLevel; + + public String paramSpecial; + public DynamicFloat paramDelta; + public DynamicFloat paramRatio = new DynamicFloat(1.0f); +} diff --git a/src/main/java/emu/grasscutter/data/binout/config/ConfigGlobalCombat.java b/src/main/java/emu/grasscutter/data/binout/config/ConfigGlobalCombat.java new file mode 100644 index 000000000..3a33941b8 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/binout/config/ConfigGlobalCombat.java @@ -0,0 +1,24 @@ +package emu.grasscutter.data.binout.config; + +import java.util.List; + +import lombok.Data; + +@Data +public class ConfigGlobalCombat { + private DefaultAbilities defaultAbilities; + //TODO: Add more indices + + @Data + public class DefaultAbilities { + private String monterEliteAbilityName; + private List nonHumanoidMoveAbilities; + private List levelDefaultAbilities; + private List levelElementAbilities; + private List levelItemAbilities; + private List levelSBuffAbilities; + private List defaultMPLevelAbilities; + private List defaultAvatarAbilities; + private List defaultTeamAbilities; + } +} diff --git a/src/main/java/emu/grasscutter/data/binout/config/ConfigLevelEntity.java b/src/main/java/emu/grasscutter/data/binout/config/ConfigLevelEntity.java index 78a1ff2c8..e22f22014 100644 --- a/src/main/java/emu/grasscutter/data/binout/config/ConfigLevelEntity.java +++ b/src/main/java/emu/grasscutter/data/binout/config/ConfigLevelEntity.java @@ -1,12 +1,14 @@ package emu.grasscutter.data.binout.config; -import emu.grasscutter.data.binout.config.fields.ConfigAbilityData; import java.util.List; + +import emu.grasscutter.data.binout.config.fields.ConfigAbilityData; import lombok.Getter; public class ConfigLevelEntity { - @Getter private List abilities; // monster abilities + @Getter private List abilities; + @Getter private List monsterAbilities; @Getter private List avatarAbilities; @Getter private List teamAbilities; @Getter private List preloadMonsterEntityIDs; diff --git a/src/main/java/emu/grasscutter/data/common/DynamicFloat.java b/src/main/java/emu/grasscutter/data/common/DynamicFloat.java index 4b94ac5c4..1393048bf 100644 --- a/src/main/java/emu/grasscutter/data/common/DynamicFloat.java +++ b/src/main/java/emu/grasscutter/data/common/DynamicFloat.java @@ -1,14 +1,22 @@ package emu.grasscutter.data.common; +import emu.grasscutter.data.excels.ProudSkillData; +import emu.grasscutter.game.ability.Ability; import it.unimi.dsi.fastutil.floats.FloatArrayList; import it.unimi.dsi.fastutil.objects.Object2FloatArrayMap; import it.unimi.dsi.fastutil.objects.Object2FloatMap; -import java.util.List; -import java.util.Optional; +import lombok.Getter; import lombok.val; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; + +@Getter public class DynamicFloat { public static DynamicFloat ZERO = new DynamicFloat(0f); + public static DynamicFloat ONE = new DynamicFloat(1f); + private List ops; private boolean dynamic = false; private float constant = 0f; @@ -33,23 +41,35 @@ public class DynamicFloat { } public String toString(boolean nextBoolean) { - String key = String.valueOf(nextBoolean); + var key = String.valueOf(nextBoolean); this.ops = List.of(new StackOp(key)); return ops.toString(); } public float get() { - return this.get(new Object2FloatArrayMap()); + return this.get(new Object2FloatArrayMap<>(), 0); } - public float get(Object2FloatMap props) { - if (!dynamic) return constant; + public float get(float defaultValue) { + return this.get(new Object2FloatArrayMap<>(), defaultValue); + } + + public float get(Ability ability, float defaultValue) { + return this.get(ability.getAbilitySpecials(), defaultValue); + } + + public float get(Ability ability) { + return this.get(ability.getAbilitySpecials(), 0f); + } + + public float get(Object2FloatMap props, float defaultValue) { + if (!this.dynamic) return constant; val fl = new FloatArrayList(); for (var op : this.ops) { switch (op.op) { case CONSTANT -> fl.push(op.fValue); - case KEY -> fl.push(props.getOrDefault(op.sValue, 0f)); + case KEY -> fl.push(props.getOrDefault(op.sValue, 0f) * (op.negative ? -1 : 1)); case ADD -> fl.push(fl.popFloat() + fl.popFloat()); case SUB -> fl.push( -fl.popFloat() + fl.popFloat()); // [f0, f1, f2] -> [f0, f1-f2] (opposite of RPN order) @@ -59,7 +79,21 @@ public class DynamicFloat { } } - return fl.popFloat(); // well-formed data will always have only one value left at this point + try { + return fl.popFloat(); // well-formed data will always have only one value left at this point + } catch(NoSuchElementException e) { + return defaultValue; + } + } + + public float get(ProudSkillData skill) { + //Construct the map + return get(skill.getParamListMap(), 0f); + } + + public float get(ProudSkillData skill, float defaultValue) { + //Construct the map + return get(skill.getParamListMap(), defaultValue); } public static class StackOp { @@ -68,6 +102,7 @@ public class DynamicFloat { public float fValue; public String sValue; public boolean bValue; + public boolean negative = false; public StackOp(String s) { switch (s.toUpperCase()) { @@ -76,6 +111,12 @@ public class DynamicFloat { case "MUL" -> this.op = Op.MUL; case "DIV" -> this.op = Op.DIV; default -> { + if (s.startsWith("%")) { + s = s.substring(1); + } else if (s.startsWith("-%")) { + s = s.substring(2); + } + this.op = Op.KEY; this.sValue = s; } diff --git a/src/main/java/emu/grasscutter/data/excels/MonsterAffixData.java b/src/main/java/emu/grasscutter/data/excels/MonsterAffixData.java new file mode 100644 index 000000000..94e6bf2a6 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/excels/MonsterAffixData.java @@ -0,0 +1,26 @@ +package emu.grasscutter.data.excels; + +import java.util.ArrayList; +import emu.grasscutter.data.GameResource; +import emu.grasscutter.data.ResourceType; +import emu.grasscutter.data.common.FightPropData; +import lombok.Getter; + +@ResourceType(name = "MonsterAffixExcelConfigData.json") +public class MonsterAffixData extends GameResource { + + private int id; + @Getter private String affix; + @Getter private String comment; + @Getter private String[] abilityName; //Declared as list but used as single element + @Getter private boolean isCommon; + @Getter private boolean preAdd; + @Getter public String isLegal; + @Getter public String iconPath; + @Getter public String generalSkillIcon; + + @Override + public int getId() { + return id; + } +} diff --git a/src/main/java/emu/grasscutter/data/excels/ProudSkillData.java b/src/main/java/emu/grasscutter/data/excels/ProudSkillData.java index 0501cc411..25a53e819 100644 --- a/src/main/java/emu/grasscutter/data/excels/ProudSkillData.java +++ b/src/main/java/emu/grasscutter/data/excels/ProudSkillData.java @@ -5,9 +5,12 @@ import emu.grasscutter.data.GameResource; import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.common.FightPropData; import emu.grasscutter.data.common.ItemParamData; +import it.unimi.dsi.fastutil.objects.Object2FloatMap; +import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap; +import lombok.Getter; + import java.util.ArrayList; import java.util.List; -import lombok.Getter; @ResourceType(name = "ProudSkillExcelConfigData.json") public class ProudSkillData extends GameResource { @@ -27,6 +30,8 @@ public class ProudSkillData extends GameResource { @Getter private long nameTextMapHash; @Transient private Iterable totalCostItems; + @Transient @Getter private Object2FloatMap paramListMap = new Object2FloatOpenHashMap<>(); + @Override public int getId() { return proudSkillId; @@ -34,8 +39,10 @@ public class ProudSkillData extends GameResource { public Iterable getTotalCostItems() { if (this.totalCostItems == null) { - ArrayList total = - (this.costItems != null) ? new ArrayList<>(this.costItems) : new ArrayList<>(1); + List total = + (this.costItems != null) ? + new ArrayList<>(this.costItems) : + new ArrayList<>(1); if (this.coinCost > 0) total.add(new ItemParamData(202, this.coinCost)); this.totalCostItems = total; } @@ -45,13 +52,18 @@ public class ProudSkillData extends GameResource { @Override public void onLoad() { // Fight props - ArrayList parsed = new ArrayList(getAddProps().length); - for (FightPropData prop : getAddProps()) { + var parsed = new ArrayList(getAddProps().length); + for (var prop : getAddProps()) { if (prop.getPropType() != null && prop.getValue() != 0f) { prop.onLoad(); parsed.add(prop); } } - this.addProps = parsed.toArray(new FightPropData[parsed.size()]); + + this.addProps = parsed.toArray(new FightPropData[0]); + + for (int i = 0; i < paramList.length; i++) { + this.paramListMap.put(Integer.toString(i + 1), paramList[i]); + } } } diff --git a/src/main/java/emu/grasscutter/data/excels/avatar/AvatarData.java b/src/main/java/emu/grasscutter/data/excels/avatar/AvatarData.java index be4bc6c4b..70bd76951 100644 --- a/src/main/java/emu/grasscutter/data/excels/avatar/AvatarData.java +++ b/src/main/java/emu/grasscutter/data/excels/avatar/AvatarData.java @@ -14,40 +14,27 @@ import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; import lombok.Getter; +import java.util.ArrayList; import java.util.List; @ResourceType(name = "AvatarExcelConfigData.json", loadPriority = LoadPriority.LOW) public class AvatarData extends GameResource { private String iconName; - @Getter - private String bodyType; - @Getter - private String qualityType; - @Getter - private int chargeEfficiency; - @Getter - private int initialWeapon; - @Getter - private WeaponType weaponType; - @Getter - private String imageName; - @Getter - private int avatarPromoteId; - @Getter - private String cutsceneShow; - @Getter - private int skillDepotId; - @Getter - private int staminaRecoverSpeed; - @Getter - private List candSkillDepotIds; - @Getter - private String avatarIdentityType; - @Getter - private List avatarPromoteRewardLevelList; - @Getter - private List avatarPromoteRewardIdList; + @Getter private String bodyType; + @Getter private String qualityType; + @Getter private int chargeEfficiency; + @Getter private int initialWeapon; + @Getter private WeaponType weaponType; + @Getter private String imageName; + @Getter private int avatarPromoteId; + @Getter private String cutsceneShow; + @Getter private int skillDepotId; + @Getter private int staminaRecoverSpeed; + @Getter private List candSkillDepotIds; + @Getter private String avatarIdentityType; + @Getter private List avatarPromoteRewardLevelList; + @Getter private List avatarPromoteRewardIdList; @Getter private long nameTextMapHash; @@ -74,6 +61,8 @@ public class AvatarData extends GameResource { private AvatarSkillDepotData skillDepot; @Getter private IntList abilities; + @Getter + private List abilitieNames = new ArrayList<>(); @Getter private List fetters; @@ -187,8 +176,10 @@ public class AvatarData extends GameResource { var info = GameData.getAbilityEmbryoInfo().get(this.name); if (info != null) { this.abilities = new IntArrayList(info.getAbilities().length); - for (var ability : info.getAbilities()) + for (var ability : info.getAbilities()) { this.abilities.add(Utils.abilityHash(ability)); + this.abilitieNames.add(ability); + } } } } diff --git a/src/main/java/emu/grasscutter/data/server/MonsterMapping.java b/src/main/java/emu/grasscutter/data/server/MonsterMapping.java new file mode 100644 index 000000000..4682a12ce --- /dev/null +++ b/src/main/java/emu/grasscutter/data/server/MonsterMapping.java @@ -0,0 +1,9 @@ +package emu.grasscutter.data.server; + +import lombok.Data; + +@Data +public class MonsterMapping { + private int monsterId; + private String monsterJson; +} diff --git a/src/main/java/emu/grasscutter/game/ability/Ability.java b/src/main/java/emu/grasscutter/game/ability/Ability.java index b77865c9e..4480a6597 100644 --- a/src/main/java/emu/grasscutter/game/ability/Ability.java +++ b/src/main/java/emu/grasscutter/game/ability/Ability.java @@ -1,45 +1,58 @@ package emu.grasscutter.game.ability; -import emu.grasscutter.data.binout.AbilityData; -import emu.grasscutter.game.entity.GameEntity; -import emu.grasscutter.server.event.entity.EntityDamageEvent; -import emu.grasscutter.utils.Utils; import java.util.HashMap; import java.util.Map; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.binout.AbilityData; +import emu.grasscutter.game.entity.GameEntity; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.proto.AbilityStringOuterClass.AbilityString; +import emu.grasscutter.utils.Utils; +import it.unimi.dsi.fastutil.objects.Object2FloatMap; +import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap; import lombok.Getter; -public final class Ability { +public class Ability { @Getter private AbilityData data; @Getter private GameEntity owner; + @Getter private Player playerOwner; @Getter private AbilityManager manager; + @Getter private Map modifiers = new HashMap<>(); + @Getter private Object2FloatMap abilitySpecials = new Object2FloatOpenHashMap<>(); + + @Getter private static Map> abilitySpecialsModified = new HashMap<>(); @Getter private int hash; - public Ability(AbilityData data, GameEntity owner) { + public Ability(AbilityData data, GameEntity owner, Player playerOwner) { this.data = data; this.owner = owner; - this.manager = owner.getScene().getWorld().getHost().getAbilityManager(); - this.hash = Utils.abilityHash(data.abilityName); + this.manager = owner.getWorld().getHost().getAbilityManager(); + + if (this.data.abilitySpecials != null) { + for(var entry : this.data.abilitySpecials.entrySet()) + abilitySpecials.put(entry.getKey(), entry.getValue().floatValue()); + } + + //if(abilitySpecialsModified.containsKey(this.data.abilityName)) {//Modify talent data + // abilitySpecials.putAll(abilitySpecialsModified.get(this.data.abilityName)); + //} + + this.playerOwner = playerOwner; + + hash = Utils.abilityHash(data.abilityName); data.initialize(); } - public void onAdded() { - if (this.data.onAdded == null) return; - for (var action : data.onAdded) { - this.manager.executeAction(this, action); - } - } + public static String getAbilityName(AbilityString abString) { + if (abString.hasStr()) return abString.getStr(); + if (abString.hasHash()) + return GameData.getAbilityHashes().get(abString.getHash()); - public void onRemoved() { - var tempModifiers = new HashMap<>(this.modifiers); - tempModifiers.values().forEach(AbilityModifierController::onRemoved); - } - - public void onBeingHit(EntityDamageEvent event) { - var tempModifiers = new HashMap<>(this.modifiers); - tempModifiers.values().forEach(m -> m.onBeingHit(event)); + return null; } } diff --git a/src/main/java/emu/grasscutter/game/ability/AbilityActionHandler.java b/src/main/java/emu/grasscutter/game/ability/AbilityActionHandler.java deleted file mode 100644 index 89b3b62a0..000000000 --- a/src/main/java/emu/grasscutter/game/ability/AbilityActionHandler.java +++ /dev/null @@ -1,7 +0,0 @@ -package emu.grasscutter.game.ability; - -import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; - -public abstract class AbilityActionHandler { - public abstract boolean execute(Ability ability, AbilityModifierAction action); -} diff --git a/src/main/java/emu/grasscutter/game/ability/AbilityLocalIdGenerator.java b/src/main/java/emu/grasscutter/game/ability/AbilityLocalIdGenerator.java index c44fe9316..99e6dbab4 100644 --- a/src/main/java/emu/grasscutter/game/ability/AbilityLocalIdGenerator.java +++ b/src/main/java/emu/grasscutter/game/ability/AbilityLocalIdGenerator.java @@ -1,59 +1,14 @@ package emu.grasscutter.game.ability; import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.binout.AbilityMixinData; import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; -import java.util.Map; import lombok.AllArgsConstructor; -public final class AbilityLocalIdGenerator { - public ConfigAbilitySubContainerType type; - public long modifierIndex = 0; - public long configIndex = 0; - public long mixinIndex = 0; - private long actionIndex = 0; - - public AbilityLocalIdGenerator(ConfigAbilitySubContainerType type) { - this.type = type; - } - - public void initializeActionLocalIds( - AbilityModifierAction[] actions, Map localIdToAction) { - if (actions == null) return; - - actionIndex = 0; - for (var action : actions) { - actionIndex++; - long id = GetLocalId(); - localIdToAction.put((int) id, action); - } - - actionIndex = 0; - } - - public long GetLocalId() { - switch (type) { - case ACTION -> { - return type.value + (configIndex << 3) + (actionIndex << 9); - } - case MIXIN -> { - return type.value + (mixinIndex << 3) + (configIndex << 9) + (actionIndex << 15); - } - case MODIFIER_ACTION -> { - return type.value + (modifierIndex << 3) + (configIndex << 9) + (actionIndex << 15); - } - case MODIFIER_MIXIN -> { - return type.value - + (modifierIndex << 3) - + (mixinIndex << 9) - + (configIndex << 15) - + (actionIndex << 21); - } - case NONE -> Grasscutter.getLogger().error("Ability local id generator using NONE type."); - } - - return -1; - } +import java.util.Map; +@SuppressWarnings("ALL") +public class AbilityLocalIdGenerator { @AllArgsConstructor public enum ConfigAbilitySubContainerType { NONE(0), @@ -62,6 +17,65 @@ public final class AbilityLocalIdGenerator { MODIFIER_ACTION(3), MODIFIER_MIXIN(4); - public final long value; + final long value; + } + + public ConfigAbilitySubContainerType type; + public long modifierIndex = 0; + public long configIndex = 0; + public long mixinIndex = 0; + private long actionIndex = 0; + + public AbilityLocalIdGenerator(ConfigAbilitySubContainerType type) + { + this.type = type; + } + + public void initializeActionLocalIds(AbilityModifierAction[] actions, Map localIdToAction) + { + if (actions == null) return; + actionIndex = 0; + for (AbilityModifierAction action : actions) { + actionIndex++; + long id = GetLocalId(); + localIdToAction.put((int) id, action); + } + + actionIndex = 0; + } + + public void initializeMixinsLocalIds(AbilityMixinData[] mixins, Map localIdToAction) + { + if (mixins == null) return; + mixinIndex = 0; + for (AbilityMixinData mixin : mixins) { + long id = GetLocalId(); + localIdToAction.put((int) id, mixin); + + mixinIndex++; + } + + mixinIndex = 0; + } + + public long GetLocalId() + { + switch (type) { + case ACTION -> { + return (long) type.value + (configIndex << 3) + (actionIndex << 9); + } + case MIXIN -> { + return (long) type.value + (mixinIndex << 3) + (configIndex << 9) + (actionIndex << 15); + } + case MODIFIER_ACTION -> { + return (long) type.value + (modifierIndex << 3) + (configIndex << 9) + (actionIndex << 15); + } + case MODIFIER_MIXIN -> { + return (long) type.value + (modifierIndex << 3) + (mixinIndex << 9) + (configIndex << 15) + (actionIndex << 21); + } + case NONE -> Grasscutter.getLogger().error("Ability local id generator using NONE type."); + } + + return -1; } } diff --git a/src/main/java/emu/grasscutter/game/ability/AbilityManager.java b/src/main/java/emu/grasscutter/game/ability/AbilityManager.java index 3bbee7cd8..98a6f92de 100644 --- a/src/main/java/emu/grasscutter/game/ability/AbilityManager.java +++ b/src/main/java/emu/grasscutter/game/ability/AbilityManager.java @@ -1,70 +1,62 @@ package emu.grasscutter.game.ability; +import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; import emu.grasscutter.data.binout.AbilityData; +import emu.grasscutter.data.binout.AbilityMixinData; +import emu.grasscutter.data.binout.AbilityModifier; import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; -import emu.grasscutter.data.binout.AbilityModifierEntry; -import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.ability.actions.AbilityAction; +import emu.grasscutter.game.ability.actions.AbilityActionHandler; +import emu.grasscutter.game.ability.mixins.AbilityMixin; +import emu.grasscutter.game.ability.mixins.AbilityMixinHandler; import emu.grasscutter.game.entity.GameEntity; -import emu.grasscutter.game.entity.gadget.GadgetGatherObject; import emu.grasscutter.game.player.BasePlayerManager; import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.quest.enums.QuestContent; import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry; import emu.grasscutter.net.proto.AbilityMetaAddAbilityOuterClass.AbilityMetaAddAbility; import emu.grasscutter.net.proto.AbilityMetaModifierChangeOuterClass.AbilityMetaModifierChange; -import emu.grasscutter.net.proto.AbilityMetaModifierDurabilityChangeOuterClass.AbilityMetaModifierDurabilityChange; import emu.grasscutter.net.proto.AbilityMetaReInitOverrideMapOuterClass.AbilityMetaReInitOverrideMap; -import emu.grasscutter.net.proto.AbilityMixinCostStaminaOuterClass.AbilityMixinCostStamina; +import emu.grasscutter.net.proto.AbilityScalarTypeOuterClass.AbilityScalarType; import emu.grasscutter.net.proto.AbilityScalarValueEntryOuterClass.AbilityScalarValueEntry; import emu.grasscutter.net.proto.ModifierActionOuterClass.ModifierAction; import io.netty.util.concurrent.FastThreadLocalThread; +import lombok.Getter; +import org.reflections.Reflections; + import java.util.HashMap; -import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import lombok.Getter; -import org.reflections.Reflections; public final class AbilityManager extends BasePlayerManager { + + private final static HashMap actionHandlers = new HashMap<>(); + private final static HashMap mixinHandlers = new HashMap<>(); + public static final ExecutorService eventExecutor; - static { - eventExecutor = - new ThreadPoolExecutor( - 4, - 4, - 60, - TimeUnit.SECONDS, - new LinkedBlockingDeque<>(1000), - FastThreadLocalThread::new, - new ThreadPoolExecutor.AbortPolicy()); - } + eventExecutor = new ThreadPoolExecutor(4, 4, + 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1000), + FastThreadLocalThread::new, new ThreadPoolExecutor.AbortPolicy()); - private final HealAbilityManager healAbilityManager; - private final Map actionHandlers; + registerHandlers(); + } @Getter private boolean abilityInvulnerable = false; public AbilityManager(Player player) { super(player); - - this.healAbilityManager = new HealAbilityManager(player); - this.actionHandlers = new HashMap<>(); - - this.registerHandlers(); } - /** Registers all present ability handlers. */ - private void registerHandlers() { - var reflections = new Reflections("emu.grasscutter.game.ability.actions"); - var handlerClasses = reflections.getSubTypesOf(AbilityActionHandler.class); + public static void registerHandlers() { + Reflections reflections = new Reflections("emu.grasscutter.game.ability.actions"); + var handlerClassesAction = reflections.getSubTypesOf(AbilityActionHandler.class); - for (var obj : handlerClasses) { + for (var obj : handlerClassesAction) { try { if (obj.isAnnotationPresent(AbilityAction.class)) { AbilityModifierAction.Type abilityAction = obj.getAnnotation(AbilityAction.class).value(); @@ -76,37 +68,74 @@ public final class AbilityManager extends BasePlayerManager { Grasscutter.getLogger().error("Unable to register handler.", e); } } + + reflections = new Reflections("emu.grasscutter.game.ability.mixins"); + var handlerClassesMixin = reflections.getSubTypesOf(AbilityMixinHandler.class); + + for (var obj : handlerClassesMixin) { + try { + if (obj.isAnnotationPresent(AbilityAction.class)) { + AbilityMixinData.Type abilityMixin = obj.getAnnotation(AbilityMixin.class).value(); + mixinHandlers.put(abilityMixin, obj.getDeclaredConstructor().newInstance()); + } else { + return; + } + } catch (Exception e) { + Grasscutter.getLogger().error("Unable to register handler.", e); + } + } } - public void executeAction(Ability ability, AbilityModifierAction action) { - var handler = actionHandlers.get(action.type); + public void executeAction(Ability ability, AbilityModifierAction action, ByteString abilityData, GameEntity target) { + AbilityActionHandler handler = actionHandlers.get(action.type); if (handler == null || ability == null) { - Grasscutter.getLogger() - .trace("Could not execute ability action {} at {}", action.type, ability); + Grasscutter.getLogger().debug("Could not execute ability action {} at {}", action.type, ability); return; } - eventExecutor.submit( - () -> { - if (!handler.execute(ability, action)) { - Grasscutter.getLogger() - .debug("exec ability action failed {} at {}", action.type, ability); - } - }); + eventExecutor.submit(() -> { + if (!handler.execute(ability, action, abilityData, target)) { + Grasscutter.getLogger().debug("Execute ability action failed {} at {}", action.type, ability); + } + }); + } + + public void executeMixin(Ability ability, AbilityMixinData mixinData, ByteString abilityData) { + AbilityMixinHandler handler = mixinHandlers.get(mixinData.type); + + if (handler == null || ability == null || mixinData == null) { + Grasscutter.getLogger().error("Could not execute ability mixin {} at {}", mixinData.type, ability); + return; + } + + eventExecutor.submit(() -> { + if (!handler.execute(ability, mixinData, abilityData)) { + Grasscutter.getLogger().error("exec ability action failed {} at {}", mixinData.type, ability); + } + }); } public void onAbilityInvoke(AbilityInvokeEntry invoke) throws Exception { - this.healAbilityManager.healHandler(invoke); + Grasscutter.getLogger().trace("Ability invoke: " + invoke + " " + invoke.getArgumentType() + " (" + invoke.getArgumentTypeValue() + "): " + this.player.getScene().getEntityById(invoke.getEntityId())); + var entity = this.player.getScene().getEntityById(invoke.getEntityId()); + if (entity != null) { + Grasscutter.getLogger().trace("Entity {} has a group of {} and a config of {}.", + invoke.getEntityId(), entity.getGroupId(), entity.getConfigId()); - if (invoke.getEntityId() == 67109298) { - Grasscutter.getLogger() - .info( - invoke.getArgumentType() - + " (" - + invoke.getArgumentTypeValue() - + "): " - + invoke.getEntityId()); + Grasscutter.getLogger().trace("Invoke type of {} ({}) has entity {}.", + invoke.getArgumentType(), invoke.getArgumentTypeValue(), entity.getId()); + } else { + Grasscutter.getLogger().debug("Invoke type of {} ({}) has no entity. (referring to {})", + invoke.getArgumentType(), invoke.getArgumentTypeValue(), invoke.getEntityId()); + } + + if (invoke.getHead().getTargetId() != 0) { + Grasscutter.getLogger().trace("Target: " + this.player.getScene().getEntityById(invoke.getHead().getTargetId())); + } + if (invoke.getHead().getLocalId() != 0) { + this.handleServerInvoke(invoke); + return; } switch (invoke.getArgumentType()) { @@ -116,17 +145,63 @@ public final class AbilityManager extends BasePlayerManager { case ABILITY_INVOKE_ARGUMENT_MIXIN_COST_STAMINA -> this.handleMixinCostStamina(invoke); case ABILITY_INVOKE_ARGUMENT_ACTION_GENERATE_ELEM_BALL -> this.handleGenerateElemBall(invoke); case ABILITY_INVOKE_ARGUMENT_META_GLOBAL_FLOAT_VALUE -> this.handleGlobalFloatValue(invoke); - case ABILITY_INVOKE_ARGUMENT_META_MODIFIER_DURABILITY_CHANGE -> this - .handleModifierDurabilityChange(invoke); + case ABILITY_INVOKE_ARGUMENT_META_MODIFIER_DURABILITY_CHANGE -> this.handleModifierDurabilityChange(invoke); case ABILITY_INVOKE_ARGUMENT_META_ADD_NEW_ABILITY -> this.handleAddNewAbility(invoke); - case ABILITY_INVOKE_ARGUMENT_NONE -> this.handleInvoke(invoke); default -> {} } } + public void handleServerInvoke(AbilityInvokeEntry invoke) { + var head = invoke.getHead(); + + var entity = this.player.getScene().getEntityById(invoke.getEntityId()); + if (entity == null) { + Grasscutter.getLogger().trace("Entity not found: {}", invoke.getEntityId()); + return; + } + + var target = this.player.getScene().getEntityById(head.getTargetId()); + if(target == null) target = entity; + + Ability ability = null; + + // Seems that target is used, but need to be sure, TODO: Research + + // Find ability or modifier's ability + if (head.getInstancedModifierId() != 0 && entity.getInstancedModifiers().containsKey(head.getInstancedModifierId())) { + ability = entity.getInstancedModifiers().get(head.getInstancedModifierId()).getAbility(); + } + + if (ability == null && head.getInstancedAbilityId() != 0 && (head.getInstancedAbilityId() - 1) < entity.getInstancedAbilities().size()) { + ability = entity.getInstancedAbilities().get(head.getInstancedAbilityId() - 1); + } + + if (ability == null) { + Grasscutter.getLogger().trace("Ability not found: ability {} modifier {}", head.getInstancedAbilityId(), head.getInstancedModifierId()); + return; + } + + //Time to reach the handlers + var action = ability.getData().localIdToAction.get(head.getLocalId()); + if (action != null) { + //Executing action + this.executeAction(ability, action, invoke.getAbilityData(), target); + return; + } else { + var mixin = ability.getData().localIdToMixin.get(head.getLocalId()); + + if(mixin != null) { + executeMixin(ability, mixin, invoke.getAbilityData()); + + return; + } + } + + Grasscutter.getLogger().trace("Action or mixin not found: local_id {} ability {} actions to ids {}", head.getLocalId(), ability.getData().abilityName, ability.getData().localIdToAction.toString()); + } + /** * Invoked when a player starts a skill. - * * @param player The player who started the skill. * @param skillId The skill ID. * @param casterId The caster ID. @@ -158,7 +233,6 @@ public final class AbilityManager extends BasePlayerManager { /** * Invoked when a player ends a skill. - * * @param player The player who started the skill. */ public void onSkillEnd(Player player) { @@ -176,293 +250,228 @@ public final class AbilityManager extends BasePlayerManager { this.abilityInvulnerable = false; } - /** - * Handles an ability invoke. - * - * @param invoke The invocation. - */ - private void handleInvoke(AbilityInvokeEntry invoke) { - var entity = this.player.getScene().getEntityById(invoke.getEntityId()); - if (entity == null) { - return; - } - - var head = invoke.getHead(); - Grasscutter.getLogger() - .trace( - "{} {} {}", - head.getInstancedAbilityId(), - entity.getInstanceToHash(), - head.getLocalId()); - - var hash = entity.getInstanceToHash().get(head.getInstancedAbilityId()); - if (hash == null) { - var abilities = entity.getAbilities().values().toArray(new Ability[0]); - - if (head.getInstancedAbilityId() <= abilities.length) { - var ability = abilities[head.getInstancedAbilityId() - 1]; - Grasscutter.getLogger().trace("-> {}", ability.getData().localIdToAction); - var action = ability.getData().localIdToAction.get(head.getLocalId()); - if (action != null) ability.getManager().executeAction(ability, action); - } + private void setAbilityOverrideValue(Ability ability, AbilityScalarValueEntry valueChange) { + if(valueChange.getValueType() != AbilityScalarType.ABILITY_SCALAR_TYPE_FLOAT) { + Grasscutter.getLogger().trace("Scalar type not supported: {}", valueChange.getValueType()); return; } - var stream = - entity.getAbilities().values().stream() - .filter( - a -> - a.getHash() == hash - || a.getData().abilityName - == entity.getInstanceToName().get(head.getInstancedAbilityId())); - stream.forEach( - ability -> { - var action = ability.getData().localIdToAction.get(head.getLocalId()); - if (action != null) ability.getManager().executeAction(ability, action); - }); + if(!valueChange.getKey().hasStr()) { + Grasscutter.getLogger().trace("TODO: Calculate all the ability value hashes"); + + return; + } + + ability.getAbilitySpecials().put(valueChange.getKey().getStr(), valueChange.getFloatValue()); + Grasscutter.getLogger().trace("Ability {} changed {} to {}", ability.getData().abilityName, valueChange.getKey().getStr(), valueChange.getFloatValue()); } private void handleOverrideParam(AbilityInvokeEntry invoke) throws Exception { var entity = this.player.getScene().getEntityById(invoke.getEntityId()); + var head = invoke.getHead(); - if (entity == null) { + if(entity == null) { + Grasscutter.getLogger().trace("Entity not found: {}", invoke.getEntityId()); return; } - var entry = AbilityScalarValueEntry.parseFrom(invoke.getAbilityData()); - entity.getMetaOverrideMap().put(entry.getKey().getStr(), entry.getFloatValue()); + var instancedAbilityIndex = head.getInstancedAbilityId() - 1; + if(instancedAbilityIndex >= entity.getInstancedAbilities().size()) { + Grasscutter.getLogger().error("Ability not found {}", head.getInstancedAbilityId()); + return; + } + + var valueChange = AbilityScalarValueEntry.parseFrom(invoke.getAbilityData()); + + var ability = entity.getInstancedAbilities().get(instancedAbilityIndex); + setAbilityOverrideValue(ability, valueChange); } private void handleReinitOverrideMap(AbilityInvokeEntry invoke) throws Exception { var entity = this.player.getScene().getEntityById(invoke.getEntityId()); + var head = invoke.getHead(); - if (entity == null) { + if(entity == null) { + Grasscutter.getLogger().trace("Entity not found: {}", invoke.getEntityId()); return; } - var map = AbilityMetaReInitOverrideMap.parseFrom(invoke.getAbilityData()); - for (var entry : map.getOverrideMapList()) { - entity.getMetaOverrideMap().put(entry.getKey().getStr(), entry.getFloatValue()); + var instancedAbilityIndex = head.getInstancedAbilityId() - 1; + if(instancedAbilityIndex >= entity.getInstancedAbilities().size()) { + Grasscutter.getLogger().error("Ability not found {}", head.getInstancedAbilityId()); + return; + } + + var ability = entity.getInstancedAbilities().get(instancedAbilityIndex); + var valueChanges = AbilityMetaReInitOverrideMap.parseFrom(invoke.getAbilityData()); + for (var variableChange : valueChanges.getOverrideMapList()) { + setAbilityOverrideValue(ability, variableChange); } } private void handleModifierChange(AbilityInvokeEntry invoke) throws Exception { - // Sanity checks - var target = this.player.getScene().getEntityById(invoke.getEntityId()); - if (target == null) { - return; - } - - var data = AbilityMetaModifierChange.parseFrom(invoke.getAbilityData()); - if (data == null) { - return; - } - - // Destroying rocks - if (target instanceof EntityGadget targetGadget - && targetGadget.getContent() instanceof GadgetGatherObject gatherObject) { - if (data.getAction() == ModifierAction.MODIFIER_ACTION_REMOVED) { - gatherObject.dropItems(this.getPlayer()); - return; - } - } - - // Sanity checks + //TODO: + var modChange = AbilityMetaModifierChange.parseFrom(invoke.getAbilityData()); var head = invoke.getHead(); - if (data.getAction() == ModifierAction.MODIFIER_ACTION_REMOVED) { - var ability = target.getAbilities().get(data.getParentAbilityName().getStr()); - if (ability != null) { - var modifier = ability.getModifiers().get(head.getInstancedModifierId()); - if (modifier != null) { - modifier.onRemoved(); - ability.getModifiers().remove(modifier); - } - } - } + if (head.getInstancedAbilityId() == 0 || + head.getInstancedModifierId() > 2000) return; // Error: TODO: display error - if (data.getAction() == ModifierAction.MODIFIER_ACTION_ADDED) { - var modifierString = data.getParentAbilityName().getStr(); - var hash = target.getInstanceToHash().get(head.getInstancedAbilityId()); - if (hash == null) return; - - target.getAbilities().values().stream() - .filter( - a -> - a.getHash() == hash - || a.getData().abilityName - == target.getInstanceToName().get(head.getInstancedAbilityId())) - .forEach( - a -> { - a.getModifiers().keySet().stream() - .filter(key -> key.compareTo(modifierString) == 0) - .forEach( - key -> { - a.getModifiers().get(key).setLocalId(head.getInstancedModifierId()); - }); - }); - } - - var sourceEntity = this.player.getScene().getEntityById(data.getApplyEntityId()); - if (sourceEntity == null) { + if (head.getIsServerbuffModifier()) { + //TODO + Grasscutter.getLogger().trace("TODO: Handle serverbuff modifier"); return; } - // This is not how it works but we will keep it for now since healing abilities dont work - // properly anyways - if (data.getAction() == ModifierAction.MODIFIER_ACTION_ADDED - && data.getParentAbilityName() != null) { - // Handle add modifier here - String modifierString = data.getParentAbilityName().getStr(); - AbilityModifierEntry modifier = GameData.getAbilityModifiers().get(modifierString); + var entity = this.player.getScene().getEntityById(invoke.getEntityId()); + if (entity == null) { + Grasscutter.getLogger().debug("Entity not found: {}", invoke.getEntityId()); + return; + } - if (modifier != null && modifier.getOnAdded().size() > 0) { - for (AbilityModifierAction action : modifier.getOnAdded()) { - this.invokeAction(action, target, sourceEntity); - } - } + if (modChange.getAction() == ModifierAction.MODIFIER_ACTION_ADDED) { + AbilityData instancedAbilityData = null; + Ability instancedAbility = null; - // Add to meta modifier list - target.getMetaModifiers().put(head.getInstancedModifierId(), modifierString); - } else if (data.getAction() == ModifierAction.MODIFIER_ACTION_REMOVED) { - // Handle remove modifier - String modifierString = target.getMetaModifiers().get(head.getInstancedModifierId()); - - if (modifierString != null) { - // Get modifier and call on remove event - AbilityModifierEntry modifier = GameData.getAbilityModifiers().get(modifierString); - - if (modifier != null && modifier.getOnRemoved().size() > 0) { - for (AbilityModifierAction action : modifier.getOnRemoved()) { - this.invokeAction(action, target, sourceEntity); + if (head.getTargetId() != 0) { + //Get ability from target entity + var targetEntity = this.player.getScene().getEntityById(head.getTargetId()); + if (targetEntity != null) { + if ((head.getInstancedAbilityId() - 1) < targetEntity.getInstancedAbilities().size()) { + instancedAbility = targetEntity.getInstancedAbilities().get(head.getInstancedAbilityId() - 1); + if (instancedAbility != null) instancedAbilityData = instancedAbility.getData(); } } - - // Remove from meta modifiers - target.getMetaModifiers().remove(head.getInstancedModifierId()); } + + if (instancedAbilityData == null) { + //search on entity base id + if (entity != null) { + if ((head.getInstancedAbilityId() - 1) < entity.getInstancedAbilities().size()) { + instancedAbility = entity.getInstancedAbilities().get(head.getInstancedAbilityId() - 1); + if (instancedAbility != null) instancedAbilityData = instancedAbility.getData(); + } + } + } + + if (instancedAbilityData == null) { + //Search for the parent ability + + //TODO: Research about hash + instancedAbilityData = GameData.getAbilityData(modChange.getParentAbilityName().getStr()); + } + + if (instancedAbilityData == null) { + Grasscutter.getLogger().trace("No ability found"); + return; //TODO: Display error message + } + + var modifierArray = instancedAbilityData.modifiers.values().toArray(); + if (modChange.getModifierLocalId() >= modifierArray.length) { + Grasscutter.getLogger().trace("Modifier local id {} not found", modChange.getModifierLocalId()); + return; + } + + var modifierData = (AbilityModifier)modifierArray[modChange.getModifierLocalId()]; + if (entity.getInstancedModifiers().containsKey(head.getInstancedModifierId())) { + Grasscutter.getLogger().trace("Replacing entity {} modifier id {} with ability {} modifier {}", invoke.getEntityId(), head.getInstancedModifierId(), instancedAbilityData.abilityName, modifierData); + } else { + Grasscutter.getLogger().trace("Adding entity {} modifier id {} with ability {} modifier {}", invoke.getEntityId(), head.getInstancedModifierId(), instancedAbilityData.abilityName, modifierData); + } + + AbilityModifierController modifier = new AbilityModifierController(instancedAbility, instancedAbilityData, modifierData); + + entity.getInstancedModifiers().put(head.getInstancedModifierId(), modifier); + + //TODO: Add all the ability modifier property change + } else if(modChange.getAction() == ModifierAction.MODIFIER_ACTION_REMOVED) { + Grasscutter.getLogger().trace("Removed on entity {} modifier id {}: {}", invoke.getEntityId(), head.getInstancedModifierId(), entity.getInstancedModifiers().get(head.getInstancedModifierId())); + + //TODO: Add debug log + + entity.getInstancedModifiers().remove(head.getInstancedModifierId()); + } else { + //TODO: Display error message + Grasscutter.getLogger().debug("Unknown action"); } } - private void handleMixinCostStamina(AbilityInvokeEntry invoke) - throws InvalidProtocolBufferException { - AbilityMixinCostStamina costStamina = - AbilityMixinCostStamina.parseFrom((invoke.getAbilityData())); - this.getPlayer().getStaminaManager().handleMixinCostStamina(costStamina.getIsSwim()); + private void handleMixinCostStamina(AbilityInvokeEntry invoke) throws InvalidProtocolBufferException { + } - private void handleGenerateElemBall(AbilityInvokeEntry invoke) - throws InvalidProtocolBufferException { - this.player.getEnergyManager().handleGenerateElemBall(invoke); + private void handleGenerateElemBall(AbilityInvokeEntry invoke) throws InvalidProtocolBufferException { + } - /** - * Handles a float value ability entry. - * - * @param invoke The ability invoke entry. - */ - private void handleGlobalFloatValue(AbilityInvokeEntry invoke) - throws InvalidProtocolBufferException { + private void handleGlobalFloatValue(AbilityInvokeEntry invoke) throws InvalidProtocolBufferException { + var entity = this.player.getScene().getEntityById(invoke.getEntityId()); + if(entity == null) return; + var entry = AbilityScalarValueEntry.parseFrom(invoke.getAbilityData()); - if (entry.getKey().hasStr() - && entry.hasFloatValue() - && entry.getFloatValue() == 2.0f - && entry.getKey().getStr().equals("_ABILITY_UziExplode_Count")) { - player.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_SKILL, 10006); + if(entry == null || !entry.hasFloatValue()) return; + + String key = null; + if(entry.getKey().hasStr()) + key = entry.getKey().getStr(); + else if(entry.getKey().hasHash()) + key = GameData.getAbilityHashes().get(entry.getKey().getHash()); + + if(key == null) return; + + if(key.startsWith("SGV_")) return; //Server does not allow to change this variables I think + switch(entry.getValueType().getNumber()) { + case AbilityScalarType.ABILITY_SCALAR_TYPE_FLOAT_VALUE: + if(!Float.isNaN(entry.getFloatValue())) entity.getGlobalAbilityValues().put(key, entry.getFloatValue()); + break; + case AbilityScalarType.ABILITY_SCALAR_TYPE_UINT_VALUE: + entity.getGlobalAbilityValues().put(key, (float)entry.getUintValue()); + break; + default: + return; } } - private void invokeAction( - AbilityModifierAction action, GameEntity target, GameEntity sourceEntity) { - switch (action.type) { - case HealHP -> {} - case LoseHP -> { - if (action.amountByTargetCurrentHPRatio == null) { - return; - } + private void invokeAction(AbilityModifierAction action, GameEntity target, GameEntity sourceEntity) { - float damageAmount = action.amount.get(); - - // if (action.amount.isDynamic && action.amount.dynamicKey != null) { - // damageAmount = - // sourceEntity.getMetaOverrideMap().getOrDefault(action.amount.dynamicKey, 0f); - // } - - if (damageAmount > 0) { - target.damage(damageAmount); - } - } - default -> {} - } } - private void handleModifierDurabilityChange(AbilityInvokeEntry invoke) - throws InvalidProtocolBufferException { - var target = this.player.getScene().getEntityById(invoke.getEntityId()); - if (target == null) { - return; - } + private void handleModifierDurabilityChange(AbilityInvokeEntry invoke) throws InvalidProtocolBufferException { - var data = AbilityMetaModifierDurabilityChange.parseFrom(invoke.getAbilityData()); - if (data == null) { - return; - } - - var head = invoke.getHead(); - var hash = target.getInstanceToHash().get(head.getInstancedAbilityId()); - if (hash == null) return; - target.getAbilities().values().stream() - .filter( - a -> - a.getHash() == hash - || a.getData().abilityName - == target.getInstanceToName().get(head.getInstancedAbilityId())) - .forEach( - a -> { - a.getModifiers().values().stream() - .filter(m -> m.getLocalId() == head.getInstancedModifierId()) - .forEach( - modifier -> { - modifier.setElementDurability(data.getRemainDurability()); - }); - }); } - private void handleAddNewAbility(AbilityInvokeEntry invoke) - throws InvalidProtocolBufferException { - var data = AbilityMetaAddAbility.parseFrom(invoke.getAbilityData()); - if (data == null) { + private void handleAddNewAbility(AbilityInvokeEntry invoke) throws InvalidProtocolBufferException { + var entity = this.player.getScene().getEntityById(invoke.getEntityId()); + + if(entity == null) { + Grasscutter.getLogger().trace("Entity not found: {}", invoke.getEntityId()); return; } - var ability = data.getAbility(); - var abilityName = ability.getAbilityName(); - if (abilityName.getHash() != 0) - Grasscutter.getLogger() - .trace("Instancing {} in to {}", abilityName.getHash(), ability.getInstancedAbilityId()); - else - Grasscutter.getLogger() - .trace("Instancing {} in to {}", abilityName.getStr(), ability.getInstancedAbilityId()); + var addAbility = AbilityMetaAddAbility.parseFrom(invoke.getAbilityData()); - var target = this.player.getScene().getEntityById(invoke.getEntityId()); - if (target == null) { + var abilityName = Ability.getAbilityName(addAbility.getAbility().getAbilityName()); + + var ability = GameData.getAbilityData(abilityName); + if(ability == null) { + Grasscutter.getLogger().trace("Ability not found: {}", abilityName); return; } - target.getInstanceToHash().put(ability.getInstancedAbilityId(), abilityName.getHash()); - target.getInstanceToName().put(ability.getInstancedAbilityId(), abilityName.getStr()); + entity.getInstancedAbilities().add(new Ability(ability, entity, player)); + + Grasscutter.getLogger().trace("Ability added to entity {} at index {}", entity.getId(), entity.getInstancedAbilities().size()); } public void addAbilityToEntity(GameEntity entity, String name) { - var data = GameData.getAbilityData(name); - if (data != null) addAbilityToEntity(entity, data, name); + AbilityData data = GameData.getAbilityData(name); + if(data != null) + addAbilityToEntity(entity, data); } - public void addAbilityToEntity(GameEntity entity, AbilityData abilityData, String id) { - var ability = new Ability(abilityData, entity); - entity.getAbilities().put(id, ability); - - ability.onAdded(); + public void addAbilityToEntity(GameEntity entity, AbilityData abilityData) { + Ability ability = new Ability(abilityData, entity, this.player); + entity.getInstancedAbilities().add(ability); //This are in order } } diff --git a/src/main/java/emu/grasscutter/game/ability/AbilityModifierController.java b/src/main/java/emu/grasscutter/game/ability/AbilityModifierController.java index 0a7cfe4dc..de97698f0 100644 --- a/src/main/java/emu/grasscutter/game/ability/AbilityModifierController.java +++ b/src/main/java/emu/grasscutter/game/ability/AbilityModifierController.java @@ -1,63 +1,18 @@ package emu.grasscutter.game.ability; +import emu.grasscutter.data.binout.AbilityData; import emu.grasscutter.data.binout.AbilityModifier; -import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; -import emu.grasscutter.server.event.entity.EntityDamageEvent; import lombok.Getter; -import lombok.Setter; -public final class AbilityModifierController { - @Getter private AbilityModifier data; +public class AbilityModifierController { + @Getter private Ability ability; - @Getter private Ability ability; // Owner ability instance + @Getter private AbilityData abilityData; + @Getter private AbilityModifier modifierData; - @Getter private float elementDurability; - - @Getter @Setter private int localId; - - public AbilityModifierController(Ability ability, AbilityModifier data) { + public AbilityModifierController(Ability ability, AbilityData abilityData, AbilityModifier modifierData) { this.ability = ability; - this.data = data; - this.elementDurability = data.elementDurability.get(); - } - - public void setElementDurability(float durability) { - this.elementDurability = durability; - - if (durability <= 0) { - onRemoved(); - ability.getModifiers().values().removeIf(a -> a == this); - } - } - - public void onAdded() { - if (data.onAdded == null) return; - - for (AbilityModifierAction action : data.onAdded) { - ability.getManager().executeAction(ability, action); - } - } - - public void onRemoved() { - if (data.onRemoved == null) return; - - for (AbilityModifierAction action : data.onRemoved) { - ability.getManager().executeAction(ability, action); - } - } - - public void onBeingHit(EntityDamageEvent event) { - if (data.onBeingHit != null) - for (var action : data.onBeingHit) { - ability.getManager().executeAction(ability, action); - } - - if (event.getAttackElementType().equals(data.elementType)) { - elementDurability -= event.getDamage(); - if (elementDurability <= 0) { - onRemoved(); - ability.getModifiers().values().removeIf(a -> a == this); - } - } + this.abilityData = abilityData; + this.modifierData = modifierData; } } diff --git a/src/main/java/emu/grasscutter/game/ability/HealAbilityManager.java b/src/main/java/emu/grasscutter/game/ability/HealAbilityManager.java deleted file mode 100644 index 0f71d0c27..000000000 --- a/src/main/java/emu/grasscutter/game/ability/HealAbilityManager.java +++ /dev/null @@ -1,214 +0,0 @@ -package emu.grasscutter.game.ability; - -import emu.grasscutter.game.entity.EntityAvatar; -import emu.grasscutter.game.entity.GameEntity; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.props.FightProperty; -import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry; -import emu.grasscutter.net.proto.AbilityMetaModifierChangeOuterClass.AbilityMetaModifierChange; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -public class HealAbilityManager { - ArrayList healDataAvatarList; - private Player player; - - public HealAbilityManager(Player player) { - this.player = player; - healDataAvatarList = new ArrayList(); - healDataAvatarList.add( - new HealDataAvatar(10000054, "Kokomi", 0) - .addHealData( - "E", - "ElementalArt_Heal_MaxHP_Base_Percentage", - "ElementalArt_Heal_Base_Amount", - false) - .addHealData("Q", "Avatar_Kokomi_ElementalBurst_Heal", 0.0172f, 212f, false)); - healDataAvatarList.add( - new HealDataAvatar(10000003, "Qin", 1).addHealData("Q", "Heal", "BurstHealConst", true)); - healDataAvatarList.add( - new HealDataAvatar(10000034, "Noel", 2) - .addHealData("E", "OnAttack_HealthRate", 0.452f, 282f, true)); - healDataAvatarList.add( - new HealDataAvatar(10000032, "Bennett", 0) - .addHealData("Q", "HealMaxHpRatio", "HealConst", false)); - healDataAvatarList.add( - new HealDataAvatar(10000039, "Diona", 0) - .addHealData("Q", "HealHPRatio", "HealHP_Const", false)); - healDataAvatarList.add( - new HealDataAvatar(10000053, "Sayu", 1) - .addHealData("Q", "Constellation_6_Damage", "Heal_BaseAmount", true) - .addHealData("Q", "Heal_AttackRatio", "Constellation_6_Heal", true)); - healDataAvatarList.add( - new HealDataAvatar(10000014, "Barbara", 0) - .addHealData("E", "HealHPOnAdded", "HealHPOnAdded_Const", true) - .addHealData("E", "HealHP_OnHittingOthers", "HealHP_Const_OnHittingOthers", true) - .addHealData("Q", "Avatar_Barbara_IdolHeal", 0.374f, 4660f, true)); - healDataAvatarList.add( - new HealDataAvatar(10000065, "Shinobu", 0) - .addHealData("E", "ElementalArt_Heal_MaxHP_Percentage", 0.064f, 795f, false)); - healDataAvatarList.add( - new HealDataAvatar(10000035, "Qiqi", 1) - .addHealData("E", "HealHP_OnHittingOthers", "HealHP_Const_OnHittingOthers", true) - .addHealData("E", "ElementalArt_HealHp_Ratio", "ElementalArt_HealHp_Const", true) - .addHealData("Q", "Avatar_Qiqi_ElementalBurst_ApplyModifier", 0.0191f, 1588f, false)); - healDataAvatarList.add( - new HealDataAvatar(10000046, "Hutao", 0) - .addHealData("Q", "Avatar_Hutao_VermilionBite_BakeMesh", 0.1166f, 0f, false)); - } - - public Player getPlayer() { - return this.player; - } - - public void healHandler(AbilityInvokeEntry invoke) throws Exception { - AbilityMetaModifierChange data = AbilityMetaModifierChange.parseFrom(invoke.getAbilityData()); - - if (data == null) { - return; - } - - GameEntity sourceEntity = player.getScene().getEntityById(data.getApplyEntityId()); - - String modifierString = ""; - if (data.getParentAbilityName() != null) modifierString = data.getParentAbilityName().getStr(); - - if (sourceEntity != null) checkAndHeal(sourceEntity, modifierString); - } - - public void checkAndHeal(GameEntity sourceEntity, String modifierString) { - int fightPropertyType = 0; - float healAmount = 0; - float ratio = 0, base = 0; - float maxHP, curHP, curAttack, curDefense; - Map map = sourceEntity.getMetaOverrideMap(); - - for (int i = 0; i < healDataAvatarList.size(); i++) { - HealDataAvatar healDataAvatar = healDataAvatarList.get(i); - if (modifierString.contains(healDataAvatar.avatarName)) { - fightPropertyType = healDataAvatar.fightPropertyType; - ArrayList healDataList = healDataAvatar.healDataList; - - for (int j = 0; j < healDataList.size(); j++) { - HealData healData = healDataList.get(j); - if (map.containsKey(healData.sRatio) || modifierString.equals(healData.sRatio)) { - if (healData.isString) { - ratio = map.get(healData.sRatio); - base = map.get(healData.sBase); - } else { - ratio = healData.fRatio; - base = healData.fBase; - } - } - - List activeTeam = player.getTeamManager().getActiveTeam(); - List needHealAvatars = new ArrayList(); - int currentIndex = player.getTeamManager().getCurrentCharacterIndex(); - EntityAvatar currentAvatar = activeTeam.get(currentIndex); - if (healData.healAll) { - needHealAvatars = activeTeam; - } else { - needHealAvatars.add(currentAvatar); - } - - EntityAvatar healActionAvatar = null; - for (int k = 0; k < activeTeam.size(); k++) { - EntityAvatar avatar = activeTeam.get(k); - int avatarId = avatar.getAvatar().getAvatarId(); - if (avatarId == healDataAvatar.avatarId) { - healActionAvatar = avatar; - break; - } - } - - if (healActionAvatar != null) { - maxHP = healActionAvatar.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); - curHP = healActionAvatar.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); - curAttack = healActionAvatar.getFightProperty(FightProperty.FIGHT_PROP_CUR_ATTACK); - curDefense = healActionAvatar.getFightProperty(FightProperty.FIGHT_PROP_CUR_DEFENSE); - - // Special case for Hu Tao: - if (healDataAvatar.avatarName.equals("Hutao") && curHP <= maxHP * 0.5 && ratio != 0) { - ratio = 0.1555f; - } - - switch (fightPropertyType) { - case 0: - healAmount = ratio * maxHP + base; - break; - case 1: - healAmount = ratio * curAttack + base; - break; - case 2: - healAmount = ratio * curDefense + base; - break; - } - } - - for (int k = 0; k < needHealAvatars.size(); k++) { - EntityAvatar avatar = needHealAvatars.get(k); - avatar.heal(healAmount); - } - } - break; - } - } - } - - private class HealData { - public boolean isString = true; - public String abilityType = ""; // "E" or "Q" - public String sRatio = ""; - public String sBase = ""; - public float fRatio = 0; - public float fBase = 0; - public boolean healAll = false; - - public HealData(String _abilityType, String _sRatio, String _sBase, boolean _healAll) { - abilityType = _abilityType; - isString = true; - sRatio = _sRatio; - sBase = _sBase; - healAll = _healAll; - } - - public HealData( - String _abilityType, String _sRatio, float _fRatio, float _fBase, boolean _healAll) { - abilityType = _abilityType; - isString = false; - sRatio = _sRatio; - fRatio = _fRatio; - fBase = _fBase; - healAll = _healAll; - } - } - - private class HealDataAvatar { - public int avatarId = 0; - public String avatarName = ""; - public int fightPropertyType = 0; // 0: maxHP, 1: curAttack, 2: curDefense - public ArrayList healDataList; - - public HealDataAvatar(int _avatarId, String _avatarName, int _fightPropertyType) { - avatarId = _avatarId; - avatarName = _avatarName; - fightPropertyType = _fightPropertyType; - healDataList = new ArrayList(); - } - - public HealDataAvatar addHealData( - String abilityType, String sRatio, String sBase, boolean healAll) { - HealData healData = new HealData(abilityType, sRatio, sBase, healAll); - healDataList.add(healData); - return this; - } - - public HealDataAvatar addHealData( - String abilityType, String sRatio, float fRatio, float fBase, boolean healAll) { - HealData healData = new HealData(abilityType, sRatio, fRatio, fBase, healAll); - healDataList.add(healData); - return this; - } - } -} diff --git a/src/main/java/emu/grasscutter/game/ability/AbilityAction.java b/src/main/java/emu/grasscutter/game/ability/actions/AbilityAction.java similarity index 85% rename from src/main/java/emu/grasscutter/game/ability/AbilityAction.java rename to src/main/java/emu/grasscutter/game/ability/actions/AbilityAction.java index 42cd519b6..3f0ca03da 100644 --- a/src/main/java/emu/grasscutter/game/ability/AbilityAction.java +++ b/src/main/java/emu/grasscutter/game/ability/actions/AbilityAction.java @@ -1,4 +1,4 @@ -package emu.grasscutter.game.ability; +package emu.grasscutter.game.ability.actions; import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; import java.lang.annotation.Retention; diff --git a/src/main/java/emu/grasscutter/game/ability/actions/AbilityActionHandler.java b/src/main/java/emu/grasscutter/game/ability/actions/AbilityActionHandler.java new file mode 100644 index 000000000..56e2c65ed --- /dev/null +++ b/src/main/java/emu/grasscutter/game/ability/actions/AbilityActionHandler.java @@ -0,0 +1,13 @@ +package emu.grasscutter.game.ability.actions; + +import com.google.protobuf.ByteString; + +import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; +import emu.grasscutter.game.ability.Ability; +import emu.grasscutter.game.entity.GameEntity; + +public abstract class AbilityActionHandler { + + public abstract boolean execute(Ability ability, AbilityModifierAction action, ByteString abilityData, GameEntity target); + +} diff --git a/src/main/java/emu/grasscutter/game/ability/actions/ActionApplyModifier.java b/src/main/java/emu/grasscutter/game/ability/actions/ActionApplyModifier.java index 79f278e09..09402cbfd 100644 --- a/src/main/java/emu/grasscutter/game/ability/actions/ActionApplyModifier.java +++ b/src/main/java/emu/grasscutter/game/ability/actions/ActionApplyModifier.java @@ -1,48 +1,37 @@ package emu.grasscutter.game.ability.actions; +import com.google.protobuf.ByteString; + import emu.grasscutter.Grasscutter; + import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; -import emu.grasscutter.data.common.DynamicFloat; import emu.grasscutter.game.ability.Ability; -import emu.grasscutter.game.ability.AbilityAction; -import emu.grasscutter.game.ability.AbilityActionHandler; import emu.grasscutter.game.ability.AbilityModifierController; +import emu.grasscutter.game.entity.GameEntity; @AbilityAction(AbilityModifierAction.Type.ApplyModifier) -public final class ActionApplyModifier extends AbilityActionHandler { +public class ActionApplyModifier extends AbilityActionHandler { @Override - public boolean execute(Ability ability, AbilityModifierAction action) { - var modifierData = ability.getData().modifiers.get(action.modifierName); - if (modifierData == null) { - Grasscutter.getLogger().debug("Modifier {} not found", action.modifierName); - return false; - } + public boolean execute(Ability ability, AbilityModifierAction action, ByteString abilityData, GameEntity target) { + //var modifierData = ability.getData().modifiers.get(action.modifierName); + //if(modifierData == null) { + // Grasscutter.getLogger().debug("Modifier {} not found", action.modifierName); + // return false; + //} +// + //if(modifierData.stacking != null && modifierData.stacking.compareTo("Unique") == 0 && + // ability.getModifiers().values().stream().filter(m -> m.getData().equals(modifierData)).count() != 0) { + // return true; + //} +// + ////TODO: Check predicates before executing all of these actions +// + //AbilityModifierController modifier = new AbilityModifierController(ability, modifierData); + //ability.getModifiers().put(action.modifierName, modifier); + //modifier.onAdded(); +// + //return true; - if (modifierData.stacking != null - && modifierData.stacking.compareTo("Unique") == 0 - && ability.getModifiers().values().stream() - .anyMatch(m -> m.getData().equals(modifierData))) { - return true; - } - - var modifier = new AbilityModifierController(ability, modifierData); - ability.getModifiers().put(action.modifierName, modifier); - modifier.onAdded(); - - if (modifierData.duration != DynamicFloat.ZERO) { - Grasscutter.getGameServer() - .getScheduler() - .scheduleAsyncTask( - () -> { - try { - Thread.sleep((int) (modifierData.duration.get() * 1000)); - modifier.onRemoved(); - } catch (InterruptedException ignored) { - Grasscutter.getLogger().error("Failed to schedule ability modifier async task."); - } - }); - } - - return true; + return false; //TODO } } diff --git a/src/main/java/emu/grasscutter/game/ability/actions/ActionCreateGadget.java b/src/main/java/emu/grasscutter/game/ability/actions/ActionCreateGadget.java new file mode 100644 index 000000000..6f37a29f7 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/ability/actions/ActionCreateGadget.java @@ -0,0 +1,47 @@ +package emu.grasscutter.game.ability.actions; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; +import emu.grasscutter.game.ability.Ability; +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.entity.GameEntity; +import emu.grasscutter.game.props.CampTargetType; +import emu.grasscutter.net.proto.AbilityActionCreateGadgetOuterClass.AbilityActionCreateGadget; +import emu.grasscutter.game.world.Position; + +@AbilityAction(AbilityModifierAction.Type.CreateGadget) +public class ActionCreateGadget extends AbilityActionHandler { + + @Override + public boolean execute(Ability ability, AbilityModifierAction action, ByteString abilityData, GameEntity target) { + if(!action.byServer) { + Grasscutter.getLogger().debug("Action not executed by server"); + + return true; + } + + var entity = ability.getOwner(); + AbilityActionCreateGadget createGadget; + try { + createGadget = AbilityActionCreateGadget.parseFrom(abilityData); + } catch (InvalidProtocolBufferException e) { + return false; + } + + var entityCreated = new EntityGadget(entity.getScene(), action.gadgetID, new Position(createGadget.getPos()), new Position(createGadget.getRot()), action.campID, CampTargetType.getTypeByName(action.campTargetType).getValue()); + if(action.ownerIsTarget) + entityCreated.setOwner(target); + else + entityCreated.setOwner(entity); + + entity.getScene().addEntity(entityCreated); + + Grasscutter.getLogger().info("Gadget {} created at pos {} rot {}", action.gadgetID, entityCreated.getPosition(), entityCreated.getRotation()); + + return true; + } + +} diff --git a/src/main/java/emu/grasscutter/game/ability/actions/ActionExecuteGadgetLua.java b/src/main/java/emu/grasscutter/game/ability/actions/ActionExecuteGadgetLua.java index c2143e2ca..54cb72595 100644 --- a/src/main/java/emu/grasscutter/game/ability/actions/ActionExecuteGadgetLua.java +++ b/src/main/java/emu/grasscutter/game/ability/actions/ActionExecuteGadgetLua.java @@ -1,20 +1,21 @@ package emu.grasscutter.game.ability.actions; +import com.google.protobuf.ByteString; + import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; import emu.grasscutter.game.ability.Ability; -import emu.grasscutter.game.ability.AbilityAction; -import emu.grasscutter.game.ability.AbilityActionHandler; +import emu.grasscutter.game.entity.GameEntity; @AbilityAction(AbilityModifierAction.Type.ExecuteGadgetLua) -public final class ActionExecuteGadgetLua extends AbilityActionHandler { +public class ActionExecuteGadgetLua extends AbilityActionHandler { @Override - public boolean execute(Ability ability, AbilityModifierAction action) { - var owner = ability.getOwner(); + public boolean execute(Ability ability, AbilityModifierAction action, ByteString abilityData, GameEntity target) { + GameEntity owner = ability.getOwner(); - if (owner.getEntityController() != null) { - owner - .getEntityController() - .onClientExecuteRequest(owner, action.param1, action.param2, action.param3); + //Investigate if we need to use target + + if(owner.getEntityController() != null) { + owner.getEntityController().onClientExecuteRequest(owner, action.param1, action.param2, action.param3); return true; } diff --git a/src/main/java/emu/grasscutter/game/ability/actions/ActionHealHP.java b/src/main/java/emu/grasscutter/game/ability/actions/ActionHealHP.java new file mode 100644 index 000000000..2b6dc8b86 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/ability/actions/ActionHealHP.java @@ -0,0 +1,69 @@ +package emu.grasscutter.game.ability.actions; + +import com.google.protobuf.ByteString; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; +import emu.grasscutter.game.ability.Ability; +import emu.grasscutter.game.entity.EntityClientGadget; +import emu.grasscutter.game.entity.GameEntity; +import emu.grasscutter.game.props.FightProperty; + +@AbilityAction(AbilityModifierAction.Type.HealHP) +public final class ActionHealHP extends AbilityActionHandler { + @Override + public boolean execute(Ability ability, AbilityModifierAction action, ByteString abilityData, GameEntity target) { + Grasscutter.getLogger().debug("Heal ability action executing 1"); + + var owner = ability.getOwner(); + //handle client gadgets, that the effective caster is the current local avatar + if (owner instanceof EntityClientGadget ownerGadget) { + owner = ownerGadget.getScene().getEntityById(ownerGadget.getOwnerEntityId()); // Caster for EntityClientGadget + Grasscutter.getLogger().debug("Owner {} has top owner {}: {}", ability.getOwner(), ownerGadget.getOwnerEntityId(), owner); + } + + if (owner == null) return false; + + ability.getAbilitySpecials().forEach((k, v) -> + Grasscutter.getLogger().trace(">>> {}: {}", k, v)); + + var amountByCasterMaxHPRatio = action.amountByCasterMaxHPRatio.get(ability); + var amountByCasterAttackRatio = action.amountByCasterAttackRatio.get(ability); + var amountByCasterCurrentHPRatio = action.amountByCasterCurrentHPRatio.get(ability); + var amountByTargetCurrentHPRatio = action.amountByTargetCurrentHPRatio.get(ability); + var amountByTargetMaxHPRatio = action.amountByTargetMaxHPRatio.get(ability); + + Grasscutter.getLogger().trace("amountByCasterMaxHPRatio: " + amountByCasterMaxHPRatio); + Grasscutter.getLogger().trace("amountByCasterAttackRatio: " + amountByCasterAttackRatio); + Grasscutter.getLogger().trace("amountByCasterCurrentHPRatio: " + amountByCasterCurrentHPRatio); + Grasscutter.getLogger().trace("amountByTargetCurrentHPRatio: " + amountByTargetCurrentHPRatio); + Grasscutter.getLogger().trace("amountByTargetMaxHPRatio: " + amountByTargetMaxHPRatio); + + var amountToRegenerate = action.amount.get(ability); + Grasscutter.getLogger().trace("Base amount: " + amountToRegenerate); + + amountToRegenerate += amountByCasterMaxHPRatio * owner.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); + amountToRegenerate += amountByCasterAttackRatio * owner.getFightProperty(FightProperty.FIGHT_PROP_CUR_ATTACK); + amountToRegenerate += amountByCasterCurrentHPRatio * owner.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); + + Grasscutter.getLogger().trace("amountToRegenerate: " + amountToRegenerate); + + var abilityRatio = 1.0f; + Grasscutter.getLogger().trace("Base abilityRatio: " + abilityRatio); + + if (!action.ignoreAbilityProperty) abilityRatio += + target.getFightProperty(FightProperty.FIGHT_PROP_HEAL_ADD) + + target.getFightProperty(FightProperty.FIGHT_PROP_HEALED_ADD); + + Grasscutter.getLogger().trace("abilityRatio: " + abilityRatio); + + Grasscutter.getLogger().trace("Sub-regenerate amount: " + amountToRegenerate); + amountToRegenerate += amountByTargetCurrentHPRatio * target.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); + amountToRegenerate += amountByTargetMaxHPRatio * target.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); + + Grasscutter.getLogger().debug(">>> Healing {} without ratios", amountToRegenerate); + target.heal(amountToRegenerate * abilityRatio * action.healRatio.get(ability, 1f), action.muteHealEffect); + + return true; + } +} diff --git a/src/main/java/emu/grasscutter/game/ability/actions/ActionKillSelf.java b/src/main/java/emu/grasscutter/game/ability/actions/ActionKillSelf.java index ea9770a74..7fad988f3 100644 --- a/src/main/java/emu/grasscutter/game/ability/actions/ActionKillSelf.java +++ b/src/main/java/emu/grasscutter/game/ability/actions/ActionKillSelf.java @@ -1,17 +1,18 @@ package emu.grasscutter.game.ability.actions; +import com.google.protobuf.ByteString; + import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; import emu.grasscutter.game.ability.Ability; -import emu.grasscutter.game.ability.AbilityAction; -import emu.grasscutter.game.ability.AbilityActionHandler; +import emu.grasscutter.game.entity.GameEntity; @AbilityAction(AbilityModifierAction.Type.KillSelf) -public final class ActionKillSelf extends AbilityActionHandler { +public class ActionKillSelf extends AbilityActionHandler { @Override - public boolean execute(Ability ability, AbilityModifierAction action) { - var owner = ability.getOwner(); + public boolean execute(Ability ability, AbilityModifierAction action, ByteString abilityData, GameEntity target) { + GameEntity owner = ability.getOwner(); owner.getScene().killEntity(owner); - return false; + return true; } } diff --git a/src/main/java/emu/grasscutter/game/ability/actions/ActionLoseHP.java b/src/main/java/emu/grasscutter/game/ability/actions/ActionLoseHP.java new file mode 100644 index 000000000..00a124631 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/ability/actions/ActionLoseHP.java @@ -0,0 +1,59 @@ +package emu.grasscutter.game.ability.actions; + +import com.google.protobuf.ByteString; +import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; +import emu.grasscutter.game.ability.Ability; +import emu.grasscutter.game.entity.EntityClientGadget; +import emu.grasscutter.game.entity.GameEntity; +import emu.grasscutter.game.props.FightProperty; + +@AbilityAction(AbilityModifierAction.Type.LoseHP) +public class ActionLoseHP extends AbilityActionHandler { + @Override + public boolean execute(Ability ability, AbilityModifierAction action, ByteString abilityData, GameEntity target) { + GameEntity owner = ability.getOwner(); + // handle client gadgets, that the effective caster is the current local avatar + if (owner instanceof EntityClientGadget ownerGadget) { + owner = ownerGadget.getScene().getEntityById(ownerGadget.getOwnerEntityId()); //Caster for EntityClientGadget + + // TODO: Do this per entity, not just the player + if (ownerGadget.getOwner().getAbilityManager().isAbilityInvulnerable()) return true; + } + if (owner == null || target == null) return false; + + if (action.enableLockHP && target.isLockHP()) { + return true; + } + + if (action.disableWhenLoading && target.getScene().getWorld().getHost().getSceneLoadState().getValue() < 2) { + return true; + } + + var amountByCasterMaxHPRatio = action.amountByCasterMaxHPRatio.get(ability); + var amountByCasterAttackRatio = action.amountByCasterAttackRatio.get(ability); + var amountByCasterCurrentHPRatio = action.amountByCasterCurrentHPRatio.get(ability); //Seems unused on server + var amountByTargetCurrentHPRatio = action.amountByTargetCurrentHPRatio.get(ability); + var amountByTargetMaxHPRatio = action.amountByTargetMaxHPRatio.get(ability); + var limboByTargetMaxHPRatio = action.limboByTargetMaxHPRatio.get(ability); + + var amountToLose = action.amount.get(ability); + amountToLose += amountByCasterMaxHPRatio * owner.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); + amountToLose += amountByCasterAttackRatio * owner.getFightProperty(FightProperty.FIGHT_PROP_CUR_ATTACK); + amountToLose += amountByCasterCurrentHPRatio * owner.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); + + var currentHp = target.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); + var maxHp = target.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); + amountToLose += amountByTargetCurrentHPRatio * currentHp; + amountToLose += amountByTargetMaxHPRatio * maxHp; + + if (limboByTargetMaxHPRatio > 1.192093e-07) + amountToLose = (float) Math.min(Math.max(currentHp - Math.max(limboByTargetMaxHPRatio * maxHp, 1.0), 0.0), amountToLose); + + if (currentHp < (amountToLose + 0.01) && !action.lethal) + amountToLose = 0; + + target.damage(amountToLose); + + return true; + } +} diff --git a/src/main/java/emu/grasscutter/game/ability/actions/ActionPredicated.java b/src/main/java/emu/grasscutter/game/ability/actions/ActionPredicated.java new file mode 100644 index 000000000..7aff770d7 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/ability/actions/ActionPredicated.java @@ -0,0 +1,16 @@ +package emu.grasscutter.game.ability.actions; + +import com.google.protobuf.ByteString; + +import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; +import emu.grasscutter.game.ability.Ability; +import emu.grasscutter.game.entity.GameEntity; + +@AbilityAction(AbilityModifierAction.Type.Predicated) +public class ActionPredicated extends AbilityActionHandler { + @Override + public boolean execute(Ability ability, AbilityModifierAction action, ByteString abilityData, GameEntity target) { + //This doesn't do nothing on server + return true; + } +} diff --git a/src/main/java/emu/grasscutter/game/ability/actions/ActionSetGlobalValueToOverrideMap.java b/src/main/java/emu/grasscutter/game/ability/actions/ActionSetGlobalValueToOverrideMap.java new file mode 100644 index 000000000..78783ca9f --- /dev/null +++ b/src/main/java/emu/grasscutter/game/ability/actions/ActionSetGlobalValueToOverrideMap.java @@ -0,0 +1,39 @@ +package emu.grasscutter.game.ability.actions; + +import com.google.protobuf.ByteString; + +import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; +import emu.grasscutter.game.ability.Ability; +import emu.grasscutter.game.entity.EntityClientGadget; +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.entity.GameEntity; + +@AbilityAction(AbilityModifierAction.Type.SetGlobalValueToOverrideMap) +public class ActionSetGlobalValueToOverrideMap extends AbilityActionHandler { + @Override + public boolean execute(Ability ability, AbilityModifierAction action, ByteString abilityData, GameEntity target) { + //TODO: + var entity = target; + if(action.isFromOwner) { + if(target instanceof EntityClientGadget gadget) + entity = entity.getScene().getEntityById(gadget.getOwnerEntityId()); + else if(target instanceof EntityGadget gadget) + entity = gadget.getOwner(); + } + + var globalValueKey = action.globalValueKey; + var abilityFormula = action.abilityFormula; + + if(!entity.getGlobalAbilityValues().containsKey(globalValueKey)) + return false; + + var globalValue = entity.getGlobalAbilityValues().getOrDefault(globalValueKey, 0.0f); + if(abilityFormula.compareTo("DummyThrowSpeed") == 0) { + globalValue = ((globalValue * 30.0f) / ((float)Math.sin(0.9424778) * 100.0f)) - 1.0f; + } + + entity.getGlobalAbilityValues().put(globalValueKey, globalValue); + + return true; + } +} diff --git a/src/main/java/emu/grasscutter/game/ability/mixins/AbilityMixin.java b/src/main/java/emu/grasscutter/game/ability/mixins/AbilityMixin.java new file mode 100644 index 000000000..2ddca36d4 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/ability/mixins/AbilityMixin.java @@ -0,0 +1,11 @@ +package emu.grasscutter.game.ability.mixins; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import emu.grasscutter.data.binout.AbilityMixinData; + +@Retention(RetentionPolicy.RUNTIME) +public @interface AbilityMixin { + AbilityMixinData.Type value(); +} diff --git a/src/main/java/emu/grasscutter/game/ability/mixins/AbilityMixinHandler.java b/src/main/java/emu/grasscutter/game/ability/mixins/AbilityMixinHandler.java new file mode 100644 index 000000000..ae2112812 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/ability/mixins/AbilityMixinHandler.java @@ -0,0 +1,12 @@ +package emu.grasscutter.game.ability.mixins; + +import com.google.protobuf.ByteString; + +import emu.grasscutter.data.binout.AbilityMixinData; +import emu.grasscutter.game.ability.Ability; + +public abstract class AbilityMixinHandler { + + public abstract boolean execute(Ability ability, AbilityMixinData mixinData, ByteString abilityData); + +} diff --git a/src/main/java/emu/grasscutter/game/activity/PlayerActivityData.java b/src/main/java/emu/grasscutter/game/activity/PlayerActivityData.java index a9cbfa1d3..a6847a40a 100644 --- a/src/main/java/emu/grasscutter/game/activity/PlayerActivityData.java +++ b/src/main/java/emu/grasscutter/game/activity/PlayerActivityData.java @@ -13,15 +13,16 @@ import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.net.proto.ActivityWatcherInfoOuterClass; import emu.grasscutter.server.packet.send.PacketActivityUpdateWatcherNotify; import emu.grasscutter.utils.JsonUtils; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; import lombok.AccessLevel; import lombok.Builder; import lombok.Data; import lombok.experimental.FieldDefaults; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + @Entity("activities") @Data @FieldDefaults(level = AccessLevel.PRIVATE) @@ -111,9 +112,13 @@ public class PlayerActivityData { } public static WatcherInfo init(ActivityWatcher watcher) { + var watcherData = watcher.getActivityWatcherData(); + var progress = watcherData != null ? + watcherData.getProgress() : 0; + return WatcherInfo.of() .watcherId(watcher.getWatcherId()) - .totalProgress(watcher.getActivityWatcherData().getProgress()) + .totalProgress(progress) .isTakenReward(false) .build(); } diff --git a/src/main/java/emu/grasscutter/game/avatar/Avatar.java b/src/main/java/emu/grasscutter/game/avatar/Avatar.java index d1f2809d0..f88c245dd 100644 --- a/src/main/java/emu/grasscutter/game/avatar/Avatar.java +++ b/src/main/java/emu/grasscutter/game/avatar/Avatar.java @@ -1,7 +1,5 @@ package emu.grasscutter.game.avatar; -import static emu.grasscutter.config.Configuration.GAME_OPTIONS; - import dev.morphia.annotations.*; import emu.grasscutter.GameConstants; import emu.grasscutter.data.GameData; @@ -24,6 +22,7 @@ import emu.grasscutter.data.excels.trial.TrialAvatarTemplateData; import emu.grasscutter.data.excels.weapon.WeaponCurveData; import emu.grasscutter.data.excels.weapon.WeaponPromoteData; import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.ability.Ability; import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.inventory.EquipType; import emu.grasscutter.game.inventory.GameItem; @@ -42,16 +41,19 @@ import emu.grasscutter.net.proto.TrialAvatarInfoOuterClass.TrialAvatarInfo; import emu.grasscutter.server.packet.send.*; import emu.grasscutter.utils.helpers.ProtoHelper; import it.unimi.dsi.fastutil.ints.*; -import java.util.*; -import java.util.stream.Stream; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import lombok.val; import org.bson.types.ObjectId; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.*; +import java.util.stream.Stream; + +import static emu.grasscutter.config.Configuration.GAME_OPTIONS; + @Entity(value = "avatars", useDiscriminator = false) public class Avatar { @Transient @Getter private final Int2ObjectMap equips; @@ -282,8 +284,9 @@ public class Avatar { .forEach(proudSkillId -> this.proudSkillList.add(proudSkillId)); this.recalcStats(); - // Send the depot change notification. - if (notify) this.owner.sendPacket(new PacketAvatarSkillDepotChangeNotify(this)); + if (notify){ + owner.sendPacket(new PacketAvatarSkillDepotChangeNotify(this)); + } } /** diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/WorldChallenge.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/WorldChallenge.java index 6da5d4f26..7f5dd9c8d 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/challenge/WorldChallenge.java +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/WorldChallenge.java @@ -13,12 +13,12 @@ import emu.grasscutter.scripts.data.SceneTrigger; import emu.grasscutter.scripts.data.ScriptArgs; import emu.grasscutter.server.packet.send.PacketDungeonChallengeBeginNotify; import emu.grasscutter.server.packet.send.PacketDungeonChallengeFinishNotify; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; import lombok.Getter; import lombok.Setter; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + @Getter @Setter public class WorldChallenge { @@ -27,7 +27,7 @@ public class WorldChallenge { private final int challengeId; private final int challengeIndex; private final List paramList; - private final int timeLimit; + private int timeLimit; private final List challengeTriggers; private final int goal; private final AtomicInteger score; @@ -112,24 +112,10 @@ public class WorldChallenge { } // TODO: record the time in PARAM2 and used in action - // TODO: Set 'eventSource' in script arguments. - // Event source should be set to '1' for timer challenges. - - var eventSource = new AtomicReference<>(""); - // TODO: This is a hack to get the event source. - // This should be properly implemented. - scriptManager - .getTriggersByEvent(EventType.EVENT_CHALLENGE_SUCCESS) - .forEach( - trigger -> { - if (trigger.currentGroup.id == this.getGroup().id) { - eventSource.set(trigger.getSource()); - } - }); scriptManager.callEvent( new ScriptArgs(this.getGroup().id, EventType.EVENT_CHALLENGE_SUCCESS) .setParam2(finishedTime) - .setEventSource(eventSource.get())); + .setEventSource(this.getChallengeIndex())); this.getScene() .triggerDungeonEvent( @@ -145,23 +131,10 @@ public class WorldChallenge { this.finish(false); // TODO: Set 'eventSource' in script arguments. - // Event source should be set to '1' for timer challenges. - var eventSource = new AtomicReference<>(""); - // TODO: This is a hack to get the event source. - // This should be properly implemented. var scriptManager = this.getScene().getScriptManager(); - scriptManager - .getTriggersByEvent(EventType.EVENT_CHALLENGE_FAIL) - .forEach( - trigger -> { - if (trigger.currentGroup.id == this.getGroup().id) { - eventSource.set(trigger.getSource()); - } - }); - scriptManager.callEvent( new ScriptArgs(this.getGroup().id, EventType.EVENT_CHALLENGE_FAIL) - .setEventSource(eventSource.get())); + .setEventSource(this.getChallengeIndex())); challengeTriggers.forEach(t -> t.onFinish(this)); } diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactory.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactory.java index b71930896..1047e7f14 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactory.java +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactory.java @@ -18,6 +18,7 @@ public abstract class ChallengeFactory { challengeFactoryHandlers.add(new KillMonsterTimeChallengeFactoryHandler()); challengeFactoryHandlers.add(new SurviveChallengeFactoryHandler()); challengeFactoryHandlers.add(new TriggerInTimeChallengeFactoryHandler()); + challengeFactoryHandlers.add(new KillMonsterCountInTimeIncChallengeFactoryHandler()); } public static WorldChallenge getChallenge( diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillMonsterCountInTimeIncChallengeFactoryHandler.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillMonsterCountInTimeIncChallengeFactoryHandler.java new file mode 100644 index 000000000..4ebd5ed1b --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillMonsterCountInTimeIncChallengeFactoryHandler.java @@ -0,0 +1,33 @@ +package emu.grasscutter.game.dungeons.challenge.factory; + +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType; +import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger; +import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterCountTrigger; +import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTimeIncTrigger; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.scripts.data.SceneGroup; +import lombok.val; + +import java.util.List; + +public class KillMonsterCountInTimeIncChallengeFactoryHandler implements ChallengeFactoryHandler{ + @Override + public boolean isThisType(ChallengeType challengeType) { + return challengeType == ChallengeType.CHALLENGE_TIME_FLY; + } + + @Override + public WorldChallenge build(int challengeIndex, int challengeId, int groupId, int monsterCount, int timeLimit, int timeInc, Scene scene, SceneGroup group) { + val realGroup = scene.getScriptManager().getGroupById(groupId); + return new WorldChallenge( + scene, realGroup, + challengeId, // Id + challengeIndex, // Index + List.of(monsterCount, timeLimit, timeInc), + timeLimit, // Limit + monsterCount, // Goal + List.of(new KillMonsterCountTrigger(), new InTimeTrigger(), new KillMonsterTimeIncTrigger(timeInc)) + ); + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/KillMonsterTimeIncTrigger.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/KillMonsterTimeIncTrigger.java new file mode 100644 index 000000000..9f854db35 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/KillMonsterTimeIncTrigger.java @@ -0,0 +1,26 @@ +package emu.grasscutter.game.dungeons.challenge.trigger; + +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.entity.EntityMonster; +import emu.grasscutter.server.packet.send.PacketChallengeDataNotify; + +public class KillMonsterTimeIncTrigger extends ChallengeTrigger{ + + private int increment; + + public KillMonsterTimeIncTrigger(int increment) { + this.increment = increment; + } + + @Override + public void onBegin(WorldChallenge challenge) { + //challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 0, challenge.getScore().get())); + } + + @Override + public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) { + challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 0, increment)); + + challenge.setTimeLimit(challenge.getTimeLimit() + increment); + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java b/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java index 869c8dd46..4321e0894 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java @@ -69,6 +69,8 @@ public class EntityAvatar extends GameEntity { Grasscutter.getLogger() .error("Unable to create EntityAvatar instance; provided scene is null."); } + + this.initAbilities(); } @Override @@ -100,11 +102,13 @@ public class EntityAvatar extends GameEntity { return getAvatar().getFightProperties(); } + /** + * @return The entity ID of the avatar's equipped weapon. + */ public int getWeaponEntityId() { - if (getAvatar().getWeapon() != null) { - return getAvatar().getWeapon().getWeaponEntityId(); - } - return 0; + var avatar = this.getAvatar(); + return avatar.getWeapon() == null ? 0 : + avatar.getWeapon().getWeaponEntityId(); } @Override @@ -125,28 +129,38 @@ public class EntityAvatar extends GameEntity { } @Override - public float heal(float amount) { + public void initAbilities() { + } + + private void addConfigAbility(String abilityName){ + var data = GameData.getAbilityData(abilityName); + if (data != null) this.getScene().getWorld() + .getHost().getAbilityManager().addAbilityToEntity(this, data); + } + + @Override + public float heal(float amount, boolean mute) { // Do not heal character if they are dead if (!this.isAlive()) { return 0f; } - float healed = super.heal(amount); + float healed = super.heal(amount, mute); if (healed > 0f) { - getScene() - .broadcastPacket( - new PacketEntityFightPropChangeReasonNotify( - this, - FightProperty.FIGHT_PROP_CUR_HP, - healed, - PropChangeReason.PROP_CHANGE_REASON_ABILITY, - ChangeHpReason.CHANGE_HP_REASON_ADD_ABILITY)); + getScene().broadcastPacket( + new PacketEntityFightPropChangeReasonNotify(this, FightProperty.FIGHT_PROP_CUR_HP, healed, mute ? PropChangeReason.PROP_CHANGE_REASON_NONE : PropChangeReason.PROP_CHANGE_REASON_ABILITY, ChangeHpReason.CHANGE_HP_REASON_ADD_ABILITY) + ); } return healed; } + @Override + public float heal(float amount) { + return this.heal(amount, false); + } + public void clearEnergy(ChangeEnergyReason reason) { // Fight props. val curEnergyProp = this.getAvatar().getSkillDepot().getElementType().getCurEnergyProp(); diff --git a/src/main/java/emu/grasscutter/game/entity/EntityBaseGadget.java b/src/main/java/emu/grasscutter/game/entity/EntityBaseGadget.java index bd17acca6..d10fe5c4f 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityBaseGadget.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityBaseGadget.java @@ -18,14 +18,23 @@ public abstract class EntityBaseGadget extends GameEntity { @Getter(onMethod_ = @Override) protected final Position rotation; + @Getter private final int campId; + @Getter private final int campType; + public EntityBaseGadget(Scene scene) { this(scene, null, null); } public EntityBaseGadget(Scene scene, Position position, Position rotation) { + this(scene, position, rotation, 0, 0); + } + + public EntityBaseGadget(Scene scene, Position position, Position rotation, int campId, int campType) { super(scene); this.position = position != null ? position.clone() : new Position(); this.rotation = rotation != null ? rotation.clone() : new Position(); + this.campId = campId; + this.campType = campType; } public abstract int getGadgetId(); diff --git a/src/main/java/emu/grasscutter/game/entity/EntityClientGadget.java b/src/main/java/emu/grasscutter/game/entity/EntityClientGadget.java index c25528a4b..e9ea6c860 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityClientGadget.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityClientGadget.java @@ -1,5 +1,9 @@ package emu.grasscutter.game.entity; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.binout.config.ConfigEntityGadget; +import emu.grasscutter.data.binout.config.fields.ConfigAbilityData; +import emu.grasscutter.data.excels.GadgetData; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.world.Position; @@ -28,31 +32,53 @@ public class EntityClientGadget extends EntityBaseGadget { @Getter(onMethod_ = @Override) private int gadgetId; - @Getter private int campId; - @Getter private int campType; @Getter private int ownerEntityId; @Getter private int targetEntityId; @Getter private boolean asyncLoad; @Getter private int originalOwnerEntityId; + @Getter private final GadgetData gadgetData; + private ConfigEntityGadget configGadget; + public EntityClientGadget(Scene scene, Player player, EvtCreateGadgetNotify notify) { - super(scene, new Position(notify.getInitPos()), new Position(notify.getInitEulerAngles())); + super(scene, new Position(notify.getInitPos()), new Position(notify.getInitEulerAngles()), notify.getCampId(), notify.getCampType()); this.owner = player; this.id = notify.getEntityId(); this.gadgetId = notify.getConfigId(); - this.campId = notify.getCampId(); - this.campType = notify.getCampType(); this.ownerEntityId = notify.getPropOwnerEntityId(); this.targetEntityId = notify.getTargetEntityId(); this.asyncLoad = notify.getIsAsyncLoad(); + this.gadgetData = GameData.getGadgetDataMap().get(gadgetId); + if (gadgetData != null && gadgetData.getJsonName() != null) { + this.configGadget = GameData.getGadgetConfigData().get(gadgetData.getJsonName()); + } + GameEntity owner = scene.getEntityById(this.ownerEntityId); if (owner instanceof EntityClientGadget ownerGadget) { this.originalOwnerEntityId = ownerGadget.getOriginalOwnerEntityId(); } else { this.originalOwnerEntityId = this.ownerEntityId; } + + this.initAbilities(); + } + + @Override + public void initAbilities() { + if(this.configGadget != null && this.configGadget.getAbilities() != null) { + for (var ability : this.configGadget.getAbilities()) { + addConfigAbility(ability); + } + } + } + + private void addConfigAbility(ConfigAbilityData abilityData){ + var data = GameData.getAbilityData(abilityData.getAbilityName()); + if (data != null) + owner.getAbilityManager().addAbilityToEntity( + this, data); } @Override diff --git a/src/main/java/emu/grasscutter/game/entity/EntityGadget.java b/src/main/java/emu/grasscutter/game/entity/EntityGadget.java index b85dae75d..918b56685 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityGadget.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityGadget.java @@ -1,5 +1,6 @@ package emu.grasscutter.game.entity; +import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; import emu.grasscutter.data.binout.config.ConfigEntityGadget; import emu.grasscutter.data.binout.config.fields.ConfigAbilityData; @@ -38,13 +39,14 @@ import emu.grasscutter.server.packet.send.PacketSceneTimeNotify; import emu.grasscutter.utils.helpers.ProtoHelper; import it.unimi.dsi.fastutil.ints.Int2FloatMap; import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; -import java.util.ArrayList; -import java.util.List; -import javax.annotation.Nullable; import lombok.Getter; import lombok.Setter; import lombok.ToString; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + @ToString(callSuper = true) public class EntityGadget extends EntityBaseGadget { @Getter private final GadgetData gadgetData; @@ -73,6 +75,8 @@ public class EntityGadget extends EntityBaseGadget { @Getter @Setter private int startValue = 0; // Controller related, inited to zero @Getter @Setter private int ticksSinceChange; + @Getter private boolean interactEnabled = true; + public EntityGadget(Scene scene, int gadgetId, Position pos) { this(scene, gadgetId, pos, null, null); } @@ -81,9 +85,16 @@ public class EntityGadget extends EntityBaseGadget { this(scene, gadgetId, pos, rot, null); } - public EntityGadget( - Scene scene, int gadgetId, Position pos, Position rot, GadgetContent content) { - super(scene, pos, rot); + public EntityGadget(Scene scene, int gadgetId, Position pos, Position rot, int campId, int campType) { + this(scene, gadgetId, pos, rot, null, campId, campType); + } + + public EntityGadget(Scene scene, int gadgetId, Position pos, Position rot, GadgetContent content) { + this(scene, gadgetId, pos, rot, content, 0, 0); + } + + public EntityGadget(Scene scene, int gadgetId, Position pos, Position rot, GadgetContent content, int campId, int campType) { + super(scene, pos, rot, campId, campType); this.gadgetData = GameData.getGadgetDataMap().get(gadgetId); if (gadgetData != null && gadgetData.getJsonName() != null) { @@ -98,14 +109,25 @@ public class EntityGadget extends EntityBaseGadget { this.fillFightProps(configGadget); if (GameData.getGadgetMappingMap().containsKey(gadgetId)) { - String controllerName = GameData.getGadgetMappingMap().get(gadgetId).getServerController(); + var controllerName = GameData.getGadgetMappingMap().get(gadgetId).getServerController(); this.setEntityController(EntityControllerScriptManager.getGadgetController(controllerName)); + if (this.getEntityController() == null) { + Grasscutter.getLogger().warn("Gadget controller {} not found.", controllerName); + } } - this.addConfigAbilities(); + this.initAbilities(); // TODO: move this } - private void addConfigAbilities() { + private void addConfigAbility(ConfigAbilityData abilityData){ + var data = GameData.getAbilityData(abilityData.getAbilityName()); + if(data != null) this.getScene().getWorld().getHost() + .getAbilityManager().addAbilityToEntity(this, data); + } + + @Override + public void initAbilities() { + //TODO: handle pre-dynamic, static and dynamic here if (this.configGadget != null && this.configGadget.getAbilities() != null) { for (var ability : this.configGadget.getAbilities()) { this.addConfigAbility(ability); @@ -113,14 +135,9 @@ public class EntityGadget extends EntityBaseGadget { } } - private void addConfigAbility(ConfigAbilityData abilityData) { - var data = GameData.getAbilityData(abilityData.getAbilityName()); - if (data != null) - getScene() - .getWorld() - .getHost() - .getAbilityManager() - .addAbilityToEntity(this, data, abilityData.getAbilityID()); + public void setInteractEnabled(boolean enable) { + this.interactEnabled = enable; + this.getScene().broadcastPacket(new PacketGadgetStateNotify(this, this.getState())); //Update the interact } public void setState(int state) { @@ -172,6 +189,8 @@ public class EntityGadget extends EntityBaseGadget { @Override public void onInteract(Player player, GadgetInteractReq interactReq) { + if (!this.interactEnabled) return; + if (this.getContent() == null) { return; } @@ -289,14 +308,13 @@ public class EntityGadget extends EntityBaseGadget { addAllFightPropsToEntityInfo(entityInfo); } - SceneGadgetInfo.Builder gadgetInfo = - SceneGadgetInfo.newBuilder() - .setGadgetId(this.getGadgetId()) - .setGroupId(this.getGroupId()) - .setConfigId(this.getConfigId()) - .setGadgetState(this.getState()) - .setIsEnableInteract(true) - .setAuthorityPeerId(this.getScene().getWorld().getHostPeerId()); + var gadgetInfo = SceneGadgetInfo.newBuilder() + .setGadgetId(this.getGadgetId()) + .setGroupId(this.getGroupId()) + .setConfigId(this.getConfigId()) + .setGadgetState(this.getState()) + .setIsEnableInteract(this.interactEnabled) + .setAuthorityPeerId(this.getScene().getWorld().getHostPeerId()); if (this.metaGadget != null) { gadgetInfo.setDraftId(this.metaGadget.draft_id); diff --git a/src/main/java/emu/grasscutter/game/entity/EntityItem.java b/src/main/java/emu/grasscutter/game/entity/EntityItem.java index a8b9253cb..9a7b7845a 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityItem.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityItem.java @@ -140,4 +140,10 @@ public class EntityItem extends EntityBaseGadget { return entityInfo.build(); } + + @Override + public void initAbilities() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'initAbilities'"); + } } diff --git a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java index 2edc587d0..b5d23085a 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java @@ -1,6 +1,8 @@ package emu.grasscutter.game.entity; import emu.grasscutter.data.GameData; +import emu.grasscutter.data.binout.AbilityData; +import emu.grasscutter.data.binout.config.ConfigEntityMonster; import emu.grasscutter.data.common.PropGrowCurve; import emu.grasscutter.data.excels.EnvAnimalGatherConfigData; import emu.grasscutter.data.excels.monster.MonsterCurveData; @@ -9,6 +11,7 @@ import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.*; import emu.grasscutter.game.quest.enums.QuestContent; +import emu.grasscutter.game.world.Position; import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.SceneGroupInstance; import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; @@ -28,13 +31,14 @@ import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.scripts.data.SceneMonster; import emu.grasscutter.scripts.data.ScriptArgs; import emu.grasscutter.server.event.entity.EntityDamageEvent; -import emu.grasscutter.game.world.Position; import emu.grasscutter.utils.helpers.ProtoHelper; import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; import lombok.Getter; import lombok.Setter; import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import static emu.grasscutter.scripts.constants.EventType.EVENT_SPECIFIC_MONSTER_HP_CHANGE; @@ -48,12 +52,14 @@ public class EntityMonster extends GameEntity { @Getter(onMethod_ = @Override) private final Position rotation; @Getter private final MonsterData monsterData; + @Getter private final ConfigEntityMonster configEntityMonster; @Getter private final Position bornPos; @Getter private final int level; @Getter private int weaponEntityId; @Getter @Setter private int poseId; @Getter @Setter private int aiId = -1; + @Getter private List playerOnBattle; @Nullable @Getter @Setter private SceneMonster metaMonster; public EntityMonster(Scene scene, MonsterData monsterData, Position pos, int level) { @@ -65,6 +71,13 @@ public class EntityMonster extends GameEntity { this.rotation = new Position(); this.bornPos = getPosition().clone(); this.level = level; + this.playerOnBattle = new ArrayList<>(); + + if(GameData.getMonsterMappingMap().containsKey(getMonsterId())) { + this.configEntityMonster = GameData.getMonsterConfigData().get(GameData.getMonsterMappingMap().get(getMonsterId()).getMonsterJson()); + } else { + this.configEntityMonster = null; + } // Monster weapon if (getMonsterWeaponId() > 0) { @@ -72,6 +85,86 @@ public class EntityMonster extends GameEntity { } this.recalcStats(); + + initAbilities(); + } + + private void addConfigAbility(String name){ + AbilityData data = GameData.getAbilityData(name); + if(data != null) + getScene().getWorld().getHost().getAbilityManager().addAbilityToEntity( + this, data); + } + + @Override + public void initAbilities() { + if(configEntityMonster != null) { + // Affix abilities + var optionalGroup = getScene().getLoadedGroups().stream() + .filter(g -> g.id == this.getGroupId()) + .findAny(); + List affixes = null; + if (optionalGroup.isPresent()) { + var group = optionalGroup.get(); + + SceneMonster monster = group.monsters.get(getConfigId()); + if(monster != null) affixes = monster.affix; + } + + if (affixes != null) { + for(var affixId : affixes) { + var affix = GameData.getMonsterAffixDataMap().get(affixId.intValue()); + if (!affix.isPreAdd()) continue; + + //Add the ability + for(var name : affix.getAbilityName()) { + this.addConfigAbility(name); + } + } + } + + //TODO: Research if any monster is non humanoid + for(var ability : GameData.getConfigGlobalCombat().getDefaultAbilities().getNonHumanoidMoveAbilities()) { + this.addConfigAbility(ability); + } + + if (configEntityMonster.getAbilities() != null) + for (var configAbilityData : configEntityMonster.getAbilities()) { + this.addConfigAbility(configAbilityData.abilityName); + } + + if (optionalGroup.isPresent()) { + var group = optionalGroup.get(); + SceneMonster monster = group.monsters.get(getConfigId()); + if(monster != null && monster.isElite) { + addConfigAbility(GameData.getConfigGlobalCombat().getDefaultAbilities().getMonterEliteAbilityName()); + } + } + + if (affixes != null) { + for (var affixId : affixes) { + var affix = GameData.getMonsterAffixDataMap().get(affixId.intValue()); + if(affix.isPreAdd()) continue; + + //Add the ability + for(var name : affix.getAbilityName()) { + this.addConfigAbility(name); + } + } + } + + var levelEntityConfig = getScene().getSceneData().getLevelEntityConfig(); + var config = GameData.getConfigLevelEntityDataMap().get(levelEntityConfig); + if (config == null){ + return; + } + + if (config.getMonsterAbilities() != null) { + for (var monsterAbility : config.getMonsterAbilities()) { + addConfigAbility(monsterAbility.abilityName); + } + } + } } @Override diff --git a/src/main/java/emu/grasscutter/game/entity/EntityNPC.java b/src/main/java/emu/grasscutter/game/entity/EntityNPC.java index d0fcfdb3c..49e708d8a 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityNPC.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityNPC.java @@ -79,4 +79,10 @@ public class EntityNPC extends GameEntity { return entityInfo.build(); } + + @Override + public void initAbilities() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'initAbilities'"); + } } diff --git a/src/main/java/emu/grasscutter/game/entity/EntityRegion.java b/src/main/java/emu/grasscutter/game/entity/EntityRegion.java index 2ffca9325..fd424f25c 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityRegion.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityRegion.java @@ -90,4 +90,10 @@ public class EntityRegion extends GameEntity { public int getFirstEntityId() { return entities.stream().findFirst().orElse(0); } + + @Override + public void initAbilities() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'initAbilities'"); + } } diff --git a/src/main/java/emu/grasscutter/game/entity/EntityScene.java b/src/main/java/emu/grasscutter/game/entity/EntityScene.java new file mode 100644 index 000000000..ee575481c --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/EntityScene.java @@ -0,0 +1,57 @@ +package emu.grasscutter.game.entity; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.binout.AbilityData; +import emu.grasscutter.game.props.EntityIdType; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; +import emu.grasscutter.game.world.Position; +import it.unimi.dsi.fastutil.ints.Int2FloatArrayMap; +import it.unimi.dsi.fastutil.ints.Int2FloatMap; + +public class EntityScene extends GameEntity { + + public EntityScene(Scene scene) { + super(scene); + initAbilities(); + } + + @Override + public void initAbilities() { + //Load abilities from levelElementAbilities + for(var ability : GameData.getConfigGlobalCombat().getDefaultAbilities().getLevelElementAbilities()) { + AbilityData data = GameData.getAbilityData(ability); + if(data != null) + getScene().getWorld().getHost().getAbilityManager().addAbilityToEntity( + this, data); + } + } + + @Override + public int getEntityTypeId() { + return 0x13; + } + + @Override + public Int2FloatMap getFightProperties() { + //TODO + return new Int2FloatArrayMap(); + } + + @Override + public Position getPosition() { + // TODO Auto-generated method stub + return new Position(0, 0, 0); + } + + @Override + public Position getRotation() { + return new Position(0, 0, 0); + } + + @Override + public SceneEntityInfo toProto() { + return null; + } + +} diff --git a/src/main/java/emu/grasscutter/game/entity/EntityTeam.java b/src/main/java/emu/grasscutter/game/entity/EntityTeam.java new file mode 100644 index 000000000..819ef581b --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/EntityTeam.java @@ -0,0 +1,68 @@ +package emu.grasscutter.game.entity; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.binout.AbilityData; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.EntityIdType; +import emu.grasscutter.game.world.World; +import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; +import emu.grasscutter.game.world.Position; +import it.unimi.dsi.fastutil.ints.Int2FloatArrayMap; +import it.unimi.dsi.fastutil.ints.Int2FloatMap; + +public class EntityTeam extends GameEntity { + + private Player player; + + public EntityTeam(Player player) { + super(player.getScene()); + initAbilities(); + this.id = player.getWorld().getNextEntityId(EntityIdType.TEAM); + } + + @Override + public void initAbilities() { + //Load abilities from levelElementAbilities + var defaultAbilities = GameData.getConfigGlobalCombat().getDefaultAbilities(); + if(defaultAbilities.getDefaultTeamAbilities() != null) + for(var ability : defaultAbilities.getDefaultTeamAbilities()) { + AbilityData data = GameData.getAbilityData(ability); + if(data != null) + player.getWorld().getHost().getAbilityManager().addAbilityToEntity( + this, data); + } + } + + @Override + public World getWorld() { + return player.getWorld(); + } + + @Override + public int getEntityTypeId() { + return EntityIdType.TEAM.getId(); + } + + @Override + public Int2FloatMap getFightProperties() { + //TODO + return new Int2FloatArrayMap(); + } + + @Override + public Position getPosition() { + // TODO Auto-generated method stub + return new Position(0, 0, 0); + } + + @Override + public Position getRotation() { + return new Position(0, 0, 0); + } + + @Override + public SceneEntityInfo toProto() { + return null; + } + +} diff --git a/src/main/java/emu/grasscutter/game/entity/EntityVehicle.java b/src/main/java/emu/grasscutter/game/entity/EntityVehicle.java index 88a86cce9..6e379ab96 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityVehicle.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityVehicle.java @@ -122,4 +122,10 @@ public class EntityVehicle extends EntityBaseGadget { return entityInfo.build(); } + + @Override + public void initAbilities() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'initAbilities'"); + } } diff --git a/src/main/java/emu/grasscutter/game/entity/EntityWorld.java b/src/main/java/emu/grasscutter/game/entity/EntityWorld.java new file mode 100644 index 000000000..3ba926f1c --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/EntityWorld.java @@ -0,0 +1,67 @@ +package emu.grasscutter.game.entity; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.game.props.EntityIdType; +import emu.grasscutter.game.world.Position; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.game.world.World; +import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; +import it.unimi.dsi.fastutil.ints.Int2FloatArrayMap; +import it.unimi.dsi.fastutil.ints.Int2FloatMap; +import lombok.Getter; + +public class EntityWorld extends GameEntity { + @Getter private World world; + + public EntityWorld(World world) { + super(null); + + this.world = world; + this.id = world.getNextEntityId(EntityIdType.MPLEVEL); + + this.initAbilities(); + } + + @Override + public Scene getScene() { + return this.world.getHost().getScene(); + } + + @Override + public void initAbilities() { + //Load abilities from levelElementAbilities + for (var ability : GameData.getConfigGlobalCombat() + .getDefaultAbilities().getDefaultMPLevelAbilities()) { + var data = GameData.getAbilityData(ability); + if (data != null) world.getHost() + .getAbilityManager().addAbilityToEntity(this, data); + } + } + + @Override + public int getEntityTypeId() { + return EntityIdType.TEAM.getId(); + } + + @Override + public Int2FloatMap getFightProperties() { + //TODO + return new Int2FloatArrayMap(); + } + + @Override + public Position getPosition() { + // TODO Auto-generated method stub + return new Position(0, 0, 0); + } + + @Override + public Position getRotation() { + return new Position(0, 0, 0); + } + + @Override + public SceneEntityInfo toProto() { + return null; + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/GameEntity.java b/src/main/java/emu/grasscutter/game/entity/GameEntity.java index 4978c6c6e..62bdeddd7 100644 --- a/src/main/java/emu/grasscutter/game/entity/GameEntity.java +++ b/src/main/java/emu/grasscutter/game/entity/GameEntity.java @@ -1,6 +1,7 @@ package emu.grasscutter.game.entity; import emu.grasscutter.game.ability.Ability; +import emu.grasscutter.game.ability.AbilityModifierController; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.ElementType; import emu.grasscutter.game.props.FightProperty; @@ -24,11 +25,14 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2FloatMap; import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap; -import java.util.HashMap; -import java.util.Map; import lombok.Getter; import lombok.Setter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + public abstract class GameEntity { @Getter private final Scene scene; @Getter protected int id; @@ -48,19 +52,17 @@ public abstract class GameEntity { @Getter @Setter private EntityController entityController; @Getter private ElementType lastAttackType = ElementType.None; - // Abilities - private Object2FloatMap metaOverrideMap; - private Int2ObjectMap metaModifiers; - private Map instanceToHash; - private Int2ObjectMap instanceToName; - - @Getter private Map abilities = new HashMap<>(); + @Getter private List instancedAbilities = new ArrayList<>(); + @Getter private Int2ObjectMap instancedModifiers = new Int2ObjectOpenHashMap<>(); + @Getter private Map globalAbilityValues = new HashMap<>(); public GameEntity(Scene scene) { this.scene = scene; this.motionState = MotionState.MOTION_STATE_NONE; } + public abstract void initAbilities(); + public int getEntityType() { return this.getId() >> 24; } @@ -79,36 +81,6 @@ public abstract class GameEntity { return this.isAlive() ? LifeState.LIFE_ALIVE : LifeState.LIFE_DEAD; } - public Object2FloatMap getMetaOverrideMap() { - if (this.metaOverrideMap == null) { - this.metaOverrideMap = new Object2FloatOpenHashMap<>(); - } - return this.metaOverrideMap; - } - - public Int2ObjectMap getMetaModifiers() { - if (this.metaModifiers == null) { - this.metaModifiers = new Int2ObjectOpenHashMap<>(); - } - return this.metaModifiers; - } - - public Map getInstanceToHash() { - if (this.instanceToHash == null) { - this.instanceToHash = new HashMap<>(); - } - - return this.instanceToHash; - } - - public Int2ObjectMap getInstanceToName() { - if (this.instanceToName == null) { - this.instanceToName = new Int2ObjectOpenHashMap<>(); - } - - return this.instanceToName; - } - public abstract Int2FloatMap getFightProperties(); public abstract Position getPosition(); @@ -155,6 +127,10 @@ public abstract class GameEntity { } public float heal(float amount) { + return heal(amount, false); + } + + public float heal(float amount, boolean mute) { if (this.getFightProperties() == null) { return 0f; } @@ -214,7 +190,6 @@ public abstract class GameEntity { } this.runLuaCallbacks(event); - this.runAbilityCallbacks(event); // Packets this.getScene() @@ -238,15 +213,6 @@ public abstract class GameEntity { } } - /** - * Runs the ability callbacks for {@link EntityDamageEvent}. - * - * @param event The damage event. - */ - public void runAbilityCallbacks(EntityDamageEvent event) { - this.abilities.values().forEach(ability -> ability.onBeingHit(event)); - } - /** * Move this entity to a new position. * diff --git a/src/main/java/emu/grasscutter/game/entity/gadget/GadgetAbility.java b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetAbility.java index 9065c84f6..afb3aa27c 100644 --- a/src/main/java/emu/grasscutter/game/entity/gadget/GadgetAbility.java +++ b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetAbility.java @@ -1,6 +1,6 @@ package emu.grasscutter.game.entity.gadget; -import emu.grasscutter.game.entity.EntityClientGadget; +import emu.grasscutter.game.entity.EntityBaseGadget; import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.player.Player; import emu.grasscutter.net.proto.AbilityGadgetInfoOuterClass; @@ -9,10 +9,11 @@ import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; import lombok.val; public class GadgetAbility extends GadgetContent { - private EntityClientGadget parent; + private EntityBaseGadget parent; - public GadgetAbility(EntityGadget gadget, EntityClientGadget parent) { + public GadgetAbility(EntityGadget gadget, EntityBaseGadget parent) { super(gadget); + this.parent = parent; } diff --git a/src/main/java/emu/grasscutter/game/player/TeamManager.java b/src/main/java/emu/grasscutter/game/player/TeamManager.java index 87369d2d6..0681b48be 100644 --- a/src/main/java/emu/grasscutter/game/player/TeamManager.java +++ b/src/main/java/emu/grasscutter/game/player/TeamManager.java @@ -1,7 +1,5 @@ package emu.grasscutter.game.player; -import static emu.grasscutter.config.Configuration.GAME_OPTIONS; - import dev.morphia.annotations.Entity; import dev.morphia.annotations.Transient; import emu.grasscutter.GameConstants; @@ -11,6 +9,7 @@ import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData; import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.entity.EntityBaseGadget; +import emu.grasscutter.game.entity.EntityTeam; import emu.grasscutter.game.props.ElementType; import emu.grasscutter.game.props.EnterReason; import emu.grasscutter.game.props.FightProperty; @@ -33,12 +32,15 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import java.util.*; -import java.util.stream.Stream; import lombok.Getter; import lombok.Setter; import lombok.val; +import java.util.*; +import java.util.stream.Stream; + +import static emu.grasscutter.config.Configuration.GAME_OPTIONS; + @Entity public final class TeamManager extends BasePlayerDataManager { @Transient private final List avatars; @@ -50,7 +52,7 @@ public final class TeamManager extends BasePlayerDataManager { private int currentTeamIndex; @Getter @Setter private int currentCharacterIndex; @Transient @Getter @Setter private TeamInfo mpTeam; - @Transient @Getter @Setter private int entityId; + @Transient @Getter @Setter private EntityTeam entity; @Transient private int useTemporarilyTeamIndex = -1; @Transient private List temporaryTeam; // Temporary Team for tower @@ -155,6 +157,9 @@ public final class TeamManager extends BasePlayerDataManager { } public EntityAvatar getCurrentAvatarEntity() { + // Check if any avatars are equipped. + if (this.getActiveTeam().size() == 0) return null; + if (this.currentCharacterIndex >= this.getActiveTeam().size()) { this.currentCharacterIndex = 0; // Reset to the first character. } diff --git a/src/main/java/emu/grasscutter/game/props/CampTargetType.java b/src/main/java/emu/grasscutter/game/props/CampTargetType.java new file mode 100644 index 000000000..04b204f5f --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/CampTargetType.java @@ -0,0 +1,47 @@ +package emu.grasscutter.game.props; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import emu.grasscutter.scripts.constants.IntValueEnum; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +public enum CampTargetType implements IntValueEnum{ + None (0), + Alliance (1), + Enemy (2), + Self (3), + SelfCamp (4), + All (5), + AllExceptSelf (6), + AllianceIncludeSelf (7); + + private final int value; + private static final Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); + private static final Map stringMap = new HashMap<>(); + + static { + Stream.of(values()).forEach(e -> { + map.put(e.getValue(), e); + stringMap.put(e.name(), e); + }); + } + + private CampTargetType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static CampTargetType getTypeByValue(int value) { + return map.getOrDefault(value, None); + } + + public static CampTargetType getTypeByName(String name) { + return stringMap.getOrDefault(name, None); + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/exec/ExecNotifyGroupLua.java b/src/main/java/emu/grasscutter/game/quest/exec/ExecNotifyGroupLua.java index 81fe1467b..6d4155bc4 100644 --- a/src/main/java/emu/grasscutter/game/quest/exec/ExecNotifyGroupLua.java +++ b/src/main/java/emu/grasscutter/game/quest/exec/ExecNotifyGroupLua.java @@ -54,7 +54,7 @@ public class ExecNotifyGroupLua extends QuestExecHandler { : EventType.EVENT_QUEST_START; scriptManager.callEvent( new ScriptArgs(groupId, eventType, quest.getSubQuestId()) - .setEventSource(String.valueOf(quest.getSubQuestId()))); + .setEventSource(quest.getSubQuestId())); }); return true; diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 9f8774e5c..f24584c82 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -41,14 +41,15 @@ import emu.grasscutter.server.event.player.PlayerTeleportEvent; import emu.grasscutter.server.packet.send.*; import emu.grasscutter.utils.objects.KahnsSort; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import lombok.Getter; +import lombok.Setter; +import lombok.val; + +import javax.annotation.Nullable; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; -import javax.annotation.Nullable; -import lombok.Getter; -import lombok.Setter; -import lombok.val; public final class Scene { @Getter private final World world; @@ -78,6 +79,8 @@ public final class Scene { @Getter private int tickCount = 0; @Getter private boolean isPaused = false; + @Getter private GameEntity sceneEntity; + public Scene(World world, SceneData sceneData) { this.world = world; this.sceneData = sceneData; @@ -98,6 +101,7 @@ public final class Scene { this.scriptManager = new SceneScriptManager(this); this.blossomManager = new BlossomManager(this); this.unlockedForces = new HashSet<>(); + this.sceneEntity = new EntityScene(this); } public int getId() { @@ -113,7 +117,25 @@ public final class Scene { } public GameEntity getEntityById(int id) { - return this.entities.get(id); + // Check if the scene's entity ID is referenced. + if (id == 0x13800001) return this.sceneEntity; + else if (id == this.getWorld().getLevelEntityId()) + return this.getWorld().getEntity(); + + var teamEntityPlayer = players.stream().filter(p -> p.getTeamManager().getEntity().getId() == id).findAny(); + if(teamEntityPlayer.isPresent()) + return teamEntityPlayer.get().getTeamManager().getEntity(); + + var entity = this.entities.get(id); + if (entity == null && (id >> 24) == EntityType.Avatar.getValue()) { + for (var player : this.getPlayers()) { + for (var avatar : player.getTeamManager().getActiveTeam()) { + if (avatar.getId() == id) return avatar; + } + } + } + + return entity; } public GameEntity getEntityByConfigId(int configId) { @@ -338,6 +360,14 @@ public final class Scene { addEntities(entities, VisionType.VISION_TYPE_BORN); } + public void updateEntity(GameEntity entity) { + this.broadcastPacket(new PacketSceneEntityUpdateNotify(entity)); + } + + public void updateEntity(GameEntity entity, VisionType type) { + this.broadcastPacket(new PacketSceneEntityUpdateNotify(Arrays.asList(entity), type)); + } + private static List> chopped(List list, final int L) { List> parts = new ArrayList>(); final int N = list.size(); diff --git a/src/main/java/emu/grasscutter/game/world/World.java b/src/main/java/emu/grasscutter/game/world/World.java index 18ca8c0a6..025f49341 100644 --- a/src/main/java/emu/grasscutter/game/world/World.java +++ b/src/main/java/emu/grasscutter/game/world/World.java @@ -1,9 +1,9 @@ package emu.grasscutter.game.world; -import static emu.grasscutter.server.event.player.PlayerTeleportEvent.TeleportType.SCRIPT; - import emu.grasscutter.data.GameData; import emu.grasscutter.data.excels.dungeon.DungeonData; +import emu.grasscutter.game.entity.EntityTeam; +import emu.grasscutter.game.entity.EntityWorld; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player.SceneLoadState; import emu.grasscutter.game.props.EnterReason; @@ -23,22 +23,24 @@ import emu.grasscutter.utils.ConversionUtils; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.stream.Collectors; import lombok.Getter; import lombok.val; import org.jetbrains.annotations.NotNull; -public final class World implements Iterable { +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import static emu.grasscutter.server.event.player.PlayerTeleportEvent.TeleportType.SCRIPT; + +public class World implements Iterable { @Getter private final GameServer server; @Getter private final Player host; @Getter private final List players; @Getter private final Int2ObjectMap scenes; - @Getter private int levelEntityId; + @Getter private EntityWorld entity; private int nextEntityId = 0; private int nextPeerId = 0; private int worldLevel; @@ -60,7 +62,8 @@ public final class World implements Iterable { this.players = Collections.synchronizedList(new ArrayList<>()); this.scenes = Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap<>()); - this.levelEntityId = this.getNextEntityId(EntityIdType.MPLEVEL); + // this.levelEntityId = this.getNextEntityId(EntityIdType.MPLEVEL); + this.entity = new EntityWorld(this); this.worldLevel = player.getWorldLevel(); this.isMultiplayer = isMultiplayer; @@ -70,6 +73,10 @@ public final class World implements Iterable { this.host.getServer().registerWorld(this); } + public int getLevelEntityId() { + return entity.getId(); + } + /** * Gets the peer ID of the world's host. * @@ -116,7 +123,7 @@ public final class World implements Iterable { } public int getPlayerCount() { - return this.getPlayers().size(); + return this.players.size(); } /** @@ -146,7 +153,8 @@ public final class World implements Iterable { // Set player variables player.setPeerId(this.getNextPeerId()); - player.getTeamManager().setEntityId(this.getNextEntityId(EntityIdType.TEAM)); + player.getTeamManager().setEntity(new EntityTeam(player)); + //player.getTeamManager().setEntityId(this.getNextEntityId(EntityIdType.TEAM)); // Copy main team to multiplayer team if (this.isMultiplayer()) { @@ -174,9 +182,12 @@ public final class World implements Iterable { player.sendPacket( new PacketDelTeamEntityNotify( player.getSceneId(), - this.getPlayers().stream() - .map(p -> p.getTeamManager().getEntityId()) - .collect(Collectors.toList()))); + this.getPlayers().stream() + .map(p -> + p.getTeamManager().getEntity() == null ? 0 : + p.getTeamManager().getEntity().getId()).toList() + ) + ); // Deregister this.getPlayers().remove(player); diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java index d871fe25f..5860f5da9 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -11,6 +11,7 @@ import emu.grasscutter.game.entity.gadget.GadgetWorktop; import emu.grasscutter.game.entity.gadget.platform.ConfigRoute; import emu.grasscutter.game.entity.gadget.platform.PointArrayRoute; import emu.grasscutter.game.props.ClimateType; +import emu.grasscutter.game.props.EntityIdType; import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.quest.enums.QuestCond; import emu.grasscutter.game.quest.enums.QuestContent; @@ -459,9 +460,9 @@ public class ScriptLib { private void printLog(String source, String msg){ var currentGroup = this.currentGroup.getIfExists(); if(currentGroup!=null) { - logger.debug("[LUA] {} {} {}", source, currentGroup.id, msg); + Grasscutter.getLogger().warn("[LUA] {} {} {}", source, currentGroup.id, msg); } else { - logger.debug("[LUA] {} {}", source, msg); + Grasscutter.getLogger().warn("[LUA] {} {}", source, msg); } } @@ -490,9 +491,16 @@ public class ScriptLib { public int SetMonsterBattleByGroup(int configId, int groupId) { logger.debug("[LUA] Call SetMonsterBattleByGroup with {} {}", configId,groupId); - // TODO implement scene50008_group250008057.lua uses incomplete group numbers - return 0; - } + // TODO implement scene50008_group250008057.lua uses incomplete group numbers + + // -> MonsterForceAlertNotify + var entity = getSceneScriptManager().getScene().getEntityByConfigId(configId, groupId); + if (entity instanceof EntityMonster monster) { + this.getSceneScriptManager().getScene().broadcastPacket(new PacketMonsterForceAlertNotify(monster.getId())); + } + + return 0; + } public int CauseDungeonFail(){ logger.debug("[LUA] Call CauseDungeonFail with"); @@ -809,7 +817,7 @@ public class ScriptLib { } public int IsPlayerAllAvatarDie(int sceneUid){ logger.warn("[LUA] Call unimplemented IsPlayerAllAvatarDie {}", sceneUid); - var playerEntities = getSceneScriptManager().getScene().getEntities().values().stream().filter(e -> e.getEntityType() == EntityType.Avatar.getValue()).toList(); + var playerEntities = getSceneScriptManager().getScene().getEntities().values().stream().filter(e -> e.getEntityType() == EntityIdType.AVATAR.getId()).toList(); for (GameEntity p : playerEntities){ var player = (EntityAvatar)p; if(player.isAlive()){ @@ -1581,14 +1589,23 @@ public class ScriptLib { return gadget.getGroupId(); } + public int SetGadgetEnableInteract(int groupId, int configId, boolean enable) { + EntityGadget gadget = getCurrentEntityGadget(); + if(gadget.getGroupId() != groupId || gadget.getConfigId() != configId) return -1; + + gadget.setInteractEnabled(enable); + + return 0; + } + public int[] GetGatherConfigIdList() { EntityGadget gadget = getCurrentEntityGadget(); - GameEntity[] children = (GameEntity[]) gadget.getChildren().toArray(); + var children = gadget.getChildren(); - int[] configIds = new int[children.length + 1]; - for(int i = 0; i < children.length; i++) { - configIds[i] = children[i].getConfigId(); + int[] configIds = new int[children.size()]; + for(int i = 0; i < children.size(); i++) { + configIds[i] = children.get(i).getConfigId(); } return configIds; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java b/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java index 800619d9b..4763633fe 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java @@ -3,6 +3,8 @@ package emu.grasscutter.scripts.data; import lombok.Setter; import lombok.ToString; +import java.util.List; + @ToString @Setter public class SceneMonster extends SceneObject { @@ -13,4 +15,6 @@ public class SceneMonster extends SceneObject { public int title_id; public int special_name_id; public String drop_tag; + public List affix; + public boolean isElite; } diff --git a/src/main/java/emu/grasscutter/scripts/data/ScriptArgs.java b/src/main/java/emu/grasscutter/scripts/data/ScriptArgs.java index 73f266fe9..c675a8162 100644 --- a/src/main/java/emu/grasscutter/scripts/data/ScriptArgs.java +++ b/src/main/java/emu/grasscutter/scripts/data/ScriptArgs.java @@ -79,6 +79,11 @@ public class ScriptArgs { return this; } + public ScriptArgs setEventSource(int source) { + this.source = Integer.toString(source); + return this; + } + public int getGroupId() { return group_id; } diff --git a/src/main/java/emu/grasscutter/scripts/serializer/LuaTableJacksonSerializer.java b/src/main/java/emu/grasscutter/scripts/serializer/LuaTableJacksonSerializer.java index 42c0ce4fc..224a9b7ef 100644 --- a/src/main/java/emu/grasscutter/scripts/serializer/LuaTableJacksonSerializer.java +++ b/src/main/java/emu/grasscutter/scripts/serializer/LuaTableJacksonSerializer.java @@ -5,29 +5,32 @@ import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.type.CollectionType; import com.fasterxml.jackson.databind.type.MapType; import emu.grasscutter.Grasscutter; -import java.io.IOException; -import java.util.*; import org.luaj.vm2.LuaTable; import org.luaj.vm2.LuaValue; +import java.io.IOException; +import java.util.*; + public class LuaTableJacksonSerializer extends JsonSerializer implements Serializer { private static ObjectMapper objectMapper; public LuaTableJacksonSerializer() { if (objectMapper == null) { - objectMapper = new ObjectMapper(); - objectMapper.configure(MapperFeature.PROPAGATE_TRANSIENT_MARKER, true); - // Some properties in Lua table but not in java field - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + objectMapper = JsonMapper.builder() + .configure(MapperFeature.PROPAGATE_TRANSIENT_MARKER, true) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .build(); objectMapper .configOverride(List.class) .setSetterInfo(JsonSetter.Value.forContentNulls(Nulls.AS_EMPTY)); - SimpleModule luaSerializeModule = new SimpleModule(); + + var luaSerializeModule = new SimpleModule(); luaSerializeModule.addSerializer(LuaTable.class, this); objectMapper.registerModule(luaSerializeModule); } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerClientAbilitiesInitFinishCombineNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerClientAbilitiesInitFinishCombineNotify.java new file mode 100644 index 000000000..c3d8e4bc1 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerClientAbilitiesInitFinishCombineNotify.java @@ -0,0 +1,36 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry; +import emu.grasscutter.net.proto.ClientAbilitiesInitFinishCombineNotifyOuterClass.ClientAbilitiesInitFinishCombineNotify; +import emu.grasscutter.net.proto.EntityAbilityInvokeEntryOuterClass.EntityAbilityInvokeEntry; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.utils.Utils; + +@Opcodes(PacketOpcodes.ClientAbilitiesInitFinishCombineNotify) +public class HandlerClientAbilitiesInitFinishCombineNotify extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + ClientAbilitiesInitFinishCombineNotify notif = ClientAbilitiesInitFinishCombineNotify.parseFrom(payload); + + Player player = session.getPlayer(); + + // Call skill end in the player's ability manager. + player.getAbilityManager().onSkillEnd(player); + + for(EntityAbilityInvokeEntry entry : notif.getEntityInvokeListList()) { + for (AbilityInvokeEntry ability : entry.getInvokesList()) { + player.getAbilityManager().onAbilityInvoke(ability); + player.getClientAbilityInitFinishHandler().addEntry(ability.getForwardType(), ability); + } + + if (entry.getInvokesList().size() > 0) { + session.getPlayer().getClientAbilityInitFinishHandler().update(session.getPlayer()); + } + } + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerMonsterAlertChangeNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerMonsterAlertChangeNotify.java new file mode 100644 index 000000000..0d9759e66 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerMonsterAlertChangeNotify.java @@ -0,0 +1,35 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.game.entity.EntityMonster; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.MonsterAlertChangeNotifyOuterClass.MonsterAlertChangeNotify; +import emu.grasscutter.scripts.constants.EventType; +import emu.grasscutter.scripts.data.ScriptArgs; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.MonsterAlertChangeNotify) +public class HandlerMonsterAlertChangeNotify extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + var notify = MonsterAlertChangeNotify.parseFrom(payload); + + var player = session.getPlayer(); + + if(notify.getIsAlert() != 0) { + for (var monsterId : notify.getMonsterEntityListList()) { + var monster = (EntityMonster)player.getScene().getEntityById(monsterId); + if(monster != null && monster.getPlayerOnBattle().isEmpty()) { + monster.getScene().getScriptManager().callEvent(new ScriptArgs(monster.getGroupId(), EventType.EVENT_MONSTER_BATTLE, monster.getConfigId())); + } + + if(monster != null) monster.getPlayerOnBattle().add(player); + } + } + + //TODO: Research invisible monsters + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketClientAbilitiesInitFinishCombineNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketClientAbilitiesInitFinishCombineNotify.java new file mode 100644 index 000000000..11a429210 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketClientAbilitiesInitFinishCombineNotify.java @@ -0,0 +1,21 @@ +package emu.grasscutter.server.packet.send; + +import java.util.List; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ClientAbilitiesInitFinishCombineNotifyOuterClass.ClientAbilitiesInitFinishCombineNotify; +import emu.grasscutter.net.proto.EntityAbilityInvokeEntryOuterClass.EntityAbilityInvokeEntry; + +public class PacketClientAbilitiesInitFinishCombineNotify extends BasePacket { + + public PacketClientAbilitiesInitFinishCombineNotify(List entries) { + super(PacketOpcodes.ClientAbilitiesInitFinishCombineNotify, true); + + ClientAbilitiesInitFinishCombineNotify proto = ClientAbilitiesInitFinishCombineNotify.newBuilder() + .addAllEntityInvokeList(entries) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGadgetStateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGadgetStateNotify.java index 1b46c224f..eacfb79b3 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketGadgetStateNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGadgetStateNotify.java @@ -14,7 +14,7 @@ public class PacketGadgetStateNotify extends BasePacket { GadgetStateNotify.newBuilder() .setGadgetEntityId(gadget.getId()) .setGadgetState(newState) - .setIsEnableInteract(true) + .setIsEnableInteract(gadget.isInteractEnabled()) .build(); this.setData(proto); diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketMonsterForceAlertNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketMonsterForceAlertNotify.java new file mode 100644 index 000000000..ec641cd80 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketMonsterForceAlertNotify.java @@ -0,0 +1,23 @@ +package emu.grasscutter.server.packet.send; + +import java.util.Map; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.MonsterForceAlertNotifyOuterClass.MonsterForceAlertNotify; + +//Sets openState to value +public class PacketMonsterForceAlertNotify extends BasePacket { + + public PacketMonsterForceAlertNotify(int monsterId) { + super(PacketOpcodes.MonsterForceAlertNotify); + + MonsterForceAlertNotify proto = MonsterForceAlertNotify.newBuilder() + .setMonsterEntityId(monsterId) + .build(); + + this.setData(proto); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerEnterSceneInfoNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerEnterSceneInfoNotify.java index 988b6322d..86178dc59 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerEnterSceneInfoNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerEnterSceneInfoNotify.java @@ -26,7 +26,7 @@ public class PacketPlayerEnterSceneInfoNotify extends BasePacket { proto.setTeamEnterInfo( TeamEnterSceneInfo.newBuilder() - .setTeamEntityId(player.getTeamManager().getEntityId()) // 150995833 + .setTeamEntityId(player.getTeamManager().getEntity().getId()) // 150995833 .setTeamAbilityInfo(empty) .setAbilityControlBlock( AbilityControlBlockOuterClass.AbilityControlBlock.newBuilder().build())); diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSceneEntityUpdateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneEntityUpdateNotify.java new file mode 100644 index 000000000..8787fceb0 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneEntityUpdateNotify.java @@ -0,0 +1,49 @@ +package emu.grasscutter.server.packet.send; + +import java.util.Collection; + +import emu.grasscutter.game.entity.GameEntity; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SceneEntityUpdateNotifyOuterClass.SceneEntityUpdateNotify; +import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType; + +public class PacketSceneEntityUpdateNotify extends BasePacket { + + public PacketSceneEntityUpdateNotify(GameEntity entity) { + super(PacketOpcodes.SceneEntityUpdateNotify, true); + + SceneEntityUpdateNotify.Builder proto = SceneEntityUpdateNotify.newBuilder() + .setAppearType(VisionType.VISION_TYPE_BORN) + .addEntityList(entity.toProto()); + + this.setData(proto.build()); + } + + public PacketSceneEntityUpdateNotify(GameEntity entity, VisionType vision, int param) { + super(PacketOpcodes.SceneEntityUpdateNotify, true); + + SceneEntityUpdateNotify.Builder proto = SceneEntityUpdateNotify.newBuilder() + .setAppearType(vision) + .setParam(param) + .addEntityList(entity.toProto()); + + this.setData(proto.build()); + } + + public PacketSceneEntityUpdateNotify(Player player) { + this(player.getTeamManager().getCurrentAvatarEntity()); + } + + public PacketSceneEntityUpdateNotify(Collection entities, VisionType visionType) { + super(PacketOpcodes.SceneEntityUpdateNotify, true); + + SceneEntityUpdateNotify.Builder proto = SceneEntityUpdateNotify.newBuilder() + .setAppearType(visionType); + + entities.forEach(e -> proto.addEntityList(e.toProto())); + + this.setData(proto.build()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSyncTeamEntityNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSyncTeamEntityNotify.java index 33ba6f2c6..55bbec61a 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketSyncTeamEntityNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSyncTeamEntityNotify.java @@ -16,7 +16,7 @@ public class PacketSyncTeamEntityNotify extends BasePacket { SyncTeamEntityNotify.newBuilder().setSceneId(player.getSceneId()); if (player.getWorld().isMultiplayer()) { - for (Player p : player.getWorld().getPlayers()) { + for (var p : player.getWorld()) { // Skip if same player if (player == p) { continue; @@ -25,7 +25,7 @@ public class PacketSyncTeamEntityNotify extends BasePacket { // Set info TeamEntityInfo info = TeamEntityInfo.newBuilder() - .setTeamEntityId(p.getTeamManager().getEntityId()) + .setTeamEntityId(p.getTeamManager().getEntity().getId()) .setAuthorityPeerId(p.getPeerId()) .setTeamAbilityInfo(AbilitySyncStateInfo.newBuilder()) .build(); diff --git a/src/main/java/emu/grasscutter/utils/JsonAdapters.java b/src/main/java/emu/grasscutter/utils/JsonAdapters.java index ead308018..9410d1fae 100644 --- a/src/main/java/emu/grasscutter/utils/JsonAdapters.java +++ b/src/main/java/emu/grasscutter/utils/JsonAdapters.java @@ -13,12 +13,13 @@ import emu.grasscutter.game.world.Position; import it.unimi.dsi.fastutil.floats.FloatArrayList; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; +import lombok.val; + import java.io.IOException; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.Objects; -import lombok.val; public interface JsonAdapters { class DynamicFloatAdapter extends TypeAdapter { @@ -180,15 +181,16 @@ public interface JsonAdapters { default -> false; }) { // System.out.println("Enum value field found - " + f.getName()); - boolean acc = f.isAccessible(); - f.setAccessible(true); try { - for (val constant : enumConstants) + for (var constant : enumConstants) { + var accessible = f.canAccess(constant); + f.setAccessible(true); map.put(String.valueOf(f.getInt(constant)), constant); + f.setAccessible(accessible); + } } catch (IllegalAccessException e) { // System.out.println("Failed to access enum id field."); } - f.setAccessible(acc); break; } } diff --git a/src/main/java/emu/grasscutter/utils/JsonUtils.java b/src/main/java/emu/grasscutter/utils/JsonUtils.java index 5ecc30b07..4c55eb283 100644 --- a/src/main/java/emu/grasscutter/utils/JsonUtils.java +++ b/src/main/java/emu/grasscutter/utils/JsonUtils.java @@ -116,6 +116,12 @@ public final class JsonUtils { } } + public static Map loadToMap(Path filename, Class keyType, Type valueType) throws IOException { + try (var fileReader = Files.newBufferedReader(filename, StandardCharsets.UTF_8)) { + return gson.fromJson(fileReader, TypeToken.getParameterized(Map.class, keyType, valueType).getType()); + } + } + /** * Safely JSON decodes a given string. * diff --git a/src/main/java/emu/grasscutter/utils/TsvUtils.java b/src/main/java/emu/grasscutter/utils/TsvUtils.java index 53f236037..2ded73fc1 100644 --- a/src/main/java/emu/grasscutter/utils/TsvUtils.java +++ b/src/main/java/emu/grasscutter/utils/TsvUtils.java @@ -1,7 +1,5 @@ package emu.grasscutter.utils; -import static emu.grasscutter.utils.Utils.nonRegexSplit; - import com.google.gson.*; import com.google.gson.annotations.SerializedName; import emu.grasscutter.Grasscutter; @@ -9,6 +7,8 @@ import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap; import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap; import it.unimi.dsi.fastutil.objects.Object2IntArrayMap; +import lombok.val; + import java.io.IOException; import java.lang.reflect.*; import java.nio.charset.StandardCharsets; @@ -19,7 +19,8 @@ import java.util.*; import java.util.function.Function; import java.util.stream.IntStream; import java.util.stream.Stream; -import lombok.val; + +import static emu.grasscutter.utils.Utils.nonRegexSplit; // Throughout this file, commented System.out.println debug log calls are left in. // This is because the default logger will deadlock when operating on parallel streams. @@ -126,14 +127,16 @@ public final class TsvUtils { default -> false; }) { // System.out.println("Enum value field found - " + f.getName()); - boolean acc = f.isAccessible(); - f.setAccessible(true); try { - for (val constant : enumConstants) map.put(String.valueOf(f.getInt(constant)), constant); + for (var constant : enumConstants) { + var accessible = f.canAccess(constant); + f.setAccessible(true); + map.put(String.valueOf(f.getInt(constant)), constant); + f.setAccessible(accessible); + } } catch (IllegalAccessException e) { // System.out.println("Failed to access enum id field."); } - f.setAccessible(acc); break; } }