Initial commit

This commit is contained in:
Melledy
2022-04-17 05:43:07 -07:00
commit 3b45ff7d46
1078 changed files with 367097 additions and 0 deletions

View File

@@ -0,0 +1,150 @@
package emu.grasscutter.game.gacha;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.net.proto.GachaInfoOuterClass.GachaInfo;
import emu.grasscutter.net.proto.GachaUpInfoOuterClass.GachaUpInfo;
public class GachaBanner {
private int gachaType;
private int scheduleId;
private String prefabPath;
private String previewPrefabPath;
private String titlePath;
private int costItem;
private int beginTime;
private int endTime;
private int sortId;
private int[] rateUpItems1;
private int[] rateUpItems2;
private int minItemType = 1;
private int maxItemType = 2;
private int eventChance = 50; // Chance to win a featured event item
private int softPity = 75;
private int hardPity = 90;
private BannerType bannerType = BannerType.STANDARD;
public int getGachaType() {
return gachaType;
}
public BannerType getBannerType() {
return bannerType;
}
public int getScheduleId() {
return scheduleId;
}
public String getPrefabPath() {
return prefabPath;
}
public String getPreviewPrefabPath() {
return previewPrefabPath;
}
public String getTitlePath() {
return titlePath;
}
public int getCostItem() {
return costItem;
}
public int getBeginTime() {
return beginTime;
}
public int getEndTime() {
return endTime;
}
public int getSortId() {
return sortId;
}
public int[] getRateUpItems1() {
return rateUpItems1;
}
public int[] getRateUpItems2() {
return rateUpItems2;
}
public int getMinItemType() {
return minItemType;
}
public int getMaxItemType() {
return maxItemType;
}
public int getSoftPity() {
return softPity - 1;
}
public int getHardPity() {
return hardPity - 1;
}
public int getEventChance() {
return eventChance;
}
public GachaInfo toProto() {
String record = "http://" + Grasscutter.getConfig().DispatchServerIp + "/gacha";
GachaInfo.Builder info = GachaInfo.newBuilder()
.setGachaType(this.getGachaType())
.setScheduleId(this.getScheduleId())
.setBeginTime(this.getBeginTime())
.setEndTime(this.getEndTime())
.setCostItemId(this.getCostItem())
.setCostItemNum(1)
.setGachaPrefabPath(this.getPrefabPath())
.setGachaPreviewPrefabPath(this.getPreviewPrefabPath())
.setGachaProbUrl(record)
.setGachaProbUrlOversea(record)
.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) {
GachaUpInfo.Builder upInfo = GachaUpInfo.newBuilder().setItemParentType(1);
for (int id : getRateUpItems1()) {
upInfo.addItemIdList(id);
info.addMainNameId(id);
}
info.addGachaUpInfoList(upInfo);
}
if (this.getRateUpItems2().length > 0) {
GachaUpInfo.Builder upInfo = GachaUpInfo.newBuilder().setItemParentType(2);
for (int id : getRateUpItems2()) {
upInfo.addItemIdList(id);
if (info.getSubNameIdCount() == 0) {
info.addSubNameId(id);
}
}
info.addGachaUpInfoList(upInfo);
}
return info.build();
}
public enum BannerType {
STANDARD, EVENT, WEAPON;
}
}

View File

