GachaBanner.java 12.8 KB
Newer Older
Melledy's avatar
Melledy committed
1
2
package emu.grasscutter.game.gacha;

3
4
import static emu.grasscutter.config.Configuration.*;

5
import emu.grasscutter.Grasscutter;
6
import emu.grasscutter.data.common.ItemParamData;
CamChua_VN's avatar
CamChua_VN committed
7
import emu.grasscutter.game.player.Player;
Melledy's avatar
Melledy committed
8
9
import emu.grasscutter.net.proto.GachaInfoOuterClass.GachaInfo;
import emu.grasscutter.net.proto.GachaUpInfoOuterClass.GachaUpInfo;
AnimeGitB's avatar
AnimeGitB committed
10
import emu.grasscutter.utils.Utils;
11
import lombok.Getter;
Melledy's avatar
Melledy committed
12
13

public class GachaBanner {
14
15
16
    @Getter private int gachaType = -1;
    @Getter int scheduleId = -1;
    @Getter int sortId = -1;
github-actions's avatar
github-actions committed
17
    @Getter private String prefabPath;
18
    @Getter private String previewPrefabPath;
github-actions's avatar
github-actions committed
19
20
21
22
23
    @Getter private String titlePath;
    private int costItemId = 0;
    private int costItemAmount = 1;
    private int costItemId10 = 0;
    private int costItemAmount10 = 10;
24
25
    @Getter private int beginTime = 0;
    @Getter private int endTime = 1924992000;
github-actions's avatar
github-actions committed
26
    @Getter private int gachaTimesLimit = Integer.MAX_VALUE;
27
28
    @Getter private int[] rateUpItems4 = {};
    @Getter private int[] rateUpItems5 = {};
29
30
31
32
33
34
35
36
37
38
39
40
41
42
    // This now handles default values for the fields below
    @Getter private BannerType bannerType = BannerType.STANDARD;
    // Constants used by the BannerType enum
    static final int[][] DEFAULT_WEIGHTS_4 = {{1,510}, {8,510}, {10,10000}};
    static final int[][] DEFAULT_WEIGHTS_4_WEAPON = {{1, 600}, {7, 600}, {8, 6600}, {10, 12600}};
    static final int[][] DEFAULT_WEIGHTS_5 = {{1,75}, {73,150}, {90,10000}};
    static final int[][] DEFAULT_WEIGHTS_5_CHARACTER = {{1,80}, {73,80}, {90,10000}};
    static final int[][] DEFAULT_WEIGHTS_5_WEAPON = {{1,100}, {62,100}, {73,7800}, {80,10000}};
    static final int[] DEFAULT_FALLBACK_ITEMS_4_POOL_1 = {1014, 1020, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1059, 1064, 1065, 1067, 1068, 1072};  // Default avatars
    static final int[] DEFAULT_FALLBACK_ITEMS_4_POOL_2 = {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405};  // Default weapons
    static final int[] DEFAULT_FALLBACK_ITEMS_5_POOL_1 = {1003, 1016, 1042, 1035, 1041, 1069};  // Default avatars
    static final int[] DEFAULT_FALLBACK_ITEMS_5_POOL_2 = {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502};  // Default weapons
    static final int[] EMPTY_POOL = {};  // Used to remove a type of fallback
    // These don't change between banner types (apart from Standard having three extra 4star avatars)
github-actions's avatar
github-actions committed
43
    @Getter private int[] fallbackItems3 = {11301, 11302, 11306, 12301, 12302, 12305, 13303, 14301, 14302, 14304, 15301, 15302, 15304};
44
45
46
47
48
49
50
51
52
53
    @Getter private int[] fallbackItems4Pool1 = DEFAULT_FALLBACK_ITEMS_4_POOL_1;
    @Getter private int[] fallbackItems4Pool2 = DEFAULT_FALLBACK_ITEMS_4_POOL_2;
    // Different banner types have different defaults, see above for default values and the enum for which are used where.
    @Getter private int[] fallbackItems5Pool1;
    @Getter private int[] fallbackItems5Pool2;
    private int[][] weights4;
    private int[][] weights5;
    private int eventChance4 = -1; // Chance to win a featured event item
    private int eventChance5 = -1; // Chance to win a featured event item
    //
github-actions's avatar
github-actions committed
54
    @Getter private boolean removeC6FromPool = false;
55
56
    @Getter private boolean autoStripRateUpFromFallback = true;  // Ensures that featured items won't "double dip" into the losing pool
    private int[][] poolBalanceWeights4 = {{1,255}, {17,255}, {21,10455}};  // Used to ensure that players won't go too many rolls without getting something from pool 1 (avatar) or pool 2 (weapon)
github-actions's avatar
github-actions committed
57
58
59
    private int[][] poolBalanceWeights5 = {{1,30}, {147,150}, {181,10230}};
    @Getter private int wishMaxProgress = 2;

60
61
62
63
64
65
66
67
68
69
    // Deprecated fields that were tolerated in early May 2022 but have apparently still being circulating in new custom configs
    // For now, throw up big scary errors on load telling people that they will be banned outright in a future version
    @Deprecated private int[] rateUpItems1 = {};
    @Deprecated private int[] rateUpItems2 = {};
    @Deprecated private int eventChance = -1;
    @Deprecated private int costItem = 0;
    @Deprecated private int softPity = -1;
    @Deprecated private int hardPity = -1;
    @Deprecated private int minItemType = -1;
    @Deprecated private int maxItemType = -1;
70
    @Getter private boolean deprecated = false;
71
    @Getter private boolean disabled = false;
72

73
74
75
    private void warnDeprecated(String name, String replacement) {
        Grasscutter.getLogger().error("Deprecated field found in Banners config: "+name+" was replaced back in early May 2022, use "+replacement+" instead. You MUST remove this field from your config.");
        this.deprecated = true;
76
77
    }
    public void onLoad() {
78
        // Handle deprecated configs
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
        if (eventChance != -1)
            warnDeprecated("eventChance", "eventChance4 & eventChance5");
        if (costItem != 0)
            warnDeprecated("costItem", "costItemId");
        if (softPity != -1)
            warnDeprecated("softPity", "weights5");
        if (hardPity != -1)
            warnDeprecated("hardPity", "weights5");
        if (minItemType != -1)
            warnDeprecated("minItemType", "fallbackItems[4,5]Pool[1,2]");
        if (maxItemType != -1)
            warnDeprecated("maxItemType", "fallbackItems[4,5]Pool[1,2]");
        if (rateUpItems1.length > 0)
            warnDeprecated("rateUpItems1", "rateUpItems5");
        if (rateUpItems2.length > 0)
            warnDeprecated("rateUpItems2", "rateUpItems4");
95
96

        // Handle default values
97
98
99
100
        if (this.previewPrefabPath != null && this.previewPrefabPath.equals("UI_Tab_" + this.prefabPath))
            Grasscutter.getLogger().error("Redundant field found in Banners config: previewPrefabPath does not need to be specified if it is identical to prefabPath prefixed with \"UI_Tab_\".");
        if (this.previewPrefabPath == null || this.previewPrefabPath.isEmpty())
            this.previewPrefabPath = "UI_Tab_" + this.prefabPath;
101
102
103
104
        if (this.gachaType < 0)
            this.gachaType = this.bannerType.gachaType;
        if (this.costItemId == 0)
            this.costItemId = this.bannerType.costItemId;
105
106
        if (this.costItemId10 == 0)
            this.costItemId10 = this.costItemId;
107
108
109
110
111
112
113
114
115
116
117
118
        if (this.weights4 == null)
            this.weights4 = this.bannerType.weights4;
        if (this.weights5 == null)
            this.weights5 = this.bannerType.weights5;
        if (this.eventChance4 < 0)
            this.eventChance4 = this.bannerType.eventChance4;
        if (this.eventChance5 < 0)
            this.eventChance5 = this.bannerType.eventChance5;
        if (this.fallbackItems5Pool1 == null)
            this.fallbackItems5Pool1 = this.bannerType.fallbackItems5Pool1;
        if (this.fallbackItems5Pool2 == null)
            this.fallbackItems5Pool2 = this.bannerType.fallbackItems5Pool2;
AnimeGitB's avatar
AnimeGitB committed
119
120
    }

github-actions's avatar
github-actions committed
121
122
    public ItemParamData getCost(int numRolls) {
        return switch (numRolls) {
123
124
            case 10 -> new ItemParamData(costItemId10, costItemAmount10);
            default -> new ItemParamData(costItemId, costItemAmount * numRolls);
github-actions's avatar
github-actions committed
125
126
127
        };
    }

128
    @Deprecated
github-actions's avatar
github-actions committed
129
    public int getCostItem() {
130
        return costItemId;
github-actions's avatar
github-actions committed
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
    }

    public boolean hasEpitomized() {
        return bannerType.equals(BannerType.WEAPON);
    }

    public int getWeight(int rarity, int pity) {
        return switch (rarity) {
            case 4 -> Utils.lerp(pity, weights4);
            default -> Utils.lerp(pity, weights5);
        };
    }

    public int getPoolBalanceWeight(int rarity, int pity) {
        return switch (rarity) {
            case 4 -> Utils.lerp(pity, poolBalanceWeights4);
            default -> Utils.lerp(pity, poolBalanceWeights5);
        };
    }

    public int getEventChance(int rarity) {
        return switch (rarity) {
            case 4 -> eventChance4;
154
            default -> eventChance5;
github-actions's avatar
github-actions committed
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
        };
    }

    public GachaInfo toProto(Player player) {
        // TODO: use other Nonce/key insteadof session key to ensure the overall security for the player
        String sessionKey = player.getAccount().getSessionKey();

        String record = "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://"
                        + lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress) + ":"
                        + lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort)
                        + "/gacha?s=" + sessionKey + "&gachaType=" + gachaType;
        String details = "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://"
                        + lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress) + ":"
                        + lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort)
                        + "/gacha/details?s=" + sessionKey + "&scheduleId=" + scheduleId;

        // Grasscutter.getLogger().info("record = " + record);
        PlayerGachaBannerInfo gachaInfo = player.getGachaInfo().getBannerInfo(this);
        int leftGachaTimes = switch (gachaTimesLimit) {
            case Integer.MAX_VALUE -> Integer.MAX_VALUE;
            default -> Math.max(gachaTimesLimit - gachaInfo.getTotalPulls(), 0);
        };
        GachaInfo.Builder info = GachaInfo.newBuilder()
                .setGachaType(this.getGachaType())
                .setScheduleId(this.getScheduleId())
                .setBeginTime(this.getBeginTime())
                .setEndTime(this.getEndTime())
