diff --git a/src/main/java/emu/grasscutter/GameConstants.java b/src/main/java/emu/grasscutter/GameConstants.java index 8923b338068..0411fae7c2f 100644 --- a/src/main/java/emu/grasscutter/GameConstants.java +++ b/src/main/java/emu/grasscutter/GameConstants.java @@ -3,8 +3,7 @@ import emu.grasscutter.game.world.Position; import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.objects.SparseSet; - -import java.util.Arrays; +import java.util.*; public final class GameConstants { public static String VERSION = "4.0.0"; @@ -60,4 +59,23 @@ public final class GameConstants { public static final int[] DEFAULT_ABILITY_HASHES = Arrays.stream(DEFAULT_ABILITY_STRINGS).mapToInt(Utils::abilityHash).toArray(); public static final int DEFAULT_ABILITY_NAME = Utils.abilityHash("Default"); + public static final Map YAE_MIKO_ITEM_TO_REGION_COMBINE_BONUS = new HashMap<>() {{ + put(104304, 1); + put(104307, 1); + put(104310, 2); + put(104313, 2); + put(104316, 2); + put(104320, 3); + put(104323, 3); + put(104326, 3); + put(104329, 4); + put(104332, 4); + put(104335, 4); + }}; + public static final Map> YAE_MIKO_REGION_TO_ITEM_COMBINE_BONUS = new HashMap<>() {{ + put(1, List.of(104304, 104307)); + put(2, List.of(104310, 104313, 104316)); + put(3, List.of(104320, 104323, 104326)); + put(4, List.of(104329, 104332, 104335)); + }}; } diff --git a/src/main/java/emu/grasscutter/game/combine/CombineBonusData.java b/src/main/java/emu/grasscutter/game/combine/CombineBonusData.java new file mode 100644 index 00000000000..65f32078c37 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/combine/CombineBonusData.java @@ -0,0 +1,19 @@ +package emu.grasscutter.game.combine; + +import java.util.List; +import lombok.Getter; +import lombok.Setter; + +@Getter @Setter +public class CombineBonusData { + private int avatarId; + private int combineType; + private BonusType bonusType; + private List paramVec; + + public enum BonusType { + COMBINE_BONUS_DOUBLE, + COMBINE_BONUS_REFUND, + COMBINE_BONUS_REFUND_RANDOM, + } +} diff --git a/src/main/java/emu/grasscutter/game/combine/CombineManger.java b/src/main/java/emu/grasscutter/game/combine/CombineManger.java index 08305cae10d..cd2ef150710 100644 --- a/src/main/java/emu/grasscutter/game/combine/CombineManger.java +++ b/src/main/java/emu/grasscutter/game/combine/CombineManger.java @@ -1,9 +1,11 @@ package emu.grasscutter.game.combine; +import emu.grasscutter.GameConstants; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.*; import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.excels.CombineData; +import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.ActionReason; @@ -12,15 +14,26 @@ import emu.grasscutter.server.game.*; import emu.grasscutter.server.packet.send.*; import emu.grasscutter.utils.Utils; +import io.netty.util.internal.ThreadLocalRandom; import it.unimi.dsi.fastutil.ints.*; import java.util.*; public class CombineManger extends BaseGameSystem { private static final Int2ObjectMap> reliquaryDecomposeData = new Int2ObjectOpenHashMap<>(); + private final Int2ObjectMap combineBonusData = new Int2ObjectOpenHashMap<>(); public CombineManger(GameServer server) { super(server); + + // load combine bonus data + try { + DataLoader.loadList("CombineBonus.json", CombineBonusData.class) + .forEach(entry -> combineBonusData.put(entry.getAvatarId(), entry)); + } catch (Exception ignored) { + Grasscutter.getLogger() + .error("Unable to load combine bonus data. Please place CombineBonus.json in the data folder."); + } } public static void initialize() { @@ -47,7 +60,7 @@ public boolean unlockCombineDiagram(Player player, int combineId) { return true; } - public CombineResult combineItem(Player player, int cid, int count) { + public CombineResult combineItem(Player player, int cid, int count, long avatarGuid) { // check config exist if (!GameData.getCombineDataMap().containsKey(cid)) { player.getWorld().getHost().sendPacket(new PacketCombineRsp()); @@ -81,11 +94,104 @@ public CombineResult combineItem(Player player, int cid, int count) { result.setMaterial(List.of()); result.setResult( List.of( - new ItemParamData( - combineData.getResultItemId(), combineData.getResultItemCount() * count))); - // TODO lucky characters - result.setExtra(List.of()); - result.setBack(List.of()); + new ItemParamData(combineData.getResultItemId(), combineData.getResultItemCount()))); + // lucky characters + int luckyCount = 0; + Avatar avatar = player.getAvatars().getAvatarByGuid(avatarGuid); + CombineBonusData combineBonusAvatar = combineBonusData.get(avatar.getAvatarId()); + if (combineBonusAvatar != null + && combineData.getCombineType() == combineBonusAvatar.getCombineType()) { + double luckyChange = combineBonusAvatar.getParamVec().get(0); + for (int i = 0; i < count; i++) { + if (ThreadLocalRandom.current().nextDouble() <= luckyChange) { + luckyCount++; + } + } + } + + result.setExtra(new ArrayList()); + result.setBack(new ArrayList()); + result.setRandom(new ArrayList()); + + // add lucky items + if (luckyCount > 0) { + switch (combineBonusAvatar.getBonusType()) { + case COMBINE_BONUS_DOUBLE -> { + var combineExtra = new ItemParamData(combineData.getResultItemId(), luckyCount); + player.getInventory().addItem(combineExtra); + result.getExtra().add(combineExtra); + } + case COMBINE_BONUS_REFUND -> { + if (combineData.getMaterialItems().size() == 1) { + var combineBack = new ItemParamData(combineData.getMaterialItems().get(0).getItemId(), + luckyCount); + player.getInventory().addItem(combineBack); + result.getBack().add(combineBack); + } else { + Map mapIdCount = new HashMap<>(); + for (int i = 0; i < luckyCount; i++) { + var randomId = combineData + .getMaterialItems() + .get( + ThreadLocalRandom.current() + .nextInt(combineData.getMaterialItems().size())) + .getItemId(); + mapIdCount.put(randomId, mapIdCount.getOrDefault(randomId, 0) + 1); + } + + for (var entry : mapIdCount.entrySet()) { + var combineBack = new ItemParamData(entry.getKey(), entry.getValue()); + player.getInventory().addItem(combineBack); + result.getBack().add(combineBack); + } + } + } + case COMBINE_BONUS_REFUND_RANDOM -> { + // for yae miko, "Has a 25% chance to get 1 regional Character Talent Material + // (base + // material excluded) when crafting. The rarity is that of the base material." + // from wiki + // map of material id to region id + Map itemToRegion = GameConstants.YAE_MIKO_ITEM_TO_REGION_COMBINE_BONUS; + + // get list of material id with every region + Map> regionToId = GameConstants.YAE_MIKO_REGION_TO_ITEM_COMBINE_BONUS; + + // check material id in itemToRegion + int itemId = combineData.getMaterialItems().get(0).getItemId(); + int rank = 0; // rank of material + if (itemToRegion.get(itemId) != null) + rank = 1; + if (itemToRegion.get(itemId - 1) != null) { + rank = 2; + itemId -= 1; + } + + if (rank >= 1) { // if material is regional + // get list of material id with same region + List listIdRandom = regionToId.get(itemToRegion.get(itemId)); + // remove material id from array + listIdRandom.remove(Integer.valueOf(itemId)); + + HashMap mapIdCount = new HashMap<>(); + // pick random material from list with luckyCount + for (int i = 0; i < luckyCount; i++) { + int randomId = listIdRandom.get(ThreadLocalRandom.current().nextInt(listIdRandom.size())); + mapIdCount.put(randomId, mapIdCount.getOrDefault(randomId, 0) + 1); + } + + // add to random list + for (var entry : mapIdCount.entrySet()) { + // if rank 2, add 1 to material id + var combineRandom = new ItemParamData( + (rank == 2) ? entry.getKey() + 1 : entry.getKey(), entry.getValue()); + player.getInventory().addItem(combineRandom); + result.getRandom().add(combineRandom); + } + } + } + } + } return result; } diff --git a/src/main/java/emu/grasscutter/game/combine/CombineResult.java b/src/main/java/emu/grasscutter/game/combine/CombineResult.java index df8ace74b3b..7aeb44b0ba8 100644 --- a/src/main/java/emu/grasscutter/game/combine/CombineResult.java +++ b/src/main/java/emu/grasscutter/game/combine/CombineResult.java @@ -2,42 +2,14 @@ import emu.grasscutter.data.common.ItemParamData; import java.util.List; +import lombok.Getter; +import lombok.Setter; +@Setter @Getter public class CombineResult { private List material; private List result; private List extra; private List back; - - public List getMaterial() { - return material; - } - - public void setMaterial(List material) { - this.material = material; - } - - public List getResult() { - return result; - } - - public void setResult(List result) { - this.result = result; - } - - public List getExtra() { - return extra; - } - - public void setExtra(List extra) { - this.extra = extra; - } - - public List getBack() { - return back; - } - - public void setBack(List back) { - this.back = back; - } + private List random; } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombineReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombineReq.java index 05d8bd967b6..2f6ffa5be1f 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombineReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombineReq.java @@ -20,7 +20,11 @@ public void handle(GameSession session, byte[] header, byte[] payload) throws Ex session .getServer() .getCombineSystem() - .combineItem(session.getPlayer(), req.getCombineId(), req.getCombineCount()); + .combineItem( + session.getPlayer(), + req.getCombineId(), + req.getCombineCount(), + req.getAvatarGuid()); if (result == null) { return; @@ -33,7 +37,7 @@ public void handle(GameSession session, byte[] header, byte[] payload) throws Ex toItemParamList(result.getResult()), toItemParamList(result.getExtra()), toItemParamList(result.getBack()), - toItemParamList(result.getBack()))); + toItemParamList(result.getRandom()))); } private List toItemParamList(List list) {