Commit 7925d1cd authored by Melledy's avatar Melledy
Browse files

Initial commit

parents
package emu.grasscutter.game.entity;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.common.PropGrowCurve;
import emu.grasscutter.data.def.MonsterCurveData;
import emu.grasscutter.data.def.MonsterData;
import emu.grasscutter.game.World;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.FightPropPairOuterClass.FightPropPair;
import emu.grasscutter.net.proto.MonsterBornTypeOuterClass.MonsterBornType;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneMonsterInfoOuterClass.SceneMonsterInfo;
import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
public class EntityMonster extends GenshinEntity {
private final MonsterData monsterData;
private final Int2FloatOpenHashMap fightProp;
private final Position pos;
private final Position rot;
private final Position bornPos;
private final int level;
private int weaponEntityId;
public EntityMonster(World world, MonsterData monsterData, Position pos, int level) {
super(world);
this.id = world.getNextEntityId(EntityIdType.MONSTER);
this.monsterData = monsterData;
this.fightProp = new Int2FloatOpenHashMap();
this.pos = new Position(pos);
this.rot = new Position();
this.bornPos = getPosition().clone();
this.level = level;
// Monster weapon
if (getMonsterWeaponId() > 0) {
this.weaponEntityId = world.getNextEntityId(EntityIdType.WEAPON);
}
this.recalcStats();
}
@Override
public int getId() {
return this.id;
}
public MonsterData getMonsterData() {
return monsterData;
}
public int getMonsterWeaponId() {
return getMonsterData().getWeaponId();
}
private int getMonsterId() {
return this.getMonsterData().getId();
}
public int getLevel() {
return level;
}
@Override
public Position getPosition() {
return this.pos;
}
@Override
public Position getRotation() {
return this.rot;
}
public Position getBornPos() {
return bornPos;
}
@Override
public Int2FloatOpenHashMap getFightProperties() {
return fightProp;
}
@Override
public boolean isAlive() {
return this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) > 0f;
}
@Override
public void onDeath(int killerId) {
}
public void recalcStats() {
// Monster data
MonsterData data = this.getMonsterData();
// Get hp percent, set to 100% if none
float hpPercent = this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) <= 0 ? 1f : this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) / this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
// Clear properties
this.getFightProperties().clear();
// Base stats
this.setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, data.getBaseHp());
this.setFightProperty(FightProperty.FIGHT_PROP_BASE_ATTACK, data.getBaseAttack());
this.setFightProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE, data.getBaseDefense());
this.setFightProperty(FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT, data.getPhysicalSubHurt());
this.setFightProperty(FightProperty.FIGHT_PROP_FIRE_SUB_HURT, .1f);
this.setFightProperty(FightProperty.FIGHT_PROP_ELEC_SUB_HURT, data.getElecSubHurt());
this.setFightProperty(FightProperty.FIGHT_PROP_WATER_SUB_HURT, data.getWaterSubHurt());
this.setFightProperty(FightProperty.FIGHT_PROP_GRASS_SUB_HURT, data.getGrassSubHurt());
this.setFightProperty(FightProperty.FIGHT_PROP_WIND_SUB_HURT, data.getWindSubHurt());
this.setFightProperty(FightProperty.FIGHT_PROP_ROCK_SUB_HURT, .1f);
this.setFightProperty(FightProperty.FIGHT_PROP_ICE_SUB_HURT, data.getIceSubHurt());
// Level curve
MonsterCurveData curve = GenshinData.getMonsterCurveDataMap().get(this.getLevel());
if (curve != null) {
for (PropGrowCurve growCurve : data.getPropGrowCurves()) {
FightProperty prop = FightProperty.getPropByName(growCurve.getType());
this.setFightProperty(prop, this.getFightProperty(prop) * curve.getMultByProp(growCurve.getGrowCurve()));
}
}
// Set % stats
this.setFightProperty(
FightProperty.FIGHT_PROP_MAX_HP,
(getFightProperty(FightProperty.FIGHT_PROP_BASE_HP) * (1f + getFightProperty(FightProperty.FIGHT_PROP_HP_PERCENT))) + getFightProperty(FightProperty.FIGHT_PROP_HP)
);
this.setFightProperty(
FightProperty.FIGHT_PROP_CUR_ATTACK,
(getFightProperty(FightProperty.FIGHT_PROP_BASE_ATTACK) * (1f + getFightProperty(FightProperty.FIGHT_PROP_ATTACK_PERCENT))) + getFightProperty(FightProperty.FIGHT_PROP_ATTACK)
);
this.setFightProperty(
FightProperty.FIGHT_PROP_CUR_DEFENSE,
(getFightProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE) * (1f + getFightProperty(FightProperty.FIGHT_PROP_DEFENSE_PERCENT))) + getFightProperty(FightProperty.FIGHT_PROP_DEFENSE)
);
// Set current hp
this.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * hpPercent);
}
@Override
public SceneEntityInfo toProto() {
EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(this.getBornPos().toProto()))
.setBornPos(this.getBornPos().toProto())
.build();
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.ProtEntityMonster)
.setMotionInfo(this.getMotionInfo())
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(this.getLifeState().getValue());
for (Int2FloatMap.Entry entry : getFightProperties().int2FloatEntrySet()) {
if (entry.getIntKey() == 0) {
continue;
}
FightPropPair fightProp = FightPropPair.newBuilder().setType(entry.getIntKey()).setPropValue(entry.getFloatValue()).build();
entityInfo.addFightPropList(fightProp);
}
PropPair pair = PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, getLevel()))
.build();
entityInfo.addPropList(pair);
SceneMonsterInfo.Builder monsterInfo = SceneMonsterInfo.newBuilder()
.setMonsterId(getMonsterId())
.setGroupId(133003095)
.setConfigId(95001)
.addAllAffixList(getMonsterData().getAffix())
.setAuthorityPeerId(getWorld().getHostPeerId())
.setPoseId(0)
.setBlockId(3001)
.setBornType(MonsterBornType.MonsterBornDefault)
.setSpecialNameId(40);
if (getMonsterData().getDescribeData() != null) {
monsterInfo.setTitleId(getMonsterData().getDescribeData().getTitleID());
}
if (this.getMonsterWeaponId() > 0) {
SceneWeaponInfo weaponInfo = SceneWeaponInfo.newBuilder()
.setEntityId(this.weaponEntityId)
.setGadgetId(this.getMonsterWeaponId())
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.build();
monsterInfo.setWeaponList(weaponInfo);
}
entityInfo.setMonster(monsterInfo);
return entityInfo.build();
}
}
package emu.grasscutter.game.entity;
import emu.grasscutter.game.World;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.LifeState;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
public abstract class GenshinEntity {
protected int id;
private final World world;
private MotionState moveState;
private int lastMoveSceneTimeMs;
private int lastMoveReliableSeq;
public GenshinEntity(World world) {
this.world = world;
this.moveState = MotionState.MotionNone;
}
public int getId() {
return this.id;
}
public World getWorld() {
return world;
}
public boolean isAlive() {
return true;
}
public LifeState getLifeState() {
return isAlive() ? LifeState.LIFE_ALIVE : LifeState.LIFE_DEAD;
}
public abstract Int2FloatOpenHashMap getFightProperties();
public abstract Position getPosition();
public abstract Position getRotation();
public MotionState getMotionState() {
return moveState;
}
public void setMotionState(MotionState moveState) {
this.moveState = moveState;
}
public int getLastMoveSceneTimeMs() {
return lastMoveSceneTimeMs;
}
public void setLastMoveSceneTimeMs(int lastMoveSceneTimeMs) {
this.lastMoveSceneTimeMs = lastMoveSceneTimeMs;
}
public int getLastMoveReliableSeq() {
return lastMoveReliableSeq;
}
public void setLastMoveReliableSeq(int lastMoveReliableSeq) {
this.lastMoveReliableSeq = lastMoveReliableSeq;
}
public abstract SceneEntityInfo toProto();
public abstract void onDeath(int killerId);
public void setFightProperty(FightProperty prop, float value) {
this.getFightProperties().put(prop.getId(), value);
}
private void setFightProperty(int id, float value) {
this.getFightProperties().put(id, value);
}
public void addFightProperty(FightProperty prop, float value) {
this.getFightProperties().put(prop.getId(), getFightProperty(prop) + value);
}
public float getFightProperty(FightProperty prop) {
return getFightProperties().getOrDefault(prop.getId(), 0f);
}
protected MotionInfo getMotionInfo() {
MotionInfo proto = MotionInfo.newBuilder()
.setPos(getPosition().toProto())
.setRot(getRotation().toProto())
.setSpeed(Vector.newBuilder())
.setState(this.getMotionState())
.build();
return proto;
}
}
package emu.grasscutter.game.friends;
import java.util.List;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.net.proto.DealAddFriendResultTypeOuterClass.DealAddFriendResultType;
import emu.grasscutter.server.packet.send.PacketAskAddFriendNotify;
import emu.grasscutter.server.packet.send.PacketAskAddFriendRsp;
import emu.grasscutter.server.packet.send.PacketDealAddFriendRsp;
import emu.grasscutter.server.packet.send.PacketDeleteFriendNotify;
import emu.grasscutter.server.packet.send.PacketDeleteFriendRsp;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public class FriendsList {
private final GenshinPlayer player;
private final Int2ObjectMap<Friendship> friends;
private final Int2ObjectMap<Friendship> pendingFriends;
private boolean loaded = false;
public FriendsList(GenshinPlayer player) {
this.player = player;
this.friends = new Int2ObjectOpenHashMap<Friendship>();
this.pendingFriends = new Int2ObjectOpenHashMap<Friendship>();
}
public GenshinPlayer getPlayer() {
return player;
}
public boolean hasLoaded() {
return loaded;
}
public synchronized Int2ObjectMap<Friendship> getFriends() {
return friends;
}
public synchronized Int2ObjectMap<Friendship> getPendingFriends() {
return this.pendingFriends;
}
public synchronized boolean isFriendsWith(int uid) {
return this.getFriends().containsKey(uid);
}
private synchronized Friendship getFriendshipById(int id) {
Friendship friendship = this.getFriends().get(id);
if (friendship == null) {
friendship = this.getPendingFriendById(id);
}
return friendship;
}
private synchronized Friendship getFriendById(int id) {
return this.getFriends().get(id);
}
private synchronized Friendship getPendingFriendById(int id) {
return this.getPendingFriends().get(id);
}
public void addFriend(Friendship friendship) {
getFriends().put(friendship.getFriendId(), friendship);
}
public void addPendingFriend(Friendship friendship) {
getPendingFriends().put(friendship.getFriendId(), friendship);
}
public synchronized void handleFriendRequest(int targetUid, DealAddFriendResultType result) {
// Check if player has sent friend request
Friendship myFriendship = this.getPendingFriendById(targetUid);
if (myFriendship == null) {
return;
}
// Make sure asker cant do anything
if (myFriendship.getAskerId() == this.getPlayer().getId()) {
return;
}
GenshinPlayer target = getPlayer().getSession().getServer().forceGetPlayerById(targetUid);
if (target == null) {
return; // Should never happen
}
// Get target's friendship
Friendship theirFriendship = null;
if (target.isOnline()) {
theirFriendship = target.getFriendsList().getPendingFriendById(this.getPlayer().getId());
} else {
theirFriendship = DatabaseHelper.getReverseFriendship(myFriendship);
}
if (theirFriendship == null) {
// They dont have us on their friends list anymore, rip
this.getPendingFriends().remove(myFriendship.getOwnerId());
myFriendship.delete();
return;
}
// Handle
if (result == DealAddFriendResultType.DealAddFriendAccept) { // Request accepted
myFriendship.setIsFriend(true);
theirFriendship.setIsFriend(true);
this.getPendingFriends().remove(myFriendship.getOwnerId());
this.addFriend(myFriendship);
if (target.isOnline()) {
target.getFriendsList().getPendingFriends().remove(this.getPlayer().getId());
target.getFriendsList().addFriend(theirFriendship);
}
myFriendship.save();
theirFriendship.save();
} else { // Request declined
// Delete from my pending friends
this.getPendingFriends().remove(myFriendship.getOwnerId());
myFriendship.delete();
// Delete from target uid
if (target.isOnline()) {
theirFriendship = target.getFriendsList().getPendingFriendById(this.getPlayer().getId());
}
theirFriendship.delete();
}
// Packet
this.getPlayer().sendPacket(new PacketDealAddFriendRsp(targetUid, result));
}
public synchronized void deleteFriend(int targetUid) {
Friendship myFriendship = this.getFriendById(targetUid);
if (myFriendship == null) {
return;
}
this.getFriends().remove(targetUid);
myFriendship.delete();
Friendship theirFriendship = null;
GenshinPlayer friend = myFriendship.getFriendProfile().getPlayer();
if (friend != null) {
// Friend online
theirFriendship = friend.getFriendsList().getFriendById(this.getPlayer().getId());
if (theirFriendship != null) {
friend.getFriendsList().getFriends().remove(theirFriendship.getFriendId());
theirFriendship.delete();
friend.sendPacket(new PacketDeleteFriendNotify(theirFriendship.getFriendId()));
}
} else {
// Friend offline
theirFriendship = DatabaseHelper.getReverseFriendship(myFriendship);
if (theirFriendship != null) {
theirFriendship.delete();
}
}
// Packet
this.getPlayer().sendPacket(new PacketDeleteFriendRsp(targetUid));
}
public synchronized void sendFriendRequest(int targetUid) {
GenshinPlayer target = getPlayer().getSession().getServer().forceGetPlayerById(targetUid);
if (target == null || target == this.getPlayer()) {
return;
}
// Check if friend already exists
if (this.getPendingFriends().containsKey(targetUid) || this.getFriends().containsKey(targetUid)) {
return;
}
// Create friendships
Friendship myFriendship = new Friendship(getPlayer(), target, getPlayer());
Friendship theirFriendship = new Friendship(target, getPlayer(), getPlayer());
// Add pending lists
this.addPendingFriend(myFriendship);
if (target.isOnline() && target.getFriendsList().hasLoaded()) {
target.getFriendsList().addPendingFriend(theirFriendship);
target.sendPacket(new PacketAskAddFriendNotify(theirFriendship));
}
// Save
myFriendship.save();
theirFriendship.save();
// Packets
this.getPlayer().sendPacket(new PacketAskAddFriendRsp(targetUid));
}
/** Gets total amount of potential friends
* */
public int getFullFriendCount() {
return this.getPendingFriends().size() + this.getFriends().size();
}
public synchronized void loadFromDatabase() {
if (this.hasLoaded()) {
return;
}
// Get friendships from the db
List<Friendship> friendships = DatabaseHelper.getFriends(player);
friendships.forEach(this::loadFriendFromDatabase);
// Set loaded flag
this.loaded = true;
}
private void loadFriendFromDatabase(Friendship friendship) {
// Set friendship owner
friendship.setOwner(getPlayer());
// Check if friend is online
GenshinPlayer friend = getPlayer().getSession().getServer().getPlayerById(friendship.getFriendProfile().getId());
if (friend != null) {
// Set friend to online mode
friendship.setFriendProfile(friend);
// Update our status on friend's client if theyre online
if (friend.getFriendsList().hasLoaded()) {
Friendship theirFriendship = friend.getFriendsList().getFriendshipById(getPlayer().getId());
if (theirFriendship != null) {
// Update friend profile
theirFriendship.setFriendProfile(getPlayer());
} else {
// They dont have us on their friends list anymore, rip
friendship.delete();
return;
}
}
}
// Finally, load to our friends list
if (friendship.isFriend()) {
getFriends().put(friendship.getFriendId(), friendship);
} else {
getPendingFriends().put(friendship.getFriendId(), friendship);
// TODO - Hacky fix to force client to see a notification for a friendship
if (getPendingFriends().size() == 1) {
getPlayer().getSession().send(new PacketAskAddFriendNotify(friendship));
}
}
}
public void save() {
// Update all our friends
List<Friendship> friendships = DatabaseHelper.getReverseFriends(getPlayer());
for (Friendship friend : friendships) {
friend.setFriendProfile(this.getPlayer());
friend.save();
}
}
}
package emu.grasscutter.game.friends;
import org.bson.types.ObjectId;
import dev.morphia.annotations.*;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.net.proto.FriendBriefOuterClass.FriendBrief;
import emu.grasscutter.net.proto.FriendOnlineStateOuterClass.FriendOnlineState;
import emu.grasscutter.net.proto.HeadImageOuterClass.HeadImage;
@Entity(value = "friendships", noClassnameStored = true)
public class Friendship {
@Id private ObjectId id;
@Transient private GenshinPlayer owner;
@Indexed private int ownerId;
@Indexed private int friendId;
private boolean isFriend;
private int askerId;
private PlayerProfile profile;
@Deprecated // Morphia use only
public Friendship() { }
public Friendship(GenshinPlayer owner, GenshinPlayer friend, GenshinPlayer asker) {
this.setOwner(owner);
this.ownerId = owner.getId();
this.friendId = friend.getId();
this.profile = friend.getProfile();
this.askerId = asker.getId();
}
public GenshinPlayer getOwner() {
return owner;
}
public void setOwner(GenshinPlayer owner) {
this.owner = owner;
}
public boolean isFriend() {
return isFriend;
}
public void setIsFriend(boolean b) {
this.isFriend = b;
}
public int getOwnerId() {
return ownerId;
}
public int getFriendId() {
return friendId;
}
public int getAskerId() {
return askerId;
}
public void setAskerId(int askerId) {
this.askerId = askerId;
}
public PlayerProfile getFriendProfile() {
return profile;
}
public void setFriendProfile(GenshinPlayer character) {
if (character == null || this.friendId != character.getId()) return;
this.profile = character.getProfile();
}
public boolean isOnline() {
return getFriendProfile().getPlayer() != null;
}
public void save() {
DatabaseHelper.saveFriendship(this);
}
public void delete() {
DatabaseHelper.deleteFriendship(this);
}
public FriendBrief toProto() {
FriendBrief proto = FriendBrief.newBuilder()
.setUid(getFriendProfile().getId())
.setNickname(getFriendProfile().getName())
.setLevel(getFriendProfile().getPlayerLevel())
.setAvatar(HeadImage.newBuilder().setAvatarId(getFriendProfile().getAvatarId()))
.setWorldLevel(getFriendProfile().getWorldLevel())
.setSignature(getFriendProfile().getSignature())
.setOnlineState(getFriendProfile().isOnline() ? FriendOnlineState.FRIEND_ONLINE : FriendOnlineState.FRIEND_DISCONNECT)
.setIsMpModeAvailable(true)
.setLastActiveTime(getFriendProfile().getLastActiveTime())
.setNameCardId(getFriendProfile().getNameCard())
.setParam(getFriendProfile().getDaysSinceLogin())
.setUnk1(1)
.setUnk2(3)
.build();
return proto;
}
}
package emu.grasscutter.game.friends;
import dev.morphia.annotations.*;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.utils.Utils;
public class PlayerProfile {
@Transient private GenshinPlayer player;
private int id;
private int nameCard;
private int avatarId;
private String name;
private String signature;
private int achievements;
private int playerLevel;
private int worldLevel;
private int lastActiveTime;
@Deprecated // Morphia only
public PlayerProfile() { }
public PlayerProfile(GenshinPlayer player) {
this.id = player.getId();
this.syncWithCharacter(player);
}
public int getId() {
return id;
}
public GenshinPlayer getPlayer() {
return player;
}
public synchronized void setPlayer(GenshinPlayer player) {
this.player = player;
}
public String getName() {
return name;
}
public int getNameCard() {
return nameCard;
}
public int getAvatarId() {
return avatarId;
}
public String getSignature() {
return signature;
}
public int getAchievements() {
return achievements;
}
public int getPlayerLevel() {
return playerLevel;
}
public int getWorldLevel() {
return worldLevel;
}
public int getLastActiveTime() {
return lastActiveTime;
}
public void updateLastActiveTime() {
this.lastActiveTime = Utils.getCurrentSeconds();
}
public int getDaysSinceLogin() {
return (int) Math.floor((Utils.getCurrentSeconds() - getLastActiveTime()) / 86400.0);
}
public boolean isOnline() {
return this.getPlayer() != null;
}
public void syncWithCharacter(GenshinPlayer player) {
if (player == null) {
return;
}
this.name = player.getNickname();
this.avatarId = player.getHeadImage();
this.signature = player.getSignature();
this.nameCard = player.getNameCardId();
this.playerLevel = player.getLevel();
this.worldLevel = player.getWorldLevel();
//this.achievements = 0;
this.updateLastActiveTime();
}
}
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;
}
}
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;
}
}
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;
}
}
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;
}
}
}
package emu.grasscutter.game.inventory;
import java.util.HashSet;
import java.util.Set;
public class EquipInventoryTab implements InventoryTab {
private final Set<GenshinItem> items;
private final int maxCapacity;
public EquipInventoryTab(int maxCapacity) {
this.items = new HashSet<GenshinItem>();
this.maxCapacity = maxCapacity;
}
@Override
public GenshinItem getItemById(int id) {
return null;
}
@Override
public void onAddItem(GenshinItem item) {
this.items.add(item);
}
@Override
public void onRemoveItem(GenshinItem item) {
this.items.remove(item);
}
@Override
public int getSize() {
return this.items.size();
}
@Override
public int getMaxCapacity() {
return this.maxCapacity;
}
}
package emu.grasscutter.game.inventory;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public enum EquipType {
EQUIP_NONE (0),
EQUIP_BRACER (1),
EQUIP_NECKLACE (2),
EQUIP_SHOES (3),
EQUIP_RING (4),
EQUIP_DRESS (5),
EQUIP_WEAPON (6);
private final int value;
private static final Int2ObjectMap<EquipType> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, EquipType> stringMap = new HashMap<>();
static {
Stream.of(values()).forEach(e -> {
map.put(e.getValue(), e);
stringMap.put(e.name(), e);
});
}
private EquipType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static EquipType getTypeByValue(int value) {
return map.getOrDefault(value, EQUIP_NONE);
}
public static EquipType getTypeByName(String name) {
return stringMap.getOrDefault(name, EQUIP_NONE);
}
}
package emu.grasscutter.game.inventory;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.bson.types.ObjectId;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import dev.morphia.annotations.PostLoad;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.GenshinDepot;
import emu.grasscutter.data.def.ItemData;
import emu.grasscutter.data.def.ReliquaryAffixData;
import emu.grasscutter.data.def.ReliquaryMainPropData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.EquipOuterClass.Equip;
import emu.grasscutter.net.proto.FurnitureOuterClass.Furniture;
import emu.grasscutter.net.proto.ItemHintOuterClass.ItemHint;
import emu.grasscutter.net.proto.ItemOuterClass.Item;
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
import emu.grasscutter.net.proto.MaterialOuterClass.Material;
import emu.grasscutter.net.proto.ReliquaryOuterClass.Reliquary;
import emu.grasscutter.net.proto.SceneReliquaryInfoOuterClass.SceneReliquaryInfo;
import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo;
import emu.grasscutter.net.proto.WeaponOuterClass.Weapon;
import emu.grasscutter.utils.WeightedList;
@Entity(value = "items", noClassnameStored = true)
public class GenshinItem {
@Id private ObjectId id;
@Indexed private int ownerId;
private int itemId;
private int count;
@Transient private long guid; // Player unique id
@Transient private ItemData itemData;
// Equips
private int level;
private int exp;
private int totalExp;
private int promoteLevel;
private boolean locked;
// Weapon
private List<Integer> affixes;
private int refinement = 0;
// Relic
private int mainPropId;
private List<Integer> appendPropIdList;
private int equipCharacter;
@Transient private int weaponEntityId;
public GenshinItem() {
// Morphia only
}
public GenshinItem(int itemId) {
this(GenshinData.getItemDataMap().get(itemId));
}
public GenshinItem(int itemId, int count) {
this(GenshinData.getItemDataMap().get(itemId), count);
}
public GenshinItem(ItemData data) {
this(data, 1);
}
public GenshinItem(ItemData data, int count) {
this.itemId = data.getId();
this.itemData = data;
if (data.getItemType() == ItemType.ITEM_VIRTUAL) {
this.count = count;
} else {
this.count = Math.min(count, data.getStackLimit());
}
// Equip data
if (getItemType() == ItemType.ITEM_WEAPON) {
this.level = 1;
this.affixes = new ArrayList<>(2);
if (getItemData().getSkillAffix() != null) {
for (int skillAffix : getItemData().getSkillAffix()) {
if (skillAffix > 0) {
this.affixes.add(skillAffix);
}
}
}
} else if (getItemType() == ItemType.ITEM_RELIQUARY) {
this.level = 1;
this.appendPropIdList = new ArrayList<>();
// Create main property
ReliquaryMainPropData mainPropData = GenshinDepot.getRandomRelicMainProp(getItemData().getMainPropDepotId());
if (mainPropData != null) {
this.mainPropId = mainPropData.getId();
}
// Create extra stats
if (getItemData().getAppendPropNum() > 0) {
for (int i = 0; i < getItemData().getAppendPropNum(); i++) {
this.addAppendProp();
}
}
}
}
public ObjectId getObjectId() {
return id;
}
public int getOwnerId() {
return ownerId;
}
public void setOwner(GenshinPlayer player) {
this.ownerId = player.getId();
this.guid = player.getNextGuid();
}
public int getItemId() {
return itemId;
}
public void setItemId(int itemId) {
this.itemId = itemId;
}
public long getGuid() {
return guid;
}
public ItemType getItemType() {
return this.itemData.getItemType();
}
public ItemData getItemData() {
return itemData;
}
public void setItemData(ItemData materialData) {
this.itemData = materialData;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
public int getExp() {
return exp;
}
public void setExp(int exp) {
this.exp = exp;
}
public int getTotalExp() {
return totalExp;
}
public void setTotalExp(int totalExp) {
this.totalExp = totalExp;
}
public int getPromoteLevel() {
return promoteLevel;
}
public void setPromoteLevel(int promoteLevel) {
this.promoteLevel = promoteLevel;
}
public int getEquipSlot() {
return this.getItemData().getEquipType().getValue();
}
public int getEquipCharacter() {
return equipCharacter;
}
public void setEquipCharacter(int equipCharacter) {
this.equipCharacter = equipCharacter;
}
public boolean isEquipped() {
return this.getEquipCharacter() > 0;
}
public boolean isLocked() {
return locked;
}
public void setLocked(boolean locked) {
this.locked = locked;
}
public boolean isDestroyable() {
return !this.isLocked() && !this.isEquipped();
}
public int getWeaponEntityId() {
return weaponEntityId;
}
public void setWeaponEntityId(int weaponEntityId) {
this.weaponEntityId = weaponEntityId;
}
public List<Integer> getAffixes() {
return affixes;
}
public int getRefinement() {
return refinement;
}
public void setRefinement(int refinement) {
this.refinement = refinement;
}
public int getMainPropId() {
return mainPropId;
}
public List<Integer> getAppendPropIdList() {
return appendPropIdList;
}
public void addAppendProp() {
if (this.getAppendPropIdList() == null) {
this.appendPropIdList = new ArrayList<>();
}
if (this.getAppendPropIdList().size() < 4) {
addNewAppendProp();
} else {
upgradeRandomAppendProp();
}
}
private void addNewAppendProp() {
List<ReliquaryAffixData> affixList = GenshinDepot.getRandomRelicAffixList(getItemData().getAppendPropDepotId());
if (affixList == null) {
return;
}
// Build blacklist - Dont add same stat as main/sub stat
Set<FightProperty> blacklist = new HashSet<>();
ReliquaryMainPropData mainPropData = GenshinData.getReliquaryMainPropDataMap().get(this.getMainPropId());
if (mainPropData != null) {
blacklist.add(mainPropData.getFightProp());
}
int len = Math.min(4, this.getAppendPropIdList().size());
for (int i = 0; i < len; i++) {
ReliquaryAffixData affixData = GenshinData.getReliquaryAffixDataMap().get((int) this.getAppendPropIdList().get(i));
if (affixData != null) {
blacklist.add(affixData.getFightProp());
}
}
// Build random list
WeightedList<ReliquaryAffixData> randomList = new WeightedList<>();
for (ReliquaryAffixData affix : affixList) {
if (!blacklist.contains(affix.getFightProp())) {
randomList.add(affix.getWeight(), affix);
}
}
if (randomList.size() == 0) {
return;
}
// Add random stat
ReliquaryAffixData affixData = randomList.next();
this.getAppendPropIdList().add(affixData.getId());
}
private void upgradeRandomAppendProp() {
List<ReliquaryAffixData> affixList = GenshinDepot.getRandomRelicAffixList(getItemData().getAppendPropDepotId());
if (affixList == null) {
return;
}
// Build whitelist
Set<FightProperty> whitelist = new HashSet<>();
int len = Math.min(4, this.getAppendPropIdList().size());
for (int i = 0; i < len; i++) {
ReliquaryAffixData affixData = GenshinData.getReliquaryAffixDataMap().get((int) this.getAppendPropIdList().get(i));
if (affixData != null) {
whitelist.add(affixData.getFightProp());
}
}
// Build random list
WeightedList<ReliquaryAffixData> randomList = new WeightedList<>();
for (ReliquaryAffixData affix : affixList) {
if (whitelist.contains(affix.getFightProp())) {
randomList.add(affix.getUpgradeWeight(), affix);
}
}
// Add random stat
ReliquaryAffixData affixData = randomList.next();
this.getAppendPropIdList().add(affixData.getId());
}
@PostLoad
public void onLoad() {
if (this.itemData == null) {
this.itemData = GenshinData.getItemDataMap().get(getItemId());
}
}
public void save() {
if (this.count > 0 && this.ownerId > 0) {
DatabaseHelper.saveItem(this);
} else if (this.getObjectId() != null) {
DatabaseHelper.deleteItem(this);
}
}
public SceneWeaponInfo createSceneWeaponInfo() {
SceneWeaponInfo.Builder weaponInfo = SceneWeaponInfo.newBuilder()
.setEntityId(this.getWeaponEntityId())
.setItemId(this.getItemId())
.setGuid(this.getGuid())
.setLevel(this.getLevel())
.setGadgetId(this.getItemData().getGadgetId())
.setAbilityInfo(AbilitySyncStateInfo.newBuilder().setIsInited(getAffixes().size() > 0));
if (this.getAffixes() != null && this.getAffixes().size() > 0) {
for (int affix : this.getAffixes()) {
weaponInfo.putAffixMap(affix, this.getRefinement());
}
}
return weaponInfo.build();
}
public SceneReliquaryInfo createSceneReliquaryInfo() {
SceneReliquaryInfo relicInfo = SceneReliquaryInfo.newBuilder()
.setItemId(this.getItemId())
.setGuid(this.getGuid())
.setLevel(this.getLevel())
.build();
return relicInfo;
}
public Item toProto() {
Item.Builder proto = Item.newBuilder()
.setGuid(this.getGuid())
.setItemId(this.getItemId());
switch (getItemType()) {
case ITEM_WEAPON:
Weapon.Builder weapon = Weapon.newBuilder()
.setLevel(this.getLevel())
.setExp(this.getExp())
.setPromoteLevel(this.getPromoteLevel());
if (this.getAffixes() != null && this.getAffixes().size() > 0) {
for (int affix : this.getAffixes()) {
weapon.putAffixMap(affix, this.getRefinement());
}
}
proto.setEquip(Equip.newBuilder().setWeapon(weapon).setIsLocked(this.isLocked()).build());
break;
case ITEM_RELIQUARY:
Reliquary relic = Reliquary.newBuilder()
.setLevel(this.getLevel())
.setExp(this.getExp())
.setPromoteLevel(this.getPromoteLevel())
.setMainPropId(this.getMainPropId())
.addAllAppendPropIdList(this.getAppendPropIdList())
.build();
proto.setEquip(Equip.newBuilder().setReliquary(relic).setIsLocked(this.isLocked()).build());
break;
case ITEM_MATERIAL:
Material material = Material.newBuilder()
.setCount(getCount())
.build();
proto.setMaterial(material);
break;
case ITEM_FURNITURE:
Furniture furniture = Furniture.newBuilder()
.setCount(getCount())
.build();
proto.setFurniture(furniture);
break;
default:
break;
}
return proto.build();
}
public ItemHint toItemHintProto() {
return ItemHint.newBuilder().setItemId(getItemId()).setCount(getCount()).setIsNew(false).build();
}
public ItemParam toItemParam() {
return ItemParam.newBuilder().setItemId(this.getItemId()).setCount(this.getCount()).build();
}
}
package emu.grasscutter.game.inventory;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import emu.grasscutter.GenshinConstants;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.def.AvatarCostumeData;
import emu.grasscutter.data.def.AvatarData;
import emu.grasscutter.data.def.AvatarFlycloakData;
import emu.grasscutter.data.def.ItemData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.game.avatar.AvatarStorage;
import emu.grasscutter.game.avatar.GenshinAvatar;
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
import emu.grasscutter.server.packet.send.PacketAvatarEquipChangeNotify;
import emu.grasscutter.server.packet.send.PacketStoreItemChangeNotify;
import emu.grasscutter.server.packet.send.PacketStoreItemDelNotify;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
public class Inventory implements Iterable<GenshinItem> {
private final GenshinPlayer player;
private final Long2ObjectMap<GenshinItem> store;
private final Int2ObjectMap<InventoryTab> inventoryTypes;
public Inventory(GenshinPlayer player) {
this.player = player;
this.store = new Long2ObjectOpenHashMap<>();
this.inventoryTypes = new Int2ObjectOpenHashMap<>();
this.createInventoryTab(ItemType.ITEM_WEAPON, new EquipInventoryTab(GenshinConstants.LIMIT_WEAPON));
this.createInventoryTab(ItemType.ITEM_RELIQUARY, new EquipInventoryTab(GenshinConstants.LIMIT_RELIC));
this.createInventoryTab(ItemType.ITEM_MATERIAL, new MaterialInventoryTab(GenshinConstants.LIMIT_MATERIAL));
this.createInventoryTab(ItemType.ITEM_FURNITURE, new MaterialInventoryTab(GenshinConstants.LIMIT_FURNITURE));
}
public GenshinPlayer getPlayer() {
return player;
}
public AvatarStorage getAvatarStorage() {
return this.getPlayer().getAvatars();
}
public Long2ObjectMap<GenshinItem> getItems() {
return store;
}
public Int2ObjectMap<InventoryTab> getInventoryTypes() {
return inventoryTypes;
}
public InventoryTab getInventoryTab(ItemType type) {
return getInventoryTypes().get(type.getValue());
}
public void createInventoryTab(ItemType type, InventoryTab tab) {
this.getInventoryTypes().put(type.getValue(), tab);
}
public GenshinItem getItemByGuid(long id) {
return this.getItems().get(id);
}
public boolean addItem(int itemId) {
return addItem(itemId, 1);
}
public boolean addItem(int itemId, int count) {
ItemData itemData = GenshinData.getItemDataMap().get(itemId);
if (itemData == null) {
return false;
}
GenshinItem item = new GenshinItem(itemData, count);
return addItem(item);
}
public boolean addItem(GenshinItem item) {
GenshinItem result = putItem(item);
if (result != null) {
getPlayer().sendPacket(new PacketStoreItemChangeNotify(result));
return true;
}
return false;
}
public void addItems(Collection<GenshinItem> items) {
List<GenshinItem> changedItems = new LinkedList<>();
for (GenshinItem item : items) {
GenshinItem result = putItem(item);
if (result != null) {
changedItems.add(result);
}
}
getPlayer().sendPacket(new PacketStoreItemChangeNotify(changedItems));
}
public void addItemParams(Collection<ItemParam> items) {
List<GenshinItem> changedItems = new LinkedList<>();
for (ItemParam itemParam : items) {
GenshinItem toAdd = new GenshinItem(itemParam.getItemId(), itemParam.getCount());
GenshinItem result = putItem(toAdd);
if (result != null) {
changedItems.add(result);
}
}
getPlayer().sendPacket(new PacketStoreItemChangeNotify(changedItems));
}
private synchronized GenshinItem putItem(GenshinItem item) {
// Dont add items that dont have a valid item definition.
if (item.getItemData() == null) {
return null;
}
// Add item to inventory store
ItemType type = item.getItemData().getItemType();
InventoryTab tab = getInventoryTab(type);
// Add
if (type == ItemType.ITEM_WEAPON || type == ItemType.ITEM_RELIQUARY) {
if (tab.getSize() >= tab.getMaxCapacity()) {
return null;
}
putItem(item, tab);
} else if (type == ItemType.ITEM_VIRTUAL) {
// Handle
this.addVirtualItem(item.getItemId(), item.getCount());
return null;
} else if (item.getItemData().getMaterialType() == MaterialType.MATERIAL_AVATAR) {
// Get avatar id
int avatarId = (item.getItemId() % 1000) + 10000000;
// Dont let people give themselves extra main characters
if (avatarId == GenshinConstants.MAIN_CHARACTER_MALE || avatarId == GenshinConstants.MAIN_CHARACTER_FEMALE) {
return null;
}
// Add avatar
AvatarData avatarData = GenshinData.getAvatarDataMap().get(avatarId);
if (avatarData != null && !player.getAvatars().hasAvatar(avatarId)) {
this.getPlayer().addAvatar(new GenshinAvatar(avatarData));
}
return null;
} else if (item.getItemData().getMaterialType() == MaterialType.MATERIAL_FLYCLOAK) {
AvatarFlycloakData flycloakData = GenshinData.getAvatarFlycloakDataMap().get(item.getItemId());
if (flycloakData != null && !player.getFlyCloakList().contains(item.getItemId())) {
getPlayer().addFlycloak(item.getItemId());
}
return null;
} else if (item.getItemData().getMaterialType() == MaterialType.MATERIAL_COSTUME) {
AvatarCostumeData costumeData = GenshinData.getAvatarCostumeDataItemIdMap().get(item.getItemId());
if (costumeData != null && !player.getCostumeList().contains(costumeData.getId())) {
getPlayer().addCostume(costumeData.getId());
}
return null;
} else if (item.getItemData().getMaterialType() == MaterialType.MATERIAL_NAMECARD) {
if (!player.getNameCardList().contains(item.getItemId())) {
getPlayer().addNameCard(item.getItemId());
}
return null;
} else if (tab != null) {
GenshinItem existingItem = tab.getItemById(item.getItemId());
if (existingItem == null) {
// Item type didnt exist before, we will add it to main inventory map if there is enough space
if (tab.getSize() >= tab.getMaxCapacity()) {
return null;
}
putItem(item, tab);
} else {
// Add count
existingItem.setCount(Math.min(existingItem.getCount() + item.getCount(), item.getItemData().getStackLimit()));
existingItem.save();
return existingItem;
}
}
// Set ownership and save to db
item.save();
return item;
}
private synchronized void putItem(GenshinItem item, InventoryTab tab) {
// Set owner and guid FIRST!
item.setOwner(getPlayer());
// Put in item store
getItems().put(item.getGuid(), item);
if (tab != null) {
tab.onAddItem(item);
}
}
private void addVirtualItem(int itemId, int count) {
switch (itemId) {
case 102: // Adventure exp
getPlayer().addExpDirectly(count);
break;
case 201: // Primogem
getPlayer().setPrimogems(player.getPrimogems() + count);
break;
case 202: // Mora
getPlayer().setMora(player.getMora() + count);
break;
}
}
public void removeItems(List<GenshinItem> items) {
// TODO Bulk delete
for (GenshinItem item : items) {
this.removeItem(item, item.getCount());
}
}
public boolean removeItem(long guid) {
return removeItem(guid, 1);
}
public synchronized boolean removeItem(long guid, int count) {
GenshinItem item = this.getItemByGuid(guid);
if (item == null) {
return false;
}
return removeItem(item, count);
}
public synchronized boolean removeItem(GenshinItem item) {
return removeItem(item, item.getCount());
}
public synchronized boolean removeItem(GenshinItem item, int count) {
// Sanity check
if (count <= 0 || item == null) {
return false;
}
item.setCount(item.getCount() - count);
if (item.getCount() <= 0) {
// Remove from inventory tab too
InventoryTab tab = null;
if (item.getItemData() != null) {
tab = getInventoryTab(item.getItemData().getItemType());
}
// Remove if less than 0
deleteItem(item, tab);
//
getPlayer().sendPacket(new PacketStoreItemDelNotify(item));
} else {
getPlayer().sendPacket(new PacketStoreItemChangeNotify(item));
}
// Update in db
item.save();
// Returns true on success
return true;
}
private void deleteItem(GenshinItem item, InventoryTab tab) {
getItems().remove(item.getGuid());
if (tab != null) {
tab.onRemoveItem(item);
}
}
public boolean equipItem(long avatarGuid, long equipGuid) {
GenshinAvatar avatar = getPlayer().getAvatars().getAvatarByGuid(avatarGuid);
GenshinItem item = this.getItemByGuid(equipGuid);
if (avatar != null && item != null) {
return avatar.equipItem(item, true);
}
return false;
}
public boolean unequipItem(long avatarGuid, int slot) {
GenshinAvatar avatar = getPlayer().getAvatars().getAvatarByGuid(avatarGuid);
EquipType equipType = EquipType.getTypeByValue(slot);
if (avatar != null && equipType != EquipType.EQUIP_WEAPON) {
if (avatar.unequipItem(equipType)) {
getPlayer().sendPacket(new PacketAvatarEquipChangeNotify(avatar, equipType));
avatar.recalcStats();
return true;
}
}
return false;
}
public void loadFromDatabase() {
List<GenshinItem> items = DatabaseHelper.getInventoryItems(getPlayer());
for (GenshinItem item : items) {
// Should never happen
if (item.getObjectId() == null) {
continue;
}
ItemData itemData = GenshinData.getItemDataMap().get(item.getItemId());
if (itemData == null) {
continue;
}
item.setItemData(itemData);
InventoryTab tab = null;
if (item.getItemData() != null) {
tab = getInventoryTab(item.getItemData().getItemType());
}
putItem(item, tab);
// Equip to a character if possible
if (item.isEquipped()) {
GenshinAvatar avatar = getPlayer().getAvatars().getAvatarById(item.getEquipCharacter());
boolean hasEquipped = false;
if (avatar != null) {
hasEquipped = avatar.equipItem(item, false);
}
if (!hasEquipped) {
item.setEquipCharacter(0);
item.save();
}
}
}
}
@Override
public Iterator<GenshinItem> iterator() {
return this.getItems().values().iterator();
}
}
package emu.grasscutter.game.inventory;
public interface InventoryTab {
public GenshinItem getItemById(int id);
public void onAddItem(GenshinItem item);
public void onRemoveItem(GenshinItem item);
public int getSize();
public int getMaxCapacity();
}
package emu.grasscutter.game.inventory;
public class ItemDef {
private int itemId;
private int count;
public ItemDef(int itemId, int count) {
this.itemId = itemId;
this.count = count;
}
public int getItemId() {
return itemId;
}
public void setItemId(int itemId) {
this.itemId = itemId;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
package emu.grasscutter.game.inventory;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public enum ItemQuality {
QUALITY_NONE(0),
QUALITY_WHITE(1),
QUALITY_GREEN(2),
QUALITY_BLUE(3),
QUALITY_PURPLE(4),
QUALITY_ORANGE(5),
QUALITY_ORANGE_SP(105);
private final int value;
private static final Int2ObjectMap<ItemQuality> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, ItemQuality> stringMap = new HashMap<>();
static {
Stream.of(values()).forEach(e -> {
map.put(e.getValue(), e);
stringMap.put(e.name(), e);
});
}
private ItemQuality(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static ItemQuality getTypeByValue(int value) {
return map.getOrDefault(value, QUALITY_NONE);
}
public static ItemQuality getTypeByName(String name) {
return stringMap.getOrDefault(name, QUALITY_NONE);
}
}
package emu.grasscutter.game.inventory;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public enum ItemType {
ITEM_NONE (0),
ITEM_VIRTUAL (1),
ITEM_MATERIAL (2),
ITEM_RELIQUARY (3),
ITEM_WEAPON (4),
ITEM_DISPLAY (5),
ITEM_FURNITURE (6);
private final int value;
private static final Int2ObjectMap<ItemType> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, ItemType> stringMap = new HashMap<>();
static {
Stream.of(values()).forEach(e -> {
map.put(e.getValue(), e);
stringMap.put(e.name(), e);
});
}
private ItemType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static ItemType getTypeByValue(int value) {
return map.getOrDefault(value, ITEM_NONE);
}
public static ItemType getTypeByName(String name) {
return stringMap.getOrDefault(name, ITEM_NONE);
}
}
package emu.grasscutter.game.inventory;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public class MaterialInventoryTab implements InventoryTab {
private final Int2ObjectMap<GenshinItem> items;
private final int maxCapacity;
public MaterialInventoryTab(int maxCapacity) {
this.items = new Int2ObjectOpenHashMap<>();
this.maxCapacity = maxCapacity;
}
@Override
public GenshinItem getItemById(int id) {
return this.items.get(id);
}
@Override
public void onAddItem(GenshinItem item) {
this.items.put(item.getItemId(), item);
}
@Override
public void onRemoveItem(GenshinItem item) {
this.items.remove(item.getItemId());
}
@Override
public int getSize() {
return this.items.size();
}
@Override
public int getMaxCapacity() {
return this.maxCapacity;
}
}
package emu.grasscutter.game.inventory;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public enum MaterialType {
MATERIAL_NONE (0),
MATERIAL_FOOD (1),
MATERIAL_QUEST (2),
MATERIAL_EXCHANGE (4),
MATERIAL_CONSUME (5),
MATERIAL_EXP_FRUIT (6),
MATERIAL_AVATAR (7),
MATERIAL_ADSORBATE (8),
MATERIAL_CRICKET (9),
MATERIAL_ELEM_CRYSTAL (10),
MATERIAL_WEAPON_EXP_STONE (11),
MATERIAL_CHEST (12),
MATERIAL_RELIQUARY_MATERIAL (13),
MATERIAL_AVATAR_MATERIAL (14),
MATERIAL_NOTICE_ADD_HP (15),
MATERIAL_SEA_LAMP (16),
MATERIAL_SELECTABLE_CHEST (17),
MATERIAL_FLYCLOAK (18),
MATERIAL_NAMECARD (19),
MATERIAL_TALENT (20),
MATERIAL_WIDGET (21),
MATERIAL_CHEST_BATCH_USE (22),
MATERIAL_FAKE_ABSORBATE (23),
MATERIAL_CONSUME_BATCH_USE (24),
MATERIAL_WOOD (25),
MATERIAL_FURNITURE_FORMULA (27),
MATERIAL_CHANNELLER_SLAB_BUFF (28),
MATERIAL_FURNITURE_SUITE_FORMULA (29),
MATERIAL_COSTUME (30);
private final int value;
private static final Int2ObjectMap<MaterialType> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, MaterialType> stringMap = new HashMap<>();
static {
Stream.of(values()).forEach(e -> {
map.put(e.getValue(), e);
stringMap.put(e.name(), e);
});
}
private MaterialType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static MaterialType getTypeByValue(int value) {
return map.getOrDefault(value, MATERIAL_NONE);
}
public static MaterialType getTypeByName(String name) {
return stringMap.getOrDefault(name, MATERIAL_NONE);
}
}
package emu.grasscutter.game.managers;
import emu.grasscutter.server.game.GameServer;
public class AccountManager {
private final GameServer server;
public AccountManager(GameServer server) {
this.server = server;
}
public GameServer getServer() {
return server;
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment