Compare commits

..

5 Commits

Author SHA1 Message Date
Melledy
ac3214f10a Refactor gacha banner proto creation to not do a lookup on the database 2022-06-16 08:18:24 -07:00
CamChua_VN
8f4f1887d9
Update Epitomized Path (#1254)
* Update Epitomized Path

* Update Epitomized Path

* Update Epitomized Path

* Refactor doRarePull

* Update Epitomized Path

Co-authored-by: AnimeGitUserB <AnimeGitUserB@bigblueball.in>
2022-06-16 08:01:27 -07:00
4Benj_
802fb09e1d
Custom Permission Handler (#1282)
Co-authored-by: Melledy <52122272+Melledy@users.noreply.github.com>
2022-06-16 08:00:03 -07:00
Melledy
0e6e950734 Cleanup package names 2022-06-16 07:57:57 -07:00
Melledy
8fdf9bfddf Add embryos for all skill depots for the main characters 2022-06-16 07:54:53 -07:00
51 changed files with 1725 additions and 1340 deletions

View File

@ -0,0 +1,12 @@
syntax = "proto3";
option java_package = "emu.grasscutter.net.proto";
// CmdId: 1706
// EnetChannelId: 0
// EnetIsReliable: true
// IsAllowClient: true
message AvatarChangeElementTypeReq {
uint32 scene_id = 15;
uint32 area_id = 4;
}

View File

@ -0,0 +1,10 @@
syntax = "proto3";
option java_package = "emu.grasscutter.net.proto";
// CmdId: 1708
// EnetChannelId: 0
// EnetIsReliable: true
message AvatarChangeElementTypeRsp {
int32 retcode = 15;
}

View File

@ -0,0 +1,17 @@
syntax = "proto3";
option java_package = "emu.grasscutter.net.proto";
// CmdId: 1037
// EnetChannelId: 0
// EnetIsReliable: true
message AvatarSkillDepotChangeNotify {
uint64 avatar_guid = 2;
uint32 entity_id = 8;
uint32 skill_depot_id = 9;
repeated uint32 talent_id_list = 1;
repeated uint32 proud_skill_list = 5;
uint32 core_proud_skill_level = 4;
map<uint32, uint32> skill_level_map = 10;
map<uint32, uint32> proud_skill_extra_level_map = 11;
}

13
proto/GachaWishReq.proto Normal file
View File

@ -0,0 +1,13 @@
syntax = "proto3";
option java_package = "emu.grasscutter.net.proto";
// CmdId: 1532
// EnetChannelId: 0
// EnetIsReliable: true
// IsAllowClient: true
message GachaWishReq {
uint32 gacha_type = 2;
uint32 gacha_schedule_id = 4;
uint32 item_id = 14;
}

15
proto/GachaWishRsp.proto Normal file
View File

@ -0,0 +1,15 @@
syntax = "proto3";
option java_package = "emu.grasscutter.net.proto";
// CmdId: 1517
// EnetChannelId: 0
// EnetIsReliable: true
message GachaWishRsp {
int32 retcode = 7;
uint32 gacha_type = 14;
uint32 gacha_schedule_id = 15;
uint32 wish_item_id = 3;
uint32 wish_progress = 12;
uint32 wish_max_progress = 10;
}

View File

@ -6,8 +6,10 @@ import java.util.Calendar;
import emu.grasscutter.auth.AuthenticationSystem; import emu.grasscutter.auth.AuthenticationSystem;
import emu.grasscutter.auth.DefaultAuthentication; import emu.grasscutter.auth.DefaultAuthentication;
import emu.grasscutter.command.CommandMap; import emu.grasscutter.command.CommandMap;
import emu.grasscutter.game.managers.EnergyManager.EnergyManager; import emu.grasscutter.command.DefaultPermissionHandler;
import emu.grasscutter.game.managers.StaminaManager.StaminaManager; import emu.grasscutter.command.PermissionHandler;
import emu.grasscutter.game.managers.energy.EnergyManager;
import emu.grasscutter.game.managers.stamina.StaminaManager;
import emu.grasscutter.plugin.PluginManager; import emu.grasscutter.plugin.PluginManager;
import emu.grasscutter.plugin.api.ServerHook; import emu.grasscutter.plugin.api.ServerHook;
import emu.grasscutter.scripts.ScriptLoader; import emu.grasscutter.scripts.ScriptLoader;
@ -58,6 +60,7 @@ public final class Grasscutter {
private static GameServer gameServer; private static GameServer gameServer;
private static PluginManager pluginManager; private static PluginManager pluginManager;
private static AuthenticationSystem authenticationSystem; private static AuthenticationSystem authenticationSystem;
private static PermissionHandler permissionHandler;
public static final Reflections reflector = new Reflections("emu.grasscutter"); public static final Reflections reflector = new Reflections("emu.grasscutter");
public static ConfigContainer config; public static ConfigContainer config;
@ -114,8 +117,9 @@ public final class Grasscutter {
// Initialize database. // Initialize database.
DatabaseManager.initialize(); DatabaseManager.initialize();
// Initialize the default authentication system. // Initialize the default systems.
authenticationSystem = new DefaultAuthentication(); authenticationSystem = new DefaultAuthentication();
permissionHandler = new DefaultPermissionHandler();
// Create server instances. // Create server instances.
httpServer = new HttpServer(); httpServer = new HttpServer();
@ -287,6 +291,10 @@ public final class Grasscutter {
return authenticationSystem; return authenticationSystem;
} }
public static PermissionHandler getPermissionHandler() {
return permissionHandler;
}
public static int getCurrentDayOfWeek() { public static int getCurrentDayOfWeek() {
return day; return day;
} }
@ -346,6 +354,14 @@ public final class Grasscutter {
Grasscutter.authenticationSystem = authenticationSystem; Grasscutter.authenticationSystem = authenticationSystem;
} }
/**
* Sets the permission handler for the server.
* @param permissionHandler The permission handler to use.
*/
public static void setPermissionHandler(PermissionHandler permissionHandler) {
Grasscutter.permissionHandler = permissionHandler;
}
/* /*
* Enums for the configuration. * Enums for the configuration.
*/ */

View File

@ -14,6 +14,7 @@ public final class CommandMap {
private final Map<String, Command> annotations = new HashMap<>(); private final Map<String, Command> annotations = new HashMap<>();
private final Map<String, Integer> targetPlayerIds = new HashMap<>(); private final Map<String, Integer> targetPlayerIds = new HashMap<>();
private static final String consoleId = "console"; private static final String consoleId = "console";
public CommandMap() { public CommandMap() {
this(false); this(false);
} }
@ -202,21 +203,9 @@ public final class CommandMap {
} }
} }
// Check for permission. // Check for permissions.
if (player != null) { if (!Grasscutter.getPermissionHandler().checkPermission(player, targetPlayer, this.annotations.get(label).permission(), this.annotations.get(label).permissionTargeted())) {
String permissionNode = this.annotations.get(label).permission(); return;
String permissionNodeTargeted = this.annotations.get(label).permissionTargeted();
Account account = player.getAccount();
if (player != targetPlayer) { // Additional permission required for targeting another player
if (!permissionNodeTargeted.isEmpty() && !account.hasPermission(permissionNodeTargeted)) {
CommandHandler.sendTranslatedMessage(player, "commands.generic.permission_error");
return;
}
}
if (!permissionNode.isEmpty() && !account.hasPermission(permissionNode)) {
CommandHandler.sendTranslatedMessage(player, "commands.generic.permission_error");
return;
}
} }
// Check if command has unfulfilled constraints on targetPlayer // Check if command has unfulfilled constraints on targetPlayer

View File

@ -0,0 +1,32 @@
package emu.grasscutter.command;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.player.Player;
public class DefaultPermissionHandler implements PermissionHandler {
@Override
public boolean EnablePermissionCommand() {
return true;
}
@Override
public boolean checkPermission(Player player, Player targetPlayer, String permissionNode, String permissionNodeTargeted) {
if(player == null) {
return true;
}
Account account = player.getAccount();
if (player != targetPlayer) { // Additional permission required for targeting another player
if (!permissionNodeTargeted.isEmpty() && !account.hasPermission(permissionNodeTargeted)) {
CommandHandler.sendTranslatedMessage(player, "commands.generic.permission_error");
return false;
}
}
if (!permissionNode.isEmpty() && !account.hasPermission(permissionNode)) {
CommandHandler.sendTranslatedMessage(player, "commands.generic.permission_error");
return false;
}
return true;
}
}

View File

@ -0,0 +1,8 @@
package emu.grasscutter.command;
import emu.grasscutter.game.player.Player;
public interface PermissionHandler {
public boolean EnablePermissionCommand();
public boolean checkPermission(Player player, Player targetPlayer, String permissionNode, String permissionNodeTargeted);
}

View File

@ -20,6 +20,11 @@ public final class PermissionCommand implements CommandHandler {
return; return;
} }
if(!Grasscutter.getPermissionHandler().EnablePermissionCommand()) {
CommandHandler.sendTranslatedMessage(sender, "commands.generic.permission_error");
return;
}
String action = args.get(0); String action = args.get(0);
String permission = args.get(1); String permission = args.get(1);

View File

@ -5,7 +5,7 @@ import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.managers.EnergyManager.EnergyManager; import emu.grasscutter.game.managers.energy.EnergyManager;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.player.TeamManager; import emu.grasscutter.game.player.TeamManager;
import emu.grasscutter.game.props.ElementType; import emu.grasscutter.game.props.ElementType;

View File

