mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-12-22 20:04:56 +01:00
Merge development into plugin-auth
This commit is contained in:
@@ -144,16 +144,17 @@ public class Account {
|
||||
}
|
||||
|
||||
public boolean hasPermission(String permission) {
|
||||
if (this.permissions.contains(permission) || this.permissions.contains("*")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.permissions.contains(permission)) return true;
|
||||
if(this.permissions.contains("*") && this.permissions.size() == 1) return true;
|
||||
|
||||
String[] permissionParts = permission.split("\\.");
|
||||
for (String p : this.permissions) {
|
||||
if (permissionMatchesWildcard(p, permissionParts)) {
|
||||
return true;
|
||||
}
|
||||
if (p.startsWith("-") && permissionMatchesWildcard(p.substring(1), permissionParts)) return false;
|
||||
if (permissionMatchesWildcard(p, permissionParts)) return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
return this.permissions.contains("*");
|
||||
}
|
||||
|
||||
public boolean removePermission(String permission) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import emu.grasscutter.data.custom.ScenePointEntry;
|
||||
import emu.grasscutter.data.def.DungeonData;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.game.props.SceneType;
|
||||
import emu.grasscutter.game.quest.enums.QuestTrigger;
|
||||
import emu.grasscutter.net.packet.BasePacket;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.server.game.GameServer;
|
||||
@@ -51,8 +52,9 @@ public class DungeonManager {
|
||||
int sceneId = data.getSceneId();
|
||||
player.getScene().setPrevScene(sceneId);
|
||||
|
||||
if(player.getWorld().transferPlayerToScene(player, sceneId, data)){
|
||||
if (player.getWorld().transferPlayerToScene(player, sceneId, data)) {
|
||||
player.getScene().addDungeonSettleObserver(basicDungeonSettleObserver);
|
||||
player.getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_ENTER_DUNGEON, data.getId());
|
||||
}
|
||||
|
||||
player.getScene().setPrevScenePoint(pointId);
|
||||
|
||||
@@ -2,27 +2,48 @@ package emu.grasscutter.game.gacha;
|
||||
|
||||
import emu.grasscutter.net.proto.GachaInfoOuterClass.GachaInfo;
|
||||
import emu.grasscutter.net.proto.GachaUpInfoOuterClass.GachaUpInfo;
|
||||
import emu.grasscutter.utils.Utils;
|
||||
|
||||
import static emu.grasscutter.Configuration.*;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.data.common.ItemParamData;
|
||||
|
||||
public class GachaBanner {
|
||||
private int gachaType;
|
||||
private int scheduleId;
|
||||
private String prefabPath;
|
||||
private String previewPrefabPath;
|
||||
private String titlePath;
|
||||
private int costItem;
|
||||
private int costItemId = 0;
|
||||
private int costItemAmount = 1;
|
||||
private int costItemId10 = 0;
|
||||
private int costItemAmount10 = 10;
|
||||
private int beginTime;
|
||||
private int endTime;
|
||||
private int sortId;
|
||||
private int[] rateUpItems1;
|
||||
private int[] rateUpItems2;
|
||||
private int baseYellowWeight = 60; // Max 10000
|
||||
private int basePurpleWeight = 510; // Max 10000
|
||||
private int eventChance = 50; // Chance to win a featured event item
|
||||
private int softPity = 75;
|
||||
private int hardPity = 90;
|
||||
private int[] rateUpItems4 = {};
|
||||
private int[] rateUpItems5 = {};
|
||||
private int[] fallbackItems3 = {11301, 11302, 11306, 12301, 12302, 12305, 13303, 14301, 14302, 14304, 15301, 15302, 15304};
|
||||
private int[] fallbackItems4Pool1 = {1014, 1020, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1064};
|
||||
private int[] fallbackItems4Pool2 = {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405};
|
||||
private int[] fallbackItems5Pool1 = {1003, 1016, 1042, 1035, 1041};
|
||||
private int[] fallbackItems5Pool2 = {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502};
|
||||
private boolean removeC6FromPool = false;
|
||||
private boolean autoStripRateUpFromFallback = true;
|
||||
private int[][] weights4 = {{1,510}, {8,510}, {10,10000}};
|
||||
private int[][] weights5 = {{1,75}, {73,150}, {90,10000}};
|
||||
private int[][] poolBalanceWeights4 = {{1,255}, {17,255}, {21,10455}};
|
||||
private int[][] poolBalanceWeights5 = {{1,30}, {147,150}, {181,10230}};
|
||||
private int eventChance4 = 50; // Chance to win a featured event item
|
||||
private int eventChance5 = 50; // Chance to win a featured event item
|
||||
private BannerType bannerType = BannerType.STANDARD;
|
||||
|
||||
// Kinda wanna deprecate these but they're in people's configs
|
||||
private int[] rateUpItems1 = {};
|
||||
private int[] rateUpItems2 = {};
|
||||
private int eventChance = -1;
|
||||
private int costItem = 0;
|
||||
|
||||
public int getGachaType() {
|
||||
return gachaType;
|
||||
@@ -48,8 +69,15 @@ public class GachaBanner {
|
||||
return titlePath;
|
||||
}
|
||||
|
||||
public ItemParamData getCost(int numRolls) {
|
||||
return switch (numRolls) {
|
||||
case 10 -> new ItemParamData((costItemId10 > 0) ? costItemId10 : getCostItem(), costItemAmount10);
|
||||
default -> new ItemParamData(getCostItem(), costItemAmount * numRolls);
|
||||
};
|
||||
}
|
||||
|
||||
public int getCostItem() {
|
||||
return costItem;
|
||||
return (costItem > 0) ? costItem : costItemId;
|
||||
}
|
||||
|
||||
public int getBeginTime() {
|
||||
@@ -64,32 +92,42 @@ public class GachaBanner {
|
||||
return sortId;
|
||||
}
|
||||
|
||||
public int getBaseYellowWeight() {
|
||||
return baseYellowWeight;
|
||||
public int[] getRateUpItems4() {
|
||||
return (rateUpItems2.length > 0) ? rateUpItems2 : rateUpItems4;
|
||||
}
|
||||
public int[] getRateUpItems5() {
|
||||
return (rateUpItems1.length > 0) ? rateUpItems1 : rateUpItems5;
|
||||
}
|
||||
|
||||
public int getBasePurpleWeight() {
|
||||
return basePurpleWeight;
|
||||
public int[] getFallbackItems3() {return fallbackItems3;}
|
||||
public int[] getFallbackItems4Pool1() {return fallbackItems4Pool1;}
|
||||
public int[] getFallbackItems4Pool2() {return fallbackItems4Pool2;}
|
||||
public int[] getFallbackItems5Pool1() {return fallbackItems5Pool1;}
|
||||
public int[] getFallbackItems5Pool2() {return fallbackItems5Pool2;}
|
||||
|
||||
public boolean getRemoveC6FromPool() {return removeC6FromPool;}
|
||||
public boolean getAutoStripRateUpFromFallback() {return autoStripRateUpFromFallback;}
|
||||
|
||||
|
||||
public int getWeight(int rarity, int pity) {
|
||||
return switch(rarity) {
|
||||
case 4 -> Utils.lerp(pity, weights4);
|
||||
default -> Utils.lerp(pity, weights5);
|
||||
};
|
||||
}
|
||||
|
||||
public int[] getRateUpItems1() {
|
||||
return rateUpItems1;
|
||||
public int getPoolBalanceWeight(int rarity, int pity) {
|
||||
return switch(rarity) {
|
||||
case 4 -> Utils.lerp(pity, poolBalanceWeights4);
|
||||
default -> Utils.lerp(pity, poolBalanceWeights5);
|
||||
};
|
||||
}
|
||||
|
||||
public int[] getRateUpItems2() {
|
||||
return rateUpItems2;
|
||||
}
|
||||
|
||||
public int getSoftPity() {
|
||||
return softPity - 1;
|
||||
}
|
||||
|
||||
public int getHardPity() {
|
||||
return hardPity - 1;
|
||||
}
|
||||
|
||||
public int getEventChance() {
|
||||
return eventChance;
|
||||
public int getEventChance(int rarity) {
|
||||
return switch(rarity) {
|
||||
case 4 -> eventChance4;
|
||||
default -> (eventChance > -1) ? eventChance : eventChance5;
|
||||
};
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@@ -102,34 +140,40 @@ public class GachaBanner {
|
||||
+ lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress) + ":"
|
||||
+ lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort)
|
||||
+ "/gacha?s=" + sessionKey + "&gachaType=" + gachaType;
|
||||
String details = "http" + (DISPATCH_INFO.encryption.useInRouting ? "s" : "") + "://"
|
||||
+ lr(DISPATCH_INFO.accessAddress, DISPATCH_INFO.bindAddress) + ":"
|
||||
+ lr(DISPATCH_INFO.accessPort, DISPATCH_INFO.bindPort)
|
||||
+ "/gacha/details?s=" + sessionKey + "&gachaType=" + gachaType;
|
||||
|
||||
// Grasscutter.getLogger().info("record = " + record);
|
||||
ItemParamData costItem1 = this.getCost(1);
|
||||
ItemParamData costItem10 = this.getCost(10);
|
||||
GachaInfo.Builder info = GachaInfo.newBuilder()
|
||||
.setGachaType(this.getGachaType())
|
||||
.setScheduleId(this.getScheduleId())
|
||||
.setBeginTime(this.getBeginTime())
|
||||
.setEndTime(this.getEndTime())
|
||||
.setCostItemId(this.getCostItem())
|
||||
.setCostItemNum(1)
|
||||
.setCostItemId(costItem1.getId())
|
||||
.setCostItemNum(costItem1.getCount())
|
||||
.setTenCostItemId(costItem10.getId())
|
||||
.setTenCostItemNum(costItem10.getCount())
|
||||
.setGachaPrefabPath(this.getPrefabPath())
|
||||
.setGachaPreviewPrefabPath(this.getPreviewPrefabPath())
|
||||
.setGachaProbUrl(record)
|
||||
.setGachaProbUrlOversea(record)
|
||||
.setGachaProbUrl(details)
|
||||
.setGachaProbUrlOversea(details)
|
||||
.setGachaRecordUrl(record)
|
||||
.setGachaRecordUrlOversea(record)
|
||||
.setTenCostItemId(this.getCostItem())
|
||||
.setTenCostItemNum(10)
|
||||
.setLeftGachaTimes(Integer.MAX_VALUE)
|
||||
.setGachaTimesLimit(Integer.MAX_VALUE)
|
||||
.setGachaSortId(this.getSortId());
|
||||
|
||||
if (this.getTitlePath() != null) {
|
||||
info.setGachaTitlePath(this.getTitlePath());
|
||||
}
|
||||
|
||||
if (this.getRateUpItems1().length > 0) {
|
||||
if (this.getRateUpItems5().length > 0) {
|
||||
GachaUpInfo.Builder upInfo = GachaUpInfo.newBuilder().setItemParentType(1);
|
||||
|
||||
for (int id : getRateUpItems1()) {
|
||||
for (int id : getRateUpItems5()) {
|
||||
upInfo.addItemIdList(id);
|
||||
info.addMainNameId(id);
|
||||
}
|
||||
@@ -137,10 +181,10 @@ public class GachaBanner {
|
||||
info.addGachaUpInfoList(upInfo);
|
||||
}
|
||||
|
||||
if (this.getRateUpItems2().length > 0) {
|
||||
if (this.getRateUpItems4().length > 0) {
|
||||
GachaUpInfo.Builder upInfo = GachaUpInfo.newBuilder().setItemParentType(2);
|
||||
|
||||
for (int id : getRateUpItems2()) {
|
||||
for (int id : getRateUpItems4()) {
|
||||
upInfo.addItemIdList(id);
|
||||
if (info.getSubNameIdCount() == 0) {
|
||||
info.addSubNameId(id);
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.nio.file.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
@@ -13,11 +14,12 @@ import com.google.gson.reflect.TypeToken;
|
||||
import com.sun.nio.file.SensitivityWatchEventModifier;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.common.ItemParamData;
|
||||
import emu.grasscutter.data.def.ItemData;
|
||||
import emu.grasscutter.database.DatabaseHelper;
|
||||
import emu.grasscutter.game.avatar.Avatar;
|
||||
import emu.grasscutter.game.gacha.GachaBanner.BannerType;
|
||||
import emu.grasscutter.game.inventory.GameItem;
|
||||
import emu.grasscutter.game.inventory.Inventory;
|
||||
import emu.grasscutter.game.inventory.ItemType;
|
||||
import emu.grasscutter.game.inventory.MaterialType;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
@@ -28,6 +30,7 @@ import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
|
||||
import emu.grasscutter.server.game.GameServer;
|
||||
import emu.grasscutter.server.game.GameServerTickEvent;
|
||||
import emu.grasscutter.server.packet.send.PacketDoGachaRsp;
|
||||
import emu.grasscutter.utils.Utils;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
@@ -41,15 +44,11 @@ public class GachaManager {
|
||||
private final Int2ObjectMap<GachaBanner> gachaBanners;
|
||||
private GetGachaInfoRsp cachedProto;
|
||||
WatchService watchService;
|
||||
|
||||
private final int[] yellowAvatars = new int[] {1003, 1016, 1042, 1035, 1041};
|
||||
private final int[] yellowWeapons = new int[] {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502};
|
||||
private final int[] purpleAvatars = new int[] {1006, 1014, 1015, 1020, 1021, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1064};
|
||||
private final int[] purpleWeapons = new int[] {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405};
|
||||
private final int[] blueWeapons = new int[] {11301, 11302, 11306, 12301, 12302, 12305, 13303, 14301, 14302, 14304, 15301, 15302, 15304};
|
||||
|
||||
private static final int starglitterId = 221;
|
||||
private static final int stardustId = 222;
|
||||
private int[] fallbackItems4Pool2Default = {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405};
|
||||
private int[] fallbackItems5Pool2Default = {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502};
|
||||
|
||||
public GachaManager(GameServer server) {
|
||||
this.server = server;
|
||||
@@ -66,7 +65,7 @@ public class GachaManager {
|
||||
return gachaBanners;
|
||||
}
|
||||
|
||||
public int randomRange(int min, int max) {
|
||||
public int randomRange(int min, int max) { // Both are inclusive
|
||||
return ThreadLocalRandom.current().nextInt(max - min + 1) + min;
|
||||
}
|
||||
|
||||
@@ -83,6 +82,8 @@ public class GachaManager {
|
||||
getGachaBanners().put(banner.getGachaType(), banner);
|
||||
}
|
||||
Grasscutter.getLogger().info("Banners successfully loaded.");
|
||||
|
||||
|
||||
this.cachedProto = createProto();
|
||||
} else {
|
||||
Grasscutter.getLogger().error("Unable to load banners. Banners size is 0.");
|
||||
@@ -92,13 +93,153 @@ public class GachaManager {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private class BannerPools {
|
||||
public int[] rateUpItems4;
|
||||
public int[] rateUpItems5;
|
||||
public int[] fallbackItems4Pool1;
|
||||
public int[] fallbackItems4Pool2;
|
||||
public int[] fallbackItems5Pool1;
|
||||
public int[] fallbackItems5Pool2;
|
||||
|
||||
public BannerPools(GachaBanner banner) {
|
||||
rateUpItems4 = banner.getRateUpItems4();
|
||||
rateUpItems5 = banner.getRateUpItems5();
|
||||
fallbackItems4Pool1 = banner.getFallbackItems4Pool1();
|
||||
fallbackItems4Pool2 = banner.getFallbackItems4Pool2();
|
||||
fallbackItems5Pool1 = banner.getFallbackItems5Pool1();
|
||||
fallbackItems5Pool2 = banner.getFallbackItems5Pool2();
|
||||
|
||||
if (banner.getAutoStripRateUpFromFallback()) {
|
||||
fallbackItems4Pool1 = Utils.setSubtract(fallbackItems4Pool1, rateUpItems4);
|
||||
fallbackItems4Pool2 = Utils.setSubtract(fallbackItems4Pool2, rateUpItems4);
|
||||
fallbackItems5Pool1 = Utils.setSubtract(fallbackItems5Pool1, rateUpItems5);
|
||||
fallbackItems5Pool2 = Utils.setSubtract(fallbackItems5Pool2, rateUpItems5);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeFromAllPools(int[] itemIds) {
|
||||
rateUpItems4 = Utils.setSubtract(rateUpItems4, itemIds);
|
||||
rateUpItems5 = Utils.setSubtract(rateUpItems5, itemIds);
|
||||
fallbackItems4Pool1 = Utils.setSubtract(fallbackItems4Pool1, itemIds);
|
||||
fallbackItems4Pool2 = Utils.setSubtract(fallbackItems4Pool2, itemIds);
|
||||
fallbackItems5Pool1 = Utils.setSubtract(fallbackItems5Pool1, itemIds);
|
||||
fallbackItems5Pool2 = Utils.setSubtract(fallbackItems5Pool2, itemIds);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized int checkPlayerAvatarConstellationLevel(Player player, int itemId) { // Maybe this would be useful in the Player class?
|
||||
ItemData itemData = GameData.getItemDataMap().get(itemId);
|
||||
if ((itemData == null) || (itemData.getMaterialType() != MaterialType.MATERIAL_AVATAR)){
|
||||
return -2; // Not an Avatar
|
||||
}
|
||||
Avatar avatar = player.getAvatars().getAvatarById((itemId % 1000) + 10000000);
|
||||
if (avatar == null) {
|
||||
return -1; // Doesn't have
|
||||
}
|
||||
// Constellation
|
||||
int constLevel = avatar.getCoreProudSkillLevel();
|
||||
GameItem constItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemId + 100);
|
||||
constLevel += (constItem == null)? 0 : constItem.getCount();
|
||||
return constLevel;
|
||||
}
|
||||
|
||||
private synchronized int[] removeC6FromPool(int[] itemPool, Player player) {
|
||||
IntList temp = new IntArrayList();
|
||||
for (int itemId : itemPool) {
|
||||
if (checkPlayerAvatarConstellationLevel(player, itemId) < 6) {
|
||||
temp.add(itemId);
|
||||
}
|
||||
}
|
||||
return temp.toIntArray();
|
||||
}
|
||||
|
||||
private synchronized int drawRoulette(int[] weights, int cutoff) {
|
||||
// This follows the logic laid out in issue #183
|
||||
// Simple weighted selection with an upper bound for the roll that cuts off trailing entries
|
||||
// All weights must be >= 0
|
||||
int total = 0;
|
||||
for (int weight : weights) {
|
||||
if (weight < 0) {
|
||||
throw new IllegalArgumentException("Weights must be non-negative!");
|
||||
}
|
||||
total += weight;
|
||||
}
|
||||
int roll = ThreadLocalRandom.current().nextInt((total < cutoff)? total : cutoff);
|
||||
int subTotal = 0;
|
||||
for (int i=0; i<weights.length; i++) {
|
||||
subTotal += weights[i];
|
||||
if (roll < subTotal) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
// throw new IllegalStateException();
|
||||
return 0; // This should only be reachable if total==0
|
||||
}
|
||||
|
||||
private synchronized int doRarePull(int[] featured, int[] fallback1, int[] fallback2, int rarity, GachaBanner banner, PlayerGachaBannerInfo gachaInfo) {
|
||||
int itemId = 0;
|
||||
boolean pullFeatured = (gachaInfo.getFailedFeaturedItemPulls(rarity) >= 1) // Lost previous coinflip
|
||||
|| (this.randomRange(1, 100) <= banner.getEventChance(rarity)); // Won this coinflip
|
||||
if (pullFeatured && (featured.length > 0)) {
|
||||
itemId = getRandom(featured);
|
||||
gachaInfo.setFailedFeaturedItemPulls(rarity, 0);
|
||||
} else {
|
||||
gachaInfo.addFailedFeaturedItemPulls(rarity, 1);
|
||||
if (fallback1.length < 1) {
|
||||
if (fallback2.length < 1) {
|
||||
itemId = getRandom((rarity==5)? fallbackItems5Pool2Default : fallbackItems4Pool2Default);
|
||||
} else {
|
||||
itemId = getRandom(fallback2);
|
||||
}
|
||||
} else if (fallback2.length < 1) {
|
||||
itemId = 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);
|
||||
};
|
||||
itemId = switch (chosenPool) {
|
||||
case 1:
|
||||
gachaInfo.setPityPool(rarity, 1, 0);
|
||||
yield getRandom(fallback1);
|
||||
default:
|
||||
gachaInfo.setPityPool(rarity, 2, 0);
|
||||
yield getRandom(fallback2);
|
||||
};
|
||||
}
|
||||
}
|
||||
return itemId;
|
||||
}
|
||||
|
||||
private synchronized int doPull(GachaBanner banner, PlayerGachaBannerInfo gachaInfo, BannerPools pools) {
|
||||
// Pre-increment all pity pools (yes this makes all calculations assume 1-indexed pity)
|
||||
gachaInfo.incPityAll();
|
||||
|
||||
int[] weights = {banner.getWeight(5, gachaInfo.getPity5()), banner.getWeight(4, gachaInfo.getPity4()), 10000};
|
||||
int levelWon = 5 - drawRoulette(weights, 10000);
|
||||
|
||||
return switch (levelWon) {
|
||||
case 5:
|
||||
gachaInfo.setPity5(0);
|
||||
yield doRarePull(pools.rateUpItems5, pools.fallbackItems5Pool1, pools.fallbackItems5Pool2, 5, banner, gachaInfo);
|
||||
case 4:
|
||||
gachaInfo.setPity4(0);
|
||||
yield doRarePull(pools.rateUpItems4, pools.fallbackItems4Pool1, pools.fallbackItems4Pool2, 4, banner, gachaInfo);
|
||||
default:
|
||||
yield getRandom(banner.getFallbackItems3());
|
||||
};
|
||||
}
|
||||
|
||||
public synchronized void doPulls(Player player, int gachaType, int times) {
|
||||
// Sanity check
|
||||
if (times != 10 && times != 1) {
|
||||
return;
|
||||
}
|
||||
if (player.getInventory().getInventoryTab(ItemType.ITEM_WEAPON).getSize() + times > player.getInventory().getInventoryTab(ItemType.ITEM_WEAPON).getMaxCapacity()) {
|
||||
Inventory inventory = player.getInventory();
|
||||
if (inventory.getInventoryTab(ItemType.ITEM_WEAPON).getSize() + times > inventory.getInventoryTab(ItemType.ITEM_WEAPON).getMaxCapacity()) {
|
||||
player.sendPacket(new PacketDoGachaRsp());
|
||||
return;
|
||||
}
|
||||
@@ -111,93 +252,33 @@ public class GachaManager {
|
||||
}
|
||||
|
||||
// Spend currency
|
||||
if (banner.getCostItem() > 0) {
|
||||
GameItem costItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(banner.getCostItem());
|
||||
if (costItem == null || costItem.getCount() < times) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.getInventory().removeItem(costItem, times);
|
||||
}
|
||||
|
||||
// Roll
|
||||
PlayerGachaBannerInfo gachaInfo = player.getGachaInfo().getBannerInfo(banner);
|
||||
IntList wonItems = new IntArrayList(times);
|
||||
|
||||
for (int i = 0; i < times; i++) {
|
||||
int random = this.randomRange(1, 10000);
|
||||
int itemId = 0;
|
||||
|
||||
int bonusYellowChance = gachaInfo.getPity5() >= banner.getSoftPity() ? 100 * (gachaInfo.getPity5() - banner.getSoftPity() - 1): 0;
|
||||
int yellowChance = banner.getBaseYellowWeight() + (int) Math.floor(100f * (gachaInfo.getPity5() / (banner.getSoftPity() - 1D))) + bonusYellowChance;
|
||||
int purpleChance = 10000 - (banner.getBasePurpleWeight() + (int) Math.floor(790f * (gachaInfo.getPity4() / 8f)));
|
||||
|
||||
if (random <= yellowChance || gachaInfo.getPity5() >= banner.getHardPity()) {
|
||||
if (banner.getRateUpItems1().length > 0) {
|
||||
int eventChance = this.randomRange(1, 100);
|
||||
|
||||
if (eventChance <= banner.getEventChance() || gachaInfo.getFailedFeaturedItemPulls() >= 1) {
|
||||
itemId = getRandom(banner.getRateUpItems1());
|
||||
gachaInfo.setFailedFeaturedItemPulls(0);
|
||||
} else {
|
||||
// Lost the 50/50... rip
|
||||
gachaInfo.addFailedFeaturedItemPulls(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (itemId == 0) {
|
||||
int typeChance = this.randomRange(banner.getBannerType() == BannerType.WEAPON ? 2 : 1, banner.getBannerType() == BannerType.EVENT ? 1 : 2);
|
||||
if (typeChance == 1) {
|
||||
itemId = getRandom(this.yellowAvatars);
|
||||
} else {
|
||||
itemId = getRandom(this.yellowWeapons);
|
||||
}
|
||||
}
|
||||
|
||||
// Pity
|
||||
gachaInfo.addPity4(1);
|
||||
gachaInfo.setPity5(0);
|
||||
} else if (random >= purpleChance || gachaInfo.getPity4() >= 9) {
|
||||
if (banner.getRateUpItems2().length > 0) {
|
||||
int eventChance = this.randomRange(1, 100);
|
||||
|
||||
if (eventChance >= 50) {
|
||||
itemId = getRandom(banner.getRateUpItems2());
|
||||
}
|
||||
}
|
||||
|
||||
if (itemId == 0) {
|
||||
int typeChance = this.randomRange(banner.getBannerType() == BannerType.WEAPON ? 2 : 1, banner.getBannerType() == BannerType.EVENT ? 1 : 2);
|
||||
if (typeChance == 1) {
|
||||
itemId = getRandom(this.purpleAvatars);
|
||||
} else {
|
||||
itemId = getRandom(this.purpleWeapons);
|
||||
}
|
||||
}
|
||||
|
||||
// Pity
|
||||
gachaInfo.addPity5(1);
|
||||
gachaInfo.setPity4(0);
|
||||
} else {
|
||||
itemId = getRandom(this.blueWeapons);
|
||||
|
||||
// Pity
|
||||
gachaInfo.addPity4(1);
|
||||
gachaInfo.addPity5(1);
|
||||
}
|
||||
|
||||
// Add winning item
|
||||
wonItems.add(itemId);
|
||||
ItemParamData cost = banner.getCost(times);
|
||||
if (cost.getCount() > 0 && !inventory.payItem(cost)) {
|
||||
player.sendPacket(new PacketDoGachaRsp());
|
||||
return;
|
||||
}
|
||||
|
||||
// Add to character
|
||||
PlayerGachaBannerInfo gachaInfo = player.getGachaInfo().getBannerInfo(banner);
|
||||
BannerPools pools = new BannerPools(banner);
|
||||
List<GachaItem> list = new ArrayList<>();
|
||||
int stardust = 0, starglitter = 0;
|
||||
|
||||
if (banner.getRemoveC6FromPool()) { // The ultimate form of pity (non-vanilla)
|
||||
pools.rateUpItems4 = removeC6FromPool(pools.rateUpItems4, player);
|
||||
pools.rateUpItems5 = removeC6FromPool(pools.rateUpItems5, player);
|
||||
pools.fallbackItems4Pool1 = removeC6FromPool(pools.fallbackItems4Pool1, player);
|
||||
pools.fallbackItems4Pool2 = removeC6FromPool(pools.fallbackItems4Pool2, player);
|
||||
pools.fallbackItems5Pool1 = removeC6FromPool(pools.fallbackItems5Pool1, player);
|
||||
pools.fallbackItems5Pool2 = removeC6FromPool(pools.fallbackItems5Pool2, player);
|
||||
}
|
||||
|
||||
for (int itemId : wonItems) {
|
||||
for (int i = 0; i < times; i++) {
|
||||
// Roll
|
||||
int itemId = doPull(banner, gachaInfo, pools);
|
||||
ItemData itemData = GameData.getItemDataMap().get(itemId);
|
||||
if (itemData == null) {
|
||||
continue;
|
||||
continue; // Maybe we should bail out if an item fails instead of rolling the rest?
|
||||
}
|
||||
|
||||
// Write gacha record
|
||||
@@ -210,57 +291,47 @@ public class GachaManager {
|
||||
boolean isTransferItem = false;
|
||||
|
||||
// Const check
|
||||
if (itemData.getMaterialType() == MaterialType.MATERIAL_AVATAR) {
|
||||
int avatarId = (itemData.getId() % 1000) + 10000000;
|
||||
Avatar avatar = player.getAvatars().getAvatarById(avatarId);
|
||||
if (avatar != null) {
|
||||
int constLevel = avatar.getCoreProudSkillLevel();
|
||||
int constItemId = itemData.getId() + 100;
|
||||
GameItem constItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(constItemId);
|
||||
if (constItem != null) {
|
||||
constLevel += constItem.getCount();
|
||||
int constellation = checkPlayerAvatarConstellationLevel(player, itemId);
|
||||
switch (constellation) {
|
||||
case -2: // Is weapon
|
||||
switch (itemData.getRankLevel()) {
|
||||
case 5 -> addStarglitter = 10;
|
||||
case 4 -> addStarglitter = 2;
|
||||
default -> addStardust = 15;
|
||||
}
|
||||
|
||||
if (constLevel < 6) {
|
||||
// Not max const
|
||||
addStarglitter = 2;
|
||||
// Add 1 const
|
||||
gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(ItemParam.newBuilder().setItemId(constItemId).setCount(1)).setIsTransferItemNew(constItem == null));
|
||||
player.getInventory().addItem(constItemId, 1);
|
||||
} else {
|
||||
// Is max const
|
||||
addStarglitter = 5;
|
||||
}
|
||||
|
||||
if (itemData.getRankLevel() == 5) {
|
||||
addStarglitter *= 5;
|
||||
}
|
||||
|
||||
isTransferItem = true;
|
||||
} else {
|
||||
// New
|
||||
break;
|
||||
case -1: // New character
|
||||
gachaItem.setIsGachaItemNew(true);
|
||||
}
|
||||
} else {
|
||||
// Is weapon
|
||||
switch (itemData.getRankLevel()) {
|
||||
case 5 -> addStarglitter = 10;
|
||||
case 4 -> addStarglitter = 2;
|
||||
case 3 -> addStardust = 15;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (constellation >= 6) { // C6, give consolation starglitter
|
||||
addStarglitter = (itemData.getRankLevel()==5)? 25 : 5;
|
||||
} else { // C0-C5, give constellation item
|
||||
if (banner.getRemoveC6FromPool() && constellation == 5) { // New C6, remove it from the pools so we don't get C7 in a 10pull
|
||||
pools.removeFromAllPools(new int[] {itemId});
|
||||
}
|
||||
addStarglitter = (itemData.getRankLevel()==5)? 10 : 2;
|
||||
int constItemId = itemId + 100;
|
||||
GameItem constItem = inventory.getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(constItemId);
|
||||
gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(ItemParam.newBuilder().setItemId(constItemId).setCount(1)).setIsTransferItemNew(constItem == null));
|
||||
inventory.addItem(constItemId, 1);
|
||||
}
|
||||
isTransferItem = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Create item
|
||||
GameItem item = new GameItem(itemData);
|
||||
gachaItem.setGachaItem(item.toItemParam());
|
||||
player.getInventory().addItem(item);
|
||||
inventory.addItem(item);
|
||||
|
||||
stardust += addStardust;
|
||||
starglitter += addStarglitter;
|
||||
|
||||
if (addStardust > 0) {
|
||||
gachaItem.addTokenItemList(ItemParam.newBuilder().setItemId(stardustId).setCount(addStardust));
|
||||
} if (addStarglitter > 0) {
|
||||
}
|
||||
if (addStarglitter > 0) {
|
||||
ItemParam starglitterParam = ItemParam.newBuilder().setItemId(starglitterId).setCount(addStarglitter).build();
|
||||
if (isTransferItem) {
|
||||
gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(starglitterParam));
|
||||
@@ -273,9 +344,10 @@ public class GachaManager {
|
||||
|
||||
// Add stardust/starglitter
|
||||
if (stardust > 0) {
|
||||
player.getInventory().addItem(stardustId, stardust);
|
||||
} if (starglitter > 0) {
|
||||
player.getInventory().addItem(starglitterId, starglitter);
|
||||
inventory.addItem(stardustId, stardust);
|
||||
}
|
||||
if (starglitter > 0) {
|
||||
inventory.addItem(starglitterId, starglitter);
|
||||
}
|
||||
|
||||
// Packets
|
||||
|
||||
@@ -7,6 +7,11 @@ public class PlayerGachaBannerInfo {
|
||||
private int pity5 = 0;
|
||||
private int pity4 = 0;
|
||||
private int failedFeaturedItemPulls = 0;
|
||||
private int failedFeatured4ItemPulls = 0;
|
||||
private int pity5Pool1 = 0;
|
||||
private int pity5Pool2 = 0;
|
||||
private int pity4Pool1 = 0;
|
||||
private int pity4Pool2 = 0;
|
||||
|
||||
public int getPity5() {
|
||||
return pity5;
|
||||
@@ -32,15 +37,82 @@ public class PlayerGachaBannerInfo {
|
||||
this.pity4 += amount;
|
||||
}
|
||||
|
||||
public int getFailedFeaturedItemPulls() {
|
||||
return failedFeaturedItemPulls;
|
||||
public int getFailedFeaturedItemPulls(int rarity) {
|
||||
return switch (rarity) {
|
||||
case 4 -> failedFeatured4ItemPulls;
|
||||
default -> failedFeaturedItemPulls; // 5
|
||||
};
|
||||
}
|
||||
|
||||
public void setFailedFeaturedItemPulls(int failedEventCharacterPulls) {
|
||||
this.failedFeaturedItemPulls = failedEventCharacterPulls;
|
||||
public void setFailedFeaturedItemPulls(int rarity, int amount) {
|
||||
switch (rarity) {
|
||||
case 4 -> failedFeatured4ItemPulls = amount;
|
||||
default -> failedFeaturedItemPulls = amount; // 5
|
||||
};
|
||||
}
|
||||
|
||||
public void addFailedFeaturedItemPulls(int amount) {
|
||||
failedFeaturedItemPulls += amount;
|
||||
public void addFailedFeaturedItemPulls(int rarity, int amount) {
|
||||
switch (rarity) {
|
||||
case 4 -> failedFeatured4ItemPulls += amount;
|
||||
default -> failedFeaturedItemPulls += amount; // 5
|
||||
};
|
||||
}
|
||||
|
||||
public int getPityPool(int rarity, int pool) {
|
||||
return switch (rarity) {
|
||||
case 4 -> switch (pool) {
|
||||
case 1 -> pity4Pool1;
|
||||
default -> pity4Pool2;
|
||||
};
|
||||
default -> switch (pool) {
|
||||
case 1 -> pity5Pool1;
|
||||
default -> pity5Pool2;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
public void setPityPool(int rarity, int pool, int amount) {
|
||||
switch (rarity) {
|
||||
case 4:
|
||||
switch (pool) {
|
||||
case 1 -> pity4Pool1 = amount;
|
||||
default -> pity4Pool2 = amount;
|
||||
};
|
||||
break;
|
||||
case 5:
|
||||
default:
|
||||
switch (pool) {
|
||||
case 1 -> pity5Pool1 = amount;
|
||||
default -> pity5Pool2 = amount;
|
||||
};
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
public void addPityPool(int rarity, int pool, int amount) {
|
||||
switch (rarity) {
|
||||
case 4:
|
||||
switch (pool) {
|
||||
case 1 -> pity4Pool1 += amount;
|
||||
default -> pity4Pool2 += amount;
|
||||
};
|
||||
break;
|
||||
case 5:
|
||||
default:
|
||||
switch (pool) {
|
||||
case 1 -> pity5Pool1 += amount;
|
||||
default -> pity5Pool2 += amount;
|
||||
};
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
public void incPityAll() {
|
||||
pity4++;
|
||||
pity5++;
|
||||
pity4Pool1++;
|
||||
pity4Pool2++;
|
||||
pity5Pool1++;
|
||||
pity5Pool2++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.List;
|
||||
|
||||
import emu.grasscutter.GameConstants;
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.common.ItemParamData;
|
||||
import emu.grasscutter.data.def.AvatarCostumeData;
|
||||
import emu.grasscutter.data.def.AvatarData;
|
||||
import emu.grasscutter.data.def.AvatarFlycloakData;
|
||||
@@ -256,6 +257,64 @@ public class Inventory implements Iterable<GameItem> {
|
||||
getPlayer().setCrystals(player.getCrystals() + count);
|
||||
}
|
||||
}
|
||||
|
||||
private int getVirtualItemCount(int itemId) {
|
||||
switch (itemId) {
|
||||
case 201: // Primogem
|
||||
return player.getPrimogems();
|
||||
case 202: // Mora
|
||||
return player.getMora();
|
||||
case 203: // Genesis Crystals
|
||||
return player.getCrystals();
|
||||
default:
|
||||
GameItem item = getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemId); // What if we ever want to operate on weapons/relics/furniture? :S
|
||||
return (item == null) ? 0 : item.getCount();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean payItem(int id, int count) {
|
||||
return payItem(new ItemParamData(id, count));
|
||||
}
|
||||
|
||||
public boolean payItem(ItemParamData costItem) {
|
||||
return payItems(new ItemParamData[] {costItem}, 1, null);
|
||||
}
|
||||
|
||||
public boolean payItems(ItemParamData[] costItems) {
|
||||
return payItems(costItems, 1, null);
|
||||
}
|
||||
|
||||
public boolean payItems(ItemParamData[] costItems, int quantity) {
|
||||
return payItems(costItems, quantity, null);
|
||||
}
|
||||
|
||||
public synchronized boolean payItems(ItemParamData[] costItems, int quantity, ActionReason reason) {
|
||||
// Make sure player has requisite items
|
||||
for (ItemParamData cost : costItems) {
|
||||
if (getVirtualItemCount(cost.getId()) < (cost.getCount() * quantity)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// All costs are satisfied, now remove them all
|
||||
for (ItemParamData cost : costItems) {
|
||||
switch (cost.getId()) {
|
||||
case 201 -> // Primogem
|
||||
player.setPrimogems(player.getPrimogems() - (cost.getCount() * quantity));
|
||||
case 202 -> // Mora
|
||||
player.setMora(player.getMora() - (cost.getCount() * quantity));
|
||||
case 203 -> // Genesis Crystals
|
||||
player.setCrystals(player.getCrystals() - (cost.getCount() * quantity));
|
||||
default ->
|
||||
removeItem(getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()), cost.getCount() * quantity);
|
||||
}
|
||||
}
|
||||
|
||||
if (reason != null) { // Do we need these?
|
||||
// getPlayer().sendPacket(new PacketItemAddHintNotify(changedItems, reason));
|
||||
}
|
||||
// getPlayer().sendPacket(new PacketStoreItemChangeNotify(changedItems));
|
||||
return true;
|
||||
}
|
||||
|
||||
public void removeItems(List<GameItem> items) {
|
||||
// TODO Bulk delete
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package emu.grasscutter.game.managers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -38,6 +39,8 @@ public class InventoryManager {
|
||||
|
||||
private final static int RELIC_MATERIAL_1 = 105002; // Sanctifying Unction
|
||||
private final static int RELIC_MATERIAL_2 = 105003; // Sanctifying Essence
|
||||
private final static int RELIC_MATERIAL_EXP_1 = 2500; // Sanctifying Unction
|
||||
private final static int RELIC_MATERIAL_EXP_2 = 10000; // Sanctifying Essence
|
||||
|
||||
private final static int WEAPON_ORE_1 = 104011; // Enhancement Ore
|
||||
private final static int WEAPON_ORE_2 = 104012; // Fine Enhancement Ore
|
||||
@@ -85,6 +88,7 @@ public class InventoryManager {
|
||||
int moraCost = 0;
|
||||
int expGain = 0;
|
||||
|
||||
List<GameItem> foodRelics = new ArrayList<GameItem>();
|
||||
for (long guid : foodRelicList) {
|
||||
// Add to delete queue
|
||||
GameItem food = player.getInventory().getItemByGuid(guid);
|
||||
@@ -96,23 +100,21 @@ public class InventoryManager {
|
||||
expGain += food.getItemData().getBaseConvExp();
|
||||
// Feeding artifact with exp already
|
||||
if (food.getTotalExp() > 0) {
|
||||
expGain += (int) Math.floor(food.getTotalExp() * .8f);
|
||||
expGain += (food.getTotalExp() * 4) / 5;
|
||||
}
|
||||
foodRelics.add(food);
|
||||
}
|
||||
List<ItemParamData> payList = new ArrayList<ItemParamData>();
|
||||
for (ItemParam itemParam : list) {
|
||||
GameItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemParam.getItemId());
|
||||
if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_RELIQUARY_MATERIAL) {
|
||||
continue;
|
||||
}
|
||||
int amount = Math.min(food.getCount(), itemParam.getCount());
|
||||
int gain = 0;
|
||||
if (food.getItemId() == RELIC_MATERIAL_2) {
|
||||
gain = 10000 * amount;
|
||||
} else if (food.getItemId() == RELIC_MATERIAL_1) {
|
||||
gain = 2500 * amount;
|
||||
}
|
||||
int amount = itemParam.getCount(); // Previously this capped to inventory amount, but rejecting the payment makes more sense for an invalid order
|
||||
int gain = amount * switch(itemParam.getItemId()) {
|
||||
case RELIC_MATERIAL_1 -> RELIC_MATERIAL_EXP_1;
|
||||
case RELIC_MATERIAL_2 -> RELIC_MATERIAL_EXP_2;
|
||||
default -> 0;
|
||||
};
|
||||
expGain += gain;
|
||||
moraCost += gain;
|
||||
payList.add(new ItemParamData(itemParam.getItemId(), itemParam.getCount()));
|
||||
}
|
||||
|
||||
// Make sure exp gain is valid
|
||||
@@ -120,28 +122,14 @@ public class InventoryManager {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check mora
|
||||
if (player.getMora() < moraCost) {
|
||||
// Confirm payment of materials and mora (assume food relics are payable afterwards)
|
||||
payList.add(new ItemParamData(202, moraCost));
|
||||
if (!player.getInventory().payItems(payList.toArray(new ItemParamData[0]))) {
|
||||
return;
|
||||
}
|
||||
player.setMora(player.getMora() - moraCost);
|
||||
|
||||
// Consume food items
|
||||
for (long guid : foodRelicList) {
|
||||
GameItem food = player.getInventory().getItemByGuid(guid);
|
||||
if (food == null || !food.isDestroyable()) {
|
||||
continue;
|
||||
}
|
||||
player.getInventory().removeItem(food);
|
||||
}
|
||||
for (ItemParam itemParam : list) {
|
||||
GameItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemParam.getItemId());
|
||||
if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_RELIQUARY_MATERIAL) {
|
||||
continue;
|
||||
}
|
||||
int amount = Math.min(food.getCount(), itemParam.getCount());
|
||||
player.getInventory().removeItem(food, amount);
|
||||
}
|
||||
// Consume food relics
|
||||
player.getInventory().removeItems(foodRelics);
|
||||
|
||||
// Implement random rate boost
|
||||
int rate = 1;
|
||||
@@ -231,22 +219,16 @@ public class InventoryManager {
|
||||
}
|
||||
expGain += food.getItemData().getWeaponBaseExp();
|
||||
if (food.getTotalExp() > 0) {
|
||||
expGain += (int) Math.floor(food.getTotalExp() * .8f);
|
||||
expGain += (food.getTotalExp() * 4) / 5;
|
||||
}
|
||||
}
|
||||
for (ItemParam param : itemParamList) {
|
||||
GameItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(param.getItemId());
|
||||
if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_WEAPON_EXP_STONE) {
|
||||
continue;
|
||||
}
|
||||
int amount = Math.min(param.getCount(), food.getCount());
|
||||
if (food.getItemId() == WEAPON_ORE_3) {
|
||||
expGain += 10000 * amount;
|
||||
} else if (food.getItemId() == WEAPON_ORE_2) {
|
||||
expGain += 2000 * amount;
|
||||
} else if (food.getItemId() == WEAPON_ORE_1) {
|
||||
expGain += 400 * amount;
|
||||
}
|
||||
expGain += param.getCount() * switch(param.getItemId()) {
|
||||
case WEAPON_ORE_1 -> WEAPON_ORE_EXP_1;
|
||||
case WEAPON_ORE_2 -> WEAPON_ORE_EXP_2;
|
||||
case WEAPON_ORE_3 -> WEAPON_ORE_EXP_3;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
// Try
|
||||
@@ -288,65 +270,45 @@ public class InventoryManager {
|
||||
}
|
||||
|
||||
// Get exp gain
|
||||
int expGain = 0, moraCost = 0;
|
||||
|
||||
int expGain = 0, expGainFree = 0;
|
||||
List<GameItem> foodWeapons = new ArrayList<GameItem>();
|
||||
for (long guid : foodWeaponGuidList) {
|
||||
GameItem food = player.getInventory().getItemByGuid(guid);
|
||||
if (food == null || !food.isDestroyable()) {
|
||||
continue;
|
||||
}
|
||||
expGain += food.getItemData().getWeaponBaseExp();
|
||||
moraCost += (int) Math.floor(food.getItemData().getWeaponBaseExp() * .1f);
|
||||
if (food.getTotalExp() > 0) {
|
||||
expGain += (int) Math.floor(food.getTotalExp() * .8f);
|
||||
expGainFree += (food.getTotalExp() * 4) / 5; // No tax :D
|
||||
}
|
||||
foodWeapons.add(food);
|
||||
}
|
||||
List<ItemParamData> payList = new ArrayList<ItemParamData>();
|
||||
for (ItemParam param : itemParamList) {
|
||||
GameItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(param.getItemId());
|
||||
if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_WEAPON_EXP_STONE) {
|
||||
continue;
|
||||
}
|
||||
int amount = Math.min(param.getCount(), food.getCount());
|
||||
int gain = 0;
|
||||
if (food.getItemId() == WEAPON_ORE_3) {
|
||||
gain = 10000 * amount;
|
||||
} else if (food.getItemId() == WEAPON_ORE_2) {
|
||||
gain = 2000 * amount;
|
||||
} else if (food.getItemId() == WEAPON_ORE_1) {
|
||||
gain = 400 * amount;
|
||||
}
|
||||
int amount = param.getCount(); // Previously this capped to inventory amount, but rejecting the payment makes more sense for an invalid order
|
||||
int gain = amount * switch(param.getItemId()) {
|
||||
case WEAPON_ORE_1 -> WEAPON_ORE_EXP_1;
|
||||
case WEAPON_ORE_2 -> WEAPON_ORE_EXP_2;
|
||||
case WEAPON_ORE_3 -> WEAPON_ORE_EXP_3;
|
||||
default -> 0;
|
||||
};
|
||||
expGain += gain;
|
||||
moraCost += (int) Math.floor(gain * .1f);
|
||||
payList.add(new ItemParamData(param.getItemId(), amount));
|
||||
}
|
||||
|
||||
// Make sure exp gain is valid
|
||||
int moraCost = expGain / 10;
|
||||
expGain += expGainFree;
|
||||
if (expGain <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mora check
|
||||
if (player.getMora() >= moraCost) {
|
||||
player.setMora(player.getMora() - moraCost);
|
||||
} else {
|
||||
|
||||
// Confirm payment of materials and mora (assume food weapons are payable afterwards)
|
||||
payList.add(new ItemParamData(202, moraCost));
|
||||
if (!player.getInventory().payItems(payList.toArray(new ItemParamData[0]))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Consume weapon/items used to feed
|
||||
for (long guid : foodWeaponGuidList) {
|
||||
GameItem food = player.getInventory().getItemByGuid(guid);
|
||||
if (food == null || !food.isDestroyable()) {
|
||||
continue;
|
||||
}
|
||||
player.getInventory().removeItem(food);
|
||||
}
|
||||
for (ItemParam param : itemParamList) {
|
||||
GameItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(param.getItemId());
|
||||
if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_WEAPON_EXP_STONE) {
|
||||
continue;
|
||||
}
|
||||
int amount = Math.min(param.getCount(), food.getCount());
|
||||
player.getInventory().removeItem(food, amount);
|
||||
}
|
||||
player.getInventory().removeItems(foodWeapons);
|
||||
|
||||
// Level up
|
||||
int maxLevel = promoteData.getUnlockMaxLevel();
|
||||
@@ -393,7 +355,7 @@ public class InventoryManager {
|
||||
player.sendPacket(new PacketWeaponUpgradeRsp(weapon, oldLevel, leftovers));
|
||||
}
|
||||
|
||||
private List<ItemParam> getLeftoverOres(float leftover) {
|
||||
private List<ItemParam> getLeftoverOres(int leftover) {
|
||||
List<ItemParam> leftoverOreList = new ArrayList<>(3);
|
||||
|
||||
if (leftover < WEAPON_ORE_EXP_1) {
|
||||
@@ -401,11 +363,11 @@ public class InventoryManager {
|
||||
}
|
||||
|
||||
// Get leftovers
|
||||
int ore3 = (int) Math.floor(leftover / WEAPON_ORE_EXP_3);
|
||||
int ore3 = leftover / WEAPON_ORE_EXP_3;
|
||||
leftover = leftover % WEAPON_ORE_EXP_3;
|
||||
int ore2 = (int) Math.floor(leftover / WEAPON_ORE_EXP_2);
|
||||
int ore2 = leftover / WEAPON_ORE_EXP_2;
|
||||
leftover = leftover % WEAPON_ORE_EXP_2;
|
||||
int ore1 = (int) Math.floor(leftover / WEAPON_ORE_EXP_1);
|
||||
int ore1 = leftover / WEAPON_ORE_EXP_1;
|
||||
|
||||
if (ore3 > 0) {
|
||||
leftoverOreList.add(ItemParam.newBuilder().setItemId(WEAPON_ORE_3).setCount(ore3).build());
|
||||
@@ -496,27 +458,16 @@ public class InventoryManager {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure player has promote items
|
||||
for (ItemParamData cost : nextPromoteData.getCostItems()) {
|
||||
GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId());
|
||||
if (feedItem == null || feedItem.getCount() < cost.getCount()) {
|
||||
return;
|
||||
}
|
||||
// Pay materials and mora if possible
|
||||
ItemParamData[] costs = nextPromoteData.getCostItems(); // Can this be null?
|
||||
if (nextPromoteData.getCoinCost() > 0) {
|
||||
costs = Arrays.copyOf(costs, costs.length + 1);
|
||||
costs[costs.length-1] = new ItemParamData(202, nextPromoteData.getCoinCost());
|
||||
}
|
||||
|
||||
// Mora check
|
||||
if (player.getMora() >= nextPromoteData.getCoinCost()) {
|
||||
player.setMora(player.getMora() - nextPromoteData.getCoinCost());
|
||||
} else {
|
||||
if (!player.getInventory().payItems(costs)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Consume promote filler items
|
||||
for (ItemParamData cost : nextPromoteData.getCostItems()) {
|
||||
GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId());
|
||||
player.getInventory().removeItem(feedItem, cost.getCount());
|
||||
}
|
||||
|
||||
int oldPromoteLevel = weapon.getPromoteLevel();
|
||||
weapon.setPromoteLevel(nextPromoteLevel);
|
||||
weapon.save();
|
||||
@@ -552,27 +503,16 @@ public class InventoryManager {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure player has cost items
|
||||
for (ItemParamData cost : nextPromoteData.getCostItems()) {
|
||||
GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId());
|
||||
if (feedItem == null || feedItem.getCount() < cost.getCount()) {
|
||||
return;
|
||||
}
|
||||
// Pay materials and mora if possible
|
||||
ItemParamData[] costs = nextPromoteData.getCostItems(); // Can this be null?
|
||||
if (nextPromoteData.getCoinCost() > 0) {
|
||||
costs = Arrays.copyOf(costs, costs.length + 1);
|
||||
costs[costs.length-1] = new ItemParamData(202, nextPromoteData.getCoinCost());
|
||||
}
|
||||
|
||||
// Mora check
|
||||
if (player.getMora() >= nextPromoteData.getCoinCost()) {
|
||||
player.setMora(player.getMora() - nextPromoteData.getCoinCost());
|
||||
} else {
|
||||
if (!player.getInventory().payItems(costs)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Consume promote filler items
|
||||
for (ItemParamData cost : nextPromoteData.getCostItems()) {
|
||||
GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId());
|
||||
player.getInventory().removeItem(feedItem, cost.getCount());
|
||||
}
|
||||
|
||||
// Update promote level
|
||||
avatar.setPromoteLevel(nextPromoteLevel);
|
||||
|
||||
@@ -616,34 +556,25 @@ public class InventoryManager {
|
||||
return;
|
||||
}
|
||||
|
||||
GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemId);
|
||||
|
||||
if (feedItem == null || feedItem.getItemData().getMaterialType() != MaterialType.MATERIAL_EXP_FRUIT || feedItem.getCount() < count) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calc exp
|
||||
int expGain = 0, moraCost = 0;
|
||||
int expGain = switch(itemId) {
|
||||
case AVATAR_BOOK_1 -> AVATAR_BOOK_EXP_1 * count;
|
||||
case AVATAR_BOOK_2 -> AVATAR_BOOK_EXP_2 * count;
|
||||
case AVATAR_BOOK_3 -> AVATAR_BOOK_EXP_3 * count;
|
||||
default -> 0;
|
||||
};
|
||||
|
||||
// TODO clean up
|
||||
if (itemId == AVATAR_BOOK_3) {
|
||||
expGain = AVATAR_BOOK_EXP_3 * count;
|
||||
} else if (itemId == AVATAR_BOOK_2) {
|
||||
expGain = AVATAR_BOOK_EXP_2 * count;
|
||||
} else if (itemId == AVATAR_BOOK_1) {
|
||||
expGain = AVATAR_BOOK_EXP_1 * count;
|
||||
}
|
||||
moraCost = (int) Math.floor(expGain * .2f);
|
||||
|
||||
// Mora check
|
||||
if (player.getMora() >= moraCost) {
|
||||
player.setMora(player.getMora() - moraCost);
|
||||
} else {
|
||||
// Sanity check
|
||||
if (expGain <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Payment check
|
||||
int moraCost = expGain / 5;
|
||||
ItemParamData[] costItems = new ItemParamData[] {new ItemParamData(itemId, count), new ItemParamData(202, moraCost)};
|
||||
if (!player.getInventory().payItems(costItems)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Consume items
|
||||
player.getInventory().removeItem(feedItem, count);
|
||||
|
||||
// Level up
|
||||
upgradeAvatar(player, avatar, promoteData, expGain);
|
||||
@@ -764,33 +695,15 @@ public class InventoryManager {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure player has cost items
|
||||
for (ItemParamData cost : proudSkill.getCostItems()) {
|
||||
if (cost.getId() == 0) {
|
||||
continue;
|
||||
}
|
||||
GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId());
|
||||
if (feedItem == null || feedItem.getCount() < cost.getCount()) {
|
||||
return;
|
||||
}
|
||||
// Pay materials and mora if possible
|
||||
List<ItemParamData> costs = new ArrayList<ItemParamData>(proudSkill.getCostItems()); // Can this be null?
|
||||
if (proudSkill.getCoinCost() > 0) {
|
||||
costs.add(new ItemParamData(202, proudSkill.getCoinCost()));
|
||||
}
|
||||
|
||||
// Mora check
|
||||
if (player.getMora() >= proudSkill.getCoinCost()) {
|
||||
player.setMora(player.getMora() - proudSkill.getCoinCost());
|
||||
} else {
|
||||
if (!player.getInventory().payItems(costs.toArray(new ItemParamData[0]))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Consume promote filler items
|
||||
for (ItemParamData cost : proudSkill.getCostItems()) {
|
||||
if (cost.getId() == 0) {
|
||||
continue;
|
||||
}
|
||||
GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId());
|
||||
player.getInventory().removeItem(feedItem, cost.getCount());
|
||||
}
|
||||
|
||||
// Upgrade skill
|
||||
avatar.getSkillLevelMap().put(skillId, nextLevel);
|
||||
avatar.save();
|
||||
@@ -822,14 +735,11 @@ public class InventoryManager {
|
||||
return;
|
||||
}
|
||||
|
||||
GameItem costItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(talentData.getMainCostItemId());
|
||||
if (costItem == null || costItem.getCount() < talentData.getMainCostItemCount()) {
|
||||
// Pay constellation item if possible
|
||||
if (!player.getInventory().payItem(talentData.getMainCostItemId(), 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Consume item
|
||||
player.getInventory().removeItem(costItem, talentData.getMainCostItemCount());
|
||||
|
||||
// Apply + recalc
|
||||
avatar.getTalentIdList().add(talentData.getId());
|
||||
avatar.setCoreProudSkillLevel(currentTalentLevel + 1);
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
package emu.grasscutter.game.managers.MapMarkManager;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
import emu.grasscutter.net.proto.MapMarkFromTypeOuterClass;
|
||||
import emu.grasscutter.net.proto.MapMarkPointOuterClass;
|
||||
import emu.grasscutter.net.proto.MapMarkPointTypeOuterClass;
|
||||
import emu.grasscutter.net.proto.MapMarkFromTypeOuterClass.MapMarkFromType;
|
||||
import emu.grasscutter.net.proto.MapMarkPointOuterClass.MapMarkPoint;
|
||||
import emu.grasscutter.net.proto.MapMarkPointTypeOuterClass.MapMarkPointType;
|
||||
import emu.grasscutter.utils.Position;
|
||||
|
||||
@Entity
|
||||
public class MapMark {
|
||||
private int sceneId;
|
||||
private String name;
|
||||
private Position position;
|
||||
private MapMarkPointTypeOuterClass.MapMarkPointType pointType;
|
||||
private int monsterId = 0;
|
||||
private MapMarkFromTypeOuterClass.MapMarkFromType fromType;
|
||||
private int questId = 7;
|
||||
private final int sceneId;
|
||||
private final String name;
|
||||
private final Position position;
|
||||
private final MapMarkPointType pointType;
|
||||
private final int monsterId;
|
||||
private final MapMarkFromType fromType;
|
||||
private final int questId;
|
||||
|
||||
public MapMark(Position position, MapMarkPointTypeOuterClass.MapMarkPointType type) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public MapMark(MapMarkPointOuterClass.MapMarkPoint mapMarkPoint) {
|
||||
public MapMark(MapMarkPoint mapMarkPoint) {
|
||||
this.sceneId = mapMarkPoint.getSceneId();
|
||||
this.name = mapMarkPoint.getName();
|
||||
this.position = new Position(mapMarkPoint.getPos().getX(), mapMarkPoint.getPos().getY(), mapMarkPoint.getPos().getZ());
|
||||
this.position = new Position(
|
||||
mapMarkPoint.getPos().getX(),
|
||||
mapMarkPoint.getPos().getY(),
|
||||
mapMarkPoint.getPos().getZ()
|
||||
);
|
||||
this.pointType = mapMarkPoint.getPointType();
|
||||
this.monsterId = mapMarkPoint.getMonsterId();
|
||||
this.fromType = mapMarkPoint.getFromType();
|
||||
@@ -33,41 +33,22 @@ public class MapMark {
|
||||
public int getSceneId() {
|
||||
return this.sceneId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public Position getPosition() {
|
||||
return this.position;
|
||||
}
|
||||
|
||||
public MapMarkPointTypeOuterClass.MapMarkPointType getMapMarkPointType() {
|
||||
public MapMarkPointType getMapMarkPointType() {
|
||||
return this.pointType;
|
||||
}
|
||||
|
||||
public void setMapMarkPointType(MapMarkPointTypeOuterClass.MapMarkPointType pointType) {
|
||||
this.pointType = pointType;
|
||||
}
|
||||
|
||||
public int getMonsterId() {
|
||||
return this.monsterId;
|
||||
}
|
||||
|
||||
public void setMonsterId(int monsterId) {
|
||||
this.monsterId = monsterId;
|
||||
}
|
||||
|
||||
public MapMarkFromTypeOuterClass.MapMarkFromType getMapMarkFromType() {
|
||||
public MapMarkFromType getMapMarkFromType() {
|
||||
return this.fromType;
|
||||
}
|
||||
|
||||
public int getQuestId() {
|
||||
return this.questId;
|
||||
}
|
||||
|
||||
public void setQuestId(int questId) {
|
||||
this.questId = questId;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,61 +1,90 @@
|
||||
package emu.grasscutter.game.managers.MapMarkManager;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.net.proto.MapMarkPointTypeOuterClass.MapMarkPointType;
|
||||
import emu.grasscutter.net.proto.MarkMapReqOuterClass.MarkMapReq;
|
||||
import emu.grasscutter.net.proto.MarkMapReqOuterClass.MarkMapReq.Operation;
|
||||
import emu.grasscutter.server.packet.send.PacketMarkMapRsp;
|
||||
import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify;
|
||||
import emu.grasscutter.utils.Position;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
@Entity
|
||||
public class MapMarksManager {
|
||||
|
||||
static final int mapMarkMaxCount = 150;
|
||||
public static final int mapMarkMaxCount = 150;
|
||||
private HashMap<String, MapMark> mapMarks;
|
||||
private final Player player;
|
||||
|
||||
public MapMarksManager() {
|
||||
mapMarks = new HashMap<String, MapMark>();
|
||||
public MapMarksManager(Player player) {
|
||||
this.player = player;
|
||||
this.mapMarks = player.getMapMarks();
|
||||
if (this.mapMarks == null) { this.mapMarks = new HashMap<>(); }
|
||||
}
|
||||
|
||||
public MapMarksManager(HashMap<String, MapMark> mapMarks) {
|
||||
this.mapMarks = mapMarks;
|
||||
}
|
||||
|
||||
public HashMap<String, MapMark> getAllMapMarks() {
|
||||
return mapMarks;
|
||||
}
|
||||
|
||||
public MapMark getMapMark(Position position) {
|
||||
String key = getMapMarkKey(position);
|
||||
if (mapMarks.containsKey(key)) {
|
||||
return mapMarks.get(key);
|
||||
} else {
|
||||
return null;
|
||||
public void handleMapMarkReq(MarkMapReq req) {
|
||||
Operation op = req.getOp();
|
||||
switch (op) {
|
||||
case ADD -> {
|
||||
MapMark createMark = new MapMark(req.getMark());
|
||||
// keep teleporting functionality on fishhook mark.
|
||||
if (createMark.getMapMarkPointType() == MapMarkPointType.MAP_MARK_POINT_TYPE_FISH_POOL) {
|
||||
teleport(player, createMark);
|
||||
return;
|
||||
}
|
||||
addMapMark(createMark);
|
||||
}
|
||||
case MOD -> {
|
||||
MapMark oldMark = new MapMark(req.getOld());
|
||||
removeMapMark(oldMark.getPosition());
|
||||
MapMark newMark = new MapMark(req.getMark());
|
||||
addMapMark(newMark);
|
||||
}
|
||||
case DEL -> {
|
||||
MapMark deleteMark = new MapMark(req.getMark());
|
||||
removeMapMark(deleteMark.getPosition());
|
||||
}
|
||||
}
|
||||
if (op != Operation.GET) {
|
||||
saveMapMarks();
|
||||
}
|
||||
player.getSession().send(new PacketMarkMapRsp(getMapMarks()));
|
||||
}
|
||||
|
||||
public HashMap<String, MapMark> getMapMarks() {
|
||||
return mapMarks;
|
||||
}
|
||||
|
||||
public String getMapMarkKey(Position position) {
|
||||
return "x" + (int)position.getX()+ "z" + (int)position.getZ();
|
||||
}
|
||||
|
||||
public boolean removeMapMark(Position position) {
|
||||
String key = getMapMarkKey(position);
|
||||
if (mapMarks.containsKey(key)) {
|
||||
mapMarks.remove(key);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
public void removeMapMark(Position position) {
|
||||
mapMarks.remove(getMapMarkKey(position));
|
||||
}
|
||||
|
||||
public boolean addMapMark(MapMark mapMark) {
|
||||
public void addMapMark(MapMark mapMark) {
|
||||
if (mapMarks.size() < mapMarkMaxCount) {
|
||||
if (!mapMarks.containsKey(getMapMarkKey(mapMark.getPosition()))) {
|
||||
mapMarks.put(getMapMarkKey(mapMark.getPosition()), mapMark);
|
||||
return true;
|
||||
}
|
||||
mapMarks.put(getMapMarkKey(mapMark.getPosition()), mapMark);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setMapMarks(HashMap<String, MapMark> mapMarks) {
|
||||
this.mapMarks = mapMarks;
|
||||
private void saveMapMarks() {
|
||||
player.setMapMarks(mapMarks);
|
||||
player.save();
|
||||
}
|
||||
|
||||
private void teleport(Player player, MapMark mapMark) {
|
||||
float y;
|
||||
try {
|
||||
y = (float)Integer.parseInt(mapMark.getName());
|
||||
} catch (Exception e) {
|
||||
y = 300;
|
||||
}
|
||||
Position pos = mapMark.getPosition();
|
||||
player.getPos().set(pos.getX(), y, pos.getZ());
|
||||
if (mapMark.getSceneId() != player.getSceneId()) {
|
||||
player.getWorld().transferPlayerToScene(player, mapMark.getSceneId(), player.getPos());
|
||||
}
|
||||
player.getScene().broadcastPacket(new PacketSceneEntityAppearNotify(player));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
package emu.grasscutter.game.managers;
|
||||
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.game.avatar.Avatar;
|
||||
import emu.grasscutter.game.entity.EntityAvatar;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.game.props.FightProperty;
|
||||
import emu.grasscutter.game.props.PlayerProperty;
|
||||
import emu.grasscutter.net.proto.ChangeHpReasonOuterClass;
|
||||
import emu.grasscutter.net.proto.PropChangeReasonOuterClass;
|
||||
import emu.grasscutter.net.proto.ChangeHpReasonOuterClass.ChangeHpReason;
|
||||
import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason;
|
||||
import emu.grasscutter.server.game.GameSession;
|
||||
import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
|
||||
|
||||
@@ -24,7 +22,9 @@ public class SotSManager {
|
||||
// NOTE: Spring volume balance *1 = fight prop HP *100
|
||||
|
||||
private final Player player;
|
||||
private final Logger logger = Grasscutter.getLogger();
|
||||
private Timer autoRecoverTimer;
|
||||
private final boolean enablePriorityHealing = false;
|
||||
|
||||
public final static int GlobalMaximumSpringVolume = 8500000;
|
||||
|
||||
@@ -38,6 +38,7 @@ public class SotSManager {
|
||||
|
||||
public void setIsAutoRecoveryEnabled(boolean enabled) {
|
||||
player.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, enabled ? 1 : 0);
|
||||
player.save();
|
||||
}
|
||||
|
||||
public int getAutoRecoveryPercentage() {
|
||||
@@ -46,49 +47,122 @@ public class SotSManager {
|
||||
|
||||
public void setAutoRecoveryPercentage(int percentage) {
|
||||
player.setProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, percentage);
|
||||
player.save();
|
||||
}
|
||||
|
||||
// autoRevive automatically revives all team members.
|
||||
public void autoRevive(GameSession session) {
|
||||
player.getTeamManager().getActiveTeam().forEach(entity -> {
|
||||
boolean isAlive = entity.isAlive();
|
||||
float currentHP = entity.getAvatar().getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
|
||||
float maxHP = entity.getAvatar().getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
|
||||
// Grasscutter.getLogger().debug("" + entity.getAvatar().getAvatarData().getName() + "\t" + currentHP + "/" + maxHP + "\t" + (isAlive ? "ALIVE":"DEAD"));
|
||||
float newHP = (float)(maxHP * 0.3);
|
||||
if (currentHP < newHP) {
|
||||
updateAvatarCurHP(session, entity, newHP);
|
||||
}
|
||||
if (!isAlive) {
|
||||
entity.getWorld().broadcastPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar()));
|
||||
}
|
||||
});
|
||||
public long getLastUsed() {
|
||||
return player.getSpringLastUsed();
|
||||
}
|
||||
|
||||
public void scheduleAutoRecover(GameSession session) {
|
||||
public void setLastUsed() {
|
||||
player.setSpringLastUsed(System.currentTimeMillis() / 1000);
|
||||
player.save();
|
||||
}
|
||||
|
||||
public int getMaxVolume() {
|
||||
return player.getProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME);
|
||||
}
|
||||
|
||||
public void setMaxVolume(int volume) {
|
||||
player.setProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME, volume);
|
||||
player.save();
|
||||
}
|
||||
|
||||
public int getCurrentVolume() {
|
||||
return player.getProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME);
|
||||
}
|
||||
|
||||
public void setCurrentVolume(int volume) {
|
||||
player.setProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME, volume);
|
||||
setLastUsed();
|
||||
player.save();
|
||||
}
|
||||
|
||||
public void handleEnterTransPointRegionNotify() {
|
||||
logger.trace("Player entered statue region");
|
||||
autoRevive();
|
||||
if (autoRecoverTimer == null) {
|
||||
autoRecoverTimer = new Timer();
|
||||
autoRecoverTimer.schedule(new AutoRecoverTimerTick(session), 2500);
|
||||
autoRecoverTimer.schedule(new AutoRecoverTimerTick(), 2500, 15000);
|
||||
}
|
||||
}
|
||||
|
||||
public void cancelAutoRecover() {
|
||||
public void handleExitTransPointRegionNotify() {
|
||||
logger.trace("Player left statue region");
|
||||
if (autoRecoverTimer != null) {
|
||||
autoRecoverTimer.cancel();
|
||||
autoRecoverTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private class AutoRecoverTimerTick extends TimerTask
|
||||
{
|
||||
private GameSession session;
|
||||
// autoRevive automatically revives all team members.
|
||||
public void autoRevive() {
|
||||
player.getTeamManager().getActiveTeam().forEach(entity -> {
|
||||
boolean isAlive = entity.isAlive();
|
||||
if (isAlive) {
|
||||
return;
|
||||
}
|
||||
logger.trace("Reviving avatar " + entity.getAvatar().getAvatarData().getName());
|
||||
player.getTeamManager().reviveAvatar(entity.getAvatar());
|
||||
player.getTeamManager().healAvatar(entity.getAvatar(), 30, 0);
|
||||
});
|
||||
}
|
||||
|
||||
public AutoRecoverTimerTick(GameSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
private class AutoRecoverTimerTick extends TimerTask {
|
||||
// autoRecover checks player setting to see if auto recover is enabled, and refill HP to the predefined level.
|
||||
public void run() {
|
||||
autoRecover(session);
|
||||
cancelAutoRecover();
|
||||
refillSpringVolume();
|
||||
|
||||
logger.trace("isAutoRecoveryEnabled: " + getIsAutoRecoveryEnabled() + "\tautoRecoverPercentage: " + getAutoRecoveryPercentage());
|
||||
|
||||
if (getIsAutoRecoveryEnabled()) {
|
||||
List<EntityAvatar> activeTeam = player.getTeamManager().getActiveTeam();
|
||||
// When the statue does not have enough remaining volume:
|
||||
// Enhanced experience: Enable priority healing
|
||||
// The current active character will get healed first, then sequential.
|
||||
// Vanilla experience: Disable priority healing
|
||||
// Sequential healing based on character index.
|
||||
int priorityIndex = enablePriorityHealing ? player.getTeamManager().getCurrentCharacterIndex() : -1;
|
||||
if (priorityIndex >= 0) {
|
||||
checkAndHealAvatar(activeTeam.get(priorityIndex));
|
||||
}
|
||||
for (int i = 0; i < activeTeam.size(); i++) {
|
||||
if (i != priorityIndex) {
|
||||
checkAndHealAvatar(activeTeam.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void checkAndHealAvatar(EntityAvatar entity) {
|
||||
int maxHP = (int) (entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * 100);
|
||||
int currentHP = (int) (entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) * 100);
|
||||
if (currentHP == maxHP) {
|
||||
return;
|
||||
}
|
||||
int targetHP = maxHP * getAutoRecoveryPercentage() / 100;
|
||||
|
||||
if (targetHP > currentHP) {
|
||||
int needHP = targetHP - currentHP;
|
||||
int currentVolume = getCurrentVolume();
|
||||
if (currentVolume >= needHP) {
|
||||
// sufficient
|
||||
setCurrentVolume(currentVolume - needHP);
|
||||
} else {
|
||||
// insufficient balance
|
||||
needHP = currentVolume;
|
||||
setCurrentVolume(0);
|
||||
}
|
||||
if (needHP > 0) {
|
||||
logger.trace("Healing avatar " + entity.getAvatar().getAvatarData().getName() + " +" + needHP);
|
||||
player.getTeamManager().healAvatar(entity.getAvatar(), 0, needHP);
|
||||
player.getSession().send(new PacketEntityFightPropChangeReasonNotify(entity, FightProperty.FIGHT_PROP_CUR_HP,
|
||||
((float) needHP / 100), List.of(3), PropChangeReason.PROP_CHANGE_STATUE_RECOVER,
|
||||
ChangeHpReason.ChangeHpAddStatue));
|
||||
player.getSession().send(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,84 +170,23 @@ public class SotSManager {
|
||||
// Temporary: Max spring volume depends on level of the statues in Mondstadt and Liyue. Override until we have statue level.
|
||||
// TODO: remove
|
||||
// https://genshin-impact.fandom.com/wiki/Statue_of_The_Seven#:~:text=region%20of%20Inazuma.-,Statue%20Levels,-Upon%20first%20unlocking
|
||||
player.setProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME, 8500000);
|
||||
setMaxVolume(8500000);
|
||||
// Temporary: Auto enable 100% statue recovery until we can adjust statue settings in game
|
||||
// TODO: remove
|
||||
player.setProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, 100);
|
||||
player.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, 1);
|
||||
setAutoRecoveryPercentage(100);
|
||||
setIsAutoRecoveryEnabled(true);
|
||||
|
||||
long now = System.currentTimeMillis() / 1000;
|
||||
long secondsSinceLastUsed = now - player.getSpringLastUsed();
|
||||
float percentageRefilled = (float)secondsSinceLastUsed / 15 / 100; // 15s = 1% max volume
|
||||
int maxVolume = player.getProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME);
|
||||
int currentVolume = player.getProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME);
|
||||
int maxVolume = getMaxVolume();
|
||||
int currentVolume = getCurrentVolume();
|
||||
if (currentVolume < maxVolume) {
|
||||
int volumeRefilled = (int)(percentageRefilled * maxVolume);
|
||||
int newVolume = currentVolume + volumeRefilled;
|
||||
if (currentVolume + volumeRefilled > maxVolume) {
|
||||
newVolume = maxVolume;
|
||||
}
|
||||
player.setProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME, newVolume);
|
||||
}
|
||||
player.setSpringLastUsed(now);
|
||||
player.save();
|
||||
}
|
||||
|
||||
// autoRecover checks player setting to see if auto recover is enabled, and refill HP to the predefined level.
|
||||
public void autoRecover(GameSession session) {
|
||||
// TODO: In MP, respect SotS settings from the HOST.
|
||||
boolean isAutoRecoveryEnabled = getIsAutoRecoveryEnabled();
|
||||
int autoRecoverPercentage = getAutoRecoveryPercentage();
|
||||
Grasscutter.getLogger().debug("isAutoRecoveryEnabled: " + isAutoRecoveryEnabled + "\tautoRecoverPercentage: " + autoRecoverPercentage);
|
||||
|
||||
if (isAutoRecoveryEnabled) {
|
||||
player.getTeamManager().getActiveTeam().forEach(entity -> {
|
||||
float maxHP = entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
|
||||
float currentHP = entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
|
||||
if (currentHP == maxHP) {
|
||||
return;
|
||||
}
|
||||
float targetHP = maxHP * autoRecoverPercentage / 100;
|
||||
|
||||
if (targetHP > currentHP) {
|
||||
float needHP = targetHP - currentHP;
|
||||
float needSV = needHP * 100; // convert HP needed to Spring Volume needed
|
||||
|
||||
int sotsSVBalance = player.getProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME);
|
||||
if (sotsSVBalance >= needSV) {
|
||||
// sufficient
|
||||
sotsSVBalance -= needSV;
|
||||
} else {
|
||||
// insufficient balance
|
||||
needSV = sotsSVBalance;
|
||||
sotsSVBalance = 0;
|
||||
}
|
||||
player.setProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME, sotsSVBalance);
|
||||
player.setSpringLastUsed(System.currentTimeMillis() / 1000);
|
||||
|
||||
float newHP = currentHP + needSV / 100; // convert SV to HP
|
||||
|
||||
updateAvatarCurHP(session, entity, newHP);
|
||||
}
|
||||
});
|
||||
long now = System.currentTimeMillis() / 1000;
|
||||
int secondsSinceLastUsed = (int) (now - getLastUsed());
|
||||
// 15s = 1% max volume
|
||||
int volumeRefilled = secondsSinceLastUsed * maxVolume / 15 / 100;
|
||||
logger.trace("Statue has refilled HP volume: " + volumeRefilled);
|
||||
currentVolume = Math.min(currentVolume + volumeRefilled, maxVolume);
|
||||
logger.trace("Statue remaining HP volume: " + currentVolume);
|
||||
setCurrentVolume(currentVolume);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAvatarCurHP(GameSession session, EntityAvatar entity, float newHP) {
|
||||
// TODO: Figure out why client shows current HP instead of added HP.
|
||||
// Say an avatar had 12000 and now has 14000, it should show "2000".
|
||||
// The client always show "+14000" which is incorrect.
|
||||
entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP);
|
||||
session.send(new PacketEntityFightPropChangeReasonNotify(entity, FightProperty.FIGHT_PROP_CUR_HP,
|
||||
newHP, List.of(3), PropChangeReasonOuterClass.PropChangeReason.PROP_CHANGE_STATUE_RECOVER,
|
||||
ChangeHpReasonOuterClass.ChangeHpReason.ChangeHpAddStatue));
|
||||
session.send(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP));
|
||||
|
||||
Avatar avatar = entity.getAvatar();
|
||||
avatar.setCurrentHp(newHP);
|
||||
session.send(new PacketAvatarFightPropUpdateNotify(avatar, FightProperty.FIGHT_PROP_CUR_HP));
|
||||
player.save();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@ public interface AfterUpdateStaminaListener {
|
||||
* @param reason Why updating stamina.
|
||||
* @param newStamina New Stamina value.
|
||||
*/
|
||||
void onAfterUpdateStamina(String reason, int newStamina);
|
||||
void onAfterUpdateStamina(String reason, int newStamina, boolean isCharacterStamina);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ public interface BeforeUpdateStaminaListener {
|
||||
* @param newStamina New ABSOLUTE stamina value.
|
||||
* @return true if you want to cancel this update, otherwise false.
|
||||
*/
|
||||
int onBeforeUpdateStamina(String reason, int newStamina);
|
||||
int onBeforeUpdateStamina(String reason, int newStamina, boolean isCharacterStamina);
|
||||
/**
|
||||
* onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's current stamina.
|
||||
* This gives listeners a chance to intercept this update.
|
||||
@@ -16,5 +16,5 @@ public interface BeforeUpdateStaminaListener {
|
||||
* @param consumption ConsumptionType and RELATIVE stamina change amount.
|
||||
* @return true if you want to cancel this update, otherwise false.
|
||||
*/
|
||||
Consumption onBeforeUpdateStamina(String reason, Consumption consumption);
|
||||
Consumption onBeforeUpdateStamina(String reason, Consumption consumption, boolean isCharacterStamina);
|
||||
}
|
||||
@@ -13,18 +13,19 @@ public enum ConsumptionType {
|
||||
// Slow swimming is handled per movement, not per second.
|
||||
// Arm movement frequency depends on gender/age/height.
|
||||
// TODO: Instead of cost -80 per tick, find a proper way to calculate cost.
|
||||
SKIFF(-300), // TODO: Get real value
|
||||
SKIFF_DASH(-204),
|
||||
SPRINT(-1800),
|
||||
SWIM_DASH_START(-20),
|
||||
SWIM_DASH_START(-2000),
|
||||
SWIM_DASH(-204), // -10.2 per second, 5Hz = -204 each tick
|
||||
SWIMMING(-80),
|
||||
TALENT_DASH(-300), // -1500 per second, 5Hz = -300 each tick
|
||||
TALENT_DASH_START(-1000),
|
||||
|
||||
// restore
|
||||
POWERED_FLY(500), // TODO: Get real value
|
||||
POWERED_SKIFF(2000), // TODO: Get real value
|
||||
POWERED_FLY(500),
|
||||
POWERED_SKIFF(500),
|
||||
RUN(500),
|
||||
SKIFF(500),
|
||||
STANDBY(500),
|
||||
WALK(500);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package emu.grasscutter.game.managers.StaminaManager;
|
||||
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.game.entity.EntityAvatar;
|
||||
import emu.grasscutter.game.entity.GameEntity;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
@@ -13,21 +14,21 @@ import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
|
||||
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
|
||||
import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType;
|
||||
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
|
||||
import emu.grasscutter.net.proto.VehicleInteractTypeOuterClass.VehicleInteractType;
|
||||
import emu.grasscutter.server.game.GameSession;
|
||||
import emu.grasscutter.server.packet.send.*;
|
||||
import emu.grasscutter.utils.Position;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.Math;
|
||||
import java.util.*;
|
||||
|
||||
import static emu.grasscutter.Configuration.*;
|
||||
import static emu.grasscutter.Configuration.GAME_OPTIONS;
|
||||
|
||||
public class StaminaManager {
|
||||
|
||||
// TODO: Skiff state detection?
|
||||
private final Player player;
|
||||
private final HashMap<String, HashSet<MotionState>> MotionStatesCategorized = new HashMap<>() {{
|
||||
private static final HashMap<String, HashSet<MotionState>> MotionStatesCategorized = new HashMap<>() {{
|
||||
put("CLIMB", new HashSet<>(List.of(
|
||||
MotionState.MOTION_CLIMB, // sustained, when not moving no cost no recover
|
||||
MotionState.MOTION_STANDBY_TO_CLIMB // NOT OBSERVED, see MOTION_JUMP_UP_WALL_FOR_STANDBY
|
||||
@@ -48,7 +49,7 @@ public class StaminaManager {
|
||||
)));
|
||||
put("SKIFF", new HashSet<>(List.of(
|
||||
MotionState.MOTION_SKIFF_BOARDING, // NOT OBSERVED even when boarding
|
||||
MotionState.MOTION_SKIFF_DASH, // NOT OBSERVED even when dashing
|
||||
MotionState.MOTION_SKIFF_DASH, // sustained, observed with waverider entity ID.
|
||||
MotionState.MOTION_SKIFF_NORMAL, // sustained, OBSERVED when both normal and dashing
|
||||
MotionState.MOTION_SKIFF_POWERED_DASH // sustained, recover
|
||||
)));
|
||||
@@ -108,7 +109,8 @@ public class StaminaManager {
|
||||
}};
|
||||
|
||||
private final Logger logger = Grasscutter.getLogger();
|
||||
public final static int GlobalMaximumStamina = 24000;
|
||||
public final static int GlobalCharacterMaximumStamina = 24000;
|
||||
public final static int GlobalVehicleMaxStamina = 24000;
|
||||
private Position currentCoordinates = new Position(0, 0, 0);
|
||||
private Position previousCoordinates = new Position(0, 0, 0);
|
||||
private MotionState currentState = MotionState.MOTION_STANDBY;
|
||||
@@ -122,74 +124,58 @@ public class StaminaManager {
|
||||
private int lastSkillId = 0;
|
||||
private int lastSkillCasterId = 0;
|
||||
private boolean lastSkillFirstTick = true;
|
||||
public static final HashSet<Integer> TalentMovements = new HashSet<>(List.of(
|
||||
10013, // Kamisato Ayaka
|
||||
10413 // Mona
|
||||
private int vehicleId = -1;
|
||||
private int vehicleStamina = GlobalVehicleMaxStamina;
|
||||
private static final HashSet<Integer> TalentMovements = new HashSet<>(List.of(
|
||||
10013, 10413
|
||||
));
|
||||
private static final HashMap<Integer, Float> ClimbFoodReductionMap = new HashMap<>() {{
|
||||
// TODO: get real food id
|
||||
put(0, 0.8f); // Sample food
|
||||
}};
|
||||
private static final HashMap<Integer, Float> DashFoodReductionMap = new HashMap<>() {{
|
||||
// TODO: get real food id
|
||||
put(0, 0.8f); // Sample food
|
||||
}};
|
||||
private static final HashMap<Integer, Float> FlyFoodReductionMap = new HashMap<>() {{
|
||||
// TODO: get real food id
|
||||
put(0, 0.8f); // Sample food
|
||||
}};
|
||||
private static final HashMap<Integer, Float> SwimFoodReductionMap = new HashMap<>() {{
|
||||
// TODO: get real food id
|
||||
put(0, 0.8f); // Sample food
|
||||
}};
|
||||
private static final HashMap<Integer, Float> ClimbTalentReductionMap = new HashMap<>() {{
|
||||
put(262301, 0.8f);
|
||||
}};
|
||||
private static final HashMap<Integer, Float> FlyTalentReductionMap = new HashMap<>() {{
|
||||
put(212301, 0.8f);
|
||||
put(222301, 0.8f);
|
||||
}};
|
||||
private static final HashMap<Integer, Float> SwimTalentReductionMap = new HashMap<>() {{
|
||||
put(242301, 0.8f);
|
||||
put(542301, 0.8f);
|
||||
}};
|
||||
|
||||
// TODO: Get from somewhere else, instead of hard-coded here?
|
||||
public static final HashSet<Integer> ClaymoreSkills = new HashSet<>(List.of(
|
||||
10160, // Diluc, /=2
|
||||
10201, // Razor
|
||||
10241, // Beidou
|
||||
10341, // Noelle
|
||||
10401, // Chongyun
|
||||
10441, // Xinyan
|
||||
10511, // Eula
|
||||
10531, // Sayu
|
||||
10571 // Arataki Itto, = 0
|
||||
));
|
||||
public static final HashSet<Integer> CatalystSkills = new HashSet<>(List.of(
|
||||
10060, // Lisa
|
||||
10070, // Barbara
|
||||
10271, // Ningguang
|
||||
10291, // Klee
|
||||
10411, // Mona
|
||||
10431, // Sucrose
|
||||
10481, // Yanfei
|
||||
10541, // Sangonomoiya Kokomi
|
||||
10581 // Yae Miko
|
||||
));
|
||||
public static final HashSet<Integer> PolearmSkills = new HashSet<>(List.of(
|
||||
10231, // Xiangling
|
||||
10261, // Xiao
|
||||
10301, // Zhongli
|
||||
10451, // Rosaria
|
||||
10461, // Hu Tao
|
||||
10501, // Thoma
|
||||
10521, // Raiden Shogun
|
||||
10631, // Shenhe
|
||||
10641 // Yunjin
|
||||
));
|
||||
public static final HashSet<Integer> SwordSkills = new HashSet<>(List.of(
|
||||
10024, // Kamisato Ayaka
|
||||
10031, // Jean
|
||||
10073, // Kaeya
|
||||
10321, // Bennett
|
||||
10337, // Tartaglia, melee stance (10332 switch to melee, 10336 switch to ranged stance)
|
||||
10351, // Qiqi
|
||||
10381, // Xingqiu
|
||||
10386, // Albedo
|
||||
10421, // Keqing, =-2500
|
||||
10471, // Kaedehara Kazuha
|
||||
10661, // Kamisato Ayato
|
||||
100553, // Lumine
|
||||
100540 // Aether
|
||||
));
|
||||
public static final HashSet<Integer> BowSkills = new HashSet<>(List.of(
|
||||
10041, 10043, // Amber
|
||||
10221, 10223,// Venti
|
||||
10311, 10315, // Fischl
|
||||
10331, 10335, // Tartaglia, ranged stance
|
||||
10371, // Ganyu
|
||||
10391, 10394, // Diona
|
||||
10491, // Yoimiya
|
||||
10551, 10554, // Gorou
|
||||
10561, 10564, // Kojou Sara
|
||||
10621, // Aloy
|
||||
99998, 99999 // Yelan // TODO: get real values
|
||||
));
|
||||
public static final HashSet<Integer> BowAvatars = new HashSet<>();
|
||||
public static final HashSet<Integer> CatalystAvatars = new HashSet<>();
|
||||
public static final HashSet<Integer> ClaymoreAvatars = new HashSet<>();
|
||||
public static final HashSet<Integer> PolearmAvatars = new HashSet<>();
|
||||
public static final HashSet<Integer> SwordAvatars = new HashSet<>();
|
||||
|
||||
public static void initialize() {
|
||||
// Initialize skill categories
|
||||
GameData.getAvatarDataMap().forEach((avatarId, avatarData) -> {
|
||||
switch (avatarData.getWeaponType()) {
|
||||
case "WEAPON_BOW" -> BowAvatars.add(avatarId);
|
||||
case "WEAPON_CLAYMORE" -> ClaymoreAvatars.add(avatarId);
|
||||
case "WEAPON_CATALYST" -> CatalystAvatars.add(avatarId);
|
||||
case "WEAPON_POLE" -> PolearmAvatars.add(avatarId);
|
||||
case "WEAPON_SWORD_ONE_HAND" -> SwordAvatars.add(avatarId);
|
||||
}
|
||||
});
|
||||
// TODO: Initialize foods etc.
|
||||
}
|
||||
|
||||
public StaminaManager(Player player) {
|
||||
this.player = player;
|
||||
@@ -203,6 +189,22 @@ public class StaminaManager {
|
||||
lastSkillCasterId = skillCasterId;
|
||||
}
|
||||
|
||||
public int getMaxCharacterStamina() {
|
||||
return player.getProperty(PlayerProperty.PROP_MAX_STAMINA);
|
||||
}
|
||||
|
||||
public int getCurrentCharacterStamina() {
|
||||
return player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA);
|
||||
}
|
||||
|
||||
public int getMaxVehicleStamina() {
|
||||
return GlobalVehicleMaxStamina;
|
||||
}
|
||||
|
||||
public int getCurrentVehicleStamina() {
|
||||
return vehicleStamina;
|
||||
}
|
||||
|
||||
public boolean registerBeforeUpdateStaminaListener(String listenerName, BeforeUpdateStaminaListener listener) {
|
||||
if (beforeUpdateStaminaListeners.containsKey(listenerName)) {
|
||||
return false;
|
||||
@@ -244,67 +246,71 @@ public class StaminaManager {
|
||||
return Math.abs(diffX) > 0.3 || Math.abs(diffY) > 0.2 || Math.abs(diffZ) > 0.3;
|
||||
}
|
||||
|
||||
public int updateStaminaRelative(GameSession session, Consumption consumption) {
|
||||
int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA);
|
||||
public int updateStaminaRelative(GameSession session, Consumption consumption, boolean isCharacterStamina) {
|
||||
int currentStamina = isCharacterStamina ? getCurrentCharacterStamina() : getCurrentVehicleStamina();
|
||||
if (consumption.amount == 0) {
|
||||
return currentStamina;
|
||||
}
|
||||
// notify will update
|
||||
for (Map.Entry<String, BeforeUpdateStaminaListener> listener : beforeUpdateStaminaListeners.entrySet()) {
|
||||
Consumption overriddenConsumption = listener.getValue().onBeforeUpdateStamina(consumption.type.toString(), consumption);
|
||||
Consumption overriddenConsumption = listener.getValue().onBeforeUpdateStamina(consumption.type.toString(), consumption, isCharacterStamina);
|
||||
if ((overriddenConsumption.type != consumption.type) && (overriddenConsumption.amount != consumption.amount)) {
|
||||
logger.debug("[StaminaManager] Stamina update relative(" +
|
||||
logger.debug("Stamina update relative(" +
|
||||
consumption.type.toString() + ", " + consumption.amount + ") overridden to relative(" +
|
||||
consumption.type.toString() + ", " + consumption.amount + ") by: " + listener.getKey());
|
||||
return currentStamina;
|
||||
}
|
||||
}
|
||||
int playerMaxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA);
|
||||
logger.trace(currentStamina + "/" + playerMaxStamina + "\t" + currentState + "\t" +
|
||||
int maxStamina = isCharacterStamina ? getMaxCharacterStamina() : getMaxVehicleStamina();
|
||||
logger.trace((isCharacterStamina ? "C " : "V ") + currentStamina + "/" + maxStamina + "\t" + currentState + "\t" +
|
||||
(isPlayerMoving() ? "moving" : " ") + "\t(" + consumption.type + "," +
|
||||
consumption.amount + ")");
|
||||
int newStamina = currentStamina + consumption.amount;
|
||||
if (newStamina < 0) {
|
||||
newStamina = 0;
|
||||
} else if (newStamina > playerMaxStamina) {
|
||||
newStamina = playerMaxStamina;
|
||||
} else if (newStamina > maxStamina) {
|
||||
newStamina = maxStamina;
|
||||
}
|
||||
return setStamina(session, consumption.type.toString(), newStamina);
|
||||
return setStamina(session, consumption.type.toString(), newStamina, isCharacterStamina);
|
||||
}
|
||||
|
||||
public int updateStaminaAbsolute(GameSession session, String reason, int newStamina) {
|
||||
int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA);
|
||||
public int updateStaminaAbsolute(GameSession session, String reason, int newStamina, boolean isCharacterStamina) {
|
||||
int currentStamina = isCharacterStamina ? getCurrentCharacterStamina() : getCurrentVehicleStamina();
|
||||
// notify will update
|
||||
for (Map.Entry<String, BeforeUpdateStaminaListener> listener : beforeUpdateStaminaListeners.entrySet()) {
|
||||
int overriddenNewStamina = listener.getValue().onBeforeUpdateStamina(reason, newStamina);
|
||||
int overriddenNewStamina = listener.getValue().onBeforeUpdateStamina(reason, newStamina, isCharacterStamina);
|
||||
if (overriddenNewStamina != newStamina) {
|
||||
logger.debug("[StaminaManager] Stamina update absolute(" +
|
||||
logger.debug("Stamina update absolute(" +
|
||||
reason + ", " + newStamina + ") overridden to absolute(" +
|
||||
reason + ", " + newStamina + ") by: " + listener.getKey());
|
||||
return currentStamina;
|
||||
}
|
||||
}
|
||||
int playerMaxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA);
|
||||
int maxStamina = isCharacterStamina ? getMaxCharacterStamina() : getMaxVehicleStamina();
|
||||
if (newStamina < 0) {
|
||||
newStamina = 0;
|
||||
} else if (newStamina > playerMaxStamina) {
|
||||
newStamina = playerMaxStamina;
|
||||
} else if (newStamina > maxStamina) {
|
||||
newStamina = maxStamina;
|
||||
}
|
||||
return setStamina(session, reason, newStamina);
|
||||
return setStamina(session, reason, newStamina, isCharacterStamina);
|
||||
}
|
||||
|
||||
// Returns new stamina and sends PlayerPropNotify
|
||||
public int setStamina(GameSession session, String reason, int newStamina) {
|
||||
// Returns new stamina and sends PlayerPropNotify or VehicleStaminaNotify
|
||||
public int setStamina(GameSession session, String reason, int newStamina, boolean isCharacterStamina) {
|
||||
if (!GAME_OPTIONS.staminaUsage) {
|
||||
newStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA);
|
||||
newStamina = getMaxCharacterStamina();
|
||||
}
|
||||
// set stamina if is character stamina
|
||||
if (isCharacterStamina) {
|
||||
player.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina);
|
||||
session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA));
|
||||
} else {
|
||||
vehicleStamina = newStamina;
|
||||
session.send(new PacketVehicleStaminaNotify(vehicleId, ((float) newStamina) / 100));
|
||||
}
|
||||
|
||||
// set stamina
|
||||
player.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina);
|
||||
session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA));
|
||||
// notify updated
|
||||
for (Map.Entry<String, AfterUpdateStaminaListener> listener : afterUpdateStaminaListeners.entrySet()) {
|
||||
listener.getValue().onAfterUpdateStamina(reason, newStamina);
|
||||
listener.getValue().onAfterUpdateStamina(reason, newStamina, isCharacterStamina);
|
||||
}
|
||||
return newStamina;
|
||||
}
|
||||
@@ -343,22 +349,23 @@ public class StaminaManager {
|
||||
// External trigger handler
|
||||
|
||||
public void handleEvtDoSkillSuccNotify(GameSession session, int skillId, int casterId) {
|
||||
// Ignore if skill not cast by not current active
|
||||
// Ignore if skill not cast by not current active avatar
|
||||
if (casterId != player.getTeamManager().getCurrentAvatarEntity().getId()) {
|
||||
return;
|
||||
}
|
||||
setSkillCast(skillId, casterId);
|
||||
// Handle immediate stamina cost
|
||||
if (ClaymoreSkills.contains(skillId)) {
|
||||
int currentAvatarId = player.getTeamManager().getCurrentAvatarEntity().getAvatar().getAvatarId();
|
||||
if (ClaymoreAvatars.contains(currentAvatarId)) {
|
||||
// Exclude claymore as their stamina cost starts when MixinStaminaCost gets in
|
||||
return;
|
||||
}
|
||||
// TODO: Differentiate normal attacks from charged attacks and exclude
|
||||
// TODO: Temporary: Exclude non-claymore attacks for now
|
||||
if (BowSkills.contains(skillId)
|
||||
|| SwordSkills.contains(skillId)
|
||||
|| PolearmSkills.contains(skillId)
|
||||
|| CatalystSkills.contains(skillId)
|
||||
if (BowAvatars.contains(currentAvatarId)
|
||||
|| SwordAvatars.contains(currentAvatarId)
|
||||
|| PolearmAvatars.contains(currentAvatarId)
|
||||
|| CatalystAvatars.contains(currentAvatarId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -367,7 +374,7 @@ public class StaminaManager {
|
||||
|
||||
public void handleMixinCostStamina(boolean isSwim) {
|
||||
// Talent moving and claymore avatar charged attack duration
|
||||
// logger.trace("abilityMixinCostStamina: isSwim: " + isSwim);
|
||||
// logger.trace("abilityMixinCostStamina: isSwim: " + isSwim + "\tlastSkill: " + lastSkillId);
|
||||
if (lastSkillCasterId == player.getTeamManager().getCurrentAvatarEntity().getId()) {
|
||||
handleImmediateStamina(cachedSession, lastSkillId);
|
||||
}
|
||||
@@ -381,11 +388,11 @@ public class StaminaManager {
|
||||
MotionState motionState = motionInfo.getState();
|
||||
int notifyEntityId = entity.getId();
|
||||
int currentAvatarEntityId = session.getPlayer().getTeamManager().getCurrentAvatarEntity().getId();
|
||||
if (notifyEntityId != currentAvatarEntityId) {
|
||||
if (notifyEntityId != currentAvatarEntityId && notifyEntityId != vehicleId) {
|
||||
return;
|
||||
}
|
||||
currentState = motionState;
|
||||
// logger.trace("" + currentState);
|
||||
// logger.trace(currentState + "\t" + (notifyEntityId == currentAvatarEntityId ? "character" : "vehicle"));
|
||||
Vector posVector = motionInfo.getPos();
|
||||
Position newPos = new Position(posVector.getX(), posVector.getY(), posVector.getZ());
|
||||
if (newPos.getX() != 0 && newPos.getY() != 0 && newPos.getZ() != 0) {
|
||||
@@ -395,28 +402,40 @@ public class StaminaManager {
|
||||
handleImmediateStamina(session, motionState);
|
||||
}
|
||||
|
||||
public void handleVehicleInteractReq(GameSession session, int vehicleId, VehicleInteractType vehicleInteractType) {
|
||||
if (vehicleInteractType == VehicleInteractType.VEHICLE_INTERACT_IN) {
|
||||
this.vehicleId = vehicleId;
|
||||
// Reset character stamina here to prevent falling into water immediately on ejection if char stamina is
|
||||
// close to empty when boarding.
|
||||
updateStaminaAbsolute(session, "board vehicle", getMaxCharacterStamina(), true);
|
||||
updateStaminaAbsolute(session, "board vehicle", getMaxVehicleStamina(), false);
|
||||
} else {
|
||||
this.vehicleId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Internal handler
|
||||
|
||||
private void handleImmediateStamina(GameSession session, @NotNull MotionState motionState) {
|
||||
switch (motionState) {
|
||||
case MOTION_CLIMB:
|
||||
if (currentState != MotionState.MOTION_CLIMB) {
|
||||
updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_START));
|
||||
updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_START), true);
|
||||
}
|
||||
break;
|
||||
case MOTION_DASH_BEFORE_SHAKE:
|
||||
if (previousState != MotionState.MOTION_DASH_BEFORE_SHAKE) {
|
||||
updateStaminaRelative(session, new Consumption(ConsumptionType.SPRINT));
|
||||
updateStaminaRelative(session, new Consumption(ConsumptionType.SPRINT), true);
|
||||
}
|
||||
break;
|
||||
case MOTION_CLIMB_JUMP:
|
||||
if (previousState != MotionState.MOTION_CLIMB_JUMP) {
|
||||
updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_JUMP));
|
||||
updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_JUMP), true);
|
||||
}
|
||||
break;
|
||||
case MOTION_SWIM_DASH:
|
||||
if (previousState != MotionState.MOTION_SWIM_DASH) {
|
||||
updateStaminaRelative(session, new Consumption(ConsumptionType.SWIM_DASH_START));
|
||||
updateStaminaRelative(session, new Consumption(ConsumptionType.SWIM_DASH_START), true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -424,18 +443,20 @@ public class StaminaManager {
|
||||
|
||||
private void handleImmediateStamina(GameSession session, int skillId) {
|
||||
Consumption consumption = getFightConsumption(skillId);
|
||||
updateStaminaRelative(session, consumption);
|
||||
updateStaminaRelative(session, consumption, true);
|
||||
}
|
||||
|
||||
private class SustainedStaminaHandler extends TimerTask {
|
||||
public void run() {
|
||||
boolean moving = isPlayerMoving();
|
||||
int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA);
|
||||
int maxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA);
|
||||
if (moving || (currentStamina < maxStamina)) {
|
||||
int currentCharacterStamina = getCurrentCharacterStamina();
|
||||
int maxCharacterStamina = getMaxCharacterStamina();
|
||||
int currentVehicleStamina = getCurrentVehicleStamina();
|
||||
int maxVehicleStamina = getMaxVehicleStamina();
|
||||
if (moving || (currentCharacterStamina < maxCharacterStamina) || (currentVehicleStamina < maxVehicleStamina)) {
|
||||
logger.trace("Player moving: " + moving + ", stamina full: " +
|
||||
(currentStamina >= maxStamina) + ", recalculate stamina");
|
||||
|
||||
(currentCharacterStamina >= maxCharacterStamina) + ", recalculate stamina");
|
||||
boolean isCharacterStamina = true;
|
||||
Consumption consumption;
|
||||
if (MotionStatesCategorized.get("CLIMB").contains(currentState)) {
|
||||
consumption = getClimbConsumption();
|
||||
@@ -447,43 +468,44 @@ public class StaminaManager {
|
||||
consumption = new Consumption(ConsumptionType.RUN);
|
||||
} else if (MotionStatesCategorized.get("SKIFF").contains(currentState)) {
|
||||
consumption = getSkiffConsumption();
|
||||
isCharacterStamina = false;
|
||||
} else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) {
|
||||
consumption = new Consumption(ConsumptionType.STANDBY);
|
||||
} else if (MotionStatesCategorized.get("SWIM").contains((currentState))) {
|
||||
} else if (MotionStatesCategorized.get("SWIM").contains(currentState)) {
|
||||
consumption = getSwimConsumptions();
|
||||
} else if (MotionStatesCategorized.get("WALK").contains((currentState))) {
|
||||
} else if (MotionStatesCategorized.get("WALK").contains(currentState)) {
|
||||
consumption = new Consumption(ConsumptionType.WALK);
|
||||
} else if (MotionStatesCategorized.get("OTHER").contains((currentState))) {
|
||||
} else if (MotionStatesCategorized.get("NOCOST_NORECOVER").contains(currentState)) {
|
||||
consumption = new Consumption();
|
||||
} else if (MotionStatesCategorized.get("OTHER").contains(currentState)) {
|
||||
consumption = getOtherConsumptions();
|
||||
} else {
|
||||
// ignore
|
||||
} else { // ignore
|
||||
return;
|
||||
}
|
||||
if (consumption.amount < 0) {
|
||||
/* Do not apply reduction factor when recovering stamina
|
||||
TODO: Reductions that apply to all motion types:
|
||||
Elemental Resonance
|
||||
Wind: -15%
|
||||
Skills
|
||||
Diona E: -10% while shield lasts - applies to SP+MP
|
||||
Barbara E: -12% while lasts - applies to SP+MP
|
||||
*/
|
||||
|
||||
if (consumption.amount < 0 && isCharacterStamina) {
|
||||
// Do not apply reduction factor when recovering stamina
|
||||
if (player.getTeamManager().getTeamResonances().contains(10301)) {
|
||||
consumption.amount *= 0.85f;
|
||||
}
|
||||
}
|
||||
// Delay 2 seconds before starts recovering stamina
|
||||
if (cachedSession != null) {
|
||||
// Delay 1 seconds before starts recovering stamina
|
||||
if (consumption.amount != 0 && cachedSession != null) {
|
||||
if (consumption.amount < 0) {
|
||||
staminaRecoverDelay = 0;
|
||||
}
|
||||
if (consumption.amount > 0 && consumption.type != ConsumptionType.POWERED_FLY) {
|
||||
// For POWERED_FLY recover immediately - things like Amber's gliding exam may require this.
|
||||
if (staminaRecoverDelay < 10) {
|
||||
// For others recover after 2 seconds (10 ticks) - as official server does.
|
||||
if (consumption.amount > 0
|
||||
&& consumption.type != ConsumptionType.POWERED_FLY
|
||||
&& consumption.type != ConsumptionType.POWERED_SKIFF) {
|
||||
// For POWERED_* recover immediately - things like Amber's gliding exam and skiff challenges may require this.
|
||||
if (staminaRecoverDelay < 5) {
|
||||
// For others recover after 1 seconds (5 ticks) - as official server does.
|
||||
staminaRecoverDelay++;
|
||||
consumption.amount = 0;
|
||||
logger.trace("[StaminaManager] Delaying recovery: " + staminaRecoverDelay);
|
||||
logger.trace("Delaying recovery: " + staminaRecoverDelay);
|
||||
}
|
||||
}
|
||||
updateStaminaRelative(cachedSession, consumption);
|
||||
updateStaminaRelative(cachedSession, consumption, isCharacterStamina);
|
||||
}
|
||||
}
|
||||
previousState = currentState;
|
||||
@@ -496,10 +518,11 @@ public class StaminaManager {
|
||||
}
|
||||
|
||||
private void handleDrowning() {
|
||||
int stamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA);
|
||||
// TODO: fix drowning waverider entity
|
||||
int stamina = getCurrentCharacterStamina();
|
||||
if (stamina < 10) {
|
||||
logger.trace(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" +
|
||||
player.getProperty(PlayerProperty.PROP_MAX_STAMINA) + "\t" + currentState);
|
||||
logger.trace(getCurrentCharacterStamina() + "/" +
|
||||
getMaxCharacterStamina() + "\t" + currentState);
|
||||
if (currentState != MotionState.MOTION_SWIM_IDLE) {
|
||||
killAvatar(cachedSession, cachedEntity, PlayerDieType.PLAYER_DIE_DRAWN);
|
||||
}
|
||||
@@ -517,24 +540,25 @@ public class StaminaManager {
|
||||
return getTalentMovingSustainedCost(skillCasting);
|
||||
}
|
||||
// Bow avatar charged attack
|
||||
if (BowSkills.contains(skillCasting)) {
|
||||
int currentAvatarId = player.getTeamManager().getCurrentAvatarEntity().getAvatar().getAvatarId();
|
||||
if (BowAvatars.contains(currentAvatarId)) {
|
||||
return getBowSustainedCost(skillCasting);
|
||||
}
|
||||
// Claymore avatar charged attack
|
||||
if (ClaymoreSkills.contains(skillCasting)) {
|
||||
if (ClaymoreAvatars.contains(currentAvatarId)) {
|
||||
return getClaymoreSustainedCost(skillCasting);
|
||||
}
|
||||
// Catalyst avatar charged attack
|
||||
if (CatalystSkills.contains(skillCasting)) {
|
||||
return getCatalystSustainedCost(skillCasting);
|
||||
if (CatalystAvatars.contains(currentAvatarId)) {
|
||||
return getCatalystCost(skillCasting);
|
||||
}
|
||||
// Polearm avatar charged attack
|
||||
if (PolearmSkills.contains(skillCasting)) {
|
||||
return getPolearmSustainedCost(skillCasting);
|
||||
if (PolearmAvatars.contains(currentAvatarId)) {
|
||||
return getPolearmCost(skillCasting);
|
||||
}
|
||||
// Sword avatar charged attack
|
||||
if (SwordSkills.contains(skillCasting)) {
|
||||
return getSwordSustainedCost(skillCasting);
|
||||
if (SwordAvatars.contains(skillCasting)) {
|
||||
return getSwordCost(skillCasting);
|
||||
}
|
||||
return new Consumption();
|
||||
}
|
||||
@@ -546,18 +570,8 @@ public class StaminaManager {
|
||||
consumption.amount = ConsumptionType.CLIMBING.amount;
|
||||
}
|
||||
// Climbing specific reductions
|
||||
// TODO: create a food cost reduction map
|
||||
HashMap<Integer, Float> foodReductionMap = new HashMap<>() {{
|
||||
// TODO: get real talent id
|
||||
put(0, 0.8f); // Sample food
|
||||
}};
|
||||
consumption.amount *= getFoodCostReductionFactor(foodReductionMap);
|
||||
|
||||
HashMap<Integer, Float> talentReductionMap = new HashMap<>() {{
|
||||
// TODO: get real talent id
|
||||
put(0, 0.8f); // Xiao
|
||||
}};
|
||||
consumption.amount *= getTalentCostReductionFactor(talentReductionMap);
|
||||
consumption.amount *= getFoodCostReductionFactor(ClimbFoodReductionMap);
|
||||
consumption.amount *= getTalentCostReductionFactor(ClimbTalentReductionMap);
|
||||
return consumption;
|
||||
}
|
||||
|
||||
@@ -572,13 +586,9 @@ public class StaminaManager {
|
||||
consumption.type = ConsumptionType.SWIM_DASH;
|
||||
consumption.amount = ConsumptionType.SWIM_DASH.amount;
|
||||
}
|
||||
// Reductions
|
||||
HashMap<Integer, Float> talentReductionMap = new HashMap<>() {{
|
||||
// TODO: get real talent id
|
||||
put(0, 0.8f); // Beidou
|
||||
put(1, 0.8f); // Sangonomiya Kokomi
|
||||
}};
|
||||
consumption.amount *= getTalentCostReductionFactor(talentReductionMap);
|
||||
// Swimming specific reductions
|
||||
consumption.amount *= getFoodCostReductionFactor(SwimFoodReductionMap);
|
||||
consumption.amount *= getTalentCostReductionFactor(SwimTalentReductionMap);
|
||||
return consumption;
|
||||
}
|
||||
|
||||
@@ -587,8 +597,8 @@ public class StaminaManager {
|
||||
if (currentState == MotionState.MOTION_DASH) {
|
||||
consumption.type = ConsumptionType.DASH;
|
||||
consumption.amount = ConsumptionType.DASH.amount;
|
||||
// TODO: Dashing specific reductions
|
||||
// Foods:
|
||||
// Dashing specific reductions
|
||||
consumption.amount *= getFoodCostReductionFactor(DashFoodReductionMap);
|
||||
}
|
||||
return consumption;
|
||||
}
|
||||
@@ -599,32 +609,34 @@ public class StaminaManager {
|
||||
return new Consumption(ConsumptionType.POWERED_FLY);
|
||||
}
|
||||
Consumption consumption = new Consumption(ConsumptionType.FLY);
|
||||
// Passive Talents
|
||||
HashMap<Integer, Float> talentReductionMap = new HashMap<>() {{
|
||||
put(212301, 0.8f); // Amber
|
||||
put(222301, 0.8f); // Venti
|
||||
}};
|
||||
consumption.amount *= getTalentCostReductionFactor(talentReductionMap);
|
||||
// TODO: Foods
|
||||
// Flying specific reductions
|
||||
consumption.amount *= getFoodCostReductionFactor(FlyFoodReductionMap);
|
||||
consumption.amount *= getTalentCostReductionFactor(FlyTalentReductionMap);
|
||||
return consumption;
|
||||
}
|
||||
|
||||
private Consumption getSkiffConsumption() {
|
||||
// POWERED_SKIFF, e.g. wind tunnel
|
||||
if (currentState == MotionState.MOTION_SKIFF_POWERED_DASH) {
|
||||
return new Consumption(ConsumptionType.POWERED_SKIFF);
|
||||
}
|
||||
// No known reduction for skiffing.
|
||||
return new Consumption(ConsumptionType.SKIFF);
|
||||
return switch (currentState) {
|
||||
case MOTION_SKIFF_DASH -> new Consumption(ConsumptionType.SKIFF_DASH);
|
||||
case MOTION_SKIFF_POWERED_DASH -> new Consumption(ConsumptionType.POWERED_SKIFF);
|
||||
case MOTION_SKIFF_NORMAL -> new Consumption(ConsumptionType.SKIFF);
|
||||
default -> new Consumption();
|
||||
};
|
||||
}
|
||||
|
||||
private Consumption getOtherConsumptions() {
|
||||
if (currentState == MotionState.MOTION_NOTIFY) {
|
||||
if (BowSkills.contains(lastSkillId)) {
|
||||
switch (currentState) {
|
||||
case MOTION_NOTIFY:
|
||||
// if (BowSkills.contains(lastSkillId)) {
|
||||
// return new Consumption(ConsumptionType.FIGHT, 500);
|
||||
// }
|
||||
break;
|
||||
case MOTION_FIGHT:
|
||||
// TODO: what if charged attack
|
||||
return new Consumption(ConsumptionType.FIGHT, 500);
|
||||
}
|
||||
}
|
||||
// TODO: Add other logic
|
||||
|
||||
return new Consumption();
|
||||
}
|
||||
|
||||
@@ -671,11 +683,11 @@ public class StaminaManager {
|
||||
return new Consumption(ConsumptionType.FIGHT, +500);
|
||||
}
|
||||
|
||||
private Consumption getCatalystSustainedCost(int skillId) {
|
||||
private Consumption getCatalystCost(int skillId) {
|
||||
Consumption consumption = new Consumption(ConsumptionType.FIGHT, -5000);
|
||||
// Character specific handling
|
||||
switch (skillId) {
|
||||
// TODO: Yanfei
|
||||
// TODO:
|
||||
}
|
||||
return consumption;
|
||||
}
|
||||
@@ -684,18 +696,20 @@ public class StaminaManager {
|
||||
Consumption consumption = new Consumption(ConsumptionType.FIGHT, -1333); // 4000 / 3 = 1333
|
||||
// Character specific handling
|
||||
switch (skillId) {
|
||||
case 10571: // Arataki Itto, does not consume stamina at all.
|
||||
case 10571:
|
||||
case 10532:
|
||||
consumption.amount = 0;
|
||||
break;
|
||||
case 10160: // Diluc, with talent "Relentless" stamina cost is decreased by 50%
|
||||
// TODO: How to get talent status?
|
||||
consumption.amount /= 2;
|
||||
case 10160:
|
||||
if (player.getTeamManager().getCurrentAvatarEntity().getAvatar().getProudSkillList().contains(162101)) {
|
||||
consumption.amount /= 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return consumption;
|
||||
}
|
||||
|
||||
private Consumption getPolearmSustainedCost(int skillId) {
|
||||
private Consumption getPolearmCost(int skillId) {
|
||||
Consumption consumption = new Consumption(ConsumptionType.FIGHT, -2500);
|
||||
// Character specific handling
|
||||
switch (skillId) {
|
||||
@@ -704,11 +718,11 @@ public class StaminaManager {
|
||||
return consumption;
|
||||
}
|
||||
|
||||
private Consumption getSwordSustainedCost(int skillId) {
|
||||
private Consumption getSwordCost(int skillId) {
|
||||
Consumption consumption = new Consumption(ConsumptionType.FIGHT, -2000);
|
||||
// Character specific handling
|
||||
switch (skillId) {
|
||||
case 10421: // Keqing, -2500
|
||||
case 10421:
|
||||
consumption.amount = -2500;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,9 @@ import emu.grasscutter.game.props.ActionReason;
|
||||
import emu.grasscutter.game.props.EntityType;
|
||||
import emu.grasscutter.game.props.PlayerProperty;
|
||||
import emu.grasscutter.game.props.SceneType;
|
||||
import emu.grasscutter.game.quest.GameMainQuest;
|
||||
import emu.grasscutter.game.quest.GameQuest;
|
||||
import emu.grasscutter.game.quest.QuestManager;
|
||||
import emu.grasscutter.game.shop.ShopLimit;
|
||||
import emu.grasscutter.game.managers.MapMarkManager.*;
|
||||
import emu.grasscutter.game.tower.TowerManager;
|
||||
@@ -82,6 +85,11 @@ public class Player {
|
||||
private Set<Integer> flyCloakList;
|
||||
private Set<Integer> costumeList;
|
||||
|
||||
private Integer widgetId;
|
||||
|
||||
private Set<Integer> realmList;
|
||||
private Integer currentRealmId;
|
||||
|
||||
@Transient private long nextGuid = 0;
|
||||
@Transient private int peerId;
|
||||
@Transient private World world;
|
||||
@@ -93,6 +101,7 @@ public class Player {
|
||||
@Transient private MailHandler mailHandler;
|
||||
@Transient private MessageHandler messageHandler;
|
||||
@Transient private AbilityManager abilityManager;
|
||||
@Transient private QuestManager questManager;
|
||||
|
||||
@Transient private SotSManager sotsManager;
|
||||
|
||||
@@ -132,10 +141,11 @@ public class Player {
|
||||
@Transient private final InvokeHandler<AbilityInvokeEntry> abilityInvokeHandler;
|
||||
@Transient private final InvokeHandler<AbilityInvokeEntry> clientAbilityInitFinishHandler;
|
||||
|
||||
private MapMarksManager mapMarksManager;
|
||||
@Transient private MapMarksManager mapMarksManager;
|
||||
@Transient private StaminaManager staminaManager;
|
||||
|
||||
private long springLastUsed;
|
||||
private HashMap<String, MapMark> mapMarks;
|
||||
|
||||
|
||||
@Deprecated
|
||||
@@ -147,6 +157,7 @@ public class Player {
|
||||
this.mailHandler = new MailHandler(this);
|
||||
this.towerManager = new TowerManager(this);
|
||||
this.abilityManager = new AbilityManager(this);
|
||||
this.setQuestManager(new QuestManager(this));
|
||||
this.pos = new Position();
|
||||
this.rotation = new Position();
|
||||
this.properties = new HashMap<>();
|
||||
@@ -179,7 +190,7 @@ public class Player {
|
||||
this.shopLimit = new ArrayList<>();
|
||||
this.expeditionInfo = new HashMap<>();
|
||||
this.messageHandler = null;
|
||||
this.mapMarksManager = new MapMarksManager();
|
||||
this.mapMarksManager = new MapMarksManager(this);
|
||||
this.staminaManager = new StaminaManager(this);
|
||||
this.sotsManager = new SotSManager(this);
|
||||
}
|
||||
@@ -207,7 +218,7 @@ public class Player {
|
||||
this.getPos().set(GameConstants.START_POSITION);
|
||||
this.getRotation().set(0, 307, 0);
|
||||
this.messageHandler = null;
|
||||
this.mapMarksManager = new MapMarksManager();
|
||||
this.mapMarksManager = new MapMarksManager(this);
|
||||
this.staminaManager = new StaminaManager(this);
|
||||
this.sotsManager = new SotSManager(this);
|
||||
}
|
||||
@@ -297,6 +308,39 @@ public class Player {
|
||||
this.updateProfile();
|
||||
}
|
||||
|
||||
public Integer getWidgetId() {
|
||||
return widgetId;
|
||||
}
|
||||
|
||||
public void setWidgetId(Integer widgetId) {
|
||||
this.widgetId = widgetId;
|
||||
}
|
||||
|
||||
public Set<Integer> getRealmList() {
|
||||
return realmList;
|
||||
}
|
||||
|
||||
public void setRealmList(Set<Integer> realmList) {
|
||||
this.realmList = realmList;
|
||||
}
|
||||
|
||||
public void addRealmList(int realmId) {
|
||||
if (this.realmList == null) {
|
||||
this.realmList = new HashSet<>();
|
||||
} else if (this.realmList.contains(realmId)) {
|
||||
return;
|
||||
}
|
||||
this.realmList.add(realmId);
|
||||
}
|
||||
|
||||
public Integer getCurrentRealmId() {
|
||||
return currentRealmId;
|
||||
}
|
||||
|
||||
public void setCurrentRealmId(Integer currentRealmId) {
|
||||
this.currentRealmId = currentRealmId;
|
||||
}
|
||||
|
||||
public Position getPos() {
|
||||
return pos;
|
||||
}
|
||||
@@ -411,6 +455,14 @@ public class Player {
|
||||
return towerManager;
|
||||
}
|
||||
|
||||
public QuestManager getQuestManager() {
|
||||
return questManager;
|
||||
}
|
||||
|
||||
public void setQuestManager(QuestManager questManager) {
|
||||
this.questManager = questManager;
|
||||
}
|
||||
|
||||
public PlayerGachaInfo getGachaInfo() {
|
||||
return gachaInfo;
|
||||
}
|
||||
@@ -885,9 +937,7 @@ public class Player {
|
||||
}
|
||||
|
||||
public void sendPacket(BasePacket packet) {
|
||||
if (this.hasSentAvatarDataNotify) {
|
||||
this.getSession().send(packet);
|
||||
}
|
||||
this.getSession().send(packet);
|
||||
}
|
||||
|
||||
public OnlinePlayerInfo getOnlinePlayerInfo() {
|
||||
@@ -1034,6 +1084,10 @@ public class Player {
|
||||
return abilityManager;
|
||||
}
|
||||
|
||||
public HashMap<String, MapMark> getMapMarks() { return mapMarks; }
|
||||
|
||||
public void setMapMarks(HashMap<String, MapMark> newMarks) { mapMarks = newMarks; }
|
||||
|
||||
public synchronized void onTick() {
|
||||
// Check ping
|
||||
if (this.getLastPingTime() > System.currentTimeMillis() + 60000) {
|
||||
@@ -1120,7 +1174,23 @@ public class Player {
|
||||
|
||||
this.getFriendsList().loadFromDatabase();
|
||||
this.getMailHandler().loadFromDatabase();
|
||||
this.getQuestManager().loadFromDatabase();
|
||||
|
||||
// Quest - Commented out because a problem is caused if you log out while this quest is active
|
||||
/*
|
||||
if (getQuestManager().getMainQuestById(351) == null) {
|
||||
GameQuest quest = getQuestManager().addQuest(35104);
|
||||
if (quest != null) {
|
||||
quest.finish();
|
||||
}
|
||||
|
||||
getQuestManager().addQuest(35101);
|
||||
|
||||
this.setSceneId(3);
|
||||
this.getPos().set(GameConstants.START_POSITION);
|
||||
}
|
||||
*/
|
||||
|
||||
// Create world
|
||||
World world = new World(this);
|
||||
world.addPlayer(this);
|
||||
@@ -1140,6 +1210,14 @@ public class Player {
|
||||
session.send(new PacketStoreWeightLimitNotify());
|
||||
session.send(new PacketPlayerStoreNotify(this));
|
||||
session.send(new PacketAvatarDataNotify(this));
|
||||
session.send(new PacketFinishedParentQuestNotify(this));
|
||||
session.send(new PacketQuestListNotify(this));
|
||||
session.send(new PacketCodexDataFullNotify(this));
|
||||
session.send(new PacketServerCondMeetQuestListUpdateNotify(this));
|
||||
session.send(new PacketAllWidgetDataNotify(this));
|
||||
session.send(new PacketWidgetGadgetAllDataNotify());
|
||||
session.send(new PacketPlayerHomeCompInfoNotify(this));
|
||||
session.send(new PacketHomeComfortInfoNotify(this));
|
||||
|
||||
getTodayMoonCard(); // The timer works at 0:0, some users log in after that, use this method to check if they have received a reward today or not. If not, send the reward.
|
||||
|
||||
@@ -1237,7 +1315,7 @@ public class Player {
|
||||
} else if (prop == PlayerProperty.PROP_IS_TRANSFERABLE) { // 10009
|
||||
if (!(0 <= value && value <= 1)) { return false; }
|
||||
} else if (prop == PlayerProperty.PROP_MAX_STAMINA) { // 10010
|
||||
if (!(value >= 0 && value <= StaminaManager.GlobalMaximumStamina)) { return false; }
|
||||
if (!(value >= 0 && value <= StaminaManager.GlobalCharacterMaximumStamina)) { return false; }
|
||||
} else if (prop == PlayerProperty.PROP_CUR_PERSIST_STAMINA) { // 10011
|
||||
int playerMaximumStamina = getProperty(PlayerProperty.PROP_MAX_STAMINA);
|
||||
if (!(value >= 0 && value <= playerMaximumStamina)) { return false; }
|
||||
|
||||
126
src/main/java/emu/grasscutter/game/quest/GameMainQuest.java
Normal file
126
src/main/java/emu/grasscutter/game/quest/GameMainQuest.java
Normal file
@@ -0,0 +1,126 @@
|
||||
package emu.grasscutter.game.quest;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import emu.grasscutter.server.packet.send.PacketCodexDataUpdateNotify;
|
||||
import org.bson.types.ObjectId;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
import dev.morphia.annotations.Id;
|
||||
import dev.morphia.annotations.Indexed;
|
||||
import dev.morphia.annotations.Transient;
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.database.DatabaseHelper;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.game.quest.enums.ParentQuestState;
|
||||
import emu.grasscutter.game.quest.enums.QuestState;
|
||||
import emu.grasscutter.net.proto.ChildQuestOuterClass.ChildQuest;
|
||||
import emu.grasscutter.net.proto.ParentQuestOuterClass.ParentQuest;
|
||||
import emu.grasscutter.net.proto.QuestOuterClass.Quest;
|
||||
import emu.grasscutter.server.packet.send.PacketFinishedParentQuestUpdateNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketQuestProgressUpdateNotify;
|
||||
import emu.grasscutter.utils.Utils;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
@Entity(value = "quests", useDiscriminator = false)
|
||||
public class GameMainQuest {
|
||||
@Id private ObjectId id;
|
||||
|
||||
@Indexed private int ownerUid;
|
||||
@Transient private Player owner;
|
||||
|
||||
private Map<Integer, GameQuest> childQuests;
|
||||
|
||||
private int parentQuestId;
|
||||
private int[] questVars;
|
||||
private ParentQuestState state;
|
||||
private boolean isFinished;
|
||||
|
||||
@Deprecated // Morphia only. Do not use.
|
||||
public GameMainQuest() {}
|
||||
|
||||
public GameMainQuest(Player player, int parentQuestId) {
|
||||
this.owner = player;
|
||||
this.ownerUid = player.getUid();
|
||||
this.parentQuestId = parentQuestId;
|
||||
this.childQuests = new HashMap<>();
|
||||
this.questVars = new int[5];
|
||||
this.state = ParentQuestState.PARENT_QUEST_STATE_NONE;
|
||||
}
|
||||
|
||||
public int getParentQuestId() {
|
||||
return parentQuestId;
|
||||
}
|
||||
|
||||
public int getOwnerUid() {
|
||||
return ownerUid;
|
||||
}
|
||||
|
||||
public Player getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public void setOwner(Player player) {
|
||||
if (player.getUid() != this.getOwnerUid()) return;
|
||||
this.owner = player;
|
||||
}
|
||||
|
||||
public Map<Integer, GameQuest> getChildQuests() {
|
||||
return childQuests;
|
||||
}
|
||||
|
||||
public GameQuest getChildQuestById(int id) {
|
||||
return this.getChildQuests().get(id);
|
||||
}
|
||||
|
||||
public int[] getQuestVars() {
|
||||
return questVars;
|
||||
}
|
||||
|
||||
public ParentQuestState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public boolean isFinished() {
|
||||
return isFinished;
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
this.isFinished = true;
|
||||
this.state = ParentQuestState.PARENT_QUEST_STATE_FINISHED;
|
||||
this.getOwner().getSession().send(new PacketFinishedParentQuestUpdateNotify(this));
|
||||
this.getOwner().getSession().send(new PacketCodexDataUpdateNotify(this));
|
||||
this.save();
|
||||
}
|
||||
|
||||
public void save() {
|
||||
DatabaseHelper.saveQuest(this);
|
||||
}
|
||||
|
||||
public ParentQuest toProto() {
|
||||
ParentQuest.Builder proto = ParentQuest.newBuilder()
|
||||
.setParentQuestId(getParentQuestId())
|
||||
.setIsFinished(isFinished())
|
||||
.setParentQuestState(getState().getValue());
|
||||
|
||||
for (GameQuest quest : this.getChildQuests().values()) {
|
||||
ChildQuest childQuest = ChildQuest.newBuilder()
|
||||
.setQuestId(quest.getQuestId())
|
||||
.setState(quest.getState().getValue())
|
||||
.build();
|
||||
|
||||
proto.addChildQuestList(childQuest);
|
||||
}
|
||||
|
||||
if (getQuestVars() != null) {
|
||||
for (int i : getQuestVars()) {
|
||||
proto.addQuestVar(i);
|
||||
}
|
||||
}
|
||||
|
||||
return proto.build();
|
||||
}
|
||||
}
|
||||
223
src/main/java/emu/grasscutter/game/quest/GameQuest.java
Normal file
223
src/main/java/emu/grasscutter/game/quest/GameQuest.java
Normal file
@@ -0,0 +1,223 @@
|
||||
package emu.grasscutter.game.quest;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
import dev.morphia.annotations.Transient;
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.custom.MainQuestData;
|
||||
import emu.grasscutter.data.custom.MainQuestData.SubQuestData;
|
||||
import emu.grasscutter.data.def.QuestData;
|
||||
import emu.grasscutter.data.def.QuestData.QuestCondition;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.game.quest.enums.LogicType;
|
||||
import emu.grasscutter.game.quest.enums.QuestState;
|
||||
import emu.grasscutter.net.proto.QuestOuterClass.Quest;
|
||||
import emu.grasscutter.server.packet.send.PacketCodexDataUpdateNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketQuestProgressUpdateNotify;
|
||||
import emu.grasscutter.utils.Utils;
|
||||
|
||||
@Entity
|
||||
public class GameQuest {
|
||||
@Transient private GameMainQuest mainQuest;
|
||||
@Transient private QuestData questData;
|
||||
|
||||
private int questId;
|
||||
private int mainQuestId;
|
||||
private QuestState state;
|
||||
|
||||
private int startTime;
|
||||
private int acceptTime;
|
||||
private int finishTime;
|
||||
|
||||
private int[] finishProgressList;
|
||||
private int[] failProgressList;
|
||||
|
||||
@Deprecated // Morphia only. Do not use.
|
||||
public GameQuest() {}
|
||||
|
||||
public GameQuest(GameMainQuest mainQuest, QuestData questData) {
|
||||
this.mainQuest = mainQuest;
|
||||
this.questId = questData.getId();
|
||||
this.mainQuestId = questData.getMainId();
|
||||
this.questData = questData;
|
||||
this.acceptTime = Utils.getCurrentSeconds();
|
||||
this.startTime = this.acceptTime;
|
||||
this.state = QuestState.QUEST_STATE_UNFINISHED;
|
||||
|
||||
if (questData.getFinishCond()!= null) {
|
||||
this.finishProgressList = new int[questData.getFinishCond().length];
|
||||
}
|
||||
|
||||
if (questData.getFailCond() != null) {
|
||||
this.failProgressList = new int[questData.getFailCond().length];
|
||||
}
|
||||
|
||||
this.mainQuest.getChildQuests().put(this.questId, this);
|
||||
}
|
||||
|
||||
public GameMainQuest getMainQuest() {
|
||||
return mainQuest;
|
||||
}
|
||||
|
||||
public void setMainQuest(GameMainQuest mainQuest) {
|
||||
this.mainQuest = mainQuest;
|
||||
}
|
||||
|
||||
public Player getOwner() {
|
||||
return getMainQuest().getOwner();
|
||||
}
|
||||
|
||||
public int getQuestId() {
|
||||
return questId;
|
||||
}
|
||||
|
||||
public int getMainQuestId() {
|
||||
return mainQuestId;
|
||||
}
|
||||
|
||||
public QuestData getData() {
|
||||
return questData;
|
||||
}
|
||||
|
||||
public void setConfig(QuestData config) {
|
||||
if (this.getQuestId() != config.getId()) return;
|
||||
this.questData = config;
|
||||
}
|
||||
|
||||
public QuestState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(QuestState state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public int getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
public void setStartTime(int startTime) {
|
||||
this.startTime = startTime;
|
||||
}
|
||||
|
||||
public int getAcceptTime() {
|
||||
return acceptTime;
|
||||
}
|
||||
|
||||
public void setAcceptTime(int acceptTime) {
|
||||
this.acceptTime = acceptTime;
|
||||
}
|
||||
|
||||
public int getFinishTime() {
|
||||
return finishTime;
|
||||
}
|
||||
|
||||
public void setFinishTime(int finishTime) {
|
||||
this.finishTime = finishTime;
|
||||
}
|
||||
|
||||
public int[] getFinishProgressList() {
|
||||
return finishProgressList;
|
||||
}
|
||||
|
||||
public void setFinishProgress(int index, int value) {
|
||||
finishProgressList[index] = value;
|
||||
}
|
||||
|
||||
public int[] getFailProgressList() {
|
||||
return failProgressList;
|
||||
}
|
||||
|
||||
public void setFailProgress(int index, int value) {
|
||||
failProgressList[index] = value;
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
this.state = QuestState.QUEST_STATE_FINISHED;
|
||||
this.finishTime = Utils.getCurrentSeconds();
|
||||
|
||||
if (this.getFinishProgressList() != null) {
|
||||
for (int i = 0 ; i < getFinishProgressList().length; i++) {
|
||||
getFinishProgressList()[i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
this.getOwner().getSession().send(new PacketQuestProgressUpdateNotify(this));
|
||||
this.getOwner().getSession().send(new PacketQuestListUpdateNotify(this));
|
||||
|
||||
if (this.getData().finishParent()) {
|
||||
// This quest finishes the questline - the main quest will also save the quest to db so we dont have to call save() here
|
||||
this.getMainQuest().finish();
|
||||
} else {
|
||||
// Try and accept other quests if possible
|
||||
this.tryAcceptQuestLine();
|
||||
this.save();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean tryAcceptQuestLine() {
|
||||
try {
|
||||
MainQuestData questConfig = GameData.getMainQuestDataMap().get(this.getMainQuestId());
|
||||
|
||||
for (SubQuestData subQuest : questConfig.getSubQuests()) {
|
||||
GameQuest quest = getMainQuest().getChildQuestById(subQuest.getSubId());
|
||||
|
||||
if (quest == null) {
|
||||
QuestData questData = GameData.getQuestDataMap().get(subQuest.getSubId());
|
||||
|
||||
if (questData == null || questData.getAcceptCond() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int[] accept = new int[questData.getAcceptCond().length];
|
||||
|
||||
// TODO
|
||||
for (int i = 0; i < questData.getAcceptCond().length; i++) {
|
||||
QuestCondition condition = questData.getAcceptCond()[i];
|
||||
boolean result = getOwner().getServer().getQuestHandler().triggerCondition(this, condition, condition.getParam());
|
||||
|
||||
accept[i] = result ? 1 : 0;
|
||||
}
|
||||
|
||||
boolean shouldAccept = LogicType.calculate(questData.getAcceptCondComb(), accept);
|
||||
|
||||
if (shouldAccept) {
|
||||
this.getOwner().getQuestManager().addQuest(questData.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void save() {
|
||||
getMainQuest().save();
|
||||
}
|
||||
|
||||
public Quest toProto() {
|
||||
Quest.Builder proto = Quest.newBuilder()
|
||||
.setQuestId(this.getQuestId())
|
||||
.setState(this.getState().getValue())
|
||||
.setParentQuestId(this.getMainQuestId())
|
||||
.setStartTime(this.getStartTime())
|
||||
.setStartGameTime(438)
|
||||
.setAcceptTime(this.getAcceptTime());
|
||||
|
||||
if (this.getFinishProgressList() != null) {
|
||||
for (int i : this.getFinishProgressList()) {
|
||||
proto.addFinishProgressList(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.getFailProgressList() != null) {
|
||||
for (int i : this.getFailProgressList()) {
|
||||
proto.addFailProgressList(i);
|
||||
}
|
||||
}
|
||||
|
||||
return proto.build();
|
||||
}
|
||||
}
|
||||
188
src/main/java/emu/grasscutter/game/quest/QuestManager.java
Normal file
188
src/main/java/emu/grasscutter/game/quest/QuestManager.java
Normal file
@@ -0,0 +1,188 @@
|
||||
package emu.grasscutter.game.quest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.def.QuestData;
|
||||
import emu.grasscutter.data.def.QuestData.QuestCondition;
|
||||
import emu.grasscutter.database.DatabaseHelper;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.game.quest.enums.QuestTrigger;
|
||||
import emu.grasscutter.game.quest.enums.LogicType;
|
||||
import emu.grasscutter.game.quest.enums.QuestState;
|
||||
import emu.grasscutter.server.packet.send.PacketFinishedParentQuestUpdateNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketQuestProgressUpdateNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketServerCondMeetQuestListUpdateNotify;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
public class QuestManager {
|
||||
private final Player player;
|
||||
private final Int2ObjectMap<GameMainQuest> quests;
|
||||
|
||||
public QuestManager(Player player) {
|
||||
this.player = player;
|
||||
this.quests = new Int2ObjectOpenHashMap<>();
|
||||
}
|
||||
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
public Int2ObjectMap<GameMainQuest> getQuests() {
|
||||
return quests;
|
||||
}
|
||||
|
||||
public GameMainQuest getMainQuestById(int mainQuestId) {
|
||||
return getQuests().get(mainQuestId);
|
||||
}
|
||||
|
||||
public GameQuest getQuestById(int questId) {
|
||||
QuestData questConfig = GameData.getQuestDataMap().get(questId);
|
||||
if (questConfig == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
GameMainQuest mainQuest = getQuests().get(questConfig.getMainId());
|
||||
|
||||
if (mainQuest == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return mainQuest.getChildQuests().get(questId);
|
||||
}
|
||||
|
||||
public void forEachQuest(Consumer<GameQuest> callback) {
|
||||
for (GameMainQuest mainQuest : getQuests().values()) {
|
||||
for (GameQuest quest : mainQuest.getChildQuests().values()) {
|
||||
callback.accept(quest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void forEachMainQuest(Consumer<GameMainQuest> callback) {
|
||||
for (GameMainQuest mainQuest : getQuests().values()) {
|
||||
callback.accept(mainQuest);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
public void forEachActiveQuest(Consumer<GameQuest> callback) {
|
||||
for (GameMainQuest mainQuest : getQuests().values()) {
|
||||
for (GameQuest quest : mainQuest.getChildQuests().values()) {
|
||||
if (quest.getState() != QuestState.QUEST_STATE_FINISHED) {
|
||||
callback.accept(quest);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public GameMainQuest addMainQuest(QuestData questConfig) {
|
||||
GameMainQuest mainQuest = new GameMainQuest(getPlayer(), questConfig.getMainId());
|
||||
getQuests().put(mainQuest.getParentQuestId(), mainQuest);
|
||||
|
||||
getPlayer().sendPacket(new PacketFinishedParentQuestUpdateNotify(mainQuest));
|
||||
|
||||
return mainQuest;
|
||||
}
|
||||
|
||||
public GameQuest addQuest(int questId) {
|
||||
QuestData questConfig = GameData.getQuestDataMap().get(questId);
|
||||
if (questConfig == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Main quest
|
||||
GameMainQuest mainQuest = this.getMainQuestById(questConfig.getMainId());
|
||||
|
||||
// Create main quest if it doesnt exist
|
||||
if (mainQuest == null) {
|
||||
mainQuest = addMainQuest(questConfig);
|
||||
}
|
||||
|
||||
// Sub quest
|
||||
GameQuest quest = mainQuest.getChildQuestById(questId);
|
||||
|
||||
if (quest != null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create
|
||||
quest = new GameQuest(mainQuest, questConfig);
|
||||
|
||||
// Save main quest
|
||||
mainQuest.save();
|
||||
|
||||
// Send packet
|
||||
getPlayer().sendPacket(new PacketServerCondMeetQuestListUpdateNotify(quest));
|
||||
getPlayer().sendPacket(new PacketQuestListUpdateNotify(quest));
|
||||
|
||||
return quest;
|
||||
}
|
||||
|
||||
public void triggerEvent(QuestTrigger condType, int... params) {
|
||||
Set<GameQuest> changedQuests = new HashSet<>();
|
||||
|
||||
this.forEachActiveQuest(quest -> {
|
||||
QuestData data = quest.getData();
|
||||
|
||||
for (int i = 0; i < data.getFinishCond().length; i++) {
|
||||
if (quest.getFinishProgressList() == null || quest.getFinishProgressList()[i] == 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QuestCondition condition = data.getFinishCond()[i];
|
||||
|
||||
if (condition.getType() != condType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean result = getPlayer().getServer().getQuestHandler().triggerContent(quest, condition, params);
|
||||
|
||||
if (result) {
|
||||
quest.getFinishProgressList()[i] = 1;
|
||||
|
||||
changedQuests.add(quest);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (GameQuest quest : changedQuests) {
|
||||
LogicType logicType = quest.getData().getFailCondComb();
|
||||
int[] progress = quest.getFinishProgressList();
|
||||
|
||||
// Handle logical comb
|
||||
boolean finish = LogicType.calculate(logicType, progress);
|
||||
|
||||
// Finish
|
||||
if (finish) {
|
||||
quest.finish();
|
||||
} else {
|
||||
getPlayer().sendPacket(new PacketQuestProgressUpdateNotify(quest));
|
||||
quest.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void loadFromDatabase() {
|
||||
List<GameMainQuest> quests = DatabaseHelper.getAllQuests(getPlayer());
|
||||
|
||||
for (GameMainQuest mainQuest : quests) {
|
||||
mainQuest.setOwner(this.getPlayer());
|
||||
|
||||
for (GameQuest quest : mainQuest.getChildQuests().values()) {
|
||||
quest.setMainQuest(mainQuest);
|
||||
quest.setConfig(GameData.getQuestDataMap().get(quest.getQuestId()));
|
||||
}
|
||||
|
||||
this.getQuests().put(mainQuest.getParentQuestId(), mainQuest);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/main/java/emu/grasscutter/game/quest/QuestValue.java
Normal file
11
src/main/java/emu/grasscutter/game/quest/QuestValue.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package emu.grasscutter.game.quest;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
import emu.grasscutter.game.quest.enums.QuestTrigger;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface QuestValue {
|
||||
QuestTrigger value();
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package emu.grasscutter.game.quest;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.reflections.Reflections;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.data.def.QuestData.QuestCondition;
|
||||
import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class ServerQuestHandler {
|
||||
private final Int2ObjectMap<QuestBaseHandler> condHandlers;
|
||||
private final Int2ObjectMap<QuestBaseHandler> contHandlers;
|
||||
private final Int2ObjectMap<QuestBaseHandler> execHandlers;
|
||||
|
||||
public ServerQuestHandler() {
|
||||
this.condHandlers = new Int2ObjectOpenHashMap<>();
|
||||
this.contHandlers = new Int2ObjectOpenHashMap<>();
|
||||
this.execHandlers = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
this.registerHandlers();
|
||||
}
|
||||
|
||||
public void registerHandlers() {
|
||||
this.registerHandlers(this.condHandlers, "emu.grasscutter.game.quest.conditions");
|
||||
this.registerHandlers(this.contHandlers, "emu.grasscutter.game.quest.content");
|
||||
this.registerHandlers(this.execHandlers, "emu.grasscutter.game.quest.exec");
|
||||
}
|
||||
|
||||
public void registerHandlers(Int2ObjectMap<QuestBaseHandler> map, String packageName) {
|
||||
Reflections reflections = new Reflections(packageName);
|
||||
Set<?> handlerClasses = reflections.getSubTypesOf(QuestBaseHandler.class);
|
||||
|
||||
for (Object obj : handlerClasses) {
|
||||
this.registerPacketHandler(map, (Class<? extends QuestBaseHandler>) obj);
|
||||
}
|
||||
}
|
||||
|
||||
public void registerPacketHandler(Int2ObjectMap<QuestBaseHandler> map, Class<? extends QuestBaseHandler> handlerClass) {
|
||||
try {
|
||||
QuestValue opcode = handlerClass.getAnnotation(QuestValue.class);
|
||||
|
||||
if (opcode == null || opcode.value().getValue() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
QuestBaseHandler packetHandler = (QuestBaseHandler) handlerClass.newInstance();
|
||||
|
||||
map.put(opcode.value().getValue(), packetHandler);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO make cleaner
|
||||
|
||||
public boolean triggerCondition(GameQuest quest, QuestCondition condition, int... params) {
|
||||
QuestBaseHandler handler = condHandlers.get(condition.getType().getValue());
|
||||
|
||||
if (handler == null || quest.getData() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return handler.execute(quest, condition, params);
|
||||
}
|
||||
|
||||
public boolean triggerContent(GameQuest quest, QuestCondition condition, int... params) {
|
||||
QuestBaseHandler handler = contHandlers.get(condition.getType().getValue());
|
||||
|
||||
if (handler == null || quest.getData() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return handler.execute(quest, condition, params);
|
||||
}
|
||||
|
||||
public boolean triggerExec(GameQuest quest, QuestCondition condition, int... params) {
|
||||
QuestBaseHandler handler = execHandlers.get(condition.getType().getValue());
|
||||
|
||||
if (handler == null || quest.getData() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return handler.execute(quest, condition, params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package emu.grasscutter.game.quest.conditions;
|
||||
|
||||
import emu.grasscutter.data.def.QuestData.QuestCondition;
|
||||
import emu.grasscutter.game.quest.QuestValue;
|
||||
import emu.grasscutter.game.quest.GameQuest;
|
||||
import emu.grasscutter.game.quest.enums.QuestTrigger;
|
||||
import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
|
||||
|
||||
@QuestValue(QuestTrigger.QUEST_CONTENT_NONE)
|
||||
public class BaseCondition extends QuestBaseHandler {
|
||||
|
||||
@Override
|
||||
public boolean execute(GameQuest quest, QuestCondition condition, int... params) {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package emu.grasscutter.game.quest.conditions;
|
||||
|
||||
import emu.grasscutter.data.def.QuestData.QuestCondition;
|
||||
import emu.grasscutter.game.quest.QuestValue;
|
||||
import emu.grasscutter.game.quest.GameQuest;
|
||||
import emu.grasscutter.game.quest.enums.QuestTrigger;
|
||||
import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
|
||||
|
||||
@QuestValue(QuestTrigger.QUEST_COND_PLAYER_LEVEL_EQUAL_GREATER)
|
||||
public class ConditionPlayerLevelEqualGreater extends QuestBaseHandler {
|
||||
|
||||
@Override
|
||||
public boolean execute(GameQuest quest, QuestCondition condition, int... params) {
|
||||
return quest.getOwner().getLevel() >= params[0];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package emu.grasscutter.game.quest.conditions;
|
||||
|
||||
import emu.grasscutter.data.def.QuestData.QuestCondition;
|
||||
import emu.grasscutter.game.quest.QuestValue;
|
||||
import emu.grasscutter.game.quest.GameQuest;
|
||||
import emu.grasscutter.game.quest.enums.QuestTrigger;
|
||||
import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
|
||||
|
||||
@QuestValue(QuestTrigger.QUEST_COND_STATE_EQUAL)
|
||||
public class ConditionStateEqual extends QuestBaseHandler {
|
||||
|
||||
@Override
|
||||
public boolean execute(GameQuest quest, QuestCondition condition, int... params) {
|
||||
GameQuest checkQuest = quest.getOwner().getQuestManager().getQuestById(params[0]);
|
||||
|
||||
if (checkQuest != null) {
|
||||
return checkQuest.getState().getValue() == params[1];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package emu.grasscutter.game.quest.content;
|
||||
|
||||
import emu.grasscutter.data.def.QuestData.QuestCondition;
|
||||
import emu.grasscutter.game.quest.QuestValue;
|
||||
import emu.grasscutter.game.quest.GameQuest;
|
||||
import emu.grasscutter.game.quest.enums.QuestTrigger;
|
||||
import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
|
||||
|
||||
@QuestValue(QuestTrigger.QUEST_CONTENT_NONE)
|
||||
public class BaseContent extends QuestBaseHandler {
|
||||
|
||||
@Override
|
||||
public boolean execute(GameQuest quest, QuestCondition condition, int... params) {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package emu.grasscutter.game.quest.content;
|
||||
|
||||
import emu.grasscutter.data.def.QuestData.QuestCondition;
|
||||
import emu.grasscutter.game.quest.QuestValue;
|
||||
import emu.grasscutter.game.quest.GameQuest;
|
||||
import emu.grasscutter.game.quest.enums.QuestTrigger;
|
||||
import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
|
||||
|
||||
@QuestValue(QuestTrigger.QUEST_CONTENT_COMPLETE_TALK)
|
||||
public class ContentCompleteTalk extends QuestBaseHandler {
|
||||
|
||||
@Override
|
||||
public boolean execute(GameQuest quest, QuestCondition condition, int... params) {
|
||||
return condition.getParam()[0] == params[0];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package emu.grasscutter.game.quest.content;
|
||||
|
||||
import emu.grasscutter.data.def.QuestData.QuestCondition;
|
||||
import emu.grasscutter.game.quest.QuestValue;
|
||||
import emu.grasscutter.game.quest.GameQuest;
|
||||
import emu.grasscutter.game.quest.enums.QuestTrigger;
|
||||
import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
|
||||
|
||||
@QuestValue(QuestTrigger.QUEST_CONTENT_ENTER_DUNGEON)
|
||||
public class ContentEnterDungeon extends QuestBaseHandler {
|
||||
|
||||
@Override
|
||||
public boolean execute(GameQuest quest, QuestCondition condition, int... params) {
|
||||
return condition.getParam()[0] == params[0];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package emu.grasscutter.game.quest.content;
|
||||
|
||||
import emu.grasscutter.data.def.QuestData.QuestCondition;
|
||||
import emu.grasscutter.game.quest.QuestValue;
|
||||
import emu.grasscutter.game.quest.GameQuest;
|
||||
import emu.grasscutter.game.quest.enums.QuestTrigger;
|
||||
import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
|
||||
|
||||
@QuestValue(QuestTrigger.QUEST_CONTENT_FINISH_PLOT)
|
||||
public class ContentFinishPlot extends QuestBaseHandler {
|
||||
|
||||
@Override
|
||||
public boolean execute(GameQuest quest, QuestCondition condition, int... params) {
|
||||
return condition.getParam()[0] == params[0];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package emu.grasscutter.game.quest.enums;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public enum LogicType {
|
||||
LOGIC_NONE (0),
|
||||
LOGIC_AND (1),
|
||||
LOGIC_OR (2),
|
||||
LOGIC_NOT (3),
|
||||
LOGIC_A_AND_ETCOR (4),
|
||||
LOGIC_A_AND_B_AND_ETCOR (5),
|
||||
LOGIC_A_OR_ETCAND (6),
|
||||
LOGIC_A_OR_B_OR_ETCAND (7),
|
||||
LOGIC_A_AND_B_OR_ETCAND (8);
|
||||
|
||||
private final int value;
|
||||
|
||||
LogicType(int id) {
|
||||
this.value = id;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static boolean calculate(LogicType logicType, int[] progress) {
|
||||
if (logicType == null) {
|
||||
return progress[0] == 1;
|
||||
}
|
||||
|
||||
switch (logicType) {
|
||||
case LOGIC_AND -> {
|
||||
return Arrays.stream(progress).allMatch(i -> i == 1);
|
||||
}
|
||||
case LOGIC_OR -> {
|
||||
return Arrays.stream(progress).anyMatch(i -> i == 1);
|
||||
}
|
||||
default -> {
|
||||
return Arrays.stream(progress).anyMatch(i -> i == 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package emu.grasscutter.game.quest.enums;
|
||||
|
||||
public enum ParentQuestState {
|
||||
PARENT_QUEST_STATE_NONE (0),
|
||||
PARENT_QUEST_STATE_FINISHED (1),
|
||||
PARENT_QUEST_STATE_FAILED (2),
|
||||
PARENT_QUEST_STATE_CANCELED (3);
|
||||
|
||||
private final int value;
|
||||
|
||||
ParentQuestState(int id) {
|
||||
this.value = id;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package emu.grasscutter.game.quest.enums;
|
||||
|
||||
public enum QuestGuideType {
|
||||
QUEST_GUIDE_NONE (0),
|
||||
QUEST_GUIDE_LOCATION (1),
|
||||
QUEST_GUIDE_NPC (2);
|
||||
|
||||
private final int value;
|
||||
|
||||
QuestGuideType(int id) {
|
||||
this.value = id;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package emu.grasscutter.game.quest.enums;
|
||||
|
||||
public enum QuestShowType {
|
||||
QUEST_SHOW (0),
|
||||
QUEST_HIDDEN (1);
|
||||
|
||||
private final int value;
|
||||
|
||||
QuestShowType(int id) {
|
||||
this.value = id;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package emu.grasscutter.game.quest.enums;
|
||||
|
||||
public enum QuestState {
|
||||
QUEST_STATE_NONE (0),
|
||||
QUEST_STATE_UNSTARTED (1),
|
||||
QUEST_STATE_UNFINISHED (2),
|
||||
QUEST_STATE_FINISHED (3),
|
||||
QUEST_STATE_FAILED (4);
|
||||
|
||||
private final int value;
|
||||
|
||||
QuestState(int id) {
|
||||
this.value = id;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
235
src/main/java/emu/grasscutter/game/quest/enums/QuestTrigger.java
Normal file
235
src/main/java/emu/grasscutter/game/quest/enums/QuestTrigger.java
Normal file
@@ -0,0 +1,235 @@
|
||||
package emu.grasscutter.game.quest.enums;
|
||||
|
||||
public enum QuestTrigger {
|
||||
QUEST_COND_NONE (0),
|
||||
QUEST_COND_STATE_EQUAL (1),
|
||||
QUEST_COND_STATE_NOT_EQUAL (2),
|
||||
QUEST_COND_PACK_HAVE_ITEM (3),
|
||||
QUEST_COND_AVATAR_ELEMENT_EQUAL (4),
|
||||
QUEST_COND_AVATAR_ELEMENT_NOT_EQUAL (5),
|
||||
QUEST_COND_AVATAR_CAN_CHANGE_ELEMENT (6),
|
||||
QUEST_COND_CITY_LEVEL_EQUAL_GREATER (7),
|
||||
QUEST_COND_ITEM_NUM_LESS_THAN (8),
|
||||
QUEST_COND_DAILY_TASK_START (9),
|
||||
QUEST_COND_OPEN_STATE_EQUAL (10),
|
||||
QUEST_COND_DAILY_TASK_OPEN (11),
|
||||
QUEST_COND_DAILY_TASK_REWARD_CAN_GET (12),
|
||||
QUEST_COND_DAILY_TASK_REWARD_RECEIVED (13),
|
||||
QUEST_COND_PLAYER_LEVEL_REWARD_CAN_GET (14),
|
||||
QUEST_COND_EXPLORATION_REWARD_CAN_GET (15),
|
||||
QUEST_COND_IS_WORLD_OWNER (16),
|
||||
QUEST_COND_PLAYER_LEVEL_EQUAL_GREATER (17),
|
||||
QUEST_COND_SCENE_AREA_UNLOCKED (18),
|
||||
QUEST_COND_ITEM_GIVING_ACTIVED (19),
|
||||
QUEST_COND_ITEM_GIVING_FINISHED (20),
|
||||
QUEST_COND_IS_DAYTIME (21),
|
||||
QUEST_COND_CURRENT_AVATAR (22),
|
||||
QUEST_COND_CURRENT_AREA (23),
|
||||
QUEST_COND_QUEST_VAR_EQUAL (24),
|
||||
QUEST_COND_QUEST_VAR_GREATER (25),
|
||||
QUEST_COND_QUEST_VAR_LESS (26),
|
||||
QUEST_COND_FORGE_HAVE_FINISH (27),
|
||||
QUEST_COND_DAILY_TASK_IN_PROGRESS (28),
|
||||
QUEST_COND_DAILY_TASK_FINISHED (29),
|
||||
QUEST_COND_ACTIVITY_COND (30),
|
||||
QUEST_COND_ACTIVITY_OPEN (31),
|
||||
QUEST_COND_DAILY_TASK_VAR_GT (32),
|
||||
QUEST_COND_DAILY_TASK_VAR_EQ (33),
|
||||
QUEST_COND_DAILY_TASK_VAR_LT (34),
|
||||
QUEST_COND_BARGAIN_ITEM_GT (35),
|
||||
QUEST_COND_BARGAIN_ITEM_EQ (36),
|
||||
QUEST_COND_BARGAIN_ITEM_LT (37),
|
||||
QUEST_COND_COMPLETE_TALK (38),
|
||||
QUEST_COND_NOT_HAVE_BLOSSOM_TALK (39),
|
||||
QUEST_COND_IS_CUR_BLOSSOM_TALK (40),
|
||||
QUEST_COND_QUEST_NOT_RECEIVE (41),
|
||||
QUEST_COND_QUEST_SERVER_COND_VALID (42),
|
||||
QUEST_COND_ACTIVITY_CLIENT_COND (43),
|
||||
QUEST_COND_QUEST_GLOBAL_VAR_EQUAL (44),
|
||||
QUEST_COND_QUEST_GLOBAL_VAR_GREATER (45),
|
||||
QUEST_COND_QUEST_GLOBAL_VAR_LESS (46),
|
||||
QUEST_COND_PERSONAL_LINE_UNLOCK (47),
|
||||
QUEST_COND_CITY_REPUTATION_REQUEST (48),
|
||||
QUEST_COND_MAIN_COOP_START (49),
|
||||
QUEST_COND_MAIN_COOP_ENTER_SAVE_POINT (50),
|
||||
QUEST_COND_CITY_REPUTATION_LEVEL (51),
|
||||
QUEST_COND_CITY_REPUTATION_UNLOCK (52),
|
||||
QUEST_COND_LUA_NOTIFY (53),
|
||||
QUEST_COND_CUR_CLIMATE (54),
|
||||
QUEST_COND_ACTIVITY_END (55),
|
||||
QUEST_COND_COOP_POINT_RUNNING (56),
|
||||
QUEST_COND_GADGET_TALK_STATE_EQUAL (57),
|
||||
QUEST_COND_AVATAR_FETTER_GT (58),
|
||||
QUEST_COND_AVATAR_FETTER_EQ (59),
|
||||
QUEST_COND_AVATAR_FETTER_LT (60),
|
||||
QUEST_COND_NEW_HOMEWORLD_MOUDLE_UNLOCK (61),
|
||||
QUEST_COND_NEW_HOMEWORLD_LEVEL_REWARD (62),
|
||||
QUEST_COND_NEW_HOMEWORLD_MAKE_FINISH (63),
|
||||
QUEST_COND_HOMEWORLD_NPC_EVENT (64),
|
||||
QUEST_COND_TIME_VAR_GT_EQ (65),
|
||||
QUEST_COND_TIME_VAR_PASS_DAY (66),
|
||||
QUEST_COND_HOMEWORLD_NPC_NEW_TALK (67),
|
||||
QUEST_COND_PLAYER_CHOOSE_MALE (68),
|
||||
QUEST_COND_HISTORY_GOT_ANY_ITEM (69),
|
||||
QUEST_COND_LEARNED_RECIPE (70),
|
||||
QUEST_COND_LUNARITE_REGION_UNLOCKED (71),
|
||||
QUEST_COND_LUNARITE_HAS_REGION_HINT_COUNT (72),
|
||||
QUEST_COND_LUNARITE_COLLECT_FINISH (73),
|
||||
QUEST_COND_LUNARITE_MARK_ALL_FINISH (74),
|
||||
QUEST_COND_NEW_HOMEWORLD_SHOP_ITEM (75),
|
||||
QUEST_COND_SCENE_POINT_UNLOCK (76),
|
||||
QUEST_COND_SCENE_LEVEL_TAG_EQ (77),
|
||||
|
||||
QUEST_CONTENT_NONE (0),
|
||||
QUEST_CONTENT_KILL_MONSTER (1),
|
||||
QUEST_CONTENT_COMPLETE_TALK (2),
|
||||
QUEST_CONTENT_MONSTER_DIE (3),
|
||||
QUEST_CONTENT_FINISH_PLOT (4),
|
||||
QUEST_CONTENT_OBTAIN_ITEM (5),
|
||||
QUEST_CONTENT_TRIGGER_FIRE (6),
|
||||
QUEST_CONTENT_CLEAR_GROUP_MONSTER (7),
|
||||
QUEST_CONTENT_NOT_FINISH_PLOT (8),
|
||||
QUEST_CONTENT_ENTER_DUNGEON (9),
|
||||
QUEST_CONTENT_ENTER_MY_WORLD (10),
|
||||
QUEST_CONTENT_FINISH_DUNGEON (11),
|
||||
QUEST_CONTENT_DESTROY_GADGET (12),
|
||||
QUEST_CONTENT_OBTAIN_MATERIAL_WITH_SUBTYPE (13),
|
||||
QUEST_CONTENT_NICK_NAME (14),
|
||||
QUEST_CONTENT_WORKTOP_SELECT (15),
|
||||
QUEST_CONTENT_SEAL_BATTLE_RESULT (16),
|
||||
QUEST_CONTENT_ENTER_ROOM (17),
|
||||
QUEST_CONTENT_GAME_TIME_TICK (18),
|
||||
QUEST_CONTENT_FAIL_DUNGEON (19),
|
||||
QUEST_CONTENT_LUA_NOTIFY (20),
|
||||
QUEST_CONTENT_TEAM_DEAD (21),
|
||||
QUEST_CONTENT_COMPLETE_ANY_TALK (22),
|
||||
QUEST_CONTENT_UNLOCK_TRANS_POINT (23),
|
||||
QUEST_CONTENT_ADD_QUEST_PROGRESS (24),
|
||||
QUEST_CONTENT_INTERACT_GADGET (25),
|
||||
QUEST_CONTENT_DAILY_TASK_COMP_FINISH (26),
|
||||
QUEST_CONTENT_FINISH_ITEM_GIVING (27),
|
||||
QUEST_CONTENT_SKILL (107),
|
||||
QUEST_CONTENT_CITY_LEVEL_UP (109),
|
||||
QUEST_CONTENT_PATTERN_GROUP_CLEAR_MONSTER (110),
|
||||
QUEST_CONTENT_ITEM_LESS_THAN (111),
|
||||
QUEST_CONTENT_PLAYER_LEVEL_UP (112),
|
||||
QUEST_CONTENT_DUNGEON_OPEN_STATUE (113),
|
||||
QUEST_CONTENT_UNLOCK_AREA (114),
|
||||
QUEST_CONTENT_OPEN_CHEST_WITH_GADGET_ID (115),
|
||||
QUEST_CONTENT_UNLOCK_TRANS_POINT_WITH_TYPE (116),
|
||||
QUEST_CONTENT_FINISH_DAILY_DUNGEON (117),
|
||||
QUEST_CONTENT_FINISH_WEEKLY_DUNGEON (118),
|
||||
QUEST_CONTENT_QUEST_VAR_EQUAL (119),
|
||||
QUEST_CONTENT_QUEST_VAR_GREATER (120),
|
||||
QUEST_CONTENT_QUEST_VAR_LESS (121),
|
||||
QUEST_CONTENT_OBTAIN_VARIOUS_ITEM (122),
|
||||
QUEST_CONTENT_FINISH_TOWER_LEVEL (123),
|
||||
QUEST_CONTENT_BARGAIN_SUCC (124),
|
||||
QUEST_CONTENT_BARGAIN_FAIL (125),
|
||||
QUEST_CONTENT_ITEM_LESS_THAN_BARGAIN (126),
|
||||
QUEST_CONTENT_ACTIVITY_TRIGGER_FAILED (127),
|
||||
QUEST_CONTENT_MAIN_COOP_ENTER_SAVE_POINT (128),
|
||||
QUEST_CONTENT_ANY_MANUAL_TRANSPORT (129),
|
||||
QUEST_CONTENT_USE_ITEM (130),
|
||||
QUEST_CONTENT_MAIN_COOP_ENTER_ANY_SAVE_POINT (131),
|
||||
QUEST_CONTENT_ENTER_MY_HOME_WORLD (132),
|
||||
QUEST_CONTENT_ENTER_MY_WORLD_SCENE (133),
|
||||
QUEST_CONTENT_TIME_VAR_GT_EQ (134),
|
||||
QUEST_CONTENT_TIME_VAR_PASS_DAY (135),
|
||||
QUEST_CONTENT_QUEST_STATE_EQUAL (136),
|
||||
QUEST_CONTENT_QUEST_STATE_NOT_EQUAL (137),
|
||||
QUEST_CONTENT_UNLOCKED_RECIPE (138),
|
||||
QUEST_CONTENT_NOT_UNLOCKED_RECIPE (139),
|
||||
QUEST_CONTENT_FISHING_SUCC (140),
|
||||
QUEST_CONTENT_ENTER_ROGUE_DUNGEON (141),
|
||||
QUEST_CONTENT_USE_WIDGET (142),
|
||||
QUEST_CONTENT_CAPTURE_SUCC (143),
|
||||
QUEST_CONTENT_CAPTURE_USE_CAPTURETAG_LIST (144),
|
||||
QUEST_CONTENT_CAPTURE_USE_MATERIAL_LIST (145),
|
||||
QUEST_CONTENT_ENTER_VEHICLE (147),
|
||||
QUEST_CONTENT_SCENE_LEVEL_TAG_EQ (148),
|
||||
QUEST_CONTENT_LEAVE_SCENE (149),
|
||||
QUEST_CONTENT_LEAVE_SCENE_RANGE (150),
|
||||
QUEST_CONTENT_IRODORI_FINISH_FLOWER_COMBINATION (151),
|
||||
QUEST_CONTENT_IRODORI_POETRY_REACH_MIN_PROGRESS (152),
|
||||
QUEST_CONTENT_IRODORI_POETRY_FINISH_FILL_POETRY (153),
|
||||
|
||||
QUEST_EXEC_NONE (0),
|
||||
QUEST_EXEC_DEL_PACK_ITEM (1),
|
||||
QUEST_EXEC_UNLOCK_POINT (2),
|
||||
QUEST_EXEC_UNLOCK_AREA (3),
|
||||
QUEST_EXEC_UNLOCK_FORCE (4),
|
||||
QUEST_EXEC_LOCK_FORCE (5),
|
||||
QUEST_EXEC_CHANGE_AVATAR_ELEMET (6),
|
||||
QUEST_EXEC_REFRESH_GROUP_MONSTER (7),
|
||||
QUEST_EXEC_SET_IS_FLYABLE (8),
|
||||
QUEST_EXEC_SET_IS_WEATHER_LOCKED (9),
|
||||
QUEST_EXEC_SET_IS_GAME_TIME_LOCKED (10),
|
||||
QUEST_EXEC_SET_IS_TRANSFERABLE (11),
|
||||
QUEST_EXEC_GRANT_TRIAL_AVATAR (12),
|
||||
QUEST_EXEC_OPEN_BORED (13),
|
||||
QUEST_EXEC_ROLLBACK_QUEST (14),
|
||||
QUEST_EXEC_NOTIFY_GROUP_LUA (15),
|
||||
QUEST_EXEC_SET_OPEN_STATE (16),
|
||||
QUEST_EXEC_LOCK_POINT (17),
|
||||
QUEST_EXEC_DEL_PACK_ITEM_BATCH (18),
|
||||
QUEST_EXEC_REFRESH_GROUP_SUITE (19),
|
||||
QUEST_EXEC_REMOVE_TRIAL_AVATAR (20),
|
||||
QUEST_EXEC_SET_GAME_TIME (21),
|
||||
QUEST_EXEC_SET_WEATHER_GADGET (22),
|
||||
QUEST_EXEC_ADD_QUEST_PROGRESS (23),
|
||||
QUEST_EXEC_NOTIFY_DAILY_TASK (24),
|
||||
QUEST_EXEC_CREATE_PATTERN_GROUP (25),
|
||||
QUEST_EXEC_REMOVE_PATTERN_GROUP (26),
|
||||
QUEST_EXEC_REFRESH_GROUP_SUITE_RANDOM (27),
|
||||
QUEST_EXEC_ACTIVE_ITEM_GIVING (28),
|
||||
QUEST_EXEC_DEL_ALL_SPECIFIC_PACK_ITEM (29),
|
||||
QUEST_EXEC_ROLLBACK_PARENT_QUEST (30),
|
||||
QUEST_EXEC_LOCK_AVATAR_TEAM (31),
|
||||
QUEST_EXEC_UNLOCK_AVATAR_TEAM (32),
|
||||
QUEST_EXEC_UPDATE_PARENT_QUEST_REWARD_INDEX (33),
|
||||
QUEST_EXEC_SET_DAILY_TASK_VAR (34),
|
||||
QUEST_EXEC_INC_DAILY_TASK_VAR (35),
|
||||
QUEST_EXEC_DEC_DAILY_TASK_VAR (36),
|
||||
QUEST_EXEC_ACTIVE_ACTIVITY_COND_STATE (37),
|
||||
QUEST_EXEC_INACTIVE_ACTIVITY_COND_STATE (38),
|
||||
QUEST_EXEC_ADD_CUR_AVATAR_ENERGY (39),
|
||||
QUEST_EXEC_START_BARGAIN (41),
|
||||
QUEST_EXEC_STOP_BARGAIN (42),
|
||||
QUEST_EXEC_SET_QUEST_GLOBAL_VAR (43),
|
||||
QUEST_EXEC_INC_QUEST_GLOBAL_VAR (44),
|
||||
QUEST_EXEC_DEC_QUEST_GLOBAL_VAR (45),
|
||||
QUEST_EXEC_REGISTER_DYNAMIC_GROUP (46),
|
||||
QUEST_EXEC_UNREGISTER_DYNAMIC_GROUP (47),
|
||||
QUEST_EXEC_SET_QUEST_VAR (48),
|
||||
QUEST_EXEC_INC_QUEST_VAR (49),
|
||||
QUEST_EXEC_DEC_QUEST_VAR (50),
|
||||
QUEST_EXEC_RANDOM_QUEST_VAR (51),
|
||||
QUEST_EXEC_ACTIVATE_SCANNING_PIC (52),
|
||||
QUEST_EXEC_RELOAD_SCENE_TAG (53),
|
||||
QUEST_EXEC_REGISTER_DYNAMIC_GROUP_ONLY (54),
|
||||
QUEST_EXEC_CHANGE_SKILL_DEPOT (55),
|
||||
QUEST_EXEC_ADD_SCENE_TAG (56),
|
||||
QUEST_EXEC_DEL_SCENE_TAG (57),
|
||||
QUEST_EXEC_INIT_TIME_VAR (58),
|
||||
QUEST_EXEC_CLEAR_TIME_VAR (59),
|
||||
QUEST_EXEC_MODIFY_CLIMATE_AREA (60),
|
||||
QUEST_EXEC_GRANT_TRIAL_AVATAR_AND_LOCK_TEAM (61),
|
||||
QUEST_EXEC_CHANGE_MAP_AREA_STATE (62),
|
||||
QUEST_EXEC_DEACTIVE_ITEM_GIVING (63),
|
||||
QUEST_EXEC_CHANGE_SCENE_LEVEL_TAG (64),
|
||||
QUEST_EXEC_UNLOCK_PLAYER_WORLD_SCENE (65),
|
||||
QUEST_EXEC_LOCK_PLAYER_WORLD_SCENE (66),
|
||||
QUEST_EXEC_FAIL_MAINCOOP (67),
|
||||
QUEST_EXEC_MODIFY_WEATHER_AREA (68);
|
||||
|
||||
private final int value;
|
||||
|
||||
QuestTrigger(int id) {
|
||||
this.value = id;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package emu.grasscutter.game.quest.enums;
|
||||
|
||||
public enum QuestType {
|
||||
AQ (0),
|
||||
FQ (1),
|
||||
LQ (2),
|
||||
EQ (3),
|
||||
DQ (4),
|
||||
IQ (5),
|
||||
VQ (6),
|
||||
WQ (7);
|
||||
|
||||
private final int value;
|
||||
|
||||
QuestType(int id) {
|
||||
this.value = id;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package emu.grasscutter.game.quest.enums;
|
||||
|
||||
public enum ShowQuestGuideType {
|
||||
QUEST_GUIDE_ITEM_ENABLE (0),
|
||||
QUEST_GUIDE_ITEM_DISABLE (1),
|
||||
QUEST_GUIDE_ITEM_MOVE_HIDE (2);
|
||||
|
||||
private final int value;
|
||||
|
||||
ShowQuestGuideType(int id) {
|
||||
this.value = id;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package emu.grasscutter.game.quest.handlers;
|
||||
|
||||
import emu.grasscutter.data.def.QuestData.QuestCondition;
|
||||
import emu.grasscutter.game.quest.GameQuest;
|
||||
|
||||
public abstract class QuestBaseHandler {
|
||||
|
||||
public abstract boolean execute(GameQuest quest, QuestCondition condition, int... params);
|
||||
|
||||
}
|
||||
@@ -39,7 +39,8 @@ public class TowerScheduleManager {
|
||||
public TowerScheduleData getCurrentTowerScheduleData(){
|
||||
var data = GameData.getTowerScheduleDataMap().get(towerScheduleConfig.getScheduleId());
|
||||
if(data == null){
|
||||
Grasscutter.getLogger().error("Could not get current tower schedule data by config:{}", towerScheduleConfig);
|
||||
Grasscutter.getLogger().error("Could not get current tower schedule data by schedule id {}, please check your resource files",
|
||||
towerScheduleConfig.getScheduleId());
|
||||
}
|
||||
|
||||
return data;
|
||||
|
||||
@@ -10,6 +10,7 @@ import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.game.player.Player.SceneLoadState;
|
||||
import emu.grasscutter.game.props.EnterReason;
|
||||
import emu.grasscutter.game.props.EntityIdType;
|
||||
import emu.grasscutter.game.props.SceneType;
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.def.DungeonData;
|
||||
import emu.grasscutter.data.def.SceneData;
|
||||
@@ -267,6 +268,9 @@ public class World implements Iterable<Player> {
|
||||
enterReason = EnterReason.DungeonEnter;
|
||||
} else if (oldScene == newScene) {
|
||||
enterType = EnterType.ENTER_GOTO;
|
||||
} else if (newScene.getSceneType() == SceneType.SCENE_HOME_WORLD) {
|
||||
// Home
|
||||
enterType = EnterType.ENTER_SELF_HOME;
|
||||
}
|
||||
|
||||
// Teleport packet
|
||||
|
||||
Reference in New Issue
Block a user