diff --git a/src/main/java/emu/grasscutter/GameConstants.java b/src/main/java/emu/grasscutter/GameConstants.java index f7bdd506e..b07602429 100644 --- a/src/main/java/emu/grasscutter/GameConstants.java +++ b/src/main/java/emu/grasscutter/GameConstants.java @@ -35,7 +35,6 @@ public final class GameConstants { "Avatar_FallAnthem_Achievement_Listener", "GrapplingHookSkill_Ability", "Avatar_PlayerBoy_DiveStamina_Reduction", - "Ability_Avatar_Dive_Team", "Ability_Avatar_Dive_SealEcho", "Absorb_SealEcho_Bullet_01", "Absorb_SealEcho_Bullet_02", @@ -43,6 +42,9 @@ public final class GameConstants { "ActivityAbility_Absorb_Shoot", "SceneAbility_DiveVolume" }; + public static final String[] DEFAULT_TEAM_ABILITY_STRINGS = { + "Ability_Avatar_Dive_Team" + }; public static final SparseSet ILLEGAL_WEAPONS = new SparseSet(""" 10000-10008, 11411, 11506-11508, 12505, 12506, 12508, 12509, 13503, 13506, 14411, 14503, 14505, 14508, 15504-15506 diff --git a/src/main/java/emu/grasscutter/command/commands/SetPropCommand.java b/src/main/java/emu/grasscutter/command/commands/SetPropCommand.java index 5e5e779ce..07c082e7d 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetPropCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetPropCommand.java @@ -86,6 +86,12 @@ public final class SetPropCommand implements CommandHandler { this.props.put("fly", flyable); this.props.put("glider", flyable); this.props.put("canglide", flyable); + + Prop dive = new Prop("CanDive", PlayerProperty.PROP_PLAYER_CAN_DIVE, PseudoProp.CAN_DIVE); + this.props.put("dive", dive); + this.props.put("swim", dive); + this.props.put("water", dive); + this.props.put("candive", dive); } @Override @@ -129,6 +135,7 @@ public final class SetPropCommand implements CommandHandler { case SET_OPENSTATE -> this.setOpenState(targetPlayer, value, 1); case UNSET_OPENSTATE -> this.setOpenState(targetPlayer, value, 0); case UNLOCK_MAP -> unlockMap(targetPlayer, value); + case CAN_DIVE -> canDive(targetPlayer, value); default -> targetPlayer.setProperty(prop.prop, value); }; @@ -219,6 +226,20 @@ public final class SetPropCommand implements CommandHandler { return true; } + private boolean canDive(Player targetPlayer, int value) { + // allow diving and set max stamina OR not + if (value == 0) { + targetPlayer.setProperty(PlayerProperty.PROP_PLAYER_CAN_DIVE, 0); + targetPlayer.setProperty(PlayerProperty.PROP_DIVE_MAX_STAMINA, 0); + targetPlayer.setProperty(PlayerProperty.PROP_DIVE_CUR_STAMINA, 0); + } else { + targetPlayer.setProperty(PlayerProperty.PROP_PLAYER_CAN_DIVE, 1); + targetPlayer.setProperty(PlayerProperty.PROP_DIVE_MAX_STAMINA, 10000); + targetPlayer.setProperty(PlayerProperty.PROP_DIVE_CUR_STAMINA, 10000); + } + return true; + } + private boolean unlockMap(Player targetPlayer, int value) { // Unlock. GameData.getScenePointsPerScene() @@ -270,7 +291,8 @@ public final class SetPropCommand implements CommandHandler { SET_OPENSTATE, UNSET_OPENSTATE, UNLOCK_MAP, - IS_FLYABLE + IS_FLYABLE, + CAN_DIVE } static class Prop { diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index d0f6e3597..3789f1183 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -579,14 +579,20 @@ public class Player implements PlayerHook, FieldFetch { this.setOrFetch(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, 50); this.setOrFetch(PlayerProperty.PROP_IS_FLYABLE, withQuesting ? 0 : 1); + this.setOrFetch(PlayerProperty.PROP_PLAYER_CAN_DIVE, + withQuesting ? 0 : 1); this.setOrFetch(PlayerProperty.PROP_IS_TRANSFERABLE, 1); this.setOrFetch(PlayerProperty.PROP_MAX_STAMINA, withQuesting ? 10000 : 24000); + this.setOrFetch(PlayerProperty.PROP_DIVE_MAX_STAMINA, + withQuesting ? 10000 : 0); this.setOrFetch(PlayerProperty.PROP_PLAYER_RESIN, 160); // The player's current stamina is always their max stamina. this.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, this.getProperty(PlayerProperty.PROP_MAX_STAMINA)); + this.setProperty(PlayerProperty.PROP_DIVE_CUR_STAMINA, + this.getProperty(PlayerProperty.PROP_DIVE_MAX_STAMINA)); } /** @@ -1515,7 +1521,7 @@ public class Player implements PlayerHook, FieldFetch { int min = this.getPropertyMin(prop); int max = this.getPropertyMax(prop); if (min <= value && value <= max) { - int currentValue = this.properties.get(prop.getId()); + int currentValue = this.properties.getOrDefault(prop.getId(), 0); this.properties.put(prop.getId(), value); if (sendPacket) { // Send property change reasons if needed. diff --git a/src/main/java/emu/grasscutter/game/player/TeamManager.java b/src/main/java/emu/grasscutter/game/player/TeamManager.java index fc02bdacd..c25b95bcc 100644 --- a/src/main/java/emu/grasscutter/game/player/TeamManager.java +++ b/src/main/java/emu/grasscutter/game/player/TeamManager.java @@ -1,53 +1,48 @@ 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; -import emu.grasscutter.Grasscutter; +import dev.morphia.annotations.*; +import emu.grasscutter.*; import emu.grasscutter.data.GameData; 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; -import emu.grasscutter.game.world.Position; -import emu.grasscutter.game.world.Scene; -import emu.grasscutter.game.world.World; -import emu.grasscutter.net.packet.BasePacket; -import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.game.entity.*; +import emu.grasscutter.game.props.*; +import emu.grasscutter.game.world.*; +import emu.grasscutter.net.packet.*; +import emu.grasscutter.net.proto.*; import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType; import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType; import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode; import emu.grasscutter.net.proto.TrialAvatarGrantRecordOuterClass.TrialAvatarGrantRecord.GrantReason; -import emu.grasscutter.net.proto.VisionTypeOuterClass; import emu.grasscutter.server.event.entity.EntityCreationEvent; import emu.grasscutter.server.event.player.PlayerTeamDeathEvent; import emu.grasscutter.server.packet.send.*; import emu.grasscutter.utils.Utils; -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.ints.*; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import lombok.*; + import java.util.*; import java.util.stream.Stream; -import lombok.Getter; -import lombok.Setter; -import lombok.val; + +import static emu.grasscutter.config.Configuration.GAME_OPTIONS; @Entity public final class TeamManager extends BasePlayerDataManager { @Transient private final List avatars; @Transient @Getter private final Set gadgets; @Transient @Getter private final IntSet teamResonances; - @Transient @Getter private final IntSet teamResonancesConfig; + @Transient + @Getter + private final IntSet teamResonancesConfig; + @Transient + @Getter + @Setter + private Set teamAbilityEmbryos; // This needs to be a LinkedHashMap to guarantee insertion order. - @Getter private LinkedHashMap teams; + @Getter + private LinkedHashMap teams; private int currentTeamIndex; @Getter @Setter private int currentCharacterIndex; @Transient @Getter @Setter private TeamInfo mpTeam; @@ -69,6 +64,7 @@ public final class TeamManager extends BasePlayerDataManager { this.gadgets = new HashSet<>(); this.teamResonances = new IntOpenHashSet(); this.teamResonancesConfig = new IntOpenHashSet(); + this.teamAbilityEmbryos = new HashSet<>(); this.trialAvatars = new HashMap<>(); this.trialAvatarTeam = new TeamInfo(); } @@ -84,6 +80,42 @@ public final class TeamManager extends BasePlayerDataManager { } } + // Add team ability embryos, NOT to be confused with avatarAbilties. + // These should include the ones in LevelEntity (according to levelEntityConfig field in sceneId) + // rn only apply to big world defaults, but will fix scaramouch domain circles (BinOutput/LevelEntity/Level_Monster_Nada_setting) + public AbilityControlBlockOuterClass.AbilityControlBlock getAbilityControlBlock() { + AbilityControlBlockOuterClass.AbilityControlBlock.Builder abilityControlBlock = AbilityControlBlockOuterClass.AbilityControlBlock.newBuilder(); + int embryoId = 0; + + // add from default + if (Arrays.stream(GameConstants.DEFAULT_TEAM_ABILITY_STRINGS).count() > 0) { + List teamAbilties = Arrays.stream(GameConstants.DEFAULT_TEAM_ABILITY_STRINGS).toList(); + for (String skill : teamAbilties) { + AbilityEmbryoOuterClass.AbilityEmbryo emb = AbilityEmbryoOuterClass.AbilityEmbryo.newBuilder() + .setAbilityId(++embryoId) + .setAbilityNameHash(Utils.abilityHash(skill)) + .setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME) + .build(); + abilityControlBlock.addAbilityEmbryoList(emb); + } + } + + // same as avatar ability hash (add frm levelEntityConfig data) + if (this.getTeamAbilityEmbryos().size() > 0) { + for (String skill : this.getTeamAbilityEmbryos()) { + AbilityEmbryoOuterClass.AbilityEmbryo emb = AbilityEmbryoOuterClass.AbilityEmbryo.newBuilder() + .setAbilityId(++embryoId) + .setAbilityNameHash(Utils.abilityHash(skill)) + .setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME) + .build(); + abilityControlBlock.addAbilityEmbryoList(emb); + } + } + + // return block to add + return abilityControlBlock.build(); + } + public World getWorld() { return this.getPlayer().getWorld(); } diff --git a/src/main/java/emu/grasscutter/game/props/PlayerProperty.java b/src/main/java/emu/grasscutter/game/props/PlayerProperty.java index 2646842aa..fed9065c2 100644 --- a/src/main/java/emu/grasscutter/game/props/PlayerProperty.java +++ b/src/main/java/emu/grasscutter/game/props/PlayerProperty.java @@ -1,10 +1,10 @@ package emu.grasscutter.game.props; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import java.util.stream.Stream; +import it.unimi.dsi.fastutil.ints.*; import lombok.Getter; +import java.util.stream.Stream; + public enum PlayerProperty { PROP_NONE(0), PROP_EXP(1001, 0), @@ -60,7 +60,10 @@ public enum PlayerProperty { PROP_IS_AUTO_UNLOCK_SPECIFIC_EQUIP(10044), // New; unknown/un-used. PROP_PLAYER_GCG_COIN(10045), // New; unknown/un-used. PROP_PLAYER_WAIT_SUB_GCG_COIN(10046), // New; unknown/un-used. - PROP_PLAYER_ONLINE_TIME(10047); // New; unknown/un-used. + PROP_PLAYER_ONLINE_TIME(10047), // New; unknown/un-used. + PROP_PLAYER_CAN_DIVE(10048, 0, 1), // Can the player dive? [0, 1] + PROP_DIVE_MAX_STAMINA(10049, 0, 10000), // The maximum stamina of the player when diving. [0, 10000] + PROP_DIVE_CUR_STAMINA(10050, 0, 10000); // The current stamina of the player when diving. [0, 10000] private static final int inf = Integer.MAX_VALUE; // Maybe this should be something else? private static final Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); 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 86178dc59..2962d196d 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerEnterSceneInfoNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerEnterSceneInfoNotify.java @@ -3,9 +3,7 @@ package emu.grasscutter.server.packet.send; import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.player.Player; -import emu.grasscutter.net.packet.BasePacket; -import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.AbilityControlBlockOuterClass; +import emu.grasscutter.net.packet.*; import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; import emu.grasscutter.net.proto.AvatarEnterSceneInfoOuterClass.AvatarEnterSceneInfo; import emu.grasscutter.net.proto.MPLevelEntityInfoOuterClass.MPLevelEntityInfo; @@ -28,8 +26,7 @@ public class PacketPlayerEnterSceneInfoNotify extends BasePacket { TeamEnterSceneInfo.newBuilder() .setTeamEntityId(player.getTeamManager().getEntity().getId()) // 150995833 .setTeamAbilityInfo(empty) - .setAbilityControlBlock( - AbilityControlBlockOuterClass.AbilityControlBlock.newBuilder().build())); + .setAbilityControlBlock(player.getTeamManager().getAbilityControlBlock())); proto.setMpLevelEntityInfo( MPLevelEntityInfo.newBuilder() .setEntityId(player.getWorld().getLevelEntityId()) // 184550274