182
183
184
185
                .setCostItemId(this.costItemId)
                .setCostItemNum(this.costItemAmount)
                .setTenCostItemId(this.costItemId10)
                .setTenCostItemNum(this.costItemAmount10)
github-actions's avatar
github-actions committed
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
                .setGachaPrefabPath(this.getPrefabPath())
                .setGachaPreviewPrefabPath(this.getPreviewPrefabPath())
                .setGachaProbUrl(details)
                .setGachaProbUrlOversea(details)
                .setGachaRecordUrl(record)
                .setGachaRecordUrlOversea(record)
                .setLeftGachaTimes(leftGachaTimes)
                .setGachaTimesLimit(gachaTimesLimit)
                .setGachaSortId(this.getSortId());

        if (hasEpitomized()) {
            info.setWishItemId(gachaInfo.getWishItemId())
                .setWishProgress(gachaInfo.getFailedChosenItemPulls())
                .setWishMaxProgress(this.getWishMaxProgress());
        }

        if (this.getTitlePath() != null) {
            info.setTitleTextmap(this.getTitlePath());
        }

        if (this.getRateUpItems5().length > 0) {
            GachaUpInfo.Builder upInfo = GachaUpInfo.newBuilder().setItemParentType(1);

            for (int id : getRateUpItems5()) {
                upInfo.addItemIdList(id);
                info.addDisplayUp5ItemList(id);
            }

            info.addGachaUpInfoList(upInfo);
        }

        if (this.getRateUpItems4().length > 0) {
            GachaUpInfo.Builder upInfo = GachaUpInfo.newBuilder().setItemParentType(2);

            for (int id : getRateUpItems4()) {
                upInfo.addItemIdList(id);
                if (info.getDisplayUp4ItemListCount() == 0) {
                    info.addDisplayUp4ItemList(id);
                }
            }

            info.addGachaUpInfoList(upInfo);
        }

        return info.build();
    }

    public enum BannerType {
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
        STANDARD(200, 224, DEFAULT_WEIGHTS_4, DEFAULT_WEIGHTS_5, 50, 50, DEFAULT_FALLBACK_ITEMS_5_POOL_1, DEFAULT_FALLBACK_ITEMS_5_POOL_2),
        BEGINNER(100, 224, DEFAULT_WEIGHTS_4, DEFAULT_WEIGHTS_5, 50, 50, DEFAULT_FALLBACK_ITEMS_5_POOL_1, DEFAULT_FALLBACK_ITEMS_5_POOL_2),
        EVENT(301, 223, DEFAULT_WEIGHTS_4, DEFAULT_WEIGHTS_5_CHARACTER, 50, 50, DEFAULT_FALLBACK_ITEMS_5_POOL_1, DEFAULT_FALLBACK_ITEMS_5_POOL_2),  // Legacy value for CHARACTER
        CHARACTER(301, 223, DEFAULT_WEIGHTS_4, DEFAULT_WEIGHTS_5_CHARACTER, 50, 50, DEFAULT_FALLBACK_ITEMS_5_POOL_1, EMPTY_POOL),
        CHARACTER2(400, 223, DEFAULT_WEIGHTS_4, DEFAULT_WEIGHTS_5_CHARACTER, 50, 50, DEFAULT_FALLBACK_ITEMS_5_POOL_1, EMPTY_POOL),
        WEAPON(302, 223, DEFAULT_WEIGHTS_4_WEAPON, DEFAULT_WEIGHTS_5_WEAPON, 75, 75, EMPTY_POOL, DEFAULT_FALLBACK_ITEMS_5_POOL_2);

        public final int gachaType;
        public final int costItemId;
        public final int[][] weights4;
        public final int[][] weights5;
        public final int eventChance4;
        public final int eventChance5;
        public final int[] fallbackItems5Pool1;
        public final int[] fallbackItems5Pool2;

        BannerType(int gachaType, int costItemId, int[][] weights4, int[][] weights5, int eventChance4, int eventChance5, int[] fallbackItems5Pool1, int[] fallbackItems5Pool2) {
            this.gachaType = gachaType;
            this.costItemId = costItemId;
            this.weights4 = weights4;
            this.weights5 = weights5;
            this.eventChance4 = eventChance4;
            this.eventChance5 = eventChance5;
            this.fallbackItems5Pool1 = fallbackItems5Pool1;
            this.fallbackItems5Pool2 = fallbackItems5Pool2;
        }
github-actions's avatar
github-actions committed
260
    }
Melledy's avatar
Melledy committed
261
}