@@ -0,0 +1,287 @@
package emu.grasscutter.game.gacha;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import com.google.gson.reflect.TypeToken;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.def.ItemData;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.game.avatar.GenshinAvatar;
import emu.grasscutter.game.inventory.GenshinItem;
import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.inventory.MaterialType;
import emu.grasscutter.net.proto.GachaItemOuterClass.GachaItem;
import emu.grasscutter.net.proto.GachaTransferItemOuterClass.GachaTransferItem;
import emu.grasscutter.net.proto.GetGachaInfoRspOuterClass.GetGachaInfoRsp;
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketDoGachaRsp;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
public class GachaManager {
private final GameServer server;
private final Int2ObjectMap<GachaBanner> gachaBanners;
private GetGachaInfoRsp cachedProto;
private int[] yellowAvatars = new int[] {1003, 1016, 1042, 1035, 1041};
private int[] yellowWeapons = new int[] {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502};
private 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 int[] purpleWeapons = new int[] {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405};
private int[] blueWeapons = new int[] {11301, 11302, 11306, 12301, 12302, 12305, 13303, 14301, 14302, 14304, 15301, 15302, 15304};
private static int starglitterId = 221;
private static int stardustId = 222;
public GachaManager(GameServer server) {
this.server = server;
this.gachaBanners = new Int2ObjectOpenHashMap<>();
this.load();
}
public GameServer getServer() {
return server;
}
public Int2ObjectMap<GachaBanner> getGachaBanners() {
return gachaBanners;
}
public int randomRange(int min, int max) {
return ThreadLocalRandom.current().nextInt(max - min + 1) + min;
}
public int getRandom(int[] array) {
return array[randomRange(0, array.length - 1)];
}
public synchronized void load() {
try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "Banners.json")) {
List<GachaBanner> banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, GachaBanner.class).getType());
for (GachaBanner banner : banners) {
getGachaBanners().put(banner.getGachaType(), banner);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public synchronized void doPulls(GenshinPlayer 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()) {
player.sendPacket(new PacketDoGachaRsp());
return;
}
// Get banner
GachaBanner banner = this.getGachaBanners().get(gachaType);
if (banner == null) {
player.sendPacket(new PacketDoGachaRsp());
return;
}
// Spend currency
if (banner.getCostItem() > 0) {
GenshinItem 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 = 60 + (int) Math.floor(100f * (gachaInfo.getPity5() / (banner.getSoftPity() - 1D))) + bonusYellowChance;
int purpleChance = 10000 - (510 + (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.getMinItemType(), banner.getMaxItemType());
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.getMinItemType(), banner.getMaxItemType());
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);
}
// Add to character
List<GachaItem> list = new ArrayList<>();
int stardust = 0, starglitter = 0;
for (int itemId : wonItems) {
ItemData itemData = GenshinData.getItemDataMap().get(itemId);
if (itemData == null) {
continue;
}
// Create gacha item
GachaItem.Builder gachaItem = GachaItem.newBuilder();
int addStardust = 0, addStarglitter = 0;
boolean isTransferItem = false;
// Const check
if (itemData.getMaterialType() == MaterialType.MATERIAL_AVATAR) {
int avatarId = (itemData.getId() % 1000) + 10000000;
GenshinAvatar avatar = player.getAvatars().getAvatarById(avatarId);
if (avatar != null) {
int constLevel = avatar.getCoreProudSkillLevel();
int constItemId = itemData.getId() + 100;
GenshinItem constItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(constItemId);
if (constItem != null) {
constLevel += constItem.getCount();
}
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));
gachaItem.addTokenItemList(ItemParam.newBuilder().setItemId(constItemId).setCount(1));
player.getInventory().addItem(constItemId, 1);
} else {
// Is max const
addStarglitter = 5;
}
if (itemData.getRankLevel() == 5) {
addStarglitter *= 5;
}
isTransferItem = true;
} else {
// New
gachaItem.setIsGachaItemNew(true);
}
} else {
// Is weapon
switch (itemData.getRankLevel()) {
case 5:
addStarglitter = 10;
break;
case 4:
addStarglitter = 2;
break;
case 3:
addStardust = 15;
break;
}
}
// Create item
GenshinItem item = new GenshinItem(itemData);
gachaItem.setGachaItem(item.toItemParam());
player.getInventory().addItem(item);
stardust += addStardust;
starglitter += addStarglitter;
if (addStardust > 0) {
gachaItem.addTokenItemList(ItemParam.newBuilder().setItemId(stardustId).setCount(addStardust));
} if (addStarglitter > 0) {
ItemParam starglitterParam = ItemParam.newBuilder().setItemId(starglitterId).setCount(addStarglitter).build();
if (isTransferItem) {
gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(starglitterParam));
}
gachaItem.addTokenItemList(starglitterParam);
}
list.add(gachaItem.build());
}
// Add stardust/starglitter
if (stardust > 0) {
player.getInventory().addItem(stardustId, stardust);
} if (starglitter > 0) {
player.getInventory().addItem(starglitterId, starglitter);
}
// Packets
player.sendPacket(new PacketDoGachaRsp(banner, list));
}
private synchronized GetGachaInfoRsp createProto() {
GetGachaInfoRsp.Builder proto = GetGachaInfoRsp.newBuilder().setGachaRandom(12345);
for (GachaBanner banner : getGachaBanners().values()) {
proto.addGachaInfoList(banner.toProto());
}
return proto.build();
}
public GetGachaInfoRsp toProto() {
if (this.cachedProto == null) {
this.cachedProto = createProto();
}
return this.cachedProto;
}
}

View File

@@ -0,0 +1,43 @@
package emu.grasscutter.game.gacha;
public class PlayerGachaBannerInfo {
private int pity5 = 0;
private int pity4 = 0;
private int failedFeaturedItemPulls = 0;
public int getPity5() {
return pity5;
}
public void setPity5(int pity5) {
this.pity5 = pity5;
}
public void addPity5(int amount) {
this.pity5 += amount;
}
public int getPity4() {
return pity4;
}
public void setPity4(int pity4) {
this.pity4 = pity4;
}
public void addPity4(int amount) {
this.pity4 += amount;
}
public int getFailedFeaturedItemPulls() {
return failedFeaturedItemPulls;
}
public void setFailedFeaturedItemPulls(int failedEventCharacterPulls) {
this.failedFeaturedItemPulls = failedEventCharacterPulls;
}
public void addFailedFeaturedItemPulls(int amount) {
failedFeaturedItemPulls += amount;
}
}

View File

@@ -0,0 +1,37 @@
package emu.grasscutter.game.gacha;
public class PlayerGachaInfo {
private PlayerGachaBannerInfo standardBanner;
private PlayerGachaBannerInfo eventCharacterBanner;
private PlayerGachaBannerInfo eventWeaponBanner;
public PlayerGachaInfo() {
this.standardBanner = new PlayerGachaBannerInfo();
this.eventCharacterBanner = new PlayerGachaBannerInfo();
this.eventWeaponBanner = new PlayerGachaBannerInfo();
}
public PlayerGachaBannerInfo getStandardBanner() {
return standardBanner;
}
public PlayerGachaBannerInfo getEventCharacterBanner() {
return eventCharacterBanner;
}
public PlayerGachaBannerInfo getEventWeaponBanner() {
return eventWeaponBanner;
}
public PlayerGachaBannerInfo getBannerInfo(GachaBanner banner) {
switch (banner.getBannerType()) {
case EVENT:
return this.eventCharacterBanner;
case WEAPON:
return this.eventWeaponBanner;
case STANDARD:
default:
return this.standardBanner;
}
}
}