@ -77,6 +77,7 @@ public class GameData {
private static final ArrayList<CodexReliquaryData> codexReliquaryArrayList = new ArrayList<>(); private static final ArrayList<CodexReliquaryData> codexReliquaryArrayList = new ArrayList<>();
private static final Int2ObjectMap<FetterCharacterCardData> fetterCharacterCardDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<FetterCharacterCardData> fetterCharacterCardDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<RewardData> rewardDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<RewardData> rewardDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<WorldAreaData> worldAreaDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<WorldLevelData> worldLevelDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<WorldLevelData> worldLevelDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<DailyDungeonData> dailyDungeonDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<DailyDungeonData> dailyDungeonDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<DungeonData> dungeonDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<DungeonData> dungeonDataMap = new Int2ObjectOpenHashMap<>();
@ -319,6 +320,10 @@ public class GameData {
public static ArrayList<CodexReliquaryData> getcodexReliquaryArrayList(){return codexReliquaryArrayList;} public static ArrayList<CodexReliquaryData> getcodexReliquaryArrayList(){return codexReliquaryArrayList;}
public static Int2ObjectMap<WorldAreaData> getWorldAreaDataMap() {
return worldAreaDataMap;
}
public static Int2ObjectMap<WorldLevelData> getWorldLevelDataMap() { public static Int2ObjectMap<WorldLevelData> getWorldLevelDataMap() {
return worldLevelDataMap; return worldLevelDataMap;
} }

View File

@ -1,12 +1,16 @@
package emu.grasscutter.data; package emu.grasscutter.data;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import org.danilopianini.util.FlexibleQuadTree; import org.danilopianini.util.FlexibleQuadTree;
import org.danilopianini.util.SpatialIndex; import org.danilopianini.util.SpatialIndex;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.ResourceLoader.AvatarConfig;
import emu.grasscutter.data.ResourceLoader.AvatarConfigAbility;
import emu.grasscutter.data.excels.ReliquaryAffixData; import emu.grasscutter.data.excels.ReliquaryAffixData;
import emu.grasscutter.data.excels.ReliquaryMainPropData; import emu.grasscutter.data.excels.ReliquaryMainPropData;
import emu.grasscutter.game.world.SpawnDataEntry; import emu.grasscutter.game.world.SpawnDataEntry;
@ -19,6 +23,7 @@ public class GameDepot {
private static Int2ObjectMap<WeightedList<ReliquaryMainPropData>> relicMainPropDepot = new Int2ObjectOpenHashMap<>(); private static Int2ObjectMap<WeightedList<ReliquaryMainPropData>> relicMainPropDepot = new Int2ObjectOpenHashMap<>();
private static Int2ObjectMap<List<ReliquaryAffixData>> relicAffixDepot = new Int2ObjectOpenHashMap<>(); private static Int2ObjectMap<List<ReliquaryAffixData>> relicAffixDepot = new Int2ObjectOpenHashMap<>();
private static Map<String, AvatarConfig> playerAbilities = new HashMap<>();
private static Int2ObjectMap<SpatialIndex<SpawnGroupEntry>> spawnLists = new Int2ObjectOpenHashMap<>(); private static Int2ObjectMap<SpatialIndex<SpawnGroupEntry>> spawnLists = new Int2ObjectOpenHashMap<>();
public static void load() { public static void load() {
@ -61,4 +66,12 @@ public class GameDepot {
public static SpatialIndex<SpawnGroupEntry> getSpawnListById(int sceneId) { public static SpatialIndex<SpawnGroupEntry> getSpawnListById(int sceneId) {
return getSpawnLists().computeIfAbsent(sceneId, id -> new FlexibleQuadTree<>()); return getSpawnLists().computeIfAbsent(sceneId, id -> new FlexibleQuadTree<>());
} }
public static Map<String, AvatarConfig> getPlayerAbilities() {
return playerAbilities;
}
public static void setPlayerAbilities(Map<String, AvatarConfig> playerAbilities) {
GameDepot.playerAbilities = playerAbilities;
}
} }

View File

@ -66,37 +66,6 @@ public class ResourceLoader {
loadQuests(); loadQuests();
// Load scene points - must be done AFTER resources are loaded // Load scene points - must be done AFTER resources are loaded
loadScenePoints(); loadScenePoints();
// Custom - TODO move this somewhere else
try {
GameData.getAvatarSkillDepotDataMap().get(504).setAbilities(
new AbilityEmbryoEntry(
"",
new String[] {
"Avatar_PlayerBoy_ExtraAttack_Wind",
"Avatar_Player_UziExplode_Mix",
"Avatar_Player_UziExplode",
"Avatar_Player_UziExplode_Strike_01",
"Avatar_Player_UziExplode_Strike_02",
"Avatar_Player_WindBreathe",
"Avatar_Player_WindBreathe_CameraController"
}
));
GameData.getAvatarSkillDepotDataMap().get(704).setAbilities(
new AbilityEmbryoEntry(
"",
new String[] {
"Avatar_PlayerGirl_ExtraAttack_Wind",
"Avatar_Player_UziExplode_Mix",
"Avatar_Player_UziExplode",
"Avatar_Player_UziExplode_Strike_01",
"Avatar_Player_UziExplode_Strike_02",
"Avatar_Player_WindBreathe",
"Avatar_Player_WindBreathe_CameraController"
}
));
} catch (Exception e) {
Grasscutter.getLogger().error("Error loading abilities", e);
}
} }
public static void loadResources() { public static void loadResources() {
@ -244,6 +213,16 @@ public class ResourceLoader {
AbilityEmbryoEntry al = new AbilityEmbryoEntry(avatarName, config.abilities.stream().map(Object::toString).toArray(size -> new String[s])); AbilityEmbryoEntry al = new AbilityEmbryoEntry(avatarName, config.abilities.stream().map(Object::toString).toArray(size -> new String[s]));
embryoList.add(al); embryoList.add(al);
} }
File playerElementsFile = new File(Utils.toFilePath(RESOURCE("BinOutput/AbilityGroup/AbilityGroup_Other_PlayerElementAbility.json")));
if (playerElementsFile.exists()) {
try (FileReader fileReader = new FileReader(playerElementsFile)) {
GameDepot.setPlayerAbilities(Grasscutter.getGsonFactory().fromJson(fileReader, new TypeToken<Map<String, AvatarConfig>>(){}.getType()));
} catch (Exception e) {
e.printStackTrace();
}
}
} }
if (embryoList == null || embryoList.isEmpty()) { if (embryoList == null || embryoList.isEmpty()) {
@ -417,14 +396,15 @@ public class ResourceLoader {
// BinOutput configs // BinOutput configs
private static class AvatarConfig { public static class AvatarConfig {
@SerializedName(value="abilities", alternate={"targetAbilities"})
public ArrayList<AvatarConfigAbility> abilities; public ArrayList<AvatarConfigAbility> abilities;
}
private static class AvatarConfigAbility {
public String abilityName; public static class AvatarConfigAbility {
public String toString() { public String abilityName;
return abilityName; public String toString() {
} return abilityName;
} }
} }

View File

@ -3,7 +3,10 @@ package emu.grasscutter.data.excels;
import java.util.List; import java.util.List;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.GameDepot;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceLoader.AvatarConfig;
import emu.grasscutter.data.ResourceLoader.AvatarConfigAbility;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority; import emu.grasscutter.data.ResourceType.LoadPriority;
import emu.grasscutter.data.binout.AbilityEmbryoEntry; import emu.grasscutter.data.binout.AbilityEmbryoEntry;
@ -95,12 +98,21 @@ public class AvatarSkillDepotData extends GameResource {
@Override @Override
public void onLoad() { public void onLoad() {
// Set energy skill data
this.energySkillData = GameData.getAvatarSkillDataMap().get(this.energySkill); this.energySkillData = GameData.getAvatarSkillDataMap().get(this.energySkill);
if (getEnergySkillData() != null) { if (getEnergySkillData() != null) {
this.elementType = getEnergySkillData().getCostElemType(); this.elementType = getEnergySkillData().getCostElemType();
} else { } else {
this.elementType = ElementType.None; this.elementType = ElementType.None;
} }
// Set embryo abilities (if player skill depot)
if (getSkillDepotAbilityGroup() != null && getSkillDepotAbilityGroup().length() > 0) {
AvatarConfig config = GameDepot.getPlayerAbilities().get(getSkillDepotAbilityGroup());
if (config != null) {
this.setAbilities(new AbilityEmbryoEntry(getSkillDepotAbilityGroup(), config.abilities.stream().map(Object::toString).toArray(String[]::new)));
}
}
} }
public static class InherentProudSkillOpens { public static class InherentProudSkillOpens {

View File

@ -0,0 +1,32 @@
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.game.props.ElementType;
@ResourceType(name = "WorldAreaConfigData.json")
public class WorldAreaData extends GameResource {
private int ID;
private int AreaID1;
private int AreaID2;
private int SceneID;
private ElementType elementType;
@Override
public int getId() {
return (this.AreaID2 << 16) + this.AreaID1;
}
public int getSceneID() {
return this.SceneID;
}
public ElementType getElementType() {
return this.elementType;
}
@Override
public void onLoad() {
}
}

View File

@ -1,5 +1,8 @@
package emu.grasscutter.game.gacha; package emu.grasscutter.game.gacha;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.GachaInfoOuterClass.GachaInfo; import emu.grasscutter.net.proto.GachaInfoOuterClass.GachaInfo;
import emu.grasscutter.net.proto.GachaUpInfoOuterClass.GachaUpInfo; import emu.grasscutter.net.proto.GachaUpInfoOuterClass.GachaUpInfo;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
@ -44,6 +47,7 @@ public class GachaBanner {
private int[] rateUpItems2 = {}; private int[] rateUpItems2 = {};
private int eventChance = -1; private int eventChance = -1;
private int costItem = 0; private int costItem = 0;
private int wishMaxProgress = 2;
public int getGachaType() { public int getGachaType() {
return gachaType; return gachaType;
@ -108,6 +112,11 @@ public class GachaBanner {
public boolean getRemoveC6FromPool() {return removeC6FromPool;} public boolean getRemoveC6FromPool() {return removeC6FromPool;}
public boolean getAutoStripRateUpFromFallback() {return autoStripRateUpFromFallback;} public boolean getAutoStripRateUpFromFallback() {return autoStripRateUpFromFallback;}
public int getWishMaxProgress() {return wishMaxProgress;}
public boolean hasEpitomized() {
return bannerType.equals(BannerType.WEAPON);
}
public int getWeight(int rarity, int pity) { public int getWeight(int rarity, int pity) {
return switch(rarity) { return switch(rarity) {
@ -129,13 +138,11 @@ public class GachaBanner {
default -> (eventChance > -1) ? eventChance : eventChance5; default -> (eventChance > -1) ? eventChance : eventChance5;
}; };
} }
@Deprecated
public GachaInfo toProto() {
return toProto("");
}
public GachaInfo toProto(String sessionKey) { public GachaInfo toProto(Player player) {
// TODO: use other Nonce/key insteadof session key to ensure the overall security for the player
String sessionKey = player.getAccount().getSessionKey();
String record = "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://" String record = "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://"
+ lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress) + ":" + lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress) + ":"
+ lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort) + lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort)
@ -166,6 +173,15 @@ public class GachaBanner {
.setLeftGachaTimes(Integer.MAX_VALUE) .setLeftGachaTimes(Integer.MAX_VALUE)
.setGachaTimesLimit(Integer.MAX_VALUE) .setGachaTimesLimit(Integer.MAX_VALUE)
.setGachaSortId(this.getSortId()); .setGachaSortId(this.getSortId());
if(hasEpitomized()) {
PlayerGachaBannerInfo gachaInfo = player.getGachaInfo().getBannerInfo(this);
info.setWishItemId(gachaInfo.getWishItemId())
.setWishProgress(gachaInfo.getFailedChosenItemPulls())
.setWishMaxProgress(this.getWishMaxProgress());
}
if (this.getTitlePath() != null) { if (this.getTitlePath() != null) {
info.setTitleTextmap(this.getTitlePath()); info.setTitleTextmap(this.getTitlePath());
} }

View File

@ -35,6 +35,7 @@ import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.game.GameServerTickEvent; import emu.grasscutter.server.game.GameServerTickEvent;
import emu.grasscutter.server.packet.send.PacketDoGachaRsp; import emu.grasscutter.server.packet.send.PacketDoGachaRsp;
import emu.grasscutter.server.packet.send.PacketGachaWishRsp;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@ -47,8 +48,7 @@ import static emu.grasscutter.Configuration.*;
public class GachaManager { public class GachaManager {
private final GameServer server; private final GameServer server;
private final Int2ObjectMap<GachaBanner> gachaBanners; private final Int2ObjectMap<GachaBanner> gachaBanners;
private GetGachaInfoRsp cachedProto; private WatchService watchService;
WatchService watchService;
private static final int starglitterId = 221; private static final int starglitterId = 221;
private static final int stardustId = 222; private static final int stardustId = 222;
@ -87,9 +87,6 @@ public class GachaManager {
getGachaBanners().put(banner.getScheduleId(), banner); getGachaBanners().put(banner.getScheduleId(), banner);
} }
Grasscutter.getLogger().info("Banners successfully loaded."); Grasscutter.getLogger().info("Banners successfully loaded.");
this.cachedProto = createProto();
} else { } else {
Grasscutter.getLogger().error("Unable to load banners. Banners size is 0."); Grasscutter.getLogger().error("Unable to load banners. Banners size is 0.");
} }
@ -182,38 +179,59 @@ public class GachaManager {
return 0; // This should only be reachable if total==0 return 0; // This should only be reachable if total==0
} }
private synchronized int doFallbackRarePull(int[] fallback1, int[] fallback2, int rarity, GachaBanner banner, PlayerGachaBannerInfo gachaInfo) {
if (fallback1.length < 1) {
if (fallback2.length < 1) {
return getRandom((rarity==5)? fallbackItems5Pool2Default : fallbackItems4Pool2Default);
} else {
return getRandom(fallback2);
}
} else if (fallback2.length < 1) {
return getRandom(fallback1);
} else { // Both pools are possible, use the pool balancer
int pityPool1 = banner.getPoolBalanceWeight(rarity, gachaInfo.getPityPool(rarity, 1));
int pityPool2 = banner.getPoolBalanceWeight(rarity, gachaInfo.getPityPool(rarity, 2));
int chosenPool = switch ((pityPool1 >= pityPool2)? 1 : 0) { // Larger weight must come first for the hard cutoff to function correctly
case 1 -> 1 + drawRoulette(new int[] {pityPool1, pityPool2}, 10000);
default -> 2 - drawRoulette(new int[] {pityPool2, pityPool1}, 10000);
};
return switch (chosenPool) {
case 1:
gachaInfo.setPityPool(rarity, 1, 0);
yield getRandom(fallback1);
default:
gachaInfo.setPityPool(rarity, 2, 0);
yield getRandom(fallback2);
};
}
}
private synchronized int doRarePull(int[] featured, int[] fallback1, int[] fallback2, int rarity, GachaBanner banner, PlayerGachaBannerInfo gachaInfo) { private synchronized int doRarePull(int[] featured, int[] fallback1, int[] fallback2, int rarity, GachaBanner banner, PlayerGachaBannerInfo gachaInfo) {
int itemId = 0; int itemId = 0;
boolean pullFeatured = (gachaInfo.getFailedFeaturedItemPulls(rarity) >= 1) // Lost previous coinflip boolean epitomized = (banner.hasEpitomized()) && (rarity == 5) && (gachaInfo.getWishItemId() != 0);
|| (this.randomRange(1, 100) <= banner.getEventChance(rarity)); // Won this coinflip boolean pityEpitomized = (gachaInfo.getFailedChosenItemPulls() >= banner.getWishMaxProgress()); // Maximum fate points reached
if (pullFeatured && (featured.length > 0)) { boolean pityFeatured = (gachaInfo.getFailedFeaturedItemPulls(rarity) >= 1); // Lost previous coinflip
itemId = getRandom(featured); boolean rollFeatured = (this.randomRange(1, 100) <= banner.getEventChance(rarity)); // Won this coinflip
gachaInfo.setFailedFeaturedItemPulls(rarity, 0); boolean pullFeatured = pityFeatured || rollFeatured;
if (epitomized && pityEpitomized) { // Auto pick item when epitomized points reached
gachaInfo.setFailedFeaturedItemPulls(rarity, 0); // Epitomized item will always be a featured one
itemId = gachaInfo.getWishItemId();
} else { } else {
gachaInfo.addFailedFeaturedItemPulls(rarity, 1); if (pullFeatured && (featured.length > 0)) {
if (fallback1.length < 1) { gachaInfo.setFailedFeaturedItemPulls(rarity, 0);
if (fallback2.length < 1) { itemId = getRandom(featured);
itemId = getRandom((rarity==5)? fallbackItems5Pool2Default : fallbackItems4Pool2Default); } else {
} else { gachaInfo.addFailedFeaturedItemPulls(rarity, 1); // This could be moved into doFallbackRarePull but having it here makes it clearer
itemId = getRandom(fallback2); itemId = doFallbackRarePull(fallback1, fallback2, rarity, banner, gachaInfo);
} }
} else if (fallback2.length < 1) { }
itemId = getRandom(fallback1);
} else { // Both pools are possible, use the pool balancer if (epitomized) {
int pityPool1 = banner.getPoolBalanceWeight(rarity, gachaInfo.getPityPool(rarity, 1)); if(itemId == gachaInfo.getWishItemId()) { // Reset epitomized points when got wished item
int pityPool2 = banner.getPoolBalanceWeight(rarity, gachaInfo.getPityPool(rarity, 2)); gachaInfo.setFailedChosenItemPulls(0);
int chosenPool = switch ((pityPool1 >= pityPool2)? 1 : 0) { // Larger weight must come first for the hard cutoff to function correctly } else { // Add epitomized points if not get wished item
case 1 -> 1 + drawRoulette(new int[] {pityPool1, pityPool2}, 10000); gachaInfo.addFailedChosenItemPulls(1);
default -> 2 - drawRoulette(new int[] {pityPool2, pityPool1}, 10000);
};
itemId = switch (chosenPool) {
case 1:
gachaInfo.setPityPool(rarity, 1, 0);
yield getRandom(fallback1);
default:
gachaInfo.setPityPool(rarity, 2, 0);
yield getRandom(fallback2);
};
} }
} }
return itemId; return itemId;
@ -356,7 +374,7 @@ public class GachaManager {
} }
// Packets // Packets
player.sendPacket(new PacketDoGachaRsp(banner, list)); player.sendPacket(new PacketDoGachaRsp(banner, list, gachaInfo));
} }
private synchronized void startWatcher(GameServer server) { private synchronized void startWatcher(GameServer server) {
@ -399,18 +417,7 @@ public class GachaManager {
} }
} }
@Deprecated private synchronized GetGachaInfoRsp createProto(Player player) {
private synchronized GetGachaInfoRsp createProto() {
GetGachaInfoRsp.Builder proto = GetGachaInfoRsp.newBuilder().setGachaRandom(12345);
for (GachaBanner banner : getGachaBanners().values()) {
proto.addGachaInfoList(banner.toProto());
}
return proto.build();
}
private synchronized GetGachaInfoRsp createProto(String sessionKey) {
GetGachaInfoRsp.Builder proto = GetGachaInfoRsp.newBuilder().setGachaRandom(12345); GetGachaInfoRsp.Builder proto = GetGachaInfoRsp.newBuilder().setGachaRandom(12345);
long currentTime = System.currentTimeMillis() / 1000L; long currentTime = System.currentTimeMillis() / 1000L;
@ -418,22 +425,14 @@ public class GachaManager {
for (GachaBanner banner : getGachaBanners().values()) { for (GachaBanner banner : getGachaBanners().values()) {
if ((banner.getEndTime() >= currentTime && banner.getBeginTime() <= currentTime) || (banner.getBannerType() == BannerType.STANDARD)) if ((banner.getEndTime() >= currentTime && banner.getBeginTime() <= currentTime) || (banner.getBannerType() == BannerType.STANDARD))
{ {
proto.addGachaInfoList(banner.toProto(sessionKey)); proto.addGachaInfoList(banner.toProto(player));
} }
} }
return proto.build(); return proto.build();
} }
@Deprecated
public GetGachaInfoRsp toProto() {
if (this.cachedProto == null) {
this.cachedProto = createProto();
}
return this.cachedProto;
}
public GetGachaInfoRsp toProto(String sessionKey) { public GetGachaInfoRsp toProto(Player player) {
return createProto(sessionKey); return createProto(player);
} }
} }

View File

@ -12,6 +12,9 @@ public class PlayerGachaBannerInfo {
private int pity5Pool2 = 0; private int pity5Pool2 = 0;
private int pity4Pool1 = 0; private int pity4Pool1 = 0;
private int pity4Pool2 = 0; private int pity4Pool2 = 0;
private int failedChosenItemPulls = 0;
private int wishItemId = 0;
public int getPity5() { public int getPity5() {
return pity5; return pity5;
@ -36,6 +39,26 @@ public class PlayerGachaBannerInfo {
public void addPity4(int amount) { public void addPity4(int amount) {
this.pity4 += amount; this.pity4 += amount;
} }
public int getWishItemId() {
return wishItemId;
}
public void setWishItemId(int wishItemId) {
this.wishItemId = wishItemId;
}
public int getFailedChosenItemPulls() {
return failedChosenItemPulls;
}
public void setFailedChosenItemPulls(int amount) {
failedChosenItemPulls = amount;
}
public void addFailedChosenItemPulls(int amount) {
failedChosenItemPulls += amount;
}
public int getFailedFeaturedItemPulls(int rarity) { public int getFailedFeaturedItemPulls(int rarity) {
return switch (rarity) { return switch (rarity) {

View File

@ -1,4 +1,4 @@
package emu.grasscutter.game.managers.ChatManager; package emu.grasscutter.game.managers.chat;
import emu.grasscutter.command.CommandMap; import emu.grasscutter.command.CommandMap;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;

View File

@ -1,4 +1,4 @@
package emu.grasscutter.game.managers.ChatManager; package emu.grasscutter.game.managers.chat;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServer;

View File

@ -1,91 +1,91 @@
package emu.grasscutter.game.managers.DeforestationManager; package emu.grasscutter.game.managers.deforestation;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import dev.morphia.annotations.Transient; import dev.morphia.annotations.Transient;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.game.entity.EntityItem; import emu.grasscutter.game.entity.EntityItem;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.HitTreeNotifyOuterClass; import emu.grasscutter.net.proto.HitTreeNotifyOuterClass;
import emu.grasscutter.net.proto.VectorOuterClass; import emu.grasscutter.net.proto.VectorOuterClass;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
public class DeforestationManager { public class DeforestationManager {
final static int RECORD_EXPIRED_SECONDS = 60*5; // 5 min final static int RECORD_EXPIRED_SECONDS = 60*5; // 5 min
final static int RECORD_MAX_TIMES = 3; // max number of wood final static int RECORD_MAX_TIMES = 3; // max number of wood
final static int RECORD_MAX_TIMES_OTHER_HIT_TREE = 10; // if hit 10 times other trees, reset wood final static int RECORD_MAX_TIMES_OTHER_HIT_TREE = 10; // if hit 10 times other trees, reset wood
@Transient private final Player player; @Transient private final Player player;
@Transient private final ArrayList<HitTreeRecord> currentRecord; @Transient private final ArrayList<HitTreeRecord> currentRecord;
@Transient private final static HashMap<Integer, Integer> ColliderTypeToWoodItemID = new HashMap<>(); @Transient private final static HashMap<Integer, Integer> ColliderTypeToWoodItemID = new HashMap<>();
static { static {
/* define wood types which reflected to item id*/ /* define wood types which reflected to item id*/
ColliderTypeToWoodItemID.put(1,101301); ColliderTypeToWoodItemID.put(1,101301);
ColliderTypeToWoodItemID.put(2,101302); ColliderTypeToWoodItemID.put(2,101302);
ColliderTypeToWoodItemID.put(3,101303); ColliderTypeToWoodItemID.put(3,101303);
ColliderTypeToWoodItemID.put(4,101304); ColliderTypeToWoodItemID.put(4,101304);
ColliderTypeToWoodItemID.put(5,101305); ColliderTypeToWoodItemID.put(5,101305);
ColliderTypeToWoodItemID.put(6,101306); ColliderTypeToWoodItemID.put(6,101306);
ColliderTypeToWoodItemID.put(7,101307); ColliderTypeToWoodItemID.put(7,101307);
ColliderTypeToWoodItemID.put(8,101308); ColliderTypeToWoodItemID.put(8,101308);
ColliderTypeToWoodItemID.put(9,101309); ColliderTypeToWoodItemID.put(9,101309);
ColliderTypeToWoodItemID.put(10,101310); ColliderTypeToWoodItemID.put(10,101310);
ColliderTypeToWoodItemID.put(11,101311); ColliderTypeToWoodItemID.put(11,101311);
ColliderTypeToWoodItemID.put(12,101312); ColliderTypeToWoodItemID.put(12,101312);
} }
public DeforestationManager(Player player){ public DeforestationManager(Player player){
this.player = player; this.player = player;
this.currentRecord = new ArrayList<>(); this.currentRecord = new ArrayList<>();
} }
public void resetWood(){ public void resetWood(){
synchronized (currentRecord) { synchronized (currentRecord) {
currentRecord.clear(); currentRecord.clear();
} }
} }
public void onDeforestationInvoke(HitTreeNotifyOuterClass.HitTreeNotify hit){ public void onDeforestationInvoke(HitTreeNotifyOuterClass.HitTreeNotify hit){
synchronized (currentRecord) { synchronized (currentRecord) {
//Grasscutter.getLogger().info("onDeforestationInvoke! Wood records {}", currentRecord); //Grasscutter.getLogger().info("onDeforestationInvoke! Wood records {}", currentRecord);
VectorOuterClass.Vector hitPosition = hit.getHitPostion(); VectorOuterClass.Vector hitPosition = hit.getHitPostion();
int woodType = hit.getWoodType(); int woodType = hit.getWoodType();
if (ColliderTypeToWoodItemID.containsKey(woodType)) {// is a available wood type if (ColliderTypeToWoodItemID.containsKey(woodType)) {// is a available wood type
Scene scene = player.getScene(); Scene scene = player.getScene();
int itemId = ColliderTypeToWoodItemID.get(woodType); int itemId = ColliderTypeToWoodItemID.get(woodType);
int positionHash = hitPosition.hashCode(); int positionHash = hitPosition.hashCode();
HitTreeRecord record = searchRecord(positionHash); HitTreeRecord record = searchRecord(positionHash);
if (record == null) { if (record == null) {
record = new HitTreeRecord(positionHash); record = new HitTreeRecord(positionHash);
}else{ }else{
currentRecord.remove(record);// move it to last position currentRecord.remove(record);// move it to last position
} }
currentRecord.add(record); currentRecord.add(record);
if(currentRecord.size()>RECORD_MAX_TIMES_OTHER_HIT_TREE){ if(currentRecord.size()>RECORD_MAX_TIMES_OTHER_HIT_TREE){
currentRecord.remove(0); currentRecord.remove(0);
} }
if(record.record()) { if(record.record()) {
EntityItem entity = new EntityItem(scene, EntityItem entity = new EntityItem(scene,
null, null,
GameData.getItemDataMap().get(itemId), GameData.getItemDataMap().get(itemId),
new Position(hitPosition.getX(), hitPosition.getY(), hitPosition.getZ()), new Position(hitPosition.getX(), hitPosition.getY(), hitPosition.getZ()),
1, 1,
false); false);
scene.addEntity(entity); scene.addEntity(entity);
} }
//record.record()=false : too many wood they have deforested, no more wood dropped! //record.record()=false : too many wood they have deforested, no more wood dropped!
} else { } else {
Grasscutter.getLogger().warn("No wood type {} found.", woodType); Grasscutter.getLogger().warn("No wood type {} found.", woodType);
} }
} }
// unknown wood type // unknown wood type
} }
private HitTreeRecord searchRecord(int id){ private HitTreeRecord searchRecord(int id){
for (HitTreeRecord record : currentRecord) { for (HitTreeRecord record : currentRecord) {
if (record.getUnique() == id) { if (record.getUnique() == id) {
return record; return record;
} }
} }
return null; return null;
} }
} }

View File

@ -1,57 +1,57 @@
package emu.grasscutter.game.managers.DeforestationManager; package emu.grasscutter.game.managers.deforestation;
public class HitTreeRecord { public class HitTreeRecord {
private final int unique; private final int unique;
private short count; // hit this tree times private short count; // hit this tree times
private long time; // last available hitting time private long time; // last available hitting time
HitTreeRecord(int unique){ HitTreeRecord(int unique){
this.count = 0; this.count = 0;
this.time = 0; this.time = 0;
this.unique = unique; this.unique = unique;
} }
/** /**
* reset hit time * reset hit time
*/ */
private void resetTime(){ private void resetTime(){
this.time = System.currentTimeMillis(); this.time = System.currentTimeMillis();
} }
/** /**
* commit hit behavior * commit hit behavior
*/ */
public boolean record(){ public boolean record(){
if (this.count < DeforestationManager.RECORD_MAX_TIMES) { if (this.count < DeforestationManager.RECORD_MAX_TIMES) {
this.count++; this.count++;
resetTime(); resetTime();
return true; return true;
} }
// check expired // check expired
boolean isWaiting = System.currentTimeMillis() - this.time < DeforestationManager.RECORD_EXPIRED_SECONDS * 1000L; boolean isWaiting = System.currentTimeMillis() - this.time < DeforestationManager.RECORD_EXPIRED_SECONDS * 1000L;
if(isWaiting){ if(isWaiting){
return false; return false;
}else{ }else{
this.count = 1; this.count = 1;
resetTime(); resetTime();
return true; return true;
} }
} }
/** /**
* get unique id * get unique id
*/ */
public int getUnique(){ public int getUnique(){
return unique; return unique;
} }
@Override @Override
public String toString() { public String toString() {
return "HitTreeRecord{" + return "HitTreeRecord{" +
"unique=" + unique + "unique=" + unique +
", count=" + count + ", count=" + count +
", time=" + time + ", time=" + time +
'}'; '}';
} }
} }

View File

@ -1,4 +1,4 @@
package emu.grasscutter.game.managers.EnergyManager; package emu.grasscutter.game.managers.energy;
import java.util.List; import java.util.List;

View File

@ -1,4 +1,4 @@
package emu.grasscutter.game.managers.EnergyManager; package emu.grasscutter.game.managers.energy;
public class EnergyDropInfo { public class EnergyDropInfo {
private int ballId; private int ballId;

View File

@ -1,4 +1,4 @@
package emu.grasscutter.game.managers.EnergyManager; package emu.grasscutter.game.managers.energy;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader; import emu.grasscutter.data.DataLoader;

View File

@ -1,4 +1,4 @@
package emu.grasscutter.game.managers.EnergyManager; package emu.grasscutter.game.managers.energy;
import java.util.List; import java.util.List;

View File

@ -1,4 +1,4 @@
package emu.grasscutter.game.managers.EnergyManager; package emu.grasscutter.game.managers.energy;
public class SkillParticleGenerationInfo { public class SkillParticleGenerationInfo {
private int value; private int value;

View File

@ -1,4 +1,4 @@
package emu.grasscutter.game.managers.ForgingManager; package emu.grasscutter.game.managers.forging;
import dev.morphia.annotations.Entity; import dev.morphia.annotations.Entity;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;

View File

@ -1,4 +1,4 @@
package emu.grasscutter.game.managers.ForgingManager; package emu.grasscutter.game.managers.forging;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;

View File

@ -1,66 +1,66 @@
package emu.grasscutter.game.managers.MapMarkManager; package emu.grasscutter.game.managers.mapmark;
import dev.morphia.annotations.Entity; import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.MapMarkFromTypeOuterClass.MapMarkFromType; import emu.grasscutter.net.proto.MapMarkFromTypeOuterClass.MapMarkFromType;
import emu.grasscutter.net.proto.MapMarkPointOuterClass.MapMarkPoint; import emu.grasscutter.net.proto.MapMarkPointOuterClass.MapMarkPoint;
import emu.grasscutter.net.proto.MapMarkPointTypeOuterClass.MapMarkPointType; import emu.grasscutter.net.proto.MapMarkPointTypeOuterClass.MapMarkPointType;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
@Entity @Entity
public class MapMark { public class MapMark {
private int sceneId; private int sceneId;
private String name; private String name;
private Position position; private Position position;
private MapMarkPointType mapMarkPointType; private MapMarkPointType mapMarkPointType;
private int monsterId; private int monsterId;
private MapMarkFromType mapMarkFromType; private MapMarkFromType mapMarkFromType;
private int questId; private int questId;
@Deprecated // Morhpia @Deprecated // Morhpia
public MapMark() { public MapMark() {
this.mapMarkPointType = MapMarkPointType.MAP_MARK_POINT_TYPE_MONSTER; this.mapMarkPointType = MapMarkPointType.MAP_MARK_POINT_TYPE_MONSTER;
this.mapMarkFromType = MapMarkFromType.MAP_MARK_FROM_TYPE_MONSTER; this.mapMarkFromType = MapMarkFromType.MAP_MARK_FROM_TYPE_MONSTER;
} }
public MapMark(MapMarkPoint mapMarkPoint) { public MapMark(MapMarkPoint mapMarkPoint) {
this.sceneId = mapMarkPoint.getSceneId(); this.sceneId = mapMarkPoint.getSceneId();
this.name = mapMarkPoint.getName(); this.name = mapMarkPoint.getName();
this.position = new Position( this.position = new Position(
mapMarkPoint.getPos().getX(), mapMarkPoint.getPos().getX(),
mapMarkPoint.getPos().getY(), mapMarkPoint.getPos().getY(),
mapMarkPoint.getPos().getZ() mapMarkPoint.getPos().getZ()
); );
this.mapMarkPointType = mapMarkPoint.getPointType(); this.mapMarkPointType = mapMarkPoint.getPointType();
this.monsterId = mapMarkPoint.getMonsterId(); this.monsterId = mapMarkPoint.getMonsterId();
this.mapMarkFromType = mapMarkPoint.getFromType(); this.mapMarkFromType = mapMarkPoint.getFromType();
this.questId = mapMarkPoint.getQuestId(); this.questId = mapMarkPoint.getQuestId();
} }
public int getSceneId() { public int getSceneId() {
return this.sceneId; return this.sceneId;
} }
public String getName() { public String getName() {
return this.name; return this.name;
} }
public Position getPosition() { public Position getPosition() {
return this.position; return this.position;
} }
public MapMarkPointType getMapMarkPointType() { public MapMarkPointType getMapMarkPointType() {
return this.mapMarkPointType; return this.mapMarkPointType;
} }
public int getMonsterId() { public int getMonsterId() {
return this.monsterId; return this.monsterId;
} }
public MapMarkFromType getMapMarkFromType() { public MapMarkFromType getMapMarkFromType() {
return this.mapMarkFromType; return this.mapMarkFromType;
} }
public int getQuestId() { public int getQuestId() {
return this.questId; return this.questId;
} }
} }

View File

@ -1,90 +1,90 @@
package emu.grasscutter.game.managers.MapMarkManager; package emu.grasscutter.game.managers.mapmark;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.MapMarkPointTypeOuterClass.MapMarkPointType; import emu.grasscutter.net.proto.MapMarkPointTypeOuterClass.MapMarkPointType;
import emu.grasscutter.net.proto.MarkMapReqOuterClass.MarkMapReq; import emu.grasscutter.net.proto.MarkMapReqOuterClass.MarkMapReq;
import emu.grasscutter.net.proto.MarkMapReqOuterClass.MarkMapReq.Operation; import emu.grasscutter.net.proto.MarkMapReqOuterClass.MarkMapReq.Operation;
import emu.grasscutter.server.packet.send.PacketMarkMapRsp; import emu.grasscutter.server.packet.send.PacketMarkMapRsp;
import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify; import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import java.util.HashMap; import java.util.HashMap;
public class MapMarksManager { public class MapMarksManager {
public static final int mapMarkMaxCount = 150; public static final int mapMarkMaxCount = 150;
private HashMap<String, MapMark> mapMarks; private HashMap<String, MapMark> mapMarks;
private final Player player; private final Player player;
public MapMarksManager(Player player) { public MapMarksManager(Player player) {
this.player = player; this.player = player;
this.mapMarks = player.getMapMarks(); this.mapMarks = player.getMapMarks();
if (this.mapMarks == null) { this.mapMarks = new HashMap<>(); } if (this.mapMarks == null) { this.mapMarks = new HashMap<>(); }
} }
public void handleMapMarkReq(MarkMapReq req) { public void handleMapMarkReq(MarkMapReq req) {
Operation op = req.getOp(); Operation op = req.getOp();
switch (op) { switch (op) {
case OPERATION_ADD -> { case OPERATION_ADD -> {
MapMark createMark = new MapMark(req.getMark()); MapMark createMark = new MapMark(req.getMark());
// keep teleporting functionality on fishhook mark. // keep teleporting functionality on fishhook mark.
if (createMark.getMapMarkPointType() == MapMarkPointType.MAP_MARK_POINT_TYPE_FISH_POOL) { if (createMark.getMapMarkPointType() == MapMarkPointType.MAP_MARK_POINT_TYPE_FISH_POOL) {
teleport(player, createMark); teleport(player, createMark);
return; return;
} }
addMapMark(createMark); addMapMark(createMark);
} }
case OPERATION_MOD -> { case OPERATION_MOD -> {
MapMark oldMark = new MapMark(req.getOld()); MapMark oldMark = new MapMark(req.getOld());
removeMapMark(oldMark.getPosition()); removeMapMark(oldMark.getPosition());
MapMark newMark = new MapMark(req.getMark()); MapMark newMark = new MapMark(req.getMark());
addMapMark(newMark); addMapMark(newMark);
} }
case OPERATION_DEL -> { case OPERATION_DEL -> {
MapMark deleteMark = new MapMark(req.getMark()); MapMark deleteMark = new MapMark(req.getMark());
removeMapMark(deleteMark.getPosition()); removeMapMark(deleteMark.getPosition());
} }
} }
if (op != Operation.OPERATION_GET) { if (op != Operation.OPERATION_GET) {
saveMapMarks(); saveMapMarks();
} }
player.getSession().send(new PacketMarkMapRsp(getMapMarks())); player.getSession().send(new PacketMarkMapRsp(getMapMarks()));
} }
public HashMap<String, MapMark> getMapMarks() { public HashMap<String, MapMark> getMapMarks() {
return mapMarks; return mapMarks;
} }
public String getMapMarkKey(Position position) { public String getMapMarkKey(Position position) {
return "x" + (int)position.getX()+ "z" + (int)position.getZ(); return "x" + (int)position.getX()+ "z" + (int)position.getZ();
} }
public void removeMapMark(Position position) { public void removeMapMark(Position position) {
mapMarks.remove(getMapMarkKey(position)); mapMarks.remove(getMapMarkKey(position));
} }
public void addMapMark(MapMark mapMark) { public void addMapMark(MapMark mapMark) {
if (mapMarks.size() < mapMarkMaxCount) { if (mapMarks.size() < mapMarkMaxCount) {
mapMarks.put(getMapMarkKey(mapMark.getPosition()), mapMark); mapMarks.put(getMapMarkKey(mapMark.getPosition()), mapMark);
} }
} }
private void saveMapMarks() { private void saveMapMarks() {
player.setMapMarks(mapMarks); player.setMapMarks(mapMarks);
player.save(); player.save();
} }
private void teleport(Player player, MapMark mapMark) { private void teleport(Player player, MapMark mapMark) {
float y; float y;
try { try {
y = (float)Integer.parseInt(mapMark.getName()); y = (float)Integer.parseInt(mapMark.getName());
} catch (Exception e) { } catch (Exception e) {
y = 300; y = 300;
} }
Position pos = mapMark.getPosition(); Position pos = mapMark.getPosition();
player.getPos().set(pos.getX(), y, pos.getZ()); player.getPos().set(pos.getX(), y, pos.getZ());
if (mapMark.getSceneId() != player.getSceneId()) { if (mapMark.getSceneId() != player.getSceneId()) {
player.getWorld().transferPlayerToScene(player, mapMark.getSceneId(), player.getPos()); player.getWorld().transferPlayerToScene(player, mapMark.getSceneId(), player.getPos());
} }
player.getScene().broadcastPacket(new PacketSceneEntityAppearNotify(player)); player.getScene().broadcastPacket(new PacketSceneEntityAppearNotify(player));
} }
} }

View File

@ -1,12 +1,12 @@
package emu.grasscutter.game.managers.StaminaManager; package emu.grasscutter.game.managers.stamina;
public interface AfterUpdateStaminaListener { public interface AfterUpdateStaminaListener {
/** /**
* onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's current stamina. * onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's current stamina.
* This gives listeners a chance to intercept this update. * This gives listeners a chance to intercept this update.
* *
* @param reason Why updating stamina. * @param reason Why updating stamina.
* @param newStamina New Stamina value. * @param newStamina New Stamina value.
*/ */
void onAfterUpdateStamina(String reason, int newStamina, boolean isCharacterStamina); void onAfterUpdateStamina(String reason, int newStamina, boolean isCharacterStamina);
} }

View File

@ -1,20 +1,20 @@
package emu.grasscutter.game.managers.StaminaManager; package emu.grasscutter.game.managers.stamina;
public interface BeforeUpdateStaminaListener { public interface BeforeUpdateStaminaListener {
/** /**
* onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's current stamina. * onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's current stamina.
* This gives listeners a chance to intercept this update. * This gives listeners a chance to intercept this update.
* @param reason Why updating stamina. * @param reason Why updating stamina.
* @param newStamina New ABSOLUTE stamina value. * @param newStamina New ABSOLUTE stamina value.
* @return true if you want to cancel this update, otherwise false. * @return true if you want to cancel this update, otherwise false.
*/ */
int onBeforeUpdateStamina(String reason, int newStamina, boolean isCharacterStamina); int onBeforeUpdateStamina(String reason, int newStamina, boolean isCharacterStamina);
/** /**
* onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's current stamina. * onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's current stamina.
* This gives listeners a chance to intercept this update. * This gives listeners a chance to intercept this update.
* @param reason Why updating stamina. * @param reason Why updating stamina.
* @param consumption ConsumptionType and RELATIVE stamina change amount. * @param consumption ConsumptionType and RELATIVE stamina change amount.
* @return true if you want to cancel this update, otherwise false. * @return true if you want to cancel this update, otherwise false.
*/ */
Consumption onBeforeUpdateStamina(String reason, Consumption consumption, boolean isCharacterStamina); Consumption onBeforeUpdateStamina(String reason, Consumption consumption, boolean isCharacterStamina);
} }

View File

@ -1,18 +1,18 @@
package emu.grasscutter.game.managers.StaminaManager; package emu.grasscutter.game.managers.stamina;
public class Consumption { public class Consumption {
public ConsumptionType type = ConsumptionType.None; public ConsumptionType type = ConsumptionType.None;
public int amount = 0; public int amount = 0;
public Consumption(ConsumptionType type, int amount) { public Consumption(ConsumptionType type, int amount) {
this.type = type; this.type = type;
this.amount = amount; this.amount = amount;
} }
public Consumption(ConsumptionType type) { public Consumption(ConsumptionType type) {
this(type, type.amount); this(type, type.amount);
} }
public Consumption() { public Consumption() {
} }
} }

View File

@ -1,37 +1,37 @@
package emu.grasscutter.game.managers.StaminaManager; package emu.grasscutter.game.managers.stamina;
public enum ConsumptionType { public enum ConsumptionType {
None(0), None(0),
// consume // consume
CLIMBING(-150), CLIMBING(-150),
CLIMB_START(-500), CLIMB_START(-500),
CLIMB_JUMP(-2500), CLIMB_JUMP(-2500),
DASH(-360), DASH(-360),
FIGHT(0), // See StaminaManager.getFightConsumption() FIGHT(0), // See StaminaManager.getFightConsumption()
FLY(-60), FLY(-60),
// Slow swimming is handled per movement, not per second. // Slow swimming is handled per movement, not per second.
// Arm movement frequency depends on gender/age/height. // Arm movement frequency depends on gender/age/height.
// TODO: Instead of cost -80 per tick, find a proper way to calculate cost. // TODO: Instead of cost -80 per tick, find a proper way to calculate cost.
SKIFF_DASH(-204), SKIFF_DASH(-204),
SPRINT(-1800), SPRINT(-1800),
SWIM_DASH_START(-2000), SWIM_DASH_START(-2000),
SWIM_DASH(-204), // -10.2 per second, 5Hz = -204 each tick SWIM_DASH(-204), // -10.2 per second, 5Hz = -204 each tick
SWIMMING(-80), SWIMMING(-80),
TALENT_DASH(-300), // -1500 per second, 5Hz = -300 each tick TALENT_DASH(-300), // -1500 per second, 5Hz = -300 each tick
TALENT_DASH_START(-1000), TALENT_DASH_START(-1000),
// restore // restore
POWERED_FLY(500), POWERED_FLY(500),
POWERED_SKIFF(500), POWERED_SKIFF(500),
RUN(500), RUN(500),
SKIFF(500), SKIFF(500),
STANDBY(500), STANDBY(500),
WALK(500); WALK(500);
public final int amount; public final int amount;
ConsumptionType(int amount) { ConsumptionType(int amount) {
this.amount = amount; this.amount = amount;
} }
} }

View File

@ -1,73 +1,73 @@
# Stamina Manager # Stamina Manager
--- ---
## UpdateStamina ## UpdateStamina
```java ```java
// will use consumption.consumptionType as reason // will use consumption.consumptionType as reason
public int updateStaminaRelative(GameSession session, Consumption consumption); public int updateStaminaRelative(GameSession session, Consumption consumption);
``` ```
```java ```java
public int updateStaminaAbsolute(GameSession session, String reason, int newStamina) public int updateStaminaAbsolute(GameSession session, String reason, int newStamina)
``` ```
--- ---
## Pause and Resume ## Pause and Resume
```java ```java
public void startSustainedStaminaHandler() public void startSustainedStaminaHandler()
``` ```
```java ```java
public void stopSustainedStaminaHandler() public void stopSustainedStaminaHandler()
``` ```
--- ---
## Stamina change listeners and intercepting ## Stamina change listeners and intercepting
### BeforeUpdateStaminaListener ### BeforeUpdateStaminaListener
```java ```java
import emu.grasscutter.game.managers.StaminaManager.BeforeUpdateStaminaListener; import emu.grasscutter.game.managers.StaminaManager.BeforeUpdateStaminaListener;
// Listener sample: plugin disable CLIMB_JUMP stamina cost. // Listener sample: plugin disable CLIMB_JUMP stamina cost.
private class MyClass implements BeforeUpdateStaminaListener { private class MyClass implements BeforeUpdateStaminaListener {
// Make your class implement the listener, and pass in your class as a listener. // Make your class implement the listener, and pass in your class as a listener.
public MyClass() { public MyClass() {
getStaminaManager().registerBeforeUpdateStaminaListener("myClass", this); getStaminaManager().registerBeforeUpdateStaminaListener("myClass", this);
} }
@Override @Override
public boolean onBeforeUpdateStamina(String reason, int newStamina) { public boolean onBeforeUpdateStamina(String reason, int newStamina) {
// do not intercept this update // do not intercept this update
return false; return false;
} }
@Override @Override
public boolean onBeforeUpdateStamina(String reason, Consumption consumption) { public boolean onBeforeUpdateStamina(String reason, Consumption consumption) {
// Try to intercept if this update is CLIMB_JUMP // Try to intercept if this update is CLIMB_JUMP
if (consumption.consumptionType == ConsumptionType.CLIMB_JUMP) { if (consumption.consumptionType == ConsumptionType.CLIMB_JUMP) {
return true; return true;
} }
// If it is not CLIMB_JUMP, do not intercept. // If it is not CLIMB_JUMP, do not intercept.
return false; return false;
} }
} }
``` ```
### AfterUpdateStaminaListener ### AfterUpdateStaminaListener
```java ```java
import emu.grasscutter.game.managers.StaminaManager.AfterUpdateStaminaListener; import emu.grasscutter.game.managers.StaminaManager.AfterUpdateStaminaListener;
// Listener sample: plugin listens for changes already made. // Listener sample: plugin listens for changes already made.
private class MyClass implements AfterUpdateStaminaListener { private class MyClass implements AfterUpdateStaminaListener {
// Make your class implement the listener, and pass in your class as a listener. // Make your class implement the listener, and pass in your class as a listener.
public MyClass() { public MyClass() {
registerAfterUpdateStaminaListener("myClass", this); registerAfterUpdateStaminaListener("myClass", this);
} }
@Override @Override
public void onAfterUpdateStamina(String reason, int newStamina) { public void onAfterUpdateStamina(String reason, int newStamina) {
// ... // ...
} }
} }
``` ```

View File

@ -14,7 +14,6 @@ import emu.grasscutter.game.avatar.AvatarProfileData;
import emu.grasscutter.game.avatar.AvatarStorage; import emu.grasscutter.game.avatar.AvatarStorage;
import emu.grasscutter.game.entity.EntityMonster; import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.entity.EntityVehicle; import emu.grasscutter.game.entity.EntityVehicle;
import emu.grasscutter.game.managers.DeforestationManager.DeforestationManager;
import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityItem; import emu.grasscutter.game.entity.EntityItem;
import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.entity.GameEntity;
@ -28,18 +27,19 @@ import emu.grasscutter.game.mail.Mail;
import emu.grasscutter.game.mail.MailHandler; import emu.grasscutter.game.mail.MailHandler;
import emu.grasscutter.game.managers.InsectCaptureManager; import emu.grasscutter.game.managers.InsectCaptureManager;
import emu.grasscutter.game.managers.ResinManager; import emu.grasscutter.game.managers.ResinManager;
import emu.grasscutter.game.managers.StaminaManager.StaminaManager; import emu.grasscutter.game.managers.deforestation.DeforestationManager;
import emu.grasscutter.game.managers.energy.EnergyManager;
import emu.grasscutter.game.managers.forging.ActiveForgeData;
import emu.grasscutter.game.managers.forging.ForgingManager;
import emu.grasscutter.game.managers.mapmark.*;
import emu.grasscutter.game.managers.stamina.StaminaManager;
import emu.grasscutter.game.managers.SotSManager; import emu.grasscutter.game.managers.SotSManager;
import emu.grasscutter.game.managers.EnergyManager.EnergyManager;
import emu.grasscutter.game.managers.ForgingManager.ActiveForgeData;
import emu.grasscutter.game.managers.ForgingManager.ForgingManager;
import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.props.EntityType;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.props.SceneType; import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.quest.QuestManager; import emu.grasscutter.game.quest.QuestManager;
import emu.grasscutter.game.shop.ShopLimit; import emu.grasscutter.game.shop.ShopLimit;
import emu.grasscutter.game.managers.MapMarkManager.*;
import emu.grasscutter.game.tower.TowerData; import emu.grasscutter.game.tower.TowerData;
import emu.grasscutter.game.tower.TowerManager; import emu.grasscutter.game.tower.TowerManager;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;

View File

@ -10,14 +10,14 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public enum ElementType { public enum ElementType {
None (0, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY), None (0, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY),
Fire (1, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY, 10101, "TeamResonance_Fire_Lv2"), Fire (1, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY, 10101, "TeamResonance_Fire_Lv2", 2),
Water (2, FightProperty.FIGHT_PROP_CUR_WATER_ENERGY, FightProperty.FIGHT_PROP_MAX_WATER_ENERGY, 10201, "TeamResonance_Water_Lv2"), Water (2, FightProperty.FIGHT_PROP_CUR_WATER_ENERGY, FightProperty.FIGHT_PROP_MAX_WATER_ENERGY, 10201, "TeamResonance_Water_Lv2", 3),
Grass (3, FightProperty.FIGHT_PROP_CUR_GRASS_ENERGY, FightProperty.FIGHT_PROP_MAX_GRASS_ENERGY), Grass (3, FightProperty.FIGHT_PROP_CUR_GRASS_ENERGY, FightProperty.FIGHT_PROP_MAX_GRASS_ENERGY),
Electric (4, FightProperty.FIGHT_PROP_CUR_ELEC_ENERGY, FightProperty.FIGHT_PROP_MAX_ELEC_ENERGY, 10401, "TeamResonance_Electric_Lv2"), Electric (4, FightProperty.FIGHT_PROP_CUR_ELEC_ENERGY, FightProperty.FIGHT_PROP_MAX_ELEC_ENERGY, 10401, "TeamResonance_Electric_Lv2", 7),
Ice (5, FightProperty.FIGHT_PROP_CUR_ICE_ENERGY, FightProperty.FIGHT_PROP_MAX_ICE_ENERGY, 10601, "TeamResonance_Ice_Lv2"), Ice (5, FightProperty.FIGHT_PROP_CUR_ICE_ENERGY, FightProperty.FIGHT_PROP_MAX_ICE_ENERGY, 10601, "TeamResonance_Ice_Lv2", 5),
Frozen (6, FightProperty.FIGHT_PROP_CUR_ICE_ENERGY, FightProperty.FIGHT_PROP_MAX_ICE_ENERGY), Frozen (6, FightProperty.FIGHT_PROP_CUR_ICE_ENERGY, FightProperty.FIGHT_PROP_MAX_ICE_ENERGY),
Wind (7, FightProperty.FIGHT_PROP_CUR_WIND_ENERGY, FightProperty.FIGHT_PROP_MAX_WIND_ENERGY, 10301, "TeamResonance_Wind_Lv2"), Wind (7, FightProperty.FIGHT_PROP_CUR_WIND_ENERGY, FightProperty.FIGHT_PROP_MAX_WIND_ENERGY, 10301, "TeamResonance_Wind_Lv2", 4),
Rock (8, FightProperty.FIGHT_PROP_CUR_ROCK_ENERGY, FightProperty.FIGHT_PROP_MAX_ROCK_ENERGY, 10701, "TeamResonance_Rock_Lv2"), Rock (8, FightProperty.FIGHT_PROP_CUR_ROCK_ENERGY, FightProperty.FIGHT_PROP_MAX_ROCK_ENERGY, 10701, "TeamResonance_Rock_Lv2", 6),
AntiFire (9, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY), AntiFire (9, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY),
Default (255, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY, 10801, "TeamResonance_AllDifferent"); Default (255, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY, 10801, "TeamResonance_AllDifferent");
@ -25,6 +25,7 @@ public enum ElementType {
private final int teamResonanceId; private final int teamResonanceId;
private final FightProperty curEnergyProp; private final FightProperty curEnergyProp;
private final FightProperty maxEnergyProp; private final FightProperty maxEnergyProp;
private int depotValue;
private final int configHash; private final int configHash;
private static final Int2ObjectMap<ElementType> map = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<ElementType> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, ElementType> stringMap = new HashMap<>(); private static final Map<String, ElementType> stringMap = new HashMap<>();
@ -51,6 +52,11 @@ public enum ElementType {
this.configHash = 0; this.configHash = 0;
} }
} }
private ElementType(int value, FightProperty curEnergyProp, FightProperty maxEnergyProp, int teamResonanceId, String configName, int depotValue) {
this(value, curEnergyProp, maxEnergyProp, teamResonanceId, configName);
this.depotValue = depotValue;
}
public int getValue() { public int getValue() {
return value; return value;
@ -64,6 +70,10 @@ public enum ElementType {
return maxEnergyProp; return maxEnergyProp;
} }
public int getDepotValue() {
return depotValue;
}
public int getTeamResonanceId() { public int getTeamResonanceId() {
return teamResonanceId; return teamResonanceId;
} }

View File

@ -4,6 +4,7 @@ import emu.grasscutter.Grasscutter;
import emu.grasscutter.auth.AuthenticationSystem; import emu.grasscutter.auth.AuthenticationSystem;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.command.PermissionHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.http.HttpServer; import emu.grasscutter.server.http.HttpServer;
@ -97,4 +98,12 @@ public final class ServerHook {
public void setAuthSystem(AuthenticationSystem authSystem) { public void setAuthSystem(AuthenticationSystem authSystem) {
Grasscutter.setAuthenticationSystem(authSystem); Grasscutter.setAuthenticationSystem(authSystem);
} }
/**
* Sets the server's permission handler.
* @param permHandler An instance of the permission handler.
*/
public void setPermissionHandler(PermissionHandler permHandler) {
Grasscutter.setPermissionHandler(permHandler);
}
} }

View File

@ -10,10 +10,10 @@ import emu.grasscutter.game.drop.DropManager;
import emu.grasscutter.game.dungeons.DungeonManager; import emu.grasscutter.game.dungeons.DungeonManager;
import emu.grasscutter.game.expedition.ExpeditionManager; import emu.grasscutter.game.expedition.ExpeditionManager;
import emu.grasscutter.game.gacha.GachaManager; import emu.grasscutter.game.gacha.GachaManager;
import emu.grasscutter.game.managers.ChatManager.ChatManager;
import emu.grasscutter.game.managers.ChatManager.ChatManagerHandler;
import emu.grasscutter.game.managers.InventoryManager; import emu.grasscutter.game.managers.InventoryManager;
import emu.grasscutter.game.managers.MultiplayerManager; import emu.grasscutter.game.managers.MultiplayerManager;
import emu.grasscutter.game.managers.chat.ChatManager;
import emu.grasscutter.game.managers.chat.ChatManagerHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.ServerQuestHandler; import emu.grasscutter.game.quest.ServerQuestHandler;
import emu.grasscutter.game.shop.ShopManager; import emu.grasscutter.game.shop.ShopManager;

View File

@ -0,0 +1,68 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.GameConstants;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.AvatarSkillDepotData;
import emu.grasscutter.data.excels.WorldAreaData;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.AvatarChangeElementTypeReqOuterClass.AvatarChangeElementTypeReq;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketAbilityChangeNotify;
import emu.grasscutter.server.packet.send.PacketAvatarChangeElementTypeRsp;
import emu.grasscutter.server.packet.send.PacketAvatarFightPropNotify;
import emu.grasscutter.server.packet.send.PacketAvatarSkillDepotChangeNotify;
@Opcodes(PacketOpcodes.AvatarChangeElementTypeReq)
public class HandlerAvatarChangeElementTypeReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
AvatarChangeElementTypeReq req = AvatarChangeElementTypeReq.parseFrom(payload);
WorldAreaData area = GameData.getWorldAreaDataMap().get(req.getAreaId());
if (area == null || area.getElementType() == null || area.getElementType().getDepotValue() <= 0) {
session.send(new PacketAvatarChangeElementTypeRsp(Retcode.RET_SVR_ERROR_VALUE));
return;
}
// Get current avatar, should be one of the main characters
EntityAvatar mainCharacterEntity = session.getPlayer().getTeamManager().getCurrentAvatarEntity();
int intialSkillDepotId = 0;
if (mainCharacterEntity.getAvatar().getAvatarId() == GameConstants.MAIN_CHARACTER_MALE) {
intialSkillDepotId = 500;
} else if (mainCharacterEntity.getAvatar().getAvatarId() == GameConstants.MAIN_CHARACTER_FEMALE) {
intialSkillDepotId = 700;
} else {
session.send(new PacketAvatarChangeElementTypeRsp(Retcode.RET_SVR_ERROR_VALUE));
return;
}
intialSkillDepotId += area.getElementType().getDepotValue();
// Sanity checks for skill depots
Avatar mainCharacter = mainCharacterEntity.getAvatar();
AvatarSkillDepotData skillDepot = GameData.getAvatarSkillDepotDataMap().get(intialSkillDepotId);
if (skillDepot == null || skillDepot.getId() == mainCharacter.getSkillDepotId()) {
session.send(new PacketAvatarChangeElementTypeRsp(Retcode.RET_SVR_ERROR_VALUE));
return;
}
// Success
session.send(new PacketAvatarChangeElementTypeRsp());
// Set skill depot
mainCharacter.setSkillDepotData(skillDepot);
// Ability change packet
session.send(new PacketAvatarSkillDepotChangeNotify(mainCharacter));
session.send(new PacketAbilityChangeNotify(mainCharacterEntity));
session.send(new PacketAvatarFightPropNotify(mainCharacter));
}
}

View File

@ -0,0 +1,28 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.game.gacha.GachaBanner;
import emu.grasscutter.game.gacha.PlayerGachaBannerInfo;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.GachaWishReqOuterClass.GachaWishReq;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketGachaWishRsp;
@Opcodes(PacketOpcodes.GachaWishReq)
public class HandlerGachaWishReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
GachaWishReq req = GachaWishReq.parseFrom(payload);
GachaBanner banner = session.getServer().getGachaManager().getGachaBanners().get(req.getGachaScheduleId());
PlayerGachaBannerInfo gachaInfo = session.getPlayer().getGachaInfo().getBannerInfo(banner);
gachaInfo.setFailedChosenItemPulls(0);
gachaInfo.setWishItemId(req.getItemId());
session.send(new PacketGachaWishRsp(req.getGachaType(), req.getGachaScheduleId(), req.getItemId(), 0, banner.getWishMaxProgress()));
}
}

View File

@ -1,9 +1,12 @@
package emu.grasscutter.server.packet.recv; package emu.grasscutter.server.packet.recv;
import emu.grasscutter.game.gacha.GachaBanner;
import emu.grasscutter.game.gacha.PlayerGachaBannerInfo;
import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketGachaWishRsp;
import emu.grasscutter.server.packet.send.PacketGetGachaInfoRsp; import emu.grasscutter.server.packet.send.PacketGetGachaInfoRsp;
@Opcodes(PacketOpcodes.GetGachaInfoReq) @Opcodes(PacketOpcodes.GetGachaInfoReq)
@ -11,10 +14,7 @@ public class HandlerGetGachaInfoReq extends PacketHandler {
@Override @Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
session.send(new PacketGetGachaInfoRsp(session.getServer().getGachaManager(), session.send(new PacketGetGachaInfoRsp(session.getServer().getGachaManager(), session.getPlayer()));
// TODO: use other Nonce/key insteadof session key to ensure the overall security for the player
session.getPlayer().getAccount().getSessionKey())
);
} }
} }

View File

@ -0,0 +1,24 @@
package emu.grasscutter.server.packet.send;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.AvatarChangeElementTypeRspOuterClass.AvatarChangeElementTypeRsp;
public class PacketAvatarChangeElementTypeRsp extends BasePacket {
public PacketAvatarChangeElementTypeRsp() {
super(PacketOpcodes.AvatarChangeElementTypeRsp);
}
public PacketAvatarChangeElementTypeRsp(int retcode) {
super(PacketOpcodes.AvatarChangeElementTypeRsp);
if (retcode > 0) {
AvatarChangeElementTypeRsp proto = AvatarChangeElementTypeRsp.newBuilder()
.setRetcode(retcode)
.build();
this.setData(proto);
}
}
}

View File

@ -0,0 +1,26 @@
package emu.grasscutter.server.packet.send;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.AvatarSkillDepotChangeNotifyOuterClass.AvatarSkillDepotChangeNotify;
public class PacketAvatarSkillDepotChangeNotify extends BasePacket {
public PacketAvatarSkillDepotChangeNotify(Avatar avatar) {
super(PacketOpcodes.AvatarSkillDepotChangeNotify);
AvatarSkillDepotChangeNotify proto = AvatarSkillDepotChangeNotify.newBuilder()
.setAvatarGuid(avatar.getGuid())
.setEntityId(avatar.getEntityId())
.setSkillDepotId(avatar.getSkillDepotId())
.setCoreProudSkillLevel(avatar.getCoreProudSkillLevel())
.addAllTalentIdList(avatar.getTalentIdList())
.addAllProudSkillList(avatar.getProudSkillList())
.putAllSkillLevelMap(avatar.getSkillLevelMap())
.putAllProudSkillExtraLevelMap(avatar.getProudSkillBonusMap())
.build();
this.setData(proto);
}
}

View File

@ -4,6 +4,7 @@ import java.util.List;
import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.game.gacha.GachaBanner; import emu.grasscutter.game.gacha.GachaBanner;
import emu.grasscutter.game.gacha.PlayerGachaBannerInfo;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.DoGachaRspOuterClass.DoGachaRsp; import emu.grasscutter.net.proto.DoGachaRspOuterClass.DoGachaRsp;
@ -13,12 +14,12 @@ import emu.grasscutter.net.proto.RetcodeOuterClass;
public class PacketDoGachaRsp extends BasePacket { public class PacketDoGachaRsp extends BasePacket {
public PacketDoGachaRsp(GachaBanner banner, List<GachaItem> list) { public PacketDoGachaRsp(GachaBanner banner, List<GachaItem> list, PlayerGachaBannerInfo gachaInfo) {
super(PacketOpcodes.DoGachaRsp); super(PacketOpcodes.DoGachaRsp);
ItemParamData costItem = banner.getCost(1); ItemParamData costItem = banner.getCost(1);
ItemParamData costItem10 = banner.getCost(10); ItemParamData costItem10 = banner.getCost(10);
DoGachaRsp p = DoGachaRsp.newBuilder() DoGachaRsp.Builder rsp = DoGachaRsp.newBuilder()
.setGachaType(banner.getGachaType()) .setGachaType(banner.getGachaType())
.setGachaScheduleId(banner.getScheduleId()) .setGachaScheduleId(banner.getScheduleId())
.setGachaTimes(list.size()) .setGachaTimes(list.size())
@ -28,10 +29,15 @@ public class PacketDoGachaRsp extends BasePacket {
.setCostItemNum(costItem.getCount()) .setCostItemNum(costItem.getCount())
.setTenCostItemId(costItem10.getId()) .setTenCostItemId(costItem10.getId())
.setTenCostItemNum(costItem10.getCount()) .setTenCostItemNum(costItem10.getCount())
.addAllGachaItemList(list) .addAllGachaItemList(list);
.build();
if(banner.hasEpitomized()) {
rsp.setWishItemId(gachaInfo.getWishItemId())
.setWishProgress(gachaInfo.getFailedChosenItemPulls())
.setWishMaxProgress(banner.getWishMaxProgress());
}
this.setData(p); this.setData(rsp.build());
} }
public PacketDoGachaRsp() { public PacketDoGachaRsp() {

View File

@ -0,0 +1,23 @@
package emu.grasscutter.server.packet.send;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.GachaWishRspOuterClass.GachaWishRsp;
public class PacketGachaWishRsp extends BasePacket {
public PacketGachaWishRsp(int gachaType, int scheduleId, int itemId, int progress, int maxProgress) {
super(PacketOpcodes.GachaWishRsp);
GachaWishRsp proto = GachaWishRsp.newBuilder()
.setGachaType(gachaType)
.setGachaScheduleId(scheduleId)
.setWishItemId(itemId)
.setWishProgress(progress)
.setWishMaxProgress(maxProgress)
.build();
this.setData(proto);
}
}

View File

@ -1,22 +1,16 @@
package emu.grasscutter.server.packet.send; package emu.grasscutter.server.packet.send;
import emu.grasscutter.game.gacha.GachaManager; import emu.grasscutter.game.gacha.GachaManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
public class PacketGetGachaInfoRsp extends BasePacket { public class PacketGetGachaInfoRsp extends BasePacket {
@Deprecated public PacketGetGachaInfoRsp(GachaManager manager, Player player) {
public PacketGetGachaInfoRsp(GachaManager manager) {
super(PacketOpcodes.GetGachaInfoRsp); super(PacketOpcodes.GetGachaInfoRsp);
this.setData(manager.toProto()); this.setData(manager.toProto(player));
}
public PacketGetGachaInfoRsp(GachaManager manager, String sessionKey) {
super(PacketOpcodes.GetGachaInfoRsp);
this.setData(manager.toProto(sessionKey));
} }
} }

View File

@ -1,6 +1,6 @@
package emu.grasscutter.server.packet.send; package emu.grasscutter.server.packet.send;
import emu.grasscutter.game.managers.MapMarkManager.MapMark; import emu.grasscutter.game.managers.mapmark.MapMark;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;