2022-04-17 05:43:07 -07:00
package emu.grasscutter.game.gacha ;
2023-09-02 00:33:12 +00:00
import static emu.grasscutter.config.Configuration.* ;
2022-11-05 14:22:17 +10:30
import emu.grasscutter.Grasscutter ;
2022-07-04 01:27:50 +09:30
import emu.grasscutter.data.common.ItemParamData ;
2022-06-16 22:01:27 +07:00
import emu.grasscutter.game.player.Player ;
2022-04-17 05:43:07 -07:00
import emu.grasscutter.net.proto.GachaInfoOuterClass.GachaInfo ;
import emu.grasscutter.net.proto.GachaUpInfoOuterClass.GachaUpInfo ;
2022-05-06 23:39:45 +09:30
import emu.grasscutter.utils.Utils ;
2022-07-04 01:27:50 +09:30
import lombok.Getter ;
2022-04-17 05:43:07 -07:00
public class GachaBanner {
2023-05-31 20:48:16 -07:00
// 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
2022-11-25 23:25:22 +10:30
@Getter int scheduleId = - 1 ;
@Getter int sortId = - 1 ;
2023-05-31 20:48:16 -07:00
@Getter private int gachaType = - 1 ;
2022-07-21 07:21:22 +00:00
@Getter private String prefabPath ;
2022-11-24 23:48:38 +10:30
@Getter private String previewPrefabPath ;
2022-07-21 07:21:22 +00:00
@Getter private String titlePath ;
private int costItemId = 0 ;
2024-06-09 08:47:56 -03:00
private final int costItemAmount = 1 ;
2022-07-21 07:21:22 +00:00
private int costItemId10 = 0 ;
2024-06-09 08:47:56 -03:00
private final int costItemAmount10 = 10 ;
@Getter private final int beginTime = 0 ;
@Getter private final int endTime = 1924992000 ;
@Getter private final int gachaTimesLimit = Integer . MAX_VALUE ;
@Getter private final int [ ] rateUpItems4 = { } ;
@Getter private final int [ ] rateUpItems5 = { } ;
2022-11-25 23:25:22 +10:30
// This now handles default values for the fields below
2024-06-09 08:47:56 -03:00
@Getter private final BannerType bannerType = BannerType . STANDARD ;
2022-11-25 23:25:22 +10:30
// These don't change between banner types (apart from Standard having three extra 4star avatars)
2023-05-31 20:48:16 -07:00
@Getter
2024-06-09 08:47:56 -03:00
private final int [ ] fallbackItems3 = {
2023-05-31 20:48:16 -07:00
11301 , 11302 , 11306 , 12301 , 12302 , 12305 , 13303 , 14301 , 14302 , 14304 , 15301 , 15302 , 15304
} ;
2024-06-09 08:47:56 -03:00
@Getter private final int [ ] fallbackItems4Pool1 = DEFAULT_FALLBACK_ITEMS_4_POOL_1 ;
@Getter private final int [ ] fallbackItems4Pool2 = DEFAULT_FALLBACK_ITEMS_4_POOL_2 ;
2023-05-31 20:48:16 -07:00
// Different banner types have different defaults, see above for default values and the enum for
// which are used where.
2022-11-25 23:25:22 +10:30
@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
//
2024-06-09 08:47:56 -03:00
@Getter private final boolean removeC6FromPool = false ;
2023-05-31 20:48:16 -07:00
@Getter
2024-06-09 08:47:56 -03:00
private final boolean autoStripRateUpFromFallback =
2023-05-31 20:48:16 -07:00
true ; // Ensures that featured items won't "double dip" into the losing pool
2024-06-09 08:47:56 -03:00
private final int [ ] [ ] poolBalanceWeights4 = {
2023-05-31 20:48:16 -07:00
{ 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)
2024-06-09 08:47:56 -03:00
private final int [ ] [ ] poolBalanceWeights5 = { { 1 , 30 } , { 147 , 150 } , { 181 , 10230 } } ;
@Getter private final int wishMaxProgress = 2 ;
2022-07-21 07:21:22 +00:00
2023-05-31 20:48:16 -07:00
// 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
2024-06-09 08:47:56 -03:00
@Deprecated private final int [ ] rateUpItems1 = { } ;
@Deprecated private final int [ ] rateUpItems2 = { } ;
@Deprecated private final int eventChance = - 1 ;
@Deprecated private final int costItem = 0 ;
@Deprecated private final int softPity = - 1 ;
@Deprecated private final int hardPity = - 1 ;
@Deprecated private final int minItemType = - 1 ;
@Deprecated private final int maxItemType = - 1 ;
2022-11-24 23:48:38 +10:30
@Getter private boolean deprecated = false ;
2024-06-09 08:47:56 -03:00
@Getter private final boolean disabled = false ;
2022-11-05 14:22:17 +10:30
2022-11-24 23:48:38 +10:30
private void warnDeprecated ( String name , String replacement ) {
2023-05-31 20:48:16 -07:00
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. " ) ;
2022-11-24 23:48:38 +10:30
this . deprecated = true ;
2022-11-05 14:22:17 +10:30
}
2023-05-31 20:48:16 -07:00
2022-11-05 14:22:17 +10:30
public void onLoad ( ) {
2022-11-25 23:25:22 +10:30
// Handle deprecated configs
2023-05-31 20:48:16 -07:00
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 " ) ;
2022-11-25 23:25:22 +10:30
// Handle default values
2023-05-31 20:48:16 -07:00
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_ \" . " ) ;
2022-11-24 23:48:38 +10:30
if ( this . previewPrefabPath = = null | | this . previewPrefabPath . isEmpty ( ) )
this . previewPrefabPath = " UI_Tab_ " + this . prefabPath ;
2023-05-31 20:48:16 -07:00
if ( this . gachaType < 0 ) this . gachaType = this . bannerType . gachaType ;
if ( this . costItemId = = 0 ) this . costItemId = this . bannerType . costItemId ;
if ( this . costItemId10 = = 0 ) this . costItemId10 = this . costItemId ;
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 ;
2022-11-25 23:25:22 +10:30
if ( this . fallbackItems5Pool1 = = null )
this . fallbackItems5Pool1 = this . bannerType . fallbackItems5Pool1 ;
if ( this . fallbackItems5Pool2 = = null )
this . fallbackItems5Pool2 = this . bannerType . fallbackItems5Pool2 ;
2022-09-08 22:36:43 +09:30
}
2022-07-21 07:21:22 +00:00
public ItemParamData getCost ( int numRolls ) {
return switch ( numRolls ) {
2022-11-24 23:48:38 +10:30
case 10 - > new ItemParamData ( costItemId10 , costItemAmount10 ) ;
default - > new ItemParamData ( costItemId , costItemAmount * numRolls ) ;
2022-07-21 07:21:22 +00:00
} ;
}
2022-11-24 23:48:38 +10:30
@Deprecated
2022-07-21 07:21:22 +00:00
public int getCostItem ( ) {
2022-11-24 23:48:38 +10:30
return costItemId ;
2022-07-21 07:21:22 +00:00
}
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 ;
2022-11-24 23:48:38 +10:30
default - > eventChance5 ;
2022-07-21 07:21:22 +00:00
} ;
}
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 ( ) ;
2023-05-31 20:48:16 -07:00
String record =
" http "
+ ( HTTP_ENCRYPTION . useInRouting ? " s " : " " )
+ " :// "
+ lr ( HTTP_INFO . accessAddress , HTTP_INFO . bindAddress )
+ " : "
2022-07-21 07:21:22 +00:00
+ lr ( HTTP_INFO . accessPort , HTTP_INFO . bindPort )
2023-05-31 20:48:16 -07:00
+ " /gacha?s= "
+ sessionKey
+ " &gachaType= "
+ gachaType ;
String details =
" http "
+ ( HTTP_ENCRYPTION . useInRouting ? " s " : " " )
+ " :// "
+ lr ( HTTP_INFO . accessAddress , HTTP_INFO . bindAddress )
+ " : "
2022-07-21 07:21:22 +00:00
+ lr ( HTTP_INFO . accessPort , HTTP_INFO . bindPort )
2023-05-31 20:48:16 -07:00
+ " /gacha/details?s= "
+ sessionKey
+ " &scheduleId= "
+ scheduleId ;
2022-07-21 07:21:22 +00:00
// Grasscutter.getLogger().info("record = " + record);
PlayerGachaBannerInfo gachaInfo = player . getGachaInfo ( ) . getBannerInfo ( this ) ;
2023-05-31 20:48:16 -07:00
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 ( ) )
. setCostItemId ( this . costItemId )
. setCostItemNum ( this . costItemAmount )
. setTenCostItemId ( this . costItemId10 )
. setTenCostItemNum ( this . costItemAmount10 )
. setGachaPrefabPath ( this . getPrefabPath ( ) )
. setGachaPreviewPrefabPath ( this . getPreviewPrefabPath ( ) )
. setGachaProbUrl ( details )
. setGachaProbUrlOversea ( details )
. setGachaRecordUrl ( record )
. setGachaRecordUrlOversea ( record )
. setLeftGachaTimes ( leftGachaTimes )
. setGachaTimesLimit ( gachaTimesLimit )
. setGachaSortId ( this . getSortId ( ) ) ;
2022-07-21 07:21:22 +00:00
if ( hasEpitomized ( ) ) {
info . setWishItemId ( gachaInfo . getWishItemId ( ) )
2023-05-31 20:48:16 -07:00
. setWishProgress ( gachaInfo . getFailedChosenItemPulls ( ) )
. setWishMaxProgress ( this . getWishMaxProgress ( ) ) ;
2022-07-21 07:21:22 +00:00
}
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 {
2023-05-31 20:48:16 -07:00
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 ) ;
2022-11-25 23:25:22 +10:30
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 ;
2023-05-31 20:48:16 -07:00
BannerType (
int gachaType ,
int costItemId ,
int [ ] [ ] weights4 ,
int [ ] [ ] weights5 ,
int eventChance4 ,
int eventChance5 ,
int [ ] fallbackItems5Pool1 ,
int [ ] fallbackItems5Pool2 ) {
2022-11-25 23:25:22 +10:30
this . gachaType = gachaType ;
this . costItemId = costItemId ;
this . weights4 = weights4 ;
this . weights5 = weights5 ;
this . eventChance4 = eventChance4 ;
this . eventChance5 = eventChance5 ;
this . fallbackItems5Pool1 = fallbackItems5Pool1 ;
this . fallbackItems5Pool2 = fallbackItems5Pool2 ;
}
2022-07-21 07:21:22 +00:00
}
2022-04-17 05:43:07 -07:00
}