Last active
February 4, 2024 02:56
-
-
Save noaione/2eeda15d1fabe37248b2a8cc96c57c45 to your computer and use it in GitHub Desktop.
a patch for a custom server for certain turn-based rpg that kinda fix the pity system and make it customizable | head: bac849d72a282145109ab1bbd2e46b5261fe9315
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 4ef0a621d7b4aa91e81b254435ec1911eaf8e52c Mon Sep 17 00:00:00 2001 | |
From: noaione <noaione0809@gmail.com> | |
Date: Wed, 3 Jan 2024 15:49:59 +0700 | |
Subject: [PATCH] Rework gacha system and make it customizable | |
I would like someone smart enough with math to do the actual calculation | |
and re-rework the gacha system again :) | |
diff --git a/src/main/java/emu/lunarcore/Config.java b/src/main/java/emu/lunarcore/Config.java | |
index 48a5da5..c5c5f76 100644 | |
--- a/src/main/java/emu/lunarcore/Config.java | |
+++ b/src/main/java/emu/lunarcore/Config.java | |
@@ -22,10 +22,11 @@ public class Config { | |
public HttpServerConfig httpServer = new HttpServerConfig(443); | |
public GameServerConfig gameServer = new GameServerConfig(23301); | |
- | |
+ | |
public ServerOptions serverOptions = new ServerOptions(); | |
public ServerTime serverTime = new ServerTime(); | |
public ServerRates serverRates = new ServerRates(); | |
+ public GachaSystem gachaSystem = new GachaSystem(); | |
public LogOptions logOptions = new LogOptions(); | |
public DownloadData downloadData = new DownloadData(); | |
@@ -57,33 +58,33 @@ public class Config { | |
public String bindAddress = "0.0.0.0"; | |
@SerializedName(value = "bindPort", alternate = {"port"}) | |
public int bindPort; | |
- | |
+ | |
// Will return bindAddress if publicAddress is null | |
public String publicAddress = "127.0.0.1"; | |
// Will return bindPort if publicPort is null | |
public Integer publicPort; | |
- | |
+ | |
public ServerConfig(int port) { | |
this.bindPort = port; | |
} | |
- | |
+ | |
public String getPublicAddress() { | |
if (this.publicAddress != null && !this.publicAddress.isEmpty()) { | |
return this.publicAddress; | |
} | |
- | |
+ | |
return this.bindAddress; | |
} | |
- | |
+ | |
public int getPublicPort() { | |
if (this.publicPort != null && this.publicPort != 0) { | |
return this.publicPort; | |
} | |
- | |
+ | |
return this.bindPort; | |
} | |
} | |
- | |
+ | |
@Getter | |
public static class HttpServerConfig extends ServerConfig { | |
public boolean useSSL = true; | |
@@ -92,7 +93,7 @@ public class Config { | |
public HttpServerConfig(int port) { | |
super(port); | |
} | |
- | |
+ | |
public String getDisplayAddress() { | |
return (useSSL ? "https" : "http") + "://" + getPublicAddress() + ":" + getPublicPort(); | |
} | |
@@ -109,13 +110,13 @@ public class Config { | |
super(port); | |
} | |
} | |
- | |
- @Getter | |
+ | |
+ @Getter | |
public static class ServerTime { | |
public boolean spoofTime = false; | |
public Date spoofDate = new Date(1705276800000L); // January 15, 2024 12:00:00 AM (GMT) | |
} | |
- | |
+ | |
@Getter | |
public static class ServerOptions { | |
public boolean autoCreateAccount = true; | |
@@ -129,19 +130,19 @@ public class Config { | |
public boolean autoUpgradeWorldLevel = true; // Automatically upgrades world level when the player reaches a certain TB level | |
public String language = "EN"; | |
public Set<String> defaultPermissions = Set.of("*"); | |
- | |
+ | |
public ServerProfile serverFriendInfo = new ServerProfile(); | |
public WelcomeMail welcomeMail = new WelcomeMail(); | |
- | |
+ | |
public int getStaminaRecoveryRate() { | |
return staminaRecoveryRate > 0 ? staminaRecoveryRate : 1; | |
} | |
- | |
+ | |
public int getStaminaReserveRecoveryRate() { | |
return staminaReserveRecoveryRate > 0 ? staminaReserveRecoveryRate : 1; | |
} | |
} | |
- | |
+ | |
@Getter | |
public static class ServerRates { | |
public double exp = 1.0; | |
@@ -150,7 +151,32 @@ public class Config { | |
public double material = 1.0; | |
public double equip = 1.0; | |
} | |
- | |
+ | |
+ // The gacha system. | |
+ // By default, we follow MHY own pity rates. | |
+ // At 90 pulls, you are guaranteed a 5 star. | |
+ // At every 10 pulls, you are guaranteed a 4 star. | |
+ // At 75 pity (for 5 star), your gacha rate is boosted until you get a 5 star or you hit the pity. | |
+ // ---- | |
+ // Everytime you pull for dupe, you will earn a starglitter. | |
+ // - New cons: 8 starglitter | |
+ // - Max cons: 20 starglitter | |
+ // - New weapon: 5* 40, 4* 8, 3* 20 stardust | |
+ @Getter | |
+ public static class GachaSystem { | |
+ public int pity5 = 90; | |
+ public int pityWeapon5 = 80; | |
+ public int pity4 = 10; | |
+ public int boostRatePity = 75; | |
+ public int boostRateWeaponPity = 65; | |
+ public int consNew = 8; | |
+ public int consMax = 20; | |
+ public int consWeapon5 = 40; | |
+ public int consWeapon4 = 8; | |
+ // This is stardust and not starglitter | |
+ public int consWeapon3 = 20; | |
+ } | |
+ | |
@Getter | |
public static class ServerProfile { | |
public String name = "Server"; | |
@@ -161,14 +187,14 @@ public class Config { | |
public int displayAvatarId = 1001; | |
public int displayAvatarLevel = 1; | |
} | |
- | |
+ | |
@Getter | |
public static class WelcomeMail { | |
public String title; | |
public String sender; | |
public String content; | |
public List<ItemParam> attachments; | |
- | |
+ | |
public WelcomeMail() { | |
this.title = "Welcome to a LunarCore server"; | |
this.sender = "Server"; | |
@@ -182,7 +208,7 @@ public class Config { | |
); | |
} | |
} | |
- | |
+ | |
@Getter | |
public static class LogOptions { | |
public boolean commands = true; | |
@@ -190,7 +216,7 @@ public class Config { | |
public boolean packets = false; | |
public boolean filterLoopingPackets = false; | |
} | |
- | |
+ | |
@Getter | |
public static class DownloadData { | |
public String assetBundleUrl = null; | |
diff --git a/src/main/java/emu/lunarcore/LunarCore.java b/src/main/java/emu/lunarcore/LunarCore.java | |
index bcac72b..f5694a8 100644 | |
--- a/src/main/java/emu/lunarcore/LunarCore.java | |
+++ b/src/main/java/emu/lunarcore/LunarCore.java | |
@@ -43,7 +43,7 @@ public class LunarCore { | |
private static LineReaderImpl reader; | |
@Getter private static boolean usingDumbTerminal; | |
- | |
+ | |
private static long timeOffset = 0; | |
static { | |
@@ -70,9 +70,52 @@ public class LunarCore { | |
LunarCore.getLogger().info("Game version: " + GameConstants.VERSION); | |
boolean generateHandbook = true; | |
+ // Verify gacha system | |
+ LunarCore.getLogger().info("Verifying gacha system..."); | |
+ emu.lunarcore.Config.GachaSystem gachaSystem = getConfig().getGachaSystem(); | |
+ // Check if pity5 is more than 2 | |
+ if (gachaSystem.getPity5() < 2) { | |
+ LunarCore.getLogger().error("`gachaSystem.pity5` must be at least 2."); | |
+ System.exit(1); | |
+ } | |
+ // Check if boostRatePity is more than 1 | |
+ if (gachaSystem.getBoostRatePity() < 2) { | |
+ LunarCore.getLogger().error("`gachaSystem.boostRatePity` must be at least 1."); | |
+ System.exit(1); | |
+ } | |
+ // Check if boostRatePity is more than pity5 | |
+ if (gachaSystem.getBoostRatePity() >= gachaSystem.getPity5()) { | |
+ LunarCore.getLogger().error("`gachaSystem.boostRatePity` must be less than `gachaSystem.pity5`."); | |
+ System.exit(1); | |
+ } | |
+ // Check if pity4 is more than 10 or less than 2 | |
+ if (gachaSystem.getPity4() > 10 || gachaSystem.getPity4() < 2) { | |
+ LunarCore.getLogger().error("`gachaSystem.pity4` must be between 2 and 10."); | |
+ System.exit(1); | |
+ } | |
+ // Check if pity4 is more than pity5 | |
+ if (gachaSystem.getPity4() >= gachaSystem.getPity5()) { | |
+ LunarCore.getLogger().error("`gachaSystem.pity4` must be less than `gachaSystem.pity5`."); | |
+ System.exit(1); | |
+ } | |
+ if (gachaSystem.getConsNew() < 1 || gachaSystem.getConsMax() < 1) { | |
+ LunarCore.getLogger().error("`gachaSystem.consNew` and `gachaSystem.consMax` must be at least 1."); | |
+ System.exit(1); | |
+ } | |
+ // Ensure that both consNew and consMax when multiplied by 2.5 does not have a remainder | |
+ if (gachaSystem.getConsNew() * 2.5 % 1 != 0 || gachaSystem.getConsMax() * 2.5 % 1 != 0) { | |
+ LunarCore.getLogger().error("`gachaSystem.consNew` and `gachaSystem.consMax` when multiplied by 2.5 must not have a remainder."); | |
+ System.exit(1); | |
+ } | |
+ // Check consWeapon5/4/3 is more than 1 | |
+ if (gachaSystem.getConsWeapon5() < 1 || gachaSystem.getConsWeapon4() < 1 || gachaSystem.getConsWeapon3() < 1) { | |
+ LunarCore.getLogger().error("`gachaSystem.consWeapon5`, `gachaSystem.consWeapon4`, and `gachaSystem.consWeapon3` must be at least 1."); | |
+ System.exit(1); | |
+ } | |
+ | |
// Load commands | |
LunarCore.commandManager = new CommandManager(); | |
- | |
+ | |
// Load plugin manager | |
LunarCore.pluginManager = new PluginManager(); | |
@@ -138,7 +181,7 @@ public class LunarCore { | |
} catch (Exception exception) { | |
LunarCore.getLogger().error("Unable to start the game server.", exception); | |
} | |
- | |
+ | |
// Hook into shutdown event | |
Runtime.getRuntime().addShutdownHook(new Thread(LunarCore::onShutdown)); | |
@@ -186,12 +229,12 @@ public class LunarCore { | |
} catch (Exception e) { | |
// Ignored | |
} | |
- | |
+ | |
// Sanity check | |
if (LunarCore.getConfig() == null) { | |
LunarCore.config = new Config(); | |
} | |
- | |
+ | |
// Save config | |
LunarCore.saveConfig(); | |
} | |
@@ -203,7 +246,7 @@ public class LunarCore { | |
.setPrettyPrinting() | |
.serializeNulls() | |
.create(); | |
- | |
+ | |
file.write(gson.toJson(config)); | |
} catch (Exception e) { | |
getLogger().error("Config save error"); | |
@@ -211,7 +254,7 @@ public class LunarCore { | |
} | |
// Build Config | |
- | |
+ | |
private static String getJarVersion() { | |
// Safely get the build config class without errors even if it hasnt been generated yet | |
try { | |
@@ -220,50 +263,50 @@ public class LunarCore { | |
} catch (Exception e) { | |
// Ignored | |
} | |
- | |
+ | |
return ""; | |
} | |
private static String getGitHash() { | |
// Use a string builder in case one of the build config fields are missing | |
StringBuilder builder = new StringBuilder(); | |
- | |
+ | |
// Safely get the build config class without errors even if it hasnt been generated yet | |
try { | |
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); | |
Class<?> buildConfig = Class.forName(LunarCore.class.getPackageName() + ".BuildConfig"); | |
- | |
+ | |
String hash = buildConfig.getField("GIT_HASH").get(null).toString(); | |
builder.append(hash); | |
- | |
+ | |
String timestamp = buildConfig.getField("GIT_TIMESTAMP").get(null).toString(); | |
long time = Long.parseLong(timestamp) * 1000; | |
builder.append(" (" + sf.format(new Date(time)) + ")"); | |
} catch (Exception e) { | |
// Ignored | |
} | |
- | |
+ | |
if (builder.isEmpty()) { | |
return ""; | |
} else { | |
return builder.toString(); | |
} | |
} | |
- | |
+ | |
/** | |
* Returns the current server's time in milliseconds to send to the client. Can be used to spoof server time. | |
*/ | |
public static long currentServerTime() { | |
return convertToServerTime(System.currentTimeMillis()); | |
} | |
- | |
+ | |
/** | |
* Converts a timestamp (in milliseconds) to the server time | |
*/ | |
public static long convertToServerTime(long time) { | |
return time + timeOffset; | |
} | |
- | |
+ | |
private static void updateServerTimeOffset() { | |
var timeOptions = LunarCore.getConfig().getServerTime(); | |
if (timeOptions.isSpoofTime() && timeOptions.getSpoofDate() != null) { | |
diff --git a/src/main/java/emu/lunarcore/game/gacha/GachaRiggedDice.java b/src/main/java/emu/lunarcore/game/gacha/GachaRiggedDice.java | |
new file mode 100644 | |
index 0000000..352b17c | |
--- /dev/null | |
+++ b/src/main/java/emu/lunarcore/game/gacha/GachaRiggedDice.java | |
@@ -0,0 +1,159 @@ | |
+package emu.lunarcore.game.gacha; | |
+ | |
+import java.util.concurrent.ThreadLocalRandom; | |
+ | |
+/** | |
+ * The following is a class implementation of the dice roll probability | |
+ * | |
+ * It use MHY own gacha rates to calculate the probability of getting a 5* or 4* character/weapon | |
+ * | |
+ * The following is the probability of getting a 5* character/weapon | |
+ * - 0.6% for character banner | |
+ * - 0.8% for weapon banner | |
+ * | |
+ * The following is the probability of getting a 4* character/weapon | |
+ * - 5.1% for character banner | |
+ * - 6.0% for weapon banner | |
+ */ | |
+public class GachaRiggedDice { | |
+ // Won state from the rigged dice | |
+ public enum WonState { | |
+ // 5* character/weapon | |
+ SuperRare, | |
+ // 4* character/weapon | |
+ VeryRare, | |
+ // 3* weapon | |
+ Rare, | |
+ } | |
+ | |
+ private double[] faces; | |
+ private PlayerGachaBannerInfo gachaInfo; | |
+ private GachaType gachaType; | |
+ private emu.lunarcore.Config.GachaSystem config; | |
+ | |
+ public GachaRiggedDice(PlayerGachaBannerInfo info, GachaType type, emu.lunarcore.Config.GachaSystem config) { | |
+ this.gachaInfo = info; | |
+ this.gachaType = type; | |
+ this.config = config; | |
+ // 3 sided dice (3*, 4*, 5*) | |
+ // the dice include the probability of getting each rarity | |
+ // starting from 3* to 5* | |
+ // All of this number would be added up to 100% | |
+ // All numbers in percentage | |
+ if (type == GachaType.WeaponUp) { | |
+ // Weapon have different rates | |
+ this.faces = new double[]{93.2, 6.0, 0.8}; | |
+ } else { | |
+ this.faces = new double[]{94.3, 5.1, 0.6}; | |
+ } | |
+ } | |
+ | |
+ public double makeRoll(double min, double max) { | |
+ return ThreadLocalRandom.current().nextDouble(max - min + 1) + min; | |
+ } | |
+ | |
+ public double[] getFacesWithBoostRate() { | |
+ // Clone the faces | |
+ double[] newFaces = new double[faces.length]; | |
+ System.arraycopy(faces, 0, newFaces, 0, faces.length); | |
+ | |
+ // Check if the pity is high enough to boost the rate | |
+ int maxPity = getConfigMaxPity5(); | |
+ if (this.gachaInfo.getPity5() < getConfigBoostRate()) { | |
+ // Nope, not high enough | |
+ return newFaces; | |
+ } | |
+ | |
+ // High enough, let's boost the rate on the 5* character/weapon | |
+ // Convert baseline percentage to decimal | |
+ double baseline = newFaces[2]; | |
+ double boostArea = maxPity - getConfigBoostRate(); | |
+ double untilPity = (boostArea - (maxPity - this.gachaInfo.getPity5())) + 1; | |
+ | |
+ double boostRate = 100 * (untilPity / (boostArea - 1)); | |
+ double boostedRate = baseline + boostRate; | |
+ if (boostedRate >= 100) { | |
+ newFaces[0] = 0; | |
+ newFaces[1] = 0; | |
+ newFaces[2] = 100; | |
+ return newFaces; | |
+ } | |
+ | |
+ // Adjust the rate by just subtracting the 5* rate | |
+ newFaces[2] = boostedRate; | |
+ double totalRate = 100.0; | |
+ double remainingRate = totalRate - boostedRate; | |
+ | |
+ // Adjust the first face | |
+ if (remainingRate > newFaces[0]) { | |
+ // First face is exhausted | |
+ newFaces[0] = 0; | |
+ remainingRate -= newFaces[0]; | |
+ } else { | |
+ // First face is not exhausted | |
+ newFaces[0] -= 100 - remainingRate; | |
+ } | |
+ | |
+ // Adjust the second face | |
+ if (remainingRate > newFaces[1]) { | |
+ // First and second face is exhausted | |
+ newFaces[1] = 0; | |
+ return newFaces; | |
+ } else { | |
+ // Second face is not exhausted | |
+ newFaces[1] -= remainingRate; | |
+ return newFaces; | |
+ } | |
+ } | |
+ | |
+ private int getConfigBoostRate() { | |
+ if (this.gachaType == GachaType.WeaponUp) { | |
+ return this.config.getBoostRateWeaponPity(); | |
+ } | |
+ return this.config.getBoostRatePity(); | |
+ } | |
+ | |
+ private int getConfigMaxPity5() { | |
+ if (this.gachaType == GachaType.WeaponUp) { | |
+ return this.config.getPityWeapon5(); | |
+ } | |
+ return this.config.getPity5(); | |
+ } | |
+ | |
+ public WonState roll() { | |
+ // Increase pity | |
+ this.gachaInfo.addPity4(1); | |
+ this.gachaInfo.addPity5(1); | |
+ | |
+ // Do the dice roll without upgrading the pity | |
+ if (this.gachaInfo.getPity4() >= this.config.getPity4()) { | |
+ // Guaranteed 4* | |
+ this.gachaInfo.setPity4(0); | |
+ return WonState.VeryRare; | |
+ } | |
+ | |
+ if (this.gachaInfo.getPity5() >= getConfigMaxPity5()) { | |
+ // Guaranteed 5* | |
+ this.gachaInfo.setPity5(0); | |
+ return WonState.SuperRare; | |
+ } | |
+ | |
+ // Roll the dice | |
+ // Adjust for boost rate when the pity is high enough. | |
+ double myRoll = makeRoll(1d, 1000d); | |
+ double[] diceFacesAdjusted = getFacesWithBoostRate(); | |
+ | |
+ if (myRoll <= diceFacesAdjusted[2] * 10) { | |
+ // 5* character/weapon, reset pity 5 and keep pity 4 | |
+ this.gachaInfo.setPity5(0); | |
+ return WonState.SuperRare; | |
+ } else if (myRoll <= (diceFacesAdjusted[2] + diceFacesAdjusted[1]) * 10) { | |
+ // 4* character/weapon, reset pity 4 and keep pity 5 | |
+ this.gachaInfo.setPity4(0); | |
+ return WonState.VeryRare; | |
+ } else { | |
+ // 3* weapon, add pity 4 and 5 | |
+ return WonState.Rare; | |
+ } | |
+ } | |
+} | |
diff --git a/src/main/java/emu/lunarcore/game/gacha/GachaService.java b/src/main/java/emu/lunarcore/game/gacha/GachaService.java | |
index 5bf0c38..878d63c 100644 | |
--- a/src/main/java/emu/lunarcore/game/gacha/GachaService.java | |
+++ b/src/main/java/emu/lunarcore/game/gacha/GachaService.java | |
@@ -43,14 +43,14 @@ public class GachaService extends BaseGameService { | |
private int[] purpleWeapons = new int[] {21000, 21001, 21002, 21003, 21004, 21005, 21006, 21007, 21008, 21009, 21010, 21011, 21012, 21013, 21014, 21015, 21016, 21017, 21018, 21019, 21020}; | |
private int[] blueWeapons = new int[] {20000, 20001, 20002, 20003, 20004, 20005, 20006, 20007, 20008, 20009, 20010, 20011, 20012, 20013, 20014, 20015, 20016, 20017, 20018, 20019, 20020}; | |
private int[] defaultFeaturedIds = new int[] {23002, 1003, 1101, 1104, 23000, 23003}; | |
- | |
+ | |
private static int starglightId = 252; | |
private static int embersId = 251; | |
public GachaService(GameServer server) { | |
super(server); | |
this.gachaBanners = new Int2ObjectOpenHashMap<>(); | |
- | |
+ | |
try { | |
this.watch(); | |
} catch (Exception e) { | |
@@ -65,15 +65,15 @@ public class GachaService extends BaseGameService { | |
public int getRandom(int[] array) { | |
return array[randomRange(0, array.length - 1)]; | |
} | |
- | |
+ | |
private String getBannerFileName() { | |
return LunarCore.getConfig().getDataDir() + "/Banners.json"; | |
} | |
- | |
+ | |
public void watch() throws Exception { | |
// Load banners first | |
this.loadBanners(); | |
- | |
+ | |
// Create watch service | |
this.watchService = FileSystems.getDefault().newWatchService(); | |
Path watchPath = Paths.get(LunarCore.getConfig().getDataDir()); | |
@@ -88,12 +88,12 @@ public class GachaService extends BaseGameService { | |
if (event.context() == null) { | |
continue; | |
} | |
- | |
+ | |
if (event.context() instanceof Path path && path.toString().equals("Banners.json")) { | |
loadBanners(); | |
} | |
} | |
- | |
+ | |
key.reset(); | |
} | |
} catch (Exception e) { | |
@@ -105,7 +105,7 @@ public class GachaService extends BaseGameService { | |
public synchronized void loadBanners() { | |
this.getGachaBanners().clear(); | |
- | |
+ | |
try (FileReader fileReader = new FileReader(getBannerFileName())) { | |
List<GachaBanner> banners = JsonUtils.loadToList(fileReader, GachaBanner.class); | |
for (GachaBanner banner : banners) { | |
@@ -119,7 +119,7 @@ public class GachaService extends BaseGameService { | |
public synchronized void doPulls(Player player, int gachaId, int times) { | |
// Sanity checks | |
if (times != 10 && times != 1) return; | |
- | |
+ | |
// Prevent player from using gacha if they are at max light cones | |
if (player.getInventory().getTabByItemType(ItemMainType.Equipment).getSize() >= player.getInventory().getTabByItemType(ItemMainType.Equipment).getMaxCapacity()) { | |
player.sendPacket(new PacketDoGachaScRsp(Retcode.EQUIPMENT_EXCEED_LIMIT)); | |
@@ -143,7 +143,7 @@ public class GachaService extends BaseGameService { | |
player.getInventory().removeItem(costItem, times); | |
} | |
- | |
+ | |
// Add gacha ceiling | |
if (banner.getGachaType() == GachaType.Normal || banner.getGachaType() == GachaType.Newbie) { | |
player.getGachaInfo().addCeilingNum(times); | |
@@ -153,24 +153,33 @@ public class GachaService extends BaseGameService { | |
// Roll | |
PlayerGachaBannerInfo gachaInfo = player.getGachaInfo().getBannerInfo(banner.getGachaType()); | |
IntList wonItems = new IntArrayList(times); | |
+ var gachaSystem = LunarCore.getConfig().getGachaSystem(); | |
+ GachaRiggedDice riggedDice = new GachaRiggedDice(gachaInfo, banner.getGachaType(), gachaSystem); | |
for (int i = 0; i < times; i++) { | |
- int random = this.randomRange(1, 10000); | |
int itemId = 0; | |
- int bonusYellowChance = gachaInfo.getPity5() >= 74 ? 100 * (gachaInfo.getPity5() - 73): 0; | |
- int yellowChance = 60 + (int) Math.floor(100f * (gachaInfo.getPity5() / 73f)) + bonusYellowChance; | |
- int purpleChance = 10000 - (510 + (int) Math.floor(790f * (gachaInfo.getPity4() / 8f))); | |
+ // Roll dice, see the file for implementation info. | |
+ // We do not need to adjust pity here since pity is adjusted automatically | |
+ // in the GachaRiggedDice class. | |
+ GachaRiggedDice.WonState wonState = riggedDice.roll(); | |
- if (random <= yellowChance || gachaInfo.getPity5() >= 89) { | |
+ if (wonState == GachaRiggedDice.WonState.SuperRare) { | |
+ // Check rate-up | |
if (banner.getRateUpItems5().length > 0) { | |
int eventChance = this.randomRange(1, 100); | |
+ int bannerChance = 50; | |
+ if (banner.getGachaType() == GachaType.WeaponUp) { | |
+ bannerChance = 75; | |
+ } | |
- if (eventChance <= banner.getEventChance() || gachaInfo.getFailedFeaturedItemPulls() >= 1) { | |
+ if (eventChance <= bannerChance || gachaInfo.getFailedFeaturedItemPulls() >= 1) { | |
+ // Win/guarantee | |
itemId = getRandom(banner.getRateUpItems5()); | |
+ // Reset | |
gachaInfo.setFailedFeaturedItemPulls(0); | |
} else { | |
- // Lost the 50/50... rip | |
+ // Lost the 50/50... point and laugh at this person | |
gachaInfo.addFailedFeaturedItemPulls(1); | |
} | |
} | |
@@ -183,15 +192,12 @@ public class GachaService extends BaseGameService { | |
itemId = getRandom(this.yellowWeapons); | |
} | |
} | |
- | |
- // Pity | |
- gachaInfo.addPity4(1); | |
- gachaInfo.setPity5(0); | |
- } else if (random >= purpleChance || gachaInfo.getPity4() >= 9) { | |
+ } else if (wonState == GachaRiggedDice.WonState.VeryRare) { | |
+ // Check banner rate-up | |
if (banner.getRateUpItems4().length > 0) { | |
int eventChance = this.randomRange(1, 100); | |
- | |
if (eventChance >= 50) { | |
+ // Win/guarantee | |
itemId = getRandom(banner.getRateUpItems4()); | |
} | |
} | |
@@ -204,16 +210,8 @@ public class GachaService extends BaseGameService { | |
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 | |
@@ -242,7 +240,7 @@ public class GachaService extends BaseGameService { | |
GameAvatar avatar = player.getAvatars().getAvatarById(avatarId); | |
if (avatar != null) { | |
int dupeLevel = avatar.getRank(); | |
- int dupeItemId = avatar.getExcel().getRankUpItemId(); | |
+ int dupeItemId = avatar.getExcel().getRankUpItemId(); | |
GameItem dupeItem = player.getInventory().getMaterialByItemId(avatar.getExcel().getRankUpItemId()); | |
if (dupeItem != null) { | |
dupeLevel += dupeItem.getCount(); | |
@@ -250,13 +248,13 @@ public class GachaService extends BaseGameService { | |
if (dupeLevel < 6) { | |
// Not max const | |
- addStarglitter = 8; | |
+ addStarglitter = gachaSystem.getConsNew(); | |
// Add 1 rank | |
gachaItem.getTransferItemList().addItemList(Item.newInstance().setItemId(dupeItemId).setNum(1)); | |
player.getInventory().addItem(dupeItemId, 1); | |
} else { | |
// Is max rank | |
- addStarglitter = 20; | |
+ addStarglitter = gachaSystem.getConsMax(); | |
} | |
if (itemData.getRarity() == ItemRarity.SuperRare) { | |
@@ -270,17 +268,16 @@ public class GachaService extends BaseGameService { | |
// Is weapon | |
switch (itemData.getRarity()) { | |
case SuperRare: | |
- addStarglitter = 40; | |
+ addStarglitter = gachaSystem.getConsWeapon5(); | |
break; | |
case VeryRare: | |
- addStarglitter = 8; | |
+ addStarglitter = gachaSystem.getConsWeapon4(); | |
break; | |
case Rare: | |
- addStardust = 20; | |
+ addStardust = gachaSystem.getConsWeapon3(); | |
break; | |
default: | |
break; | |
} | |
} | |
// Create item | |
@@ -297,7 +294,7 @@ public class GachaService extends BaseGameService { | |
} if (addStarglitter > 0) { | |
gachaItem.getTokenItem().addItemList(Item.newInstance().setItemId(starglightId).setNum(addStarglitter)); | |
} | |
- | |
+ | |
// Add to gacha item list rsp | |
list.add(gachaItem); | |
} | |
@@ -312,32 +309,32 @@ public class GachaService extends BaseGameService { | |
// Packets | |
player.sendPacket(new PacketDoGachaScRsp(player, banner, times, list)); | |
} | |
- | |
+ | |
public List<GameItem> exchangeGachaCeiling(Player player, int avatarId) { | |
// Sanity check | |
if (player.getGachaInfo().getCeilingNum() < GameConstants.GACHA_CEILING_MAX || player.getGachaInfo().isCeilingClaimed()) { | |
return null; | |
} | |
- | |
+ | |
// Make sure the player is getting a valid avatar | |
if (!Utils.arrayContains(this.getYellowAvatars(), avatarId)) { | |
return null; | |
} | |
- | |
+ | |
// Add items | |
List<GameItem> items = new ArrayList<>(); | |
- | |
+ | |
if (player.getAvatars().hasAvatar(avatarId)) { | |
// Add eidolon if player already has the avatar | |
items.add(new GameItem(avatarId + 10000)); | |
} else { | |
items.add(new GameItem(avatarId)); | |
} | |
- | |
+ | |
player.getInventory().addItems(items); | |
player.getGachaInfo().setCeilingClaimed(true); | |
player.save(); | |
- | |
+ | |
return items; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment