mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2026-02-06 18:17:00 +01:00
Initial commit
This commit is contained in:
98
src/main/java/emu/grasscutter/game/Account.java
Normal file
98
src/main/java/emu/grasscutter/game/Account.java
Normal file
@@ -0,0 +1,98 @@
|
||||
package emu.grasscutter.game;
|
||||
|
||||
import dev.morphia.annotations.Collation;
|
||||
import dev.morphia.annotations.Entity;
|
||||
import dev.morphia.annotations.Id;
|
||||
import dev.morphia.annotations.Indexed;
|
||||
import emu.grasscutter.database.DatabaseHelper;
|
||||
import emu.grasscutter.utils.Crypto;
|
||||
import emu.grasscutter.utils.Utils;
|
||||
import dev.morphia.annotations.IndexOptions;
|
||||
|
||||
@Entity(value = "accounts", noClassnameStored = true)
|
||||
public class Account {
|
||||
@Id private String id;
|
||||
|
||||
@Indexed(options = @IndexOptions(unique = true))
|
||||
@Collation(locale = "simple", caseLevel = true)
|
||||
private String username;
|
||||
private String password; // Unused for now
|
||||
|
||||
private int playerId;
|
||||
private String email;
|
||||
|
||||
private String token;
|
||||
private String sessionKey; // Session token for dispatch server
|
||||
|
||||
@Deprecated
|
||||
public Account() {}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public int getPlayerId() {
|
||||
return this.playerId;
|
||||
}
|
||||
|
||||
public void setPlayerId(int playerId) {
|
||||
this.playerId = playerId;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getSessionKey() {
|
||||
return this.sessionKey;
|
||||
}
|
||||
|
||||
public String generateSessionKey() {
|
||||
this.sessionKey = Utils.bytesToHex(Crypto.createSessionKey(32));
|
||||
this.save();
|
||||
return this.sessionKey;
|
||||
}
|
||||
|
||||
// TODO make unique
|
||||
public String generateLoginToken() {
|
||||
this.token = Utils.bytesToHex(Crypto.createSessionKey(32));
|
||||
this.save();
|
||||
return this.token;
|
||||
}
|
||||
|
||||
public void save() {
|
||||
DatabaseHelper.saveAccount(this);
|
||||
}
|
||||
}
|
||||
29
src/main/java/emu/grasscutter/game/CoopRequest.java
Normal file
29
src/main/java/emu/grasscutter/game/CoopRequest.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package emu.grasscutter.game;
|
||||
|
||||
public class CoopRequest {
|
||||
private final GenshinPlayer requester;
|
||||
private final long requestTime;
|
||||
private final long expireTime;
|
||||
|
||||
public CoopRequest(GenshinPlayer requester) {
|
||||
this.requester = requester;
|
||||
this.requestTime = System.currentTimeMillis();
|
||||
this.expireTime = this.requestTime + 10000;
|
||||
}
|
||||
|
||||
public GenshinPlayer getRequester() {
|
||||
return requester;
|
||||
}
|
||||
|
||||
public long getRequestTime() {
|
||||
return requestTime;
|
||||
}
|
||||
|
||||
public long getExpireTime() {
|
||||
return expireTime;
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
return System.currentTimeMillis() > getExpireTime();
|
||||
}
|
||||
}
|
||||
759
src/main/java/emu/grasscutter/game/GenshinPlayer.java
Normal file
759
src/main/java/emu/grasscutter/game/GenshinPlayer.java
Normal file
@@ -0,0 +1,759 @@
|
||||
package emu.grasscutter.game;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import dev.morphia.annotations.*;
|
||||
import emu.grasscutter.GenshinConstants;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.data.GenshinData;
|
||||
import emu.grasscutter.data.def.PlayerLevelData;
|
||||
import emu.grasscutter.database.DatabaseHelper;
|
||||
import emu.grasscutter.game.avatar.AvatarProfileData;
|
||||
import emu.grasscutter.game.avatar.AvatarStorage;
|
||||
import emu.grasscutter.game.avatar.GenshinAvatar;
|
||||
import emu.grasscutter.game.entity.EntityItem;
|
||||
import emu.grasscutter.game.entity.GenshinEntity;
|
||||
import emu.grasscutter.game.friends.FriendsList;
|
||||
import emu.grasscutter.game.friends.PlayerProfile;
|
||||
import emu.grasscutter.game.gacha.PlayerGachaInfo;
|
||||
import emu.grasscutter.game.inventory.GenshinItem;
|
||||
import emu.grasscutter.game.inventory.Inventory;
|
||||
import emu.grasscutter.game.props.ActionReason;
|
||||
import emu.grasscutter.game.props.PlayerProperty;
|
||||
import emu.grasscutter.net.packet.GenshinPacket;
|
||||
import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry;
|
||||
import emu.grasscutter.net.proto.BirthdayOuterClass.Birthday;
|
||||
import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry;
|
||||
import emu.grasscutter.net.proto.HeadImageOuterClass.HeadImage;
|
||||
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
|
||||
import emu.grasscutter.net.proto.MpSettingTypeOuterClass.MpSettingType;
|
||||
import emu.grasscutter.net.proto.OnlinePlayerInfoOuterClass.OnlinePlayerInfo;
|
||||
import emu.grasscutter.net.proto.PlayerApplyEnterMpReasonOuterClass.PlayerApplyEnterMpReason;
|
||||
import emu.grasscutter.net.proto.PlayerLocationInfoOuterClass.PlayerLocationInfo;
|
||||
import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail;
|
||||
import emu.grasscutter.server.game.GameServer;
|
||||
import emu.grasscutter.server.game.GameSession;
|
||||
import emu.grasscutter.server.packet.send.PacketAbilityInvocationsNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketAvatarAddNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketAvatarDataNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketAvatarGainCostumeNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketAvatarGainFlycloakNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketCombatInvocationsNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
|
||||
import emu.grasscutter.server.packet.send.PacketItemAddHintNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketOpenStateUpdateNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketPlayerApplyEnterMpResultNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketPlayerDataNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketPlayerEnterSceneNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketPlayerPropNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketPlayerStoreNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketPrivateChatNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketSetNameCardRsp;
|
||||
import emu.grasscutter.server.packet.send.PacketStoreWeightLimitNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketUnlockNameCardNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketWorldPlayerRTTNotify;
|
||||
import emu.grasscutter.utils.Position;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
@Entity(value = "players", noClassnameStored = true)
|
||||
public class GenshinPlayer {
|
||||
@Id private int id;
|
||||
@Indexed(options = @IndexOptions(unique = true)) private String accountId;
|
||||
|
||||
@Transient private Account account;
|
||||
private String nickname;
|
||||
private String signature;
|
||||
private int headImage;
|
||||
private int nameCardId = 210001;
|
||||
private Position pos;
|
||||
private Position rotation;
|
||||
|
||||
private Map<Integer, Integer> properties;
|
||||
private Set<Integer> nameCardList;
|
||||
private Set<Integer> flyCloakList;
|
||||
private Set<Integer> costumeList;
|
||||
|
||||
@Transient private long nextGuid = 0;
|
||||
@Transient private int peerId;
|
||||
@Transient private World world;
|
||||
@Transient private GameSession session;
|
||||
@Transient private AvatarStorage avatars;
|
||||
@Transient private Inventory inventory;
|
||||
@Transient private FriendsList friendsList;
|
||||
|
||||
private TeamManager teamManager;
|
||||
private PlayerGachaInfo gachaInfo;
|
||||
private PlayerProfile playerProfile;
|
||||
private MpSettingType mpSetting = MpSettingType.MpSettingEnterAfterApply;
|
||||
private boolean showAvatar;
|
||||
private ArrayList<AvatarProfileData> shownAvatars;
|
||||
|
||||
private int sceneId;
|
||||
private int regionId;
|
||||
private int mainCharacterId;
|
||||
private boolean godmode;
|
||||
|
||||
@Transient private boolean paused;
|
||||
@Transient private int enterSceneToken;
|
||||
@Transient private SceneLoadState sceneState;
|
||||
@Transient private boolean hasSentAvatarDataNotify;
|
||||
|
||||
@Transient private final Int2ObjectMap<CoopRequest> coopRequests;
|
||||
@Transient private final InvokeHandler<CombatInvokeEntry> combatInvokeHandler;
|
||||
@Transient private final InvokeHandler<AbilityInvokeEntry> abilityInvokeHandler;
|
||||
|
||||
@Deprecated @SuppressWarnings({ "rawtypes", "unchecked" }) // Morphia only!
|
||||
public GenshinPlayer() {
|
||||
this.inventory = new Inventory(this);
|
||||
this.avatars = new AvatarStorage(this);
|
||||
this.friendsList = new FriendsList(this);
|
||||
this.pos = new Position();
|
||||
this.rotation = new Position();
|
||||
this.properties = new HashMap<>();
|
||||
for (PlayerProperty prop : PlayerProperty.values()) {
|
||||
if (prop.getId() < 10000) {
|
||||
continue;
|
||||
}
|
||||
this.properties.put(prop.getId(), 0);
|
||||
}
|
||||
this.setSceneId(3);
|
||||
this.setRegionId(1);
|
||||
this.sceneState = SceneLoadState.NONE;
|
||||
|
||||
this.coopRequests = new Int2ObjectOpenHashMap<>();
|
||||
this.combatInvokeHandler = new InvokeHandler(PacketCombatInvocationsNotify.class);
|
||||
this.abilityInvokeHandler = new InvokeHandler(PacketAbilityInvocationsNotify.class);
|
||||
}
|
||||
|
||||
// On player creation
|
||||
public GenshinPlayer(GameSession session) {
|
||||
this();
|
||||
this.account = session.getAccount();
|
||||
this.accountId = this.getAccount().getId();
|
||||
this.session = session;
|
||||
this.nickname = "Traveler";
|
||||
this.signature = "";
|
||||
this.teamManager = new TeamManager(this);
|
||||
this.gachaInfo = new PlayerGachaInfo();
|
||||
this.playerProfile = new PlayerProfile(this);
|
||||
this.nameCardList = new HashSet<>();
|
||||
this.flyCloakList = new HashSet<>();
|
||||
this.costumeList = new HashSet<>();
|
||||
this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, 1);
|
||||
this.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, 1);
|
||||
this.setProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, 50);
|
||||
this.setProperty(PlayerProperty.PROP_IS_FLYABLE, 1);
|
||||
this.setProperty(PlayerProperty.PROP_IS_TRANSFERABLE, 1);
|
||||
this.setProperty(PlayerProperty.PROP_MAX_STAMINA, 24000);
|
||||
this.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, 24000);
|
||||
this.setProperty(PlayerProperty.PROP_PLAYER_RESIN, 160);
|
||||
this.getFlyCloakList().add(140001);
|
||||
this.getNameCardList().add(210001);
|
||||
this.getPos().set(GenshinConstants.START_POSITION);
|
||||
this.getRotation().set(0, 307, 0);
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public long getNextGuid() {
|
||||
long nextId = ++this.nextGuid;
|
||||
return ((long) this.getId() << 32) + nextId;
|
||||
}
|
||||
|
||||
public Account getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
public void setAccount(Account account) {
|
||||
this.account = account;
|
||||
this.account.setPlayerId(getId());
|
||||
}
|
||||
|
||||
public GameSession getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public void setSession(GameSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
public boolean isOnline() {
|
||||
return this.getSession() != null && this.getSession().isActive();
|
||||
}
|
||||
|
||||
public GameServer getServer() {
|
||||
return this.getSession().getServer();
|
||||
}
|
||||
|
||||
public synchronized World getWorld() {
|
||||
return this.world;
|
||||
}
|
||||
|
||||
public synchronized void setWorld(World world) {
|
||||
this.world = world;
|
||||
}
|
||||
|
||||
public int getGmLevel() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public String getNickname() {
|
||||
return nickname;
|
||||
}
|
||||
|
||||
public void setNickname(String nickName) {
|
||||
this.nickname = nickName;
|
||||
this.updateProfile();
|
||||
}
|
||||
|
||||
public int getHeadImage() {
|
||||
return headImage;
|
||||
}
|
||||
|
||||
public void setHeadImage(int picture) {
|
||||
this.headImage = picture;
|
||||
this.updateProfile();
|
||||
}
|
||||
|
||||
public String getSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
public void setSignature(String signature) {
|
||||
this.signature = signature;
|
||||
this.updateProfile();
|
||||
}
|
||||
|
||||
public Position getPos() {
|
||||
return pos;
|
||||
}
|
||||
|
||||
public Position getRotation() {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
public int getLevel() {
|
||||
return this.getProperty(PlayerProperty.PROP_PLAYER_LEVEL);
|
||||
}
|
||||
|
||||
public int getExp() {
|
||||
return this.getProperty(PlayerProperty.PROP_PLAYER_EXP);
|
||||
}
|
||||
|
||||
public int getWorldLevel() {
|
||||
return this.getProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL);
|
||||
}
|
||||
|
||||
public int getPrimogems() {
|
||||
return this.getProperty(PlayerProperty.PROP_PLAYER_HCOIN);
|
||||
}
|
||||
|
||||
public void setPrimogems(int primogem) {
|
||||
this.setProperty(PlayerProperty.PROP_PLAYER_HCOIN, primogem);
|
||||
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_HCOIN));
|
||||
}
|
||||
|
||||
public int getMora() {
|
||||
return this.getProperty(PlayerProperty.PROP_PLAYER_SCOIN);
|
||||
}
|
||||
|
||||
public void setMora(int mora) {
|
||||
this.setProperty(PlayerProperty.PROP_PLAYER_SCOIN, mora);
|
||||
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_SCOIN));
|
||||
}
|
||||
|
||||
private int getExpRequired(int level) {
|
||||
PlayerLevelData levelData = GenshinData.getPlayerLevelDataMap().get(level);
|
||||
return levelData != null ? levelData.getExp() : 0;
|
||||
}
|
||||
|
||||
private float getExpModifier() {
|
||||
return Grasscutter.getConfig().getGameRates().ADVENTURE_EXP_RATE;
|
||||
}
|
||||
|
||||
// Affected by exp rate
|
||||
public void earnExp(int exp) {
|
||||
addExpDirectly((int) (exp * getExpModifier()));
|
||||
}
|
||||
|
||||
// Directly give player exp
|
||||
public void addExpDirectly(int gain) {
|
||||
boolean hasLeveledUp = false;
|
||||
int level = getLevel();
|
||||
int exp = getExp();
|
||||
int reqExp = getExpRequired(level);
|
||||
|
||||
exp += gain;
|
||||
|
||||
while (exp >= reqExp && reqExp > 0) {
|
||||
exp -= reqExp;
|
||||
level += 1;
|
||||
reqExp = getExpRequired(level);
|
||||
hasLeveledUp = true;
|
||||
}
|
||||
|
||||
if (hasLeveledUp) {
|
||||
// Set level property
|
||||
this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, level);
|
||||
// Update social status
|
||||
this.updateProfile();
|
||||
// Update player with packet
|
||||
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_LEVEL));
|
||||
}
|
||||
|
||||
// Set exp
|
||||
this.setProperty(PlayerProperty.PROP_PLAYER_EXP, exp);
|
||||
|
||||
// Update player with packet
|
||||
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_EXP));
|
||||
}
|
||||
|
||||
private void updateProfile() {
|
||||
getProfile().syncWithCharacter(this);
|
||||
}
|
||||
|
||||
public boolean isFirstLoginEnterScene() {
|
||||
return !this.hasSentAvatarDataNotify;
|
||||
}
|
||||
|
||||
public TeamManager getTeamManager() {
|
||||
return this.teamManager;
|
||||
}
|
||||
|
||||
public PlayerGachaInfo getGachaInfo() {
|
||||
return gachaInfo;
|
||||
}
|
||||
|
||||
public PlayerProfile getProfile() {
|
||||
if (this.playerProfile == null) {
|
||||
this.playerProfile = new PlayerProfile(this);
|
||||
this.save();
|
||||
}
|
||||
return playerProfile;
|
||||
}
|
||||
|
||||
public Map<Integer, Integer> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
public void setProperty(PlayerProperty prop, int value) {
|
||||
getProperties().put(prop.getId(), value);
|
||||
}
|
||||
|
||||
public int getProperty(PlayerProperty prop) {
|
||||
return getProperties().get(prop.getId());
|
||||
}
|
||||
|
||||
public Set<Integer> getFlyCloakList() {
|
||||
return flyCloakList;
|
||||
}
|
||||
|
||||
public Set<Integer> getCostumeList() {
|
||||
return costumeList;
|
||||
}
|
||||
|
||||
public Set<Integer> getNameCardList() {
|
||||
return this.nameCardList;
|
||||
}
|
||||
|
||||
public MpSettingType getMpSetting() {
|
||||
return mpSetting;
|
||||
}
|
||||
|
||||
public synchronized Int2ObjectMap<CoopRequest> getCoopRequests() {
|
||||
return coopRequests;
|
||||
}
|
||||
|
||||
public InvokeHandler<CombatInvokeEntry> getCombatInvokeHandler() {
|
||||
return this.combatInvokeHandler;
|
||||
}
|
||||
|
||||
public InvokeHandler<AbilityInvokeEntry> getAbilityInvokeHandler() {
|
||||
return this.abilityInvokeHandler;
|
||||
}
|
||||
|
||||
public void setMpSetting(MpSettingType mpSetting) {
|
||||
this.mpSetting = mpSetting;
|
||||
}
|
||||
|
||||
public AvatarStorage getAvatars() {
|
||||
return avatars;
|
||||
}
|
||||
|
||||
public Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
public FriendsList getFriendsList() {
|
||||
return this.friendsList;
|
||||
}
|
||||
|
||||
public int getEnterSceneToken() {
|
||||
return enterSceneToken;
|
||||
}
|
||||
|
||||
public void setEnterSceneToken(int enterSceneToken) {
|
||||
this.enterSceneToken = enterSceneToken;
|
||||
}
|
||||
|
||||
public int getNameCardId() {
|
||||
return nameCardId;
|
||||
}
|
||||
|
||||
public void setNameCardId(int nameCardId) {
|
||||
this.nameCardId = nameCardId;
|
||||
this.updateProfile();
|
||||
}
|
||||
|
||||
public int getMainCharacterId() {
|
||||
return mainCharacterId;
|
||||
}
|
||||
|
||||
public void setMainCharacterId(int mainCharacterId) {
|
||||
this.mainCharacterId = mainCharacterId;
|
||||
}
|
||||
|
||||
public int getPeerId() {
|
||||
return peerId;
|
||||
}
|
||||
|
||||
public void setPeerId(int peerId) {
|
||||
this.peerId = peerId;
|
||||
}
|
||||
|
||||
public int getClientTime() {
|
||||
return session.getClientTime();
|
||||
}
|
||||
|
||||
public long getLastPingTime() {
|
||||
return session.getLastPingTime();
|
||||
}
|
||||
|
||||
public boolean isPaused() {
|
||||
return paused;
|
||||
}
|
||||
|
||||
public void setPaused(boolean newPauseState) {
|
||||
boolean oldPauseState = this.paused;
|
||||
this.paused = newPauseState;
|
||||
|
||||
if (newPauseState && !oldPauseState) {
|
||||
this.onPause();
|
||||
} else if (oldPauseState && !newPauseState) {
|
||||
this.onUnpause();
|
||||
}
|
||||
}
|
||||
|
||||
public SceneLoadState getSceneLoadState() {
|
||||
return sceneState;
|
||||
}
|
||||
|
||||
public void setSceneLoadState(SceneLoadState sceneState) {
|
||||
this.sceneState = sceneState;
|
||||
}
|
||||
|
||||
public boolean isInMultiplayer() {
|
||||
return this.getWorld() != null && this.getWorld().isMultiplayer();
|
||||
}
|
||||
|
||||
public int getSceneId() {
|
||||
return sceneId;
|
||||
}
|
||||
|
||||
public void setSceneId(int sceneId) {
|
||||
this.sceneId = sceneId;
|
||||
}
|
||||
|
||||
public int getRegionId() {
|
||||
return regionId;
|
||||
}
|
||||
|
||||
public void setRegionId(int regionId) {
|
||||
this.regionId = regionId;
|
||||
}
|
||||
|
||||
public boolean hasGodmode() {
|
||||
return godmode;
|
||||
}
|
||||
|
||||
public void setGodmode(boolean godmode) {
|
||||
this.godmode = godmode;
|
||||
}
|
||||
|
||||
public boolean hasSentAvatarDataNotify() {
|
||||
return hasSentAvatarDataNotify;
|
||||
}
|
||||
|
||||
public void setHasSentAvatarDataNotify(boolean hasSentAvatarDataNotify) {
|
||||
this.hasSentAvatarDataNotify = hasSentAvatarDataNotify;
|
||||
}
|
||||
|
||||
public void addAvatar(GenshinAvatar avatar) {
|
||||
boolean result = getAvatars().addAvatar(avatar);
|
||||
|
||||
if (result) {
|
||||
// Add starting weapon
|
||||
getAvatars().addStartingWeapon(avatar);
|
||||
|
||||
// Try adding to team if possible
|
||||
//List<EntityAvatar> currentTeam = this.getTeamManager().getCurrentTeam();
|
||||
boolean addedToTeam = false;
|
||||
|
||||
/*
|
||||
if (currentTeam.size() <= GenshinConstants.MAX_AVATARS_IN_TEAM) {
|
||||
addedToTeam = currentTeam
|
||||
}
|
||||
*/
|
||||
|
||||
// Done
|
||||
if (hasSentAvatarDataNotify()) {
|
||||
// Recalc stats
|
||||
avatar.recalcStats();
|
||||
// Packet
|
||||
sendPacket(new PacketAvatarAddNotify(avatar, addedToTeam));
|
||||
}
|
||||
} else {
|
||||
// Failed adding avatar
|
||||
}
|
||||
}
|
||||
|
||||
public void addFlycloak(int flycloakId) {
|
||||
this.getFlyCloakList().add(flycloakId);
|
||||
this.sendPacket(new PacketAvatarGainFlycloakNotify(flycloakId));
|
||||
}
|
||||
|
||||
public void addCostume(int costumeId) {
|
||||
this.getCostumeList().add(costumeId);
|
||||
this.sendPacket(new PacketAvatarGainCostumeNotify(costumeId));
|
||||
}
|
||||
|
||||
public void addNameCard(int nameCardId) {
|
||||
this.getNameCardList().add(nameCardId);
|
||||
this.sendPacket(new PacketUnlockNameCardNotify(nameCardId));
|
||||
}
|
||||
|
||||
public void setNameCard(int nameCardId) {
|
||||
if (!this.getNameCardList().contains(nameCardId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setNameCardId(nameCardId);
|
||||
|
||||
this.sendPacket(new PacketSetNameCardRsp(nameCardId));
|
||||
}
|
||||
|
||||
public void dropMessage(Object message) {
|
||||
this.sendPacket(new PacketPrivateChatNotify(GenshinConstants.SERVER_CONSOLE_UID, getId(), message.toString()));
|
||||
}
|
||||
|
||||
public void interactWith(int gadgetEntityId) {
|
||||
GenshinEntity entity = getWorld().getEntityById(gadgetEntityId);
|
||||
|
||||
if (entity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete
|
||||
entity.getWorld().removeEntity(entity);
|
||||
|
||||
// Handle
|
||||
if (entity instanceof EntityItem) {
|
||||
// Pick item
|
||||
EntityItem drop = (EntityItem) entity;
|
||||
GenshinItem item = new GenshinItem(drop.getItemData(), drop.getCount());
|
||||
// Add to inventory
|
||||
boolean success = getInventory().addItem(item);
|
||||
if (success) {
|
||||
this.sendPacket(new PacketGadgetInteractRsp(drop, InteractType.InteractPickItem));
|
||||
this.sendPacket(new PacketItemAddHintNotify(item, ActionReason.SubfieldDrop));
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public void onPause() {
|
||||
|
||||
}
|
||||
|
||||
public void onUnpause() {
|
||||
|
||||
}
|
||||
|
||||
public void sendPacket(GenshinPacket packet) {
|
||||
if (this.hasSentAvatarDataNotify) {
|
||||
this.getSession().send(packet);
|
||||
}
|
||||
}
|
||||
|
||||
public OnlinePlayerInfo getOnlinePlayerInfo() {
|
||||
OnlinePlayerInfo.Builder onlineInfo = OnlinePlayerInfo.newBuilder()
|
||||
.setUid(this.getId())
|
||||
.setNickname(this.getNickname())
|
||||
.setPlayerLevel(this.getLevel())
|
||||
.setMpSettingType(this.getMpSetting())
|
||||
.setNameCardId(this.getNameCardId())
|
||||
.setSignature(this.getSignature())
|
||||
.setAvatar(HeadImage.newBuilder().setAvatarId(this.getHeadImage()));
|
||||
|
||||
if (this.getWorld() != null) {
|
||||
onlineInfo.setCurPlayerNumInWorld(this.getWorld().getPlayers().indexOf(this) + 1);
|
||||
} else {
|
||||
onlineInfo.setCurPlayerNumInWorld(1);
|
||||
}
|
||||
|
||||
return onlineInfo.build();
|
||||
}
|
||||
|
||||
public SocialDetail.Builder getSocialDetail() {
|
||||
SocialDetail.Builder social = SocialDetail.newBuilder()
|
||||
.setUid(this.getId())
|
||||
.setAvatar(HeadImage.newBuilder().setAvatarId(this.getHeadImage()))
|
||||
.setNickname(this.getNickname())
|
||||
.setSignature(this.getSignature())
|
||||
.setLevel(this.getLevel())
|
||||
.setBirthday(Birthday.newBuilder())
|
||||
.setWorldLevel(this.getWorldLevel())
|
||||
.setUnk1(1)
|
||||
.setUnk3(1)
|
||||
.setNameCardId(this.getNameCardId())
|
||||
.setFinishAchievementNum(0);
|
||||
return social;
|
||||
}
|
||||
|
||||
public PlayerLocationInfo getPlayerLocationInfo() {
|
||||
return PlayerLocationInfo.newBuilder()
|
||||
.setUid(this.getId())
|
||||
.setPos(this.getPos().toProto())
|
||||
.setRot(this.getRotation().toProto())
|
||||
.build();
|
||||
}
|
||||
|
||||
public synchronized void onTick() {
|
||||
// Check ping
|
||||
if (this.getLastPingTime() > System.currentTimeMillis() + 60000) {
|
||||
this.getSession().close();
|
||||
return;
|
||||
}
|
||||
// Check co-op requests
|
||||
Iterator<CoopRequest> it = this.getCoopRequests().values().iterator();
|
||||
while (it.hasNext()) {
|
||||
CoopRequest req = it.next();
|
||||
if (req.isExpired()) {
|
||||
req.getRequester().sendPacket(new PacketPlayerApplyEnterMpResultNotify(this, false, PlayerApplyEnterMpReason.SystemJudge));
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
// Ping
|
||||
if (this.getWorld() != null) {
|
||||
this.sendPacket(new PacketWorldPlayerRTTNotify(this.getWorld())); // Player ping
|
||||
}
|
||||
}
|
||||
|
||||
@PostLoad
|
||||
private void onLoad() {
|
||||
this.getTeamManager().setPlayer(this);
|
||||
}
|
||||
|
||||
public void save() {
|
||||
DatabaseHelper.savePlayer(this);
|
||||
}
|
||||
|
||||
public void onLogin() {
|
||||
// Make sure these exist
|
||||
if (this.getTeamManager() == null) {
|
||||
this.teamManager = new TeamManager(this);
|
||||
} if (this.getGachaInfo() == null) {
|
||||
this.gachaInfo = new PlayerGachaInfo();
|
||||
} if (this.nameCardList == null) {
|
||||
this.nameCardList = new HashSet<>();
|
||||
} if (this.costumeList == null) {
|
||||
this.costumeList = new HashSet<>();
|
||||
}
|
||||
|
||||
// Check if player object exists in server
|
||||
// TODO - optimize
|
||||
GenshinPlayer exists = this.getServer().getPlayerById(getId());
|
||||
if (exists != null) {
|
||||
exists.getSession().close();
|
||||
}
|
||||
|
||||
// Load from db
|
||||
this.getAvatars().loadFromDatabase();
|
||||
this.getInventory().loadFromDatabase();
|
||||
this.getAvatars().postLoad();
|
||||
|
||||
this.getFriendsList().loadFromDatabase();
|
||||
|
||||
// Create world
|
||||
World world = new World(this);
|
||||
world.addPlayer(this);
|
||||
|
||||
// Add to gameserver
|
||||
if (getSession().isActive()) {
|
||||
getServer().registerPlayer(this);
|
||||
getProfile().setPlayer(this); // Set online
|
||||
}
|
||||
|
||||
// Multiplayer setting
|
||||
this.setProperty(PlayerProperty.PROP_PLAYER_MP_SETTING_TYPE, this.getMpSetting().getNumber());
|
||||
this.setProperty(PlayerProperty.PROP_IS_MP_MODE_AVAILABLE, 1);
|
||||
|
||||
// Packets
|
||||
session.send(new PacketPlayerDataNotify(this)); // Player data
|
||||
session.send(new PacketStoreWeightLimitNotify());
|
||||
session.send(new PacketPlayerStoreNotify(this));
|
||||
session.send(new PacketAvatarDataNotify(this));
|
||||
|
||||
session.send(new PacketPlayerEnterSceneNotify(this)); // Enter game world
|
||||
session.send(new PacketOpenStateUpdateNotify());
|
||||
|
||||
// First notify packets sent
|
||||
this.setHasSentAvatarDataNotify(true);
|
||||
}
|
||||
|
||||
public void onLogout() {
|
||||
// Leave world
|
||||
if (this.getWorld() != null) {
|
||||
this.getWorld().removePlayer(this);
|
||||
}
|
||||
|
||||
// Status stuff
|
||||
this.getProfile().syncWithCharacter(this);
|
||||
this.getProfile().setPlayer(null); // Set offline
|
||||
|
||||
this.getCoopRequests().clear();
|
||||
|
||||
// Save to db
|
||||
this.save();
|
||||
this.getTeamManager().saveAvatars();
|
||||
this.getFriendsList().save();
|
||||
}
|
||||
|
||||
public enum SceneLoadState {
|
||||
NONE (0), LOADING (1), INIT (2), LOADED (3);
|
||||
|
||||
private final int value;
|
||||
|
||||
private SceneLoadState(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
66
src/main/java/emu/grasscutter/game/InvokeHandler.java
Normal file
66
src/main/java/emu/grasscutter/game/InvokeHandler.java
Normal file
@@ -0,0 +1,66 @@
|
||||
package emu.grasscutter.game;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import emu.grasscutter.net.packet.GenshinPacket;
|
||||
import emu.grasscutter.net.proto.ForwardTypeOuterClass.ForwardType;
|
||||
|
||||
public class InvokeHandler<T> {
|
||||
private final List<T> entryListForwardAll;
|
||||
private final List<T> entryListForwardAllExceptCur;
|
||||
private final List<T> entryListForwardHost;
|
||||
private final Class<? extends GenshinPacket> packetClass;
|
||||
|
||||
public InvokeHandler(Class<? extends GenshinPacket> packetClass) {
|
||||
this.entryListForwardAll = new ArrayList<>();
|
||||
this.entryListForwardAllExceptCur = new ArrayList<>();
|
||||
this.entryListForwardHost = new ArrayList<>();
|
||||
this.packetClass = packetClass;
|
||||
}
|
||||
|
||||
public synchronized void addEntry(ForwardType forward, T entry) {
|
||||
switch (forward) {
|
||||
case ForwardToAll:
|
||||
entryListForwardAll.add(entry);
|
||||
break;
|
||||
case ForwardToAllExceptCur:
|
||||
case ForwardToAllExistExceptCur:
|
||||
entryListForwardAllExceptCur.add(entry);
|
||||
break;
|
||||
case ForwardToHost:
|
||||
entryListForwardHost.add(entry);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void update(GenshinPlayer player) {
|
||||
if (player.getWorld() == null) {
|
||||
this.entryListForwardAll.clear();
|
||||
this.entryListForwardAllExceptCur.clear();
|
||||
this.entryListForwardHost.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (entryListForwardAll.size() > 0) {
|
||||
GenshinPacket packet = packetClass.getDeclaredConstructor(List.class).newInstance(this.entryListForwardAll);
|
||||
player.getWorld().broadcastPacket(packet);
|
||||
this.entryListForwardAll.clear();
|
||||
}
|
||||
if (entryListForwardAllExceptCur.size() > 0) {
|
||||
GenshinPacket packet = packetClass.getDeclaredConstructor(List.class).newInstance(this.entryListForwardAllExceptCur);
|
||||
player.getWorld().broadcastPacketToOthers(player, packet);
|
||||
this.entryListForwardAllExceptCur.clear();
|
||||
}
|
||||
if (entryListForwardHost.size() > 0) {
|
||||
GenshinPacket packet = packetClass.getDeclaredConstructor(List.class).newInstance(this.entryListForwardHost);
|
||||
player.getWorld().getHost().sendPacket(packet);
|
||||
this.entryListForwardHost.clear();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
73
src/main/java/emu/grasscutter/game/TeamInfo.java
Normal file
73
src/main/java/emu/grasscutter/game/TeamInfo.java
Normal file
@@ -0,0 +1,73 @@
|
||||
package emu.grasscutter.game;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import emu.grasscutter.GenshinConstants;
|
||||
import emu.grasscutter.game.avatar.GenshinAvatar;
|
||||
|
||||
public class TeamInfo {
|
||||
private String name;
|
||||
private List<Integer> avatars;
|
||||
|
||||
public TeamInfo() {
|
||||
this.name = "";
|
||||
this.avatars = new ArrayList<>(GenshinConstants.MAX_AVATARS_IN_TEAM);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public List<Integer> getAvatars() {
|
||||
return avatars;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return avatars.size();
|
||||
}
|
||||
|
||||
public boolean contains(GenshinAvatar avatar) {
|
||||
return getAvatars().contains(avatar.getAvatarId());
|
||||
}
|
||||
|
||||
public boolean addAvatar(GenshinAvatar avatar) {
|
||||
if (size() >= GenshinConstants.MAX_AVATARS_IN_TEAM || contains(avatar)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
getAvatars().add(avatar.getAvatarId());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean removeAvatar(int slot) {
|
||||
if (size() <= 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
getAvatars().remove(slot);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void copyFrom(TeamInfo team) {
|
||||
copyFrom(team, GenshinConstants.MAX_AVATARS_IN_TEAM);
|
||||
}
|
||||
|
||||
public void copyFrom(TeamInfo team, int maxTeamSize) {
|
||||
// Clear
|
||||
this.getAvatars().clear();
|
||||
|
||||
// Copy from team
|
||||
int len = Math.min(team.getAvatars().size(), maxTeamSize);
|
||||
for (int i = 0; i < len; i++) {
|
||||
int id = team.getAvatars().get(i);
|
||||
this.getAvatars().add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
484
src/main/java/emu/grasscutter/game/TeamManager.java
Normal file
484
src/main/java/emu/grasscutter/game/TeamManager.java
Normal file
@@ -0,0 +1,484 @@
|
||||
package emu.grasscutter.game;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import dev.morphia.annotations.Transient;
|
||||
import emu.grasscutter.GenshinConstants;
|
||||
import emu.grasscutter.data.def.AvatarSkillDepotData;
|
||||
import emu.grasscutter.game.avatar.GenshinAvatar;
|
||||
import emu.grasscutter.game.entity.EntityAvatar;
|
||||
import emu.grasscutter.game.entity.EntityGadget;
|
||||
import emu.grasscutter.game.props.ElementType;
|
||||
import emu.grasscutter.game.props.EnterReason;
|
||||
import emu.grasscutter.game.props.FightProperty;
|
||||
import emu.grasscutter.net.packet.GenshinPacket;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType;
|
||||
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
|
||||
import emu.grasscutter.server.packet.send.PacketAvatarDieAnimationEndRsp;
|
||||
import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketAvatarTeamUpdateNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketChangeAvatarRsp;
|
||||
import emu.grasscutter.server.packet.send.PacketChangeMpTeamAvatarRsp;
|
||||
import emu.grasscutter.server.packet.send.PacketChangeTeamNameRsp;
|
||||
import emu.grasscutter.server.packet.send.PacketChooseCurAvatarTeamRsp;
|
||||
import emu.grasscutter.server.packet.send.PacketPlayerEnterSceneNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketSceneTeamUpdateNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketSetUpAvatarTeamRsp;
|
||||
import emu.grasscutter.server.packet.send.PacketWorldPlayerDieNotify;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
|
||||
public class TeamManager {
|
||||
@Transient private GenshinPlayer player;
|
||||
|
||||
private Map<Integer, TeamInfo> teams;
|
||||
private int currentTeamIndex;
|
||||
private int currentCharacterIndex;
|
||||
|
||||
@Transient private TeamInfo mpTeam;
|
||||
@Transient private int entityId;
|
||||
@Transient private final List<EntityAvatar> avatars;
|
||||
@Transient private final List<EntityGadget> gadgets;
|
||||
@Transient private final IntSet teamResonances;
|
||||
@Transient private final IntSet teamResonancesConfig;
|
||||
|
||||
public TeamManager() {
|
||||
this.mpTeam = new TeamInfo();
|
||||
this.avatars = new ArrayList<>();
|
||||
this.gadgets = new ArrayList<>();
|
||||
this.teamResonances = new IntOpenHashSet();
|
||||
this.teamResonancesConfig = new IntOpenHashSet();
|
||||
}
|
||||
|
||||
public TeamManager(GenshinPlayer player) {
|
||||
this();
|
||||
this.player = player;
|
||||
|
||||
this.teams = new HashMap<>();
|
||||
this.currentTeamIndex = 1;
|
||||
for (int i = 1; i <= GenshinConstants.MAX_TEAMS; i++) {
|
||||
this.teams.put(i, new TeamInfo());
|
||||
}
|
||||
}
|
||||
|
||||
public GenshinPlayer getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
public World getWorld() {
|
||||
return player.getWorld();
|
||||
}
|
||||
|
||||
public void setPlayer(GenshinPlayer player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
public Map<Integer, TeamInfo> getTeams() {
|
||||
return this.teams;
|
||||
}
|
||||
|
||||
public TeamInfo getMpTeam() {
|
||||
return mpTeam;
|
||||
}
|
||||
|
||||
public void setMpTeam(TeamInfo mpTeam) {
|
||||
this.mpTeam = mpTeam;
|
||||
}
|
||||
|
||||
public int getCurrentTeamId() {
|
||||
// Starts from 1
|
||||
return currentTeamIndex;
|
||||
}
|
||||
|
||||
private void setCurrentTeamId(int currentTeamIndex) {
|
||||
this.currentTeamIndex = currentTeamIndex;
|
||||
}
|
||||
|
||||
public int getCurrentCharacterIndex() {
|
||||
return currentCharacterIndex;
|
||||
}
|
||||
|
||||
public void setCurrentCharacterIndex(int currentCharacterIndex) {
|
||||
this.currentCharacterIndex = currentCharacterIndex;
|
||||
}
|
||||
|
||||
public long getCurrentCharacterGuid() {
|
||||
return getCurrentAvatarEntity().getAvatar().getGuid();
|
||||
}
|
||||
|
||||
public TeamInfo getCurrentTeamInfo() {
|
||||
if (this.getPlayer().isInMultiplayer()) {
|
||||
return this.getMpTeam();
|
||||
}
|
||||
return this.getTeams().get(this.currentTeamIndex);
|
||||
}
|
||||
|
||||
public TeamInfo getCurrentSinglePlayerTeamInfo() {
|
||||
return this.getTeams().get(this.currentTeamIndex);
|
||||
}
|
||||
|
||||
public int getEntityId() {
|
||||
return entityId;
|
||||
}
|
||||
|
||||
public void setEntityId(int entityId) {
|
||||
this.entityId = entityId;
|
||||
}
|
||||
|
||||
public IntSet getTeamResonances() {
|
||||
return teamResonances;
|
||||
}
|
||||
|
||||
public IntSet getTeamResonancesConfig() {
|
||||
return teamResonancesConfig;
|
||||
}
|
||||
|
||||
public List<EntityAvatar> getActiveTeam() {
|
||||
return avatars;
|
||||
}
|
||||
|
||||
public EntityAvatar getCurrentAvatarEntity() {
|
||||
return getActiveTeam().get(currentCharacterIndex);
|
||||
}
|
||||
|
||||
public boolean isSpawned() {
|
||||
return getPlayer().getWorld() != null && getPlayer().getWorld().getEntities().containsKey(getCurrentAvatarEntity().getId());
|
||||
}
|
||||
|
||||
public int getMaxTeamSize() {
|
||||
if (getPlayer().isInMultiplayer()) {
|
||||
if (getPlayer().getWorld().getHost() == this.getPlayer()) {
|
||||
return Math.max(1, (int) Math.ceil(GenshinConstants.MAX_AVATARS_IN_TEAM / (double) getWorld().getPlayerCount()));
|
||||
}
|
||||
return Math.max(1, (int) Math.floor(GenshinConstants.MAX_AVATARS_IN_TEAM / (double) getWorld().getPlayerCount()));
|
||||
}
|
||||
return GenshinConstants.MAX_AVATARS_IN_TEAM;
|
||||
}
|
||||
|
||||
// Methods
|
||||
|
||||
public void updateTeamResonances() {
|
||||
Int2IntOpenHashMap map = new Int2IntOpenHashMap();
|
||||
|
||||
this.getTeamResonances().clear();
|
||||
this.getTeamResonancesConfig().clear();
|
||||
|
||||
for (EntityAvatar entity : getActiveTeam()) {
|
||||
AvatarSkillDepotData skillData = entity.getAvatar().getAvatarData().getSkillDepot();
|
||||
if (skillData != null) {
|
||||
map.addTo(skillData.getElementType().getValue(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (Int2IntMap.Entry e : map.int2IntEntrySet()) {
|
||||
if (e.getIntValue() >= 2) {
|
||||
ElementType element = ElementType.getTypeByValue(e.getIntKey());
|
||||
if (element.getTeamResonanceId() != 0) {
|
||||
this.getTeamResonances().add(element.getTeamResonanceId());
|
||||
this.getTeamResonancesConfig().add(element.getConfigHash());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No resonances
|
||||
if (this.getTeamResonances().size() == 0) {
|
||||
this.getTeamResonances().add(ElementType.Default.getTeamResonanceId());
|
||||
this.getTeamResonancesConfig().add(ElementType.Default.getTeamResonanceId());
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTeamEntities(GenshinPacket responsePacket) {
|
||||
// Sanity check - Should never happen
|
||||
if (this.getCurrentTeamInfo().getAvatars().size() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If current team has changed
|
||||
EntityAvatar currentEntity = this.getCurrentAvatarEntity();
|
||||
Int2ObjectMap<EntityAvatar> existingAvatars = new Int2ObjectOpenHashMap<>();
|
||||
int prevSelectedAvatarIndex = -1;
|
||||
|
||||
for (EntityAvatar entity : getActiveTeam()) {
|
||||
existingAvatars.put(entity.getAvatar().getAvatarId(), entity);
|
||||
}
|
||||
|
||||
// Clear active team entity list
|
||||
this.getActiveTeam().clear();
|
||||
|
||||
// Add back entities into team
|
||||
for (int i = 0; i < this.getCurrentTeamInfo().getAvatars().size(); i++) {
|
||||
int avatarId = this.getCurrentTeamInfo().getAvatars().get(i);
|
||||
EntityAvatar entity = null;
|
||||
|
||||
if (existingAvatars.containsKey(avatarId)) {
|
||||
entity = existingAvatars.get(avatarId);
|
||||
existingAvatars.remove(avatarId);
|
||||
if (entity == currentEntity) {
|
||||
prevSelectedAvatarIndex = i;
|
||||
}
|
||||
} else {
|
||||
entity = new EntityAvatar(getPlayer().getWorld(), getPlayer().getAvatars().getAvatarById(avatarId));
|
||||
}
|
||||
|
||||
this.getActiveTeam().add(entity);
|
||||
}
|
||||
|
||||
// Unload removed entities
|
||||
for (EntityAvatar entity : existingAvatars.values()) {
|
||||
getPlayer().getWorld().removeEntity(entity);
|
||||
entity.getAvatar().save();
|
||||
}
|
||||
|
||||
// Set new selected character index
|
||||
if (prevSelectedAvatarIndex == -1) {
|
||||
// Previous selected avatar is not in the same spot, we will select the current one in the prev slot
|
||||
prevSelectedAvatarIndex = Math.min(this.currentCharacterIndex, getCurrentTeamInfo().getAvatars().size() - 1);
|
||||
}
|
||||
this.currentCharacterIndex = prevSelectedAvatarIndex;
|
||||
|
||||
// Update team resonances
|
||||
updateTeamResonances();
|
||||
|
||||
// Packets
|
||||
getPlayer().getWorld().broadcastPacket(new PacketSceneTeamUpdateNotify(getPlayer()));
|
||||
|
||||
// Run callback
|
||||
if (responsePacket != null) {
|
||||
getPlayer().sendPacket(responsePacket);
|
||||
}
|
||||
|
||||
// Check if character changed
|
||||
if (currentEntity != getCurrentAvatarEntity()) {
|
||||
// Remove and Add
|
||||
getWorld().replaceEntity(currentEntity, getCurrentAvatarEntity());
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void setupAvatarTeam(int teamId, List<Long> list) {
|
||||
// Sanity checks
|
||||
if (list.size() == 0 || list.size() > getMaxTeamSize() || getPlayer().isInMultiplayer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get team
|
||||
TeamInfo teamInfo = this.getTeams().get(teamId);
|
||||
if (teamInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set team data
|
||||
LinkedHashSet<GenshinAvatar> newTeam = new LinkedHashSet<>();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
GenshinAvatar avatar = getPlayer().getAvatars().getAvatarByGuid(list.get(i));
|
||||
if (avatar == null || newTeam.contains(avatar)) {
|
||||
// Should never happen
|
||||
return;
|
||||
}
|
||||
newTeam.add(avatar);
|
||||
}
|
||||
|
||||
// Clear current team info and add avatars from our new team
|
||||
teamInfo.getAvatars().clear();
|
||||
for (GenshinAvatar avatar : newTeam) {
|
||||
teamInfo.addAvatar(avatar);
|
||||
}
|
||||
|
||||
// Update packet
|
||||
getPlayer().sendPacket(new PacketAvatarTeamUpdateNotify(getPlayer()));
|
||||
|
||||
// Update entites
|
||||
if (teamId == this.getCurrentTeamId()) {
|
||||
this.updateTeamEntities(new PacketSetUpAvatarTeamRsp(getPlayer(), teamId, teamInfo));
|
||||
} else {
|
||||
getPlayer().sendPacket(new PacketSetUpAvatarTeamRsp(getPlayer(), teamId, teamInfo));
|
||||
}
|
||||
}
|
||||
|
||||
public void setupMpTeam(List<Long> list) {
|
||||
// Sanity checks
|
||||
if (list.size() == 0 || list.size() > getMaxTeamSize() || !getPlayer().isInMultiplayer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
TeamInfo teamInfo = this.getMpTeam();
|
||||
|
||||
// Set team data
|
||||
LinkedHashSet<GenshinAvatar> newTeam = new LinkedHashSet<>();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
GenshinAvatar avatar = getPlayer().getAvatars().getAvatarByGuid(list.get(i));
|
||||
if (avatar == null || newTeam.contains(avatar)) {
|
||||
// Should never happen
|
||||
return;
|
||||
}
|
||||
newTeam.add(avatar);
|
||||
}
|
||||
|
||||
// Clear current team info and add avatars from our new team
|
||||
teamInfo.getAvatars().clear();
|
||||
for (GenshinAvatar avatar : newTeam) {
|
||||
teamInfo.addAvatar(avatar);
|
||||
}
|
||||
|
||||
// Packet
|
||||
this.updateTeamEntities(new PacketChangeMpTeamAvatarRsp(getPlayer(), teamInfo));
|
||||
}
|
||||
|
||||
public synchronized void setCurrentTeam(int teamId) {
|
||||
//
|
||||
if (getPlayer().isInMultiplayer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get team
|
||||
TeamInfo teamInfo = this.getTeams().get(teamId);
|
||||
if (teamInfo == null || teamInfo.getAvatars().size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set
|
||||
this.setCurrentTeamId(teamId);
|
||||
this.updateTeamEntities(new PacketChooseCurAvatarTeamRsp(teamId));
|
||||
}
|
||||
|
||||
public synchronized void setTeamName(int teamId, String teamName) {
|
||||
// Get team
|
||||
TeamInfo teamInfo = this.getTeams().get(teamId);
|
||||
if (teamInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
teamInfo.setName(teamName);
|
||||
|
||||
// Packet
|
||||
getPlayer().sendPacket(new PacketChangeTeamNameRsp(teamId, teamName));
|
||||
}
|
||||
|
||||
public synchronized void changeAvatar(long guid) {
|
||||
EntityAvatar oldEntity = this.getCurrentAvatarEntity();
|
||||
|
||||
if (guid == oldEntity.getAvatar().getGuid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
EntityAvatar newEntity = null;
|
||||
int index = -1;
|
||||
for (int i = 0; i < getActiveTeam().size(); i++) {
|
||||
if (guid == getActiveTeam().get(i).getAvatar().getGuid()) {
|
||||
index = i;
|
||||
newEntity = getActiveTeam().get(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (index < 0 || newEntity == oldEntity) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set index
|
||||
this.setCurrentCharacterIndex(index);
|
||||
|
||||
// Old entity motion state
|
||||
oldEntity.setMotionState(MotionState.MotionStandby);
|
||||
|
||||
// Remove and Add
|
||||
getWorld().replaceEntity(oldEntity, newEntity);
|
||||
getPlayer().sendPacket(new PacketChangeAvatarRsp(guid));
|
||||
}
|
||||
|
||||
public void onAvatarDie(long dieGuid) {
|
||||
EntityAvatar deadAvatar = this.getCurrentAvatarEntity();
|
||||
|
||||
if (deadAvatar.isAlive() || deadAvatar.getId() != dieGuid) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Replacement avatar
|
||||
EntityAvatar replacement = null;
|
||||
int replaceIndex = -1;
|
||||
|
||||
for (int i = 0; i < this.getActiveTeam().size(); i++) {
|
||||
EntityAvatar entity = this.getActiveTeam().get(i);
|
||||
if (entity.isAlive()) {
|
||||
replaceIndex = i;
|
||||
replacement = entity;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (replacement == null) {
|
||||
// No more living team members...
|
||||
getPlayer().sendPacket(new PacketWorldPlayerDieNotify(deadAvatar.getKilledType(), deadAvatar.getKilledBy()));
|
||||
} else {
|
||||
// Set index and spawn replacement member
|
||||
this.setCurrentCharacterIndex(replaceIndex);
|
||||
getWorld().addEntity(replacement);
|
||||
}
|
||||
|
||||
// Response packet
|
||||
getPlayer().sendPacket(new PacketAvatarDieAnimationEndRsp(deadAvatar.getId(), 0));
|
||||
}
|
||||
|
||||
public boolean reviveAvatar(GenshinAvatar avatar) {
|
||||
for (EntityAvatar entity : getActiveTeam()) {
|
||||
if (entity.getAvatar() == avatar) {
|
||||
if (entity.isAlive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
entity.setFightProperty(
|
||||
FightProperty.FIGHT_PROP_CUR_HP,
|
||||
entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * .1f
|
||||
);
|
||||
getPlayer().sendPacket(new PacketAvatarFightPropUpdateNotify(entity.getAvatar(), FightProperty.FIGHT_PROP_CUR_HP));
|
||||
getPlayer().sendPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void respawnTeam() {
|
||||
// Make sure all team members are dead
|
||||
for (EntityAvatar entity : getActiveTeam()) {
|
||||
if (entity.isAlive()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Revive all team members
|
||||
for (EntityAvatar entity : getActiveTeam()) {
|
||||
entity.setFightProperty(
|
||||
FightProperty.FIGHT_PROP_CUR_HP,
|
||||
entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * .4f
|
||||
);
|
||||
getPlayer().sendPacket(new PacketAvatarFightPropUpdateNotify(entity.getAvatar(), FightProperty.FIGHT_PROP_CUR_HP));
|
||||
getPlayer().sendPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar()));
|
||||
}
|
||||
|
||||
// Teleport player
|
||||
getPlayer().sendPacket(new PacketPlayerEnterSceneNotify(getPlayer(), EnterType.EnterSelf, EnterReason.Revival, 3, GenshinConstants.START_POSITION));
|
||||
|
||||
// Set player position
|
||||
player.setSceneId(3);
|
||||
player.getPos().set(GenshinConstants.START_POSITION);
|
||||
|
||||
// Packets
|
||||
getPlayer().sendPacket(new GenshinPacket(PacketOpcodes.WorldPlayerReviveRsp));
|
||||
}
|
||||
|
||||
public void saveAvatars() {
|
||||
// Save all avatars from active team
|
||||
for (EntityAvatar entity : getActiveTeam()) {
|
||||
entity.getAvatar().save();
|
||||
}
|
||||
}
|
||||
}
|
||||
434
src/main/java/emu/grasscutter/game/World.java
Normal file
434
src/main/java/emu/grasscutter/game/World.java
Normal file
@@ -0,0 +1,434 @@
|
||||
package emu.grasscutter.game;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import emu.grasscutter.game.entity.GenshinEntity;
|
||||
import emu.grasscutter.game.props.ClimateType;
|
||||
import emu.grasscutter.game.props.EnterReason;
|
||||
import emu.grasscutter.game.props.EntityIdType;
|
||||
import emu.grasscutter.game.props.FightProperty;
|
||||
import emu.grasscutter.game.props.LifeState;
|
||||
import emu.grasscutter.game.GenshinPlayer.SceneLoadState;
|
||||
import emu.grasscutter.game.entity.EntityAvatar;
|
||||
import emu.grasscutter.game.entity.EntityClientGadget;
|
||||
import emu.grasscutter.net.packet.GenshinPacket;
|
||||
import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
|
||||
import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType;
|
||||
import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType;
|
||||
import emu.grasscutter.server.packet.send.PacketDelTeamEntityNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketPlayerEnterSceneNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketSceneEntityDisappearNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketScenePlayerInfoNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketSceneTeamUpdateNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketSyncScenePlayTeamEntityNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketSyncTeamEntityNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketWorldPlayerInfoNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketWorldPlayerRTTNotify;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
public class World implements Iterable<GenshinPlayer> {
|
||||
private final GenshinPlayer owner;
|
||||
private final List<GenshinPlayer> players;
|
||||
|
||||
private int levelEntityId;
|
||||
private int nextEntityId = 0;
|
||||
private int nextPeerId = 0;
|
||||
private final Int2ObjectMap<GenshinEntity> entities;
|
||||
|
||||
private int worldLevel;
|
||||
private int sceneId;
|
||||
private int time;
|
||||
private ClimateType climate;
|
||||
private boolean isMultiplayer;
|
||||
private boolean isDungeon;
|
||||
|
||||
public World(GenshinPlayer player) {
|
||||
this(player, false);
|
||||
}
|
||||
|
||||
public World(GenshinPlayer player, boolean isMultiplayer) {
|
||||
this.owner = player;
|
||||
this.players = Collections.synchronizedList(new ArrayList<>());
|
||||
this.entities = new Int2ObjectOpenHashMap<>();
|
||||
this.levelEntityId = getNextEntityId(EntityIdType.MPLEVEL);
|
||||
this.sceneId = player.getSceneId();
|
||||
this.time = 8 * 60;
|
||||
this.climate = ClimateType.CLIMATE_SUNNY;
|
||||
this.worldLevel = player.getWorldLevel();
|
||||
this.isMultiplayer = isMultiplayer;
|
||||
}
|
||||
|
||||
public GenshinPlayer getHost() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public int getLevelEntityId() {
|
||||
return levelEntityId;
|
||||
}
|
||||
|
||||
public int getHostPeerId() {
|
||||
if (this.getHost() == null) {
|
||||
return 0;
|
||||
}
|
||||
return this.getHost().getPeerId();
|
||||
}
|
||||
|
||||
public int getNextPeerId() {
|
||||
return ++this.nextPeerId;
|
||||
}
|
||||
|
||||
public int getSceneId() {
|
||||
return sceneId;
|
||||
}
|
||||
|
||||
public void setSceneId(int sceneId) {
|
||||
this.sceneId = sceneId;
|
||||
}
|
||||
|
||||
public int getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void changeTime(int time) {
|
||||
this.time = time % 1440;
|
||||
}
|
||||
|
||||
public int getWorldLevel() {
|
||||
return worldLevel;
|
||||
}
|
||||
|
||||
public void setWorldLevel(int worldLevel) {
|
||||
this.worldLevel = worldLevel;
|
||||
}
|
||||
|
||||
public ClimateType getClimate() {
|
||||
return climate;
|
||||
}
|
||||
|
||||
public void setClimate(ClimateType climate) {
|
||||
this.climate = climate;
|
||||
}
|
||||
|
||||
public List<GenshinPlayer> getPlayers() {
|
||||
return players;
|
||||
}
|
||||
|
||||
public int getPlayerCount() {
|
||||
return getPlayers().size();
|
||||
}
|
||||
|
||||
public Int2ObjectMap<GenshinEntity> getEntities() {
|
||||
return this.entities;
|
||||
}
|
||||
|
||||
public boolean isInWorld(GenshinEntity entity) {
|
||||
return this.entities.containsKey(entity.getId());
|
||||
}
|
||||
|
||||
public boolean isMultiplayer() {
|
||||
return isMultiplayer;
|
||||
}
|
||||
|
||||
public boolean isDungeon() {
|
||||
return isDungeon;
|
||||
}
|
||||
|
||||
public int getNextEntityId(EntityIdType idType) {
|
||||
return (idType.getId() << 24) + ++this.nextEntityId;
|
||||
}
|
||||
|
||||
public GenshinEntity getEntityById(int id) {
|
||||
return this.entities.get(id);
|
||||
}
|
||||
|
||||
public synchronized void addPlayer(GenshinPlayer player) {
|
||||
// Check if player already in
|
||||
if (getPlayers().contains(player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove player from prev world
|
||||
if (player.getWorld() != null) {
|
||||
player.getWorld().removePlayer(player);
|
||||
}
|
||||
|
||||
// Register
|
||||
player.setWorld(this);
|
||||
getPlayers().add(player);
|
||||
|
||||
player.setPeerId(this.getNextPeerId());
|
||||
player.getTeamManager().setEntityId(getNextEntityId(EntityIdType.TEAM));
|
||||
|
||||
// TODO Update team of all players
|
||||
this.setupPlayerAvatars(player);
|
||||
|
||||
// Info packet for other players
|
||||
if (this.getPlayers().size() > 1) {
|
||||
this.updatePlayerInfos(player);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void removePlayer(GenshinPlayer player) {
|
||||
// Remove team entities
|
||||
player.sendPacket(
|
||||
new PacketDelTeamEntityNotify(
|
||||
player.getSceneId(),
|
||||
getPlayers().stream().map(p -> p.getTeamManager().getEntityId()).collect(Collectors.toList())
|
||||
)
|
||||
);
|
||||
|
||||
// Deregister
|
||||
getPlayers().remove(player);
|
||||
player.setWorld(null);
|
||||
|
||||
this.removePlayerAvatars(player);
|
||||
|
||||
// Info packet for other players
|
||||
if (this.getPlayers().size() > 0) {
|
||||
this.updatePlayerInfos(player);
|
||||
}
|
||||
|
||||
// Disband world if host leaves
|
||||
if (getHost() == player) {
|
||||
List<GenshinPlayer> kicked = new ArrayList<>(this.getPlayers());
|
||||
for (GenshinPlayer victim : kicked) {
|
||||
World world = new World(victim);
|
||||
world.addPlayer(victim);
|
||||
|
||||
victim.sendPacket(new PacketPlayerEnterSceneNotify(victim, EnterType.EnterSelf, EnterReason.TeamKick, victim.getWorld().getSceneId(), victim.getPos()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePlayerInfos(GenshinPlayer paramPlayer) {
|
||||
for (GenshinPlayer player : getPlayers()) {
|
||||
// Dont send packets if player is loading in
|
||||
if (!player.hasSentAvatarDataNotify() || player.getSceneLoadState().getValue() < SceneLoadState.INIT.getValue() || player == paramPlayer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// World player info packets
|
||||
player.getSession().send(new PacketWorldPlayerInfoNotify(this));
|
||||
player.getSession().send(new PacketScenePlayerInfoNotify(this));
|
||||
player.getSession().send(new PacketWorldPlayerRTTNotify(this));
|
||||
|
||||
// Team packets
|
||||
player.getSession().send(new PacketSceneTeamUpdateNotify(player));
|
||||
player.getSession().send(new PacketSyncTeamEntityNotify(player));
|
||||
player.getSession().send(new PacketSyncScenePlayTeamEntityNotify(player));
|
||||
}
|
||||
}
|
||||
|
||||
private void addEntityDirectly(GenshinEntity entity) {
|
||||
getEntities().put(entity.getId(), entity);
|
||||
}
|
||||
|
||||
public synchronized void addEntity(GenshinEntity entity) {
|
||||
this.addEntityDirectly(entity);
|
||||
this.broadcastPacket(new PacketSceneEntityAppearNotify(entity));
|
||||
}
|
||||
|
||||
public synchronized void addEntities(Collection<GenshinEntity> entities) {
|
||||
for (GenshinEntity entity : entities) {
|
||||
this.addEntityDirectly(entity);
|
||||
}
|
||||
|
||||
this.broadcastPacket(new PacketSceneEntityAppearNotify(entities, VisionType.VisionBorn));
|
||||
}
|
||||
|
||||
private GenshinEntity removeEntityDirectly(GenshinEntity entity) {
|
||||
return getEntities().remove(entity.getId());
|
||||
}
|
||||
|
||||
public void removeEntity(GenshinEntity entity) {
|
||||
this.removeEntity(entity, VisionType.VisionDie);
|
||||
}
|
||||
|
||||
public synchronized void removeEntity(GenshinEntity entity, VisionType visionType) {
|
||||
GenshinEntity removed = this.removeEntityDirectly(entity);
|
||||
if (removed != null) {
|
||||
this.broadcastPacket(new PacketSceneEntityDisappearNotify(removed, visionType));
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void replaceEntity(EntityAvatar oldEntity, EntityAvatar newEntity) {
|
||||
this.removeEntityDirectly(oldEntity);
|
||||
this.addEntityDirectly(newEntity);
|
||||
this.broadcastPacket(new PacketSceneEntityDisappearNotify(oldEntity, VisionType.VisionReplace));
|
||||
this.broadcastPacket(new PacketSceneEntityAppearNotify(newEntity, VisionType.VisionReplace, oldEntity.getId()));
|
||||
}
|
||||
|
||||
private void setupPlayerAvatars(GenshinPlayer player) {
|
||||
// Copy main team to mp team
|
||||
if (this.isMultiplayer()) {
|
||||
player.getTeamManager().getMpTeam().copyFrom(player.getTeamManager().getCurrentSinglePlayerTeamInfo(), player.getTeamManager().getMaxTeamSize());
|
||||
}
|
||||
|
||||
// Clear entities from old team
|
||||
player.getTeamManager().getActiveTeam().clear();
|
||||
|
||||
// Add new entities for player
|
||||
TeamInfo teamInfo = player.getTeamManager().getCurrentTeamInfo();
|
||||
for (int avatarId : teamInfo.getAvatars()) {
|
||||
EntityAvatar entity = new EntityAvatar(this, player.getAvatars().getAvatarById(avatarId));
|
||||
player.getTeamManager().getActiveTeam().add(entity);
|
||||
}
|
||||
}
|
||||
|
||||
private void removePlayerAvatars(GenshinPlayer player) {
|
||||
Iterator<EntityAvatar> it = player.getTeamManager().getActiveTeam().iterator();
|
||||
while (it.hasNext()) {
|
||||
this.removeEntity(it.next(), VisionType.VisionRemove);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
public void spawnPlayer(GenshinPlayer player) {
|
||||
if (isInWorld(player.getTeamManager().getCurrentAvatarEntity())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.getTeamManager().getCurrentAvatarEntity().getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) <= 0f) {
|
||||
player.getTeamManager().getCurrentAvatarEntity().setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 1f);
|
||||
}
|
||||
|
||||
this.addEntity(player.getTeamManager().getCurrentAvatarEntity());
|
||||
}
|
||||
|
||||
public void showOtherEntities(GenshinPlayer player) {
|
||||
List<GenshinEntity> entities = new LinkedList<>();
|
||||
GenshinEntity currentEntity = player.getTeamManager().getCurrentAvatarEntity();
|
||||
|
||||
for (GenshinEntity entity : this.getEntities().values()) {
|
||||
if (entity == currentEntity) {
|
||||
continue;
|
||||
}
|
||||
entities.add(entity);
|
||||
}
|
||||
|
||||
player.sendPacket(new PacketSceneEntityAppearNotify(entities, VisionType.VisionMeet));
|
||||
}
|
||||
|
||||
public void handleAttack(AttackResult result) {
|
||||
//GenshinEntity attacker = getEntityById(result.getAttackerId());
|
||||
GenshinEntity target = getEntityById(result.getDefenseId());
|
||||
|
||||
if (target == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Godmode check
|
||||
if (target instanceof EntityAvatar) {
|
||||
if (((EntityAvatar) target).getPlayer().hasGodmode()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Lose hp
|
||||
target.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, -result.getDamage());
|
||||
|
||||
// Check if dead
|
||||
boolean isDead = false;
|
||||
if (target.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) <= 0f) {
|
||||
target.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f);
|
||||
isDead = true;
|
||||
}
|
||||
|
||||
// Packets
|
||||
this.broadcastPacket(new PacketEntityFightPropUpdateNotify(target, FightProperty.FIGHT_PROP_CUR_HP));
|
||||
|
||||
// Check if dead
|
||||
if (isDead) {
|
||||
this.killEntity(target, result.getAttackerId());
|
||||
}
|
||||
}
|
||||
|
||||
public void killEntity(GenshinEntity target, int attackerId) {
|
||||
// Packet
|
||||
this.broadcastPacket(new PacketLifeStateChangeNotify(attackerId, target, LifeState.LIFE_DEAD));
|
||||
this.removeEntity(target);
|
||||
|
||||
// Death event
|
||||
target.onDeath(attackerId);
|
||||
}
|
||||
|
||||
// Gadgets
|
||||
|
||||
public void onPlayerCreateGadget(EntityClientGadget gadget) {
|
||||
// Directly add
|
||||
this.addEntityDirectly(gadget);
|
||||
|
||||
// Add to owner's gadget list TODO
|
||||
|
||||
// Optimization
|
||||
if (this.getPlayerCount() == 1 && this.getPlayers().get(0) == gadget.getOwner()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.broadcastPacketToOthers(gadget.getOwner(), new PacketSceneEntityAppearNotify(gadget));
|
||||
}
|
||||
|
||||
public void onPlayerDestroyGadget(int entityId) {
|
||||
GenshinEntity entity = getEntities().get(entityId);
|
||||
|
||||
if (entity == null || !(entity instanceof EntityClientGadget)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get and remove entity
|
||||
EntityClientGadget gadget = (EntityClientGadget) entity;
|
||||
this.removeEntityDirectly(gadget);
|
||||
|
||||
// Remove from owner's gadget list TODO
|
||||
|
||||
// Optimization
|
||||
if (this.getPlayerCount() == 1 && this.getPlayers().get(0) == gadget.getOwner()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.broadcastPacketToOthers(gadget.getOwner(), new PacketSceneEntityDisappearNotify(gadget, VisionType.VisionDie));
|
||||
}
|
||||
|
||||
// Broadcasting
|
||||
|
||||
public void broadcastPacket(GenshinPacket packet) {
|
||||
// Send to all players - might have to check if player has been sent data packets
|
||||
for (GenshinPlayer player : this.getPlayers()) {
|
||||
player.getSession().send(packet);
|
||||
}
|
||||
}
|
||||
|
||||
public void broadcastPacketToOthers(GenshinPlayer excludedPlayer, GenshinPacket packet) {
|
||||
// Optimization
|
||||
if (this.getPlayerCount() == 1 && this.getPlayers().get(0) == excludedPlayer) {
|
||||
return;
|
||||
}
|
||||
// Send to all players - might have to check if player has been sent data packets
|
||||
for (GenshinPlayer player : this.getPlayers()) {
|
||||
if (player == excludedPlayer) {
|
||||
continue;
|
||||
}
|
||||
// Send
|
||||
player.getSession().send(packet);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<GenshinPlayer> iterator() {
|
||||
return getPlayers().iterator();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package emu.grasscutter.game.avatar;
|
||||
|
||||
public class AvatarProfileData {
|
||||
private int avatarId;
|
||||
private int level;
|
||||
|
||||
public AvatarProfileData(GenshinAvatar avatar) {
|
||||
this.update(avatar);
|
||||
}
|
||||
|
||||
public int getAvatarId() {
|
||||
return avatarId;
|
||||
}
|
||||
|
||||
public int getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
public void update(GenshinAvatar avatar) {
|
||||
this.avatarId = avatar.getAvatarId();
|
||||
this.level = avatar.getLevel();
|
||||
}
|
||||
}
|
||||
124
src/main/java/emu/grasscutter/game/avatar/AvatarStat.java
Normal file
124
src/main/java/emu/grasscutter/game/avatar/AvatarStat.java
Normal file
@@ -0,0 +1,124 @@
|
||||
package emu.grasscutter.game.avatar;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
public enum AvatarStat {
|
||||
FIGHT_PROP_NONE(0),
|
||||
FIGHT_PROP_BASE_HP(1),
|
||||
FIGHT_PROP_HP(2),
|
||||
FIGHT_PROP_HP_PERCENT(3),
|
||||
FIGHT_PROP_BASE_ATTACK(4),
|
||||
FIGHT_PROP_ATTACK(5),
|
||||
FIGHT_PROP_ATTACK_PERCENT(6),
|
||||
FIGHT_PROP_BASE_DEFENSE(7),
|
||||
FIGHT_PROP_DEFENSE(8),
|
||||
FIGHT_PROP_DEFENSE_PERCENT(9),
|
||||
FIGHT_PROP_BASE_SPEED(10),
|
||||
FIGHT_PROP_SPEED_PERCENT(11),
|
||||
FIGHT_PROP_HP_MP_PERCENT(12),
|
||||
FIGHT_PROP_ATTACK_MP_PERCENT(13),
|
||||
FIGHT_PROP_CRITICAL(20),
|
||||
FIGHT_PROP_ANTI_CRITICAL(21),
|
||||
FIGHT_PROP_CRITICAL_HURT(22),
|
||||
FIGHT_PROP_CHARGE_EFFICIENCY(23),
|
||||
FIGHT_PROP_ADD_HURT(24),
|
||||
FIGHT_PROP_SUB_HURT(25),
|
||||
FIGHT_PROP_HEAL_ADD(26),
|
||||
FIGHT_PROP_HEALED_ADD(27),
|
||||
FIGHT_PROP_ELEMENT_MASTERY(28),
|
||||
FIGHT_PROP_PHYSICAL_SUB_HURT(29),
|
||||
FIGHT_PROP_PHYSICAL_ADD_HURT(30),
|
||||
FIGHT_PROP_DEFENCE_IGNORE_RATIO(31),
|
||||
FIGHT_PROP_DEFENCE_IGNORE_DELTA(32),
|
||||
FIGHT_PROP_FIRE_ADD_HURT(40),
|
||||
FIGHT_PROP_ELEC_ADD_HURT(41),
|
||||
FIGHT_PROP_WATER_ADD_HURT(42),
|
||||
FIGHT_PROP_GRASS_ADD_HURT(43),
|
||||
FIGHT_PROP_WIND_ADD_HURT(44),
|
||||
FIGHT_PROP_ROCK_ADD_HURT(45),
|
||||
FIGHT_PROP_ICE_ADD_HURT(46),
|
||||
FIGHT_PROP_HIT_HEAD_ADD_HURT(47),
|
||||
FIGHT_PROP_FIRE_SUB_HURT(50),
|
||||
FIGHT_PROP_ELEC_SUB_HURT(51),
|
||||
FIGHT_PROP_WATER_SUB_HURT(52),
|
||||
FIGHT_PROP_GRASS_SUB_HURT(53),
|
||||
FIGHT_PROP_WIND_SUB_HURT(54),
|
||||
FIGHT_PROP_ROCK_SUB_HURT(55),
|
||||
FIGHT_PROP_ICE_SUB_HURT(56),
|
||||
FIGHT_PROP_EFFECT_HIT(60),
|
||||
FIGHT_PROP_EFFECT_RESIST(61),
|
||||
FIGHT_PROP_FREEZE_RESIST(62),
|
||||
FIGHT_PROP_TORPOR_RESIST(63),
|
||||
FIGHT_PROP_DIZZY_RESIST(64),
|
||||
FIGHT_PROP_FREEZE_SHORTEN(65),
|
||||
FIGHT_PROP_TORPOR_SHORTEN(66),
|
||||
FIGHT_PROP_DIZZY_SHORTEN(67),
|
||||
FIGHT_PROP_MAX_FIRE_ENERGY(70),
|
||||
FIGHT_PROP_MAX_ELEC_ENERGY(71),
|
||||
FIGHT_PROP_MAX_WATER_ENERGY(72),
|
||||
FIGHT_PROP_MAX_GRASS_ENERGY(73),
|
||||
FIGHT_PROP_MAX_WIND_ENERGY(74),
|
||||
FIGHT_PROP_MAX_ICE_ENERGY(75),
|
||||
FIGHT_PROP_MAX_ROCK_ENERGY(76),
|
||||
FIGHT_PROP_SKILL_CD_MINUS_RATIO(80),
|
||||
FIGHT_PROP_SHIELD_COST_MINUS_RATIO(81),
|
||||
FIGHT_PROP_CUR_FIRE_ENERGY(1000),
|
||||
FIGHT_PROP_CUR_ELEC_ENERGY(1001),
|
||||
FIGHT_PROP_CUR_WATER_ENERGY(1002),
|
||||
FIGHT_PROP_CUR_GRASS_ENERGY(1003),
|
||||
FIGHT_PROP_CUR_WIND_ENERGY(1004),
|
||||
FIGHT_PROP_CUR_ICE_ENERGY(1005),
|
||||
FIGHT_PROP_CUR_ROCK_ENERGY(1006),
|
||||
FIGHT_PROP_CUR_HP(1010),
|
||||
FIGHT_PROP_MAX_HP(2000),
|
||||
FIGHT_PROP_CUR_ATTACK(2001),
|
||||
FIGHT_PROP_CUR_DEFENSE(2002),
|
||||
FIGHT_PROP_CUR_SPEED(2003),
|
||||
FIGHT_PROP_NONEXTRA_ATTACK(3000),
|
||||
FIGHT_PROP_NONEXTRA_DEFENSE(3001),
|
||||
FIGHT_PROP_NONEXTRA_CRITICAL(3002),
|
||||
FIGHT_PROP_NONEXTRA_ANTI_CRITICAL(3003),
|
||||
FIGHT_PROP_NONEXTRA_CRITICAL_HURT(3004),
|
||||
FIGHT_PROP_NONEXTRA_CHARGE_EFFICIENCY(3005),
|
||||
FIGHT_PROP_NONEXTRA_ELEMENT_MASTERY(3006),
|
||||
FIGHT_PROP_NONEXTRA_PHYSICAL_SUB_HURT(3007),
|
||||
FIGHT_PROP_NONEXTRA_FIRE_ADD_HURT(3008),
|
||||
FIGHT_PROP_NONEXTRA_ELEC_ADD_HURT(3009),
|
||||
FIGHT_PROP_NONEXTRA_WATER_ADD_HURT(3010),
|
||||
FIGHT_PROP_NONEXTRA_GRASS_ADD_HURT(3011),
|
||||
FIGHT_PROP_NONEXTRA_WIND_ADD_HURT(3012),
|
||||
FIGHT_PROP_NONEXTRA_ROCK_ADD_HURT(3013),
|
||||
FIGHT_PROP_NONEXTRA_ICE_ADD_HURT(3014),
|
||||
FIGHT_PROP_NONEXTRA_FIRE_SUB_HURT(3015),
|
||||
FIGHT_PROP_NONEXTRA_ELEC_SUB_HURT(3016),
|
||||
FIGHT_PROP_NONEXTRA_WATER_SUB_HURT(3017),
|
||||
FIGHT_PROP_NONEXTRA_GRASS_SUB_HURT(3018),
|
||||
FIGHT_PROP_NONEXTRA_WIND_SUB_HURT(3019),
|
||||
FIGHT_PROP_NONEXTRA_ROCK_SUB_HURT(3020),
|
||||
FIGHT_PROP_NONEXTRA_ICE_SUB_HURT(3021),
|
||||
FIGHT_PROP_NONEXTRA_SKILL_CD_MINUS_RATIO(3022),
|
||||
FIGHT_PROP_NONEXTRA_SHIELD_COST_MINUS_RATIO(3023),
|
||||
FIGHT_PROP_NONEXTRA_PHYSICAL_ADD_HURT(3024);
|
||||
|
||||
private final int id;
|
||||
private static final Int2ObjectMap<AvatarStat> map = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
static {
|
||||
Stream.of(values()).forEach(e -> map.put(e.getId(), e));
|
||||
}
|
||||
|
||||
private AvatarStat(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public static AvatarStat getStatById(int value) {
|
||||
return map.getOrDefault(value, FIGHT_PROP_NONE);
|
||||
}
|
||||
}
|
||||
174
src/main/java/emu/grasscutter/game/avatar/AvatarStorage.java
Normal file
174
src/main/java/emu/grasscutter/game/avatar/AvatarStorage.java
Normal file
@@ -0,0 +1,174 @@
|
||||
package emu.grasscutter.game.avatar;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import emu.grasscutter.data.GenshinData;
|
||||
import emu.grasscutter.data.def.AvatarData;
|
||||
import emu.grasscutter.database.DatabaseHelper;
|
||||
import emu.grasscutter.game.GenshinPlayer;
|
||||
import emu.grasscutter.game.entity.EntityAvatar;
|
||||
import emu.grasscutter.game.inventory.GenshinItem;
|
||||
import emu.grasscutter.server.packet.send.PacketAvatarChangeCostumeNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketAvatarFlycloakChangeNotify;
|
||||
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 AvatarStorage implements Iterable<GenshinAvatar> {
|
||||
private final GenshinPlayer player;
|
||||
private final Int2ObjectMap<GenshinAvatar> avatars;
|
||||
private final Long2ObjectMap<GenshinAvatar> avatarsGuid;
|
||||
|
||||
public AvatarStorage(GenshinPlayer player) {
|
||||
this.player = player;
|
||||
this.avatars = new Int2ObjectOpenHashMap<>();
|
||||
this.avatarsGuid = new Long2ObjectOpenHashMap<>();
|
||||
}
|
||||
|
||||
public GenshinPlayer getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
public Int2ObjectMap<GenshinAvatar> getAvatars() {
|
||||
return avatars;
|
||||
}
|
||||
|
||||
public int getAvatarCount() {
|
||||
return this.avatars.size();
|
||||
}
|
||||
|
||||
public GenshinAvatar getAvatarById(int id) {
|
||||
return getAvatars().get(id);
|
||||
}
|
||||
|
||||
public GenshinAvatar getAvatarByGuid(long id) {
|
||||
return avatarsGuid.get(id);
|
||||
}
|
||||
|
||||
public boolean hasAvatar(int id) {
|
||||
return getAvatars().containsKey(id);
|
||||
}
|
||||
|
||||
public boolean addAvatar(GenshinAvatar avatar) {
|
||||
if (avatar.getAvatarData() == null || this.hasAvatar(avatar.getAvatarId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set owner first
|
||||
avatar.setOwner(getPlayer());
|
||||
|
||||
// Put into maps
|
||||
this.avatars.put(avatar.getAvatarId(), avatar);
|
||||
this.avatarsGuid.put(avatar.getGuid(), avatar);
|
||||
|
||||
avatar.save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void addStartingWeapon(GenshinAvatar avatar) {
|
||||
// Make sure avatar owner is this player
|
||||
if (avatar.getPlayer() != this.getPlayer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create weapon
|
||||
GenshinItem weapon = new GenshinItem(avatar.getAvatarData().getInitialWeapon());
|
||||
|
||||
if (weapon.getItemData() != null) {
|
||||
this.getPlayer().getInventory().addItem(weapon);
|
||||
|
||||
avatar.equipItem(weapon, true);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean wearFlycloak(long avatarGuid, int flycloakId) {
|
||||
GenshinAvatar avatar = this.getAvatarByGuid(avatarGuid);
|
||||
|
||||
if (avatar == null || !getPlayer().getFlyCloakList().contains(flycloakId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
avatar.setFlyCloak(flycloakId);
|
||||
avatar.save();
|
||||
|
||||
// Update
|
||||
getPlayer().sendPacket(new PacketAvatarFlycloakChangeNotify(avatar));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean changeCostume(long avatarGuid, int costumeId) {
|
||||
GenshinAvatar avatar = this.getAvatarByGuid(avatarGuid);
|
||||
|
||||
if (avatar == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (costumeId != 0 && !getPlayer().getCostumeList().contains(costumeId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO make sure avatar can wear costume
|
||||
|
||||
avatar.setCostume(costumeId);
|
||||
avatar.save();
|
||||
|
||||
// Update entity
|
||||
EntityAvatar entity = avatar.getAsEntity();
|
||||
if (entity == null) {
|
||||
entity = new EntityAvatar(avatar);
|
||||
getPlayer().sendPacket(new PacketAvatarChangeCostumeNotify(entity));
|
||||
} else {
|
||||
getPlayer().getWorld().broadcastPacket(new PacketAvatarChangeCostumeNotify(entity));
|
||||
}
|
||||
|
||||
// Done
|
||||
return true;
|
||||
}
|
||||
|
||||
public void loadFromDatabase() {
|
||||
List<GenshinAvatar> avatars = DatabaseHelper.getAvatars(getPlayer());
|
||||
|
||||
for (GenshinAvatar avatar : avatars) {
|
||||
// Should never happen
|
||||
if (avatar.getObjectId() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
AvatarData avatarData = GenshinData.getAvatarDataMap().get(avatar.getAvatarId());
|
||||
if (avatarData == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set ownerships
|
||||
avatar.setAvatarData(avatarData);
|
||||
avatar.setOwner(getPlayer());
|
||||
|
||||
// Force recalc of const boosted skills
|
||||
avatar.recalcProudSkillBonusMap();
|
||||
|
||||
// Add to avatar storage
|
||||
this.avatars.put(avatar.getAvatarId(), avatar);
|
||||
this.avatarsGuid.put(avatar.getGuid(), avatar);
|
||||
}
|
||||
}
|
||||
|
||||
public void postLoad() {
|
||||
for (GenshinAvatar avatar : this) {
|
||||
// Weapon check
|
||||
if (avatar.getWeapon() == null) {
|
||||
this.addStartingWeapon(avatar);
|
||||
}
|
||||
// Recalc stats
|
||||
avatar.recalcStats();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<GenshinAvatar> iterator() {
|
||||
return getAvatars().values().iterator();
|
||||
}
|
||||
}
|
||||
695
src/main/java/emu/grasscutter/game/avatar/GenshinAvatar.java
Normal file
695
src/main/java/emu/grasscutter/game/avatar/GenshinAvatar.java
Normal file
@@ -0,0 +1,695 @@
|
||||
package emu.grasscutter.game.avatar;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
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.PrePersist;
|
||||
import dev.morphia.annotations.Transient;
|
||||
import emu.grasscutter.data.GenshinData;
|
||||
import emu.grasscutter.data.common.FightPropData;
|
||||
import emu.grasscutter.data.custom.OpenConfigEntry;
|
||||
import emu.grasscutter.data.def.AvatarData;
|
||||
import emu.grasscutter.data.def.AvatarPromoteData;
|
||||
import emu.grasscutter.data.def.AvatarSkillData;
|
||||
import emu.grasscutter.data.def.AvatarSkillDepotData;
|
||||
import emu.grasscutter.data.def.AvatarSkillDepotData.InherentProudSkillOpens;
|
||||
import emu.grasscutter.data.def.AvatarTalentData;
|
||||
import emu.grasscutter.data.def.EquipAffixData;
|
||||
import emu.grasscutter.data.def.ReliquaryAffixData;
|
||||
import emu.grasscutter.data.def.ReliquaryLevelData;
|
||||
import emu.grasscutter.data.def.ReliquaryMainPropData;
|
||||
import emu.grasscutter.data.def.ReliquarySetData;
|
||||
import emu.grasscutter.data.def.WeaponCurveData;
|
||||
import emu.grasscutter.data.def.WeaponPromoteData;
|
||||
import emu.grasscutter.data.def.ItemData.WeaponProperty;
|
||||
import emu.grasscutter.data.def.ProudSkillData;
|
||||
import emu.grasscutter.database.DatabaseHelper;
|
||||
import emu.grasscutter.game.GenshinPlayer;
|
||||
import emu.grasscutter.game.entity.EntityAvatar;
|
||||
import emu.grasscutter.game.inventory.EquipType;
|
||||
import emu.grasscutter.game.inventory.GenshinItem;
|
||||
import emu.grasscutter.game.props.ElementType;
|
||||
import emu.grasscutter.game.props.EntityIdType;
|
||||
import emu.grasscutter.game.props.FightProperty;
|
||||
import emu.grasscutter.game.props.PlayerProperty;
|
||||
import emu.grasscutter.net.proto.AvatarFetterInfoOuterClass.AvatarFetterInfo;
|
||||
import emu.grasscutter.net.proto.AvatarInfoOuterClass.AvatarInfo;
|
||||
import emu.grasscutter.server.packet.send.PacketAvatarEquipChangeNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketAvatarFightPropNotify;
|
||||
import emu.grasscutter.utils.ProtoHelper;
|
||||
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
@Entity(value = "avatars", noClassnameStored = true)
|
||||
public class GenshinAvatar {
|
||||
@Id private ObjectId id;
|
||||
@Indexed private int ownerId; // Id of player that this avatar belongs to
|
||||
|
||||
@Transient private GenshinPlayer owner;
|
||||
@Transient private AvatarData data;
|
||||
@Transient private long guid; // Player unique id
|
||||
private int avatarId; // Id of avatar
|
||||
|
||||
private int level = 1;
|
||||
private int exp;
|
||||
private int promoteLevel;
|
||||
private int satiation; // ?
|
||||
private int satiationPenalty; // ?
|
||||
private float currentHp;
|
||||
|
||||
@Transient private final Int2ObjectMap<GenshinItem> equips;
|
||||
@Transient private final Int2FloatOpenHashMap fightProp;
|
||||
@Transient private final Set<String> bonusAbilityList;
|
||||
|
||||
private Map<Integer, Integer> skillLevelMap; // Talent levels
|
||||
private Map<Integer, Integer> proudSkillBonusMap; // Talent bonus levels (from const)
|
||||
private int skillDepotId;
|
||||
private int coreProudSkillLevel; // Constellation level
|
||||
private Set<Integer> talentIdList; // Constellation id list
|
||||
private Set<Integer> proudSkillList; // Character passives
|
||||
|
||||
private int flyCloak;
|
||||
private int costume;
|
||||
private int bornTime;
|
||||
|
||||
public GenshinAvatar() {
|
||||
// Morhpia only!
|
||||
this.equips = new Int2ObjectOpenHashMap<>();
|
||||
this.fightProp = new Int2FloatOpenHashMap();
|
||||
this.bonusAbilityList = new HashSet<>();
|
||||
this.proudSkillBonusMap = new HashMap<>(); // TODO Move to genshin avatar
|
||||
}
|
||||
|
||||
// On creation
|
||||
public GenshinAvatar(int avatarId) {
|
||||
this(GenshinData.getAvatarDataMap().get(avatarId));
|
||||
}
|
||||
|
||||
public GenshinAvatar(AvatarData data) {
|
||||
this();
|
||||
this.avatarId = data.getId();
|
||||
this.data = data;
|
||||
this.bornTime = (int) (System.currentTimeMillis() / 1000);
|
||||
this.flyCloak = 140001;
|
||||
|
||||
this.skillLevelMap = new HashMap<>();
|
||||
this.talentIdList = new HashSet<>();
|
||||
this.proudSkillList = new HashSet<>();
|
||||
|
||||
// Combat properties
|
||||
for (FightProperty prop : FightProperty.values()) {
|
||||
if (prop.getId() <= 0 || prop.getId() >= 3000) {
|
||||
continue;
|
||||
}
|
||||
this.setFightProperty(prop, 0f);
|
||||
}
|
||||
|
||||
// Skill depot
|
||||
this.setSkillDepot(getAvatarData().getSkillDepot());
|
||||
|
||||
// Set stats
|
||||
this.recalcStats();
|
||||
this.currentHp = getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
|
||||
setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, this.currentHp);
|
||||
|
||||
// Load handler
|
||||
this.onLoad();
|
||||
}
|
||||
|
||||
public GenshinPlayer getPlayer() {
|
||||
return this.owner;
|
||||
}
|
||||
|
||||
public ObjectId getObjectId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public AvatarData getAvatarData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
protected void setAvatarData(AvatarData data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public int getOwnerId() {
|
||||
return ownerId;
|
||||
}
|
||||
|
||||
public void setOwner(GenshinPlayer player) {
|
||||
this.owner = player;
|
||||
this.ownerId = player.getId();
|
||||
this.guid = player.getNextGuid();
|
||||
}
|
||||
|
||||
public int getSatiation() {
|
||||
return satiation;
|
||||
}
|
||||
|
||||
public void setSatiation(int satiation) {
|
||||
this.satiation = satiation;
|
||||
}
|
||||
|
||||
public int getSatiationPenalty() {
|
||||
return satiationPenalty;
|
||||
}
|
||||
|
||||
public void setSatiationPenalty(int satiationPenalty) {
|
||||
this.satiationPenalty = satiationPenalty;
|
||||
}
|
||||
|
||||
public AvatarData getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public long getGuid() {
|
||||
return guid;
|
||||
}
|
||||
|
||||
public int getAvatarId() {
|
||||
return avatarId;
|
||||
}
|
||||
|
||||
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 getPromoteLevel() {
|
||||
return promoteLevel;
|
||||
}
|
||||
|
||||
public void setPromoteLevel(int promoteLevel) {
|
||||
this.promoteLevel = promoteLevel;
|
||||
}
|
||||
|
||||
public Int2ObjectMap<GenshinItem> getEquips() {
|
||||
return equips;
|
||||
}
|
||||
|
||||
public GenshinItem getEquipBySlot(EquipType slot) {
|
||||
return this.getEquips().get(slot.getValue());
|
||||
}
|
||||
|
||||
private GenshinItem getEquipBySlot(int slotId) {
|
||||
return this.getEquips().get(slotId);
|
||||
}
|
||||
|
||||
public GenshinItem getWeapon() {
|
||||
return this.getEquipBySlot(EquipType.EQUIP_WEAPON);
|
||||
}
|
||||
|
||||
public int getSkillDepotId() {
|
||||
return skillDepotId;
|
||||
}
|
||||
|
||||
public void setSkillDepot(AvatarSkillDepotData skillDepot) {
|
||||
// Set id
|
||||
this.skillDepotId = skillDepot.getId();
|
||||
// Clear, then add skills
|
||||
getSkillLevelMap().clear();
|
||||
if (skillDepot.getEnergySkill() > 0) {
|
||||
getSkillLevelMap().put(skillDepot.getEnergySkill(), 1);
|
||||
}
|
||||
for (int skillId : skillDepot.getSkills()) {
|
||||
if (skillId > 0) {
|
||||
getSkillLevelMap().put(skillId, 1);
|
||||
}
|
||||
}
|
||||
// Add proud skills
|
||||
this.getProudSkillList().clear();
|
||||
for (InherentProudSkillOpens openData : skillDepot.getInherentProudSkillOpens()) {
|
||||
if (openData.getProudSkillGroupId() == 0) {
|
||||
continue;
|
||||
}
|
||||
if (openData.getNeedAvatarPromoteLevel() <= this.getPromoteLevel()) {
|
||||
int proudSkillId = (openData.getProudSkillGroupId() * 100) + 1;
|
||||
if (GenshinData.getProudSkillDataMap().containsKey(proudSkillId)) {
|
||||
this.getProudSkillList().add(proudSkillId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Map<Integer, Integer> getSkillLevelMap() {
|
||||
return skillLevelMap;
|
||||
}
|
||||
|
||||
public Map<Integer, Integer> getProudSkillBonusMap() {
|
||||
return proudSkillBonusMap;
|
||||
}
|
||||
|
||||
public Set<String> getBonusAbilityList() {
|
||||
return bonusAbilityList;
|
||||
}
|
||||
|
||||
public float getCurrentHp() {
|
||||
return currentHp;
|
||||
}
|
||||
|
||||
public void setCurrentHp(float currentHp) {
|
||||
this.currentHp = currentHp;
|
||||
}
|
||||
|
||||
public Int2FloatOpenHashMap getFightProperties() {
|
||||
return fightProp;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public Set<Integer> getTalentIdList() {
|
||||
return talentIdList;
|
||||
}
|
||||
|
||||
public int getCoreProudSkillLevel() {
|
||||
return coreProudSkillLevel;
|
||||
}
|
||||
|
||||
public void setCoreProudSkillLevel(int constLevel) {
|
||||
this.coreProudSkillLevel = constLevel;
|
||||
}
|
||||
|
||||
public Set<Integer> getProudSkillList() {
|
||||
return proudSkillList;
|
||||
}
|
||||
|
||||
public int getFlyCloak() {
|
||||
return flyCloak;
|
||||
}
|
||||
|
||||
public void setFlyCloak(int flyCloak) {
|
||||
this.flyCloak = flyCloak;
|
||||
}
|
||||
|
||||
public int getCostume() {
|
||||
return costume;
|
||||
}
|
||||
|
||||
public void setCostume(int costume) {
|
||||
this.costume = costume;
|
||||
}
|
||||
|
||||
public int getBornTime() {
|
||||
return bornTime;
|
||||
}
|
||||
|
||||
public boolean equipItem(GenshinItem item, boolean shouldRecalc) {
|
||||
EquipType itemEquipType = item.getItemData().getEquipType();
|
||||
if (itemEquipType == EquipType.EQUIP_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getEquips().containsKey(itemEquipType.getValue())) {
|
||||
unequipItem(itemEquipType);
|
||||
}
|
||||
|
||||
getEquips().put(itemEquipType.getValue(), item);
|
||||
|
||||
if (itemEquipType == EquipType.EQUIP_WEAPON && getPlayer().getWorld() != null) {
|
||||
item.setWeaponEntityId(this.getPlayer().getWorld().getNextEntityId(EntityIdType.WEAPON));
|
||||
}
|
||||
|
||||
item.setEquipCharacter(this.getAvatarId());
|
||||
item.save();
|
||||
|
||||
if (shouldRecalc) {
|
||||
this.recalcStats();
|
||||
}
|
||||
|
||||
if (this.getPlayer().hasSentAvatarDataNotify()) {
|
||||
this.getPlayer().sendPacket(new PacketAvatarEquipChangeNotify(this, item));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean unequipItem(EquipType slot) {
|
||||
GenshinItem item = getEquips().remove(slot.getValue());
|
||||
|
||||
if (item != null) {
|
||||
item.setEquipCharacter(0);
|
||||
item.save();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void recalcStats() {
|
||||
// Setup
|
||||
AvatarData data = this.getAvatarData();
|
||||
AvatarPromoteData promoteData = GenshinData.getAvatarPromoteData(data.getAvatarPromoteId(), this.getPromoteLevel());
|
||||
Int2IntOpenHashMap setMap = new Int2IntOpenHashMap();
|
||||
this.getBonusAbilityList().clear();
|
||||
|
||||
// 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.getLevel()));
|
||||
this.setFightProperty(FightProperty.FIGHT_PROP_BASE_ATTACK, data.getBaseAttack(this.getLevel()));
|
||||
this.setFightProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE, data.getBaseDefense(this.getLevel()));
|
||||
this.setFightProperty(FightProperty.FIGHT_PROP_CRITICAL, data.getBaseCritical());
|
||||
this.setFightProperty(FightProperty.FIGHT_PROP_CRITICAL_HURT, data.getBaseCriticalHurt());
|
||||
this.setFightProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, 1f);
|
||||
|
||||
if (promoteData != null) {
|
||||
for (FightPropData fightPropData : promoteData.getAddProps()) {
|
||||
this.addFightProperty(fightPropData.getProp(), fightPropData.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
// Set energy usage
|
||||
if (data.getSkillDepot() != null && data.getSkillDepot().getEnergySkillData() != null) {
|
||||
ElementType element = data.getSkillDepot().getElementType();
|
||||
this.setFightProperty(element.getEnergyProperty(), data.getSkillDepot().getEnergySkillData().getCostElemVal());
|
||||
this.setFightProperty((element.getEnergyProperty().getId() % 70) + 1000, data.getSkillDepot().getEnergySkillData().getCostElemVal());
|
||||
}
|
||||
|
||||
// Artifacts
|
||||
for (int slotId = 1; slotId <= 5; slotId++) {
|
||||
// Get artifact
|
||||
GenshinItem equip = this.getEquipBySlot(slotId);
|
||||
if (equip == null) {
|
||||
continue;
|
||||
}
|
||||
// Artifact main stat
|
||||
ReliquaryMainPropData mainPropData = GenshinData.getReliquaryMainPropDataMap().get(equip.getMainPropId());
|
||||
if (mainPropData != null) {
|
||||
ReliquaryLevelData levelData = GenshinData.getRelicLevelData(equip.getItemData().getRankLevel(), equip.getLevel());
|
||||
if (levelData != null) {
|
||||
this.addFightProperty(mainPropData.getFightProp(), levelData.getPropValue(mainPropData.getFightProp()));
|
||||
}
|
||||
}
|
||||
// Artifact sub stats
|
||||
for (int appendPropId : equip.getAppendPropIdList()) {
|
||||
ReliquaryAffixData affixData = GenshinData.getReliquaryAffixDataMap().get(appendPropId);
|
||||
if (affixData != null) {
|
||||
this.addFightProperty(affixData.getFightProp(), affixData.getPropValue());
|
||||
}
|
||||
}
|
||||
// Set bonus
|
||||
if (equip.getItemData().getSetId() > 0) {
|
||||
setMap.addTo(equip.getItemData().getSetId(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Set stuff
|
||||
for (Int2IntOpenHashMap.Entry e : setMap.int2IntEntrySet()) {
|
||||
ReliquarySetData setData = GenshinData.getReliquarySetDataMap().get(e.getIntKey());
|
||||
if (setData == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate how many items are from the set
|
||||
int amount = e.getIntValue();
|
||||
|
||||
// Add affix data from set bonus
|
||||
for (int setIndex = 0; setIndex < setData.getSetNeedNum().length; setIndex++) {
|
||||
if (amount >= setData.getSetNeedNum()[setIndex]) {
|
||||
int affixId = (setData.getEquipAffixId() * 10) + setIndex;
|
||||
|
||||
EquipAffixData affix = GenshinData.getEquipAffixDataMap().get(affixId);
|
||||
if (affix == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add properties from this affix to our avatar
|
||||
for (FightPropData prop : affix.getAddProps()) {
|
||||
this.addFightProperty(prop.getProp(), prop.getValue());
|
||||
}
|
||||
|
||||
// Add any skill strings from this affix
|
||||
this.addToAbilityList(affix.getOpenConfig(), true);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Weapon
|
||||
GenshinItem weapon = this.getWeapon();
|
||||
if (weapon != null) {
|
||||
// Add stats
|
||||
WeaponCurveData curveData = GenshinData.getWeaponCurveDataMap().get(weapon.getLevel());
|
||||
if (curveData != null) {
|
||||
for (WeaponProperty weaponProperty : weapon.getItemData().getWeaponProperties()) {
|
||||
this.addFightProperty(weaponProperty.getFightProp(), weaponProperty.getInitValue() * curveData.getMultByProp(weaponProperty.getType()));
|
||||
}
|
||||
}
|
||||
// Weapon promotion stats
|
||||
WeaponPromoteData wepPromoteData = GenshinData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), weapon.getPromoteLevel());
|
||||
if (wepPromoteData != null) {
|
||||
for (FightPropData prop : wepPromoteData.getAddProps()) {
|
||||
if (prop.getValue() == 0f || prop.getProp() == null) {
|
||||
continue;
|
||||
}
|
||||
this.addFightProperty(prop.getProp(), prop.getValue());
|
||||
}
|
||||
}
|
||||
// Add weapon skill from affixes
|
||||
if (weapon.getAffixes() != null && weapon.getAffixes().size() > 0) {
|
||||
// Weapons usually dont have more than one affix but just in case...
|
||||
for (int af : weapon.getAffixes()) {
|
||||
if (af == 0) {
|
||||
continue;
|
||||
}
|
||||
// Calculate affix id
|
||||
int affixId = (af * 10) + weapon.getRefinement();
|
||||
EquipAffixData affix = GenshinData.getEquipAffixDataMap().get(affixId);
|
||||
if (affix == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add properties from this affix to our avatar
|
||||
for (FightPropData prop : affix.getAddProps()) {
|
||||
this.addFightProperty(prop.getProp(), prop.getValue());
|
||||
}
|
||||
|
||||
// Add any skill strings from this affix
|
||||
this.addToAbilityList(affix.getOpenConfig(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Proud skills
|
||||
for (int proudSkillId : this.getProudSkillList()) {
|
||||
ProudSkillData proudSkillData = GenshinData.getProudSkillDataMap().get(proudSkillId);
|
||||
if (proudSkillData == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add properties from this proud skill to our avatar
|
||||
for (FightPropData prop : proudSkillData.getAddProps()) {
|
||||
this.addFightProperty(prop.getProp(), prop.getValue());
|
||||
}
|
||||
|
||||
// Add any skill strings from this proud skill
|
||||
this.addToAbilityList(proudSkillData.getOpenConfig(), true);
|
||||
}
|
||||
|
||||
// Constellations
|
||||
if (this.getTalentIdList().size() > 0) {
|
||||
for (int talentId : this.getTalentIdList()) {
|
||||
AvatarTalentData avatarTalentData = GenshinData.getAvatarTalentDataMap().get(talentId);
|
||||
if (avatarTalentData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add any skill strings from this constellation
|
||||
this.addToAbilityList(avatarTalentData.getOpenConfig(), false);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// Packet
|
||||
if (getPlayer() != null && getPlayer().hasSentAvatarDataNotify()) {
|
||||
getPlayer().sendPacket(new PacketAvatarFightPropNotify(this));
|
||||
}
|
||||
}
|
||||
|
||||
public void addToAbilityList(String openConfig, boolean forceAdd) {
|
||||
if (openConfig == null || openConfig.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
OpenConfigEntry entry = GenshinData.getOpenConfigEntries().get(openConfig);
|
||||
if (entry == null) {
|
||||
if (forceAdd) {
|
||||
// Add config string to ability skill list anyways
|
||||
this.getBonusAbilityList().add(openConfig);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.getAddAbilities() != null) {
|
||||
for (String ability : entry.getAddAbilities()) {
|
||||
this.getBonusAbilityList().add(ability);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void recalcProudSkillBonusMap() {
|
||||
// Clear first
|
||||
this.getProudSkillBonusMap().clear();
|
||||
|
||||
// Sanity checks
|
||||
if (getData() == null || getData().getSkillDepot() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.getTalentIdList().size() > 0) {
|
||||
for (int talentId : this.getTalentIdList()) {
|
||||
AvatarTalentData avatarTalentData = GenshinData.getAvatarTalentDataMap().get(talentId);
|
||||
|
||||
if (avatarTalentData == null || avatarTalentData.getOpenConfig() == null || avatarTalentData.getOpenConfig().length() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get open config to find which skill should be boosted
|
||||
OpenConfigEntry entry = GenshinData.getOpenConfigEntries().get(avatarTalentData.getOpenConfig());
|
||||
if (entry == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int skillId = 0;
|
||||
|
||||
if (entry.getExtraTalentIndex() == 2 && this.getData().getSkillDepot().getSkills().size() >= 2) {
|
||||
// E skill
|
||||
skillId = this.getData().getSkillDepot().getSkills().get(1);
|
||||
} else if (entry.getExtraTalentIndex() == 9) {
|
||||
// Ult skill
|
||||
skillId = this.getData().getSkillDepot().getEnergySkill();
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
if (skillId == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get proud skill group id
|
||||
AvatarSkillData skillData = GenshinData.getAvatarSkillDataMap().get(skillId);
|
||||
|
||||
if (skillData == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add to bonus list
|
||||
this.getProudSkillBonusMap().put(skillData.getProudSkillGroupId(), 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public EntityAvatar getAsEntity() {
|
||||
for (EntityAvatar entity : getPlayer().getTeamManager().getActiveTeam()) {
|
||||
if (entity.getAvatar() == this) {
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getEntityId() {
|
||||
EntityAvatar entity = getAsEntity();
|
||||
return entity != null ? entity.getId() : 0;
|
||||
}
|
||||
|
||||
public void save() {
|
||||
DatabaseHelper.saveAvatar(this);
|
||||
}
|
||||
|
||||
public AvatarInfo toProto() {
|
||||
AvatarInfo.Builder avatarInfo = AvatarInfo.newBuilder()
|
||||
.setAvatarId(this.getAvatarId())
|
||||
.setGuid(this.getGuid())
|
||||
.setLifeState(1)
|
||||
.addAllTalentIdList(this.getTalentIdList())
|
||||
.putAllFightPropMap(this.getFightProperties())
|
||||
.setSkillDepotId(this.getSkillDepotId())
|
||||
.setCoreProudSkillLevel(this.getCoreProudSkillLevel())
|
||||
.putAllSkillLevelMap(this.getSkillLevelMap())
|
||||
.addAllInherentProudSkillList(this.getProudSkillList())
|
||||
.putAllProudSkillExtraLevel(getProudSkillBonusMap())
|
||||
.setAvatarType(1)
|
||||
.setBornTime(this.getBornTime())
|
||||
.setFetterInfo(AvatarFetterInfo.newBuilder().setExpLevel(1))
|
||||
.setWearingFlycloakId(this.getFlyCloak())
|
||||
.setCostumeId(this.getCostume());
|
||||
|
||||
for (GenshinItem item : this.getEquips().values()) {
|
||||
avatarInfo.addEquipGuidList(item.getGuid());
|
||||
}
|
||||
|
||||
avatarInfo.putPropMap(PlayerProperty.PROP_LEVEL.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, this.getLevel()));
|
||||
avatarInfo.putPropMap(PlayerProperty.PROP_EXP.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_EXP, this.getExp()));
|
||||
avatarInfo.putPropMap(PlayerProperty.PROP_BREAK_LEVEL.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_BREAK_LEVEL, this.getPromoteLevel()));
|
||||
avatarInfo.putPropMap(PlayerProperty.PROP_SATIATION_VAL.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_SATIATION_VAL, 0));
|
||||
avatarInfo.putPropMap(PlayerProperty.PROP_SATIATION_PENALTY_TIME.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_SATIATION_PENALTY_TIME, 0));
|
||||
|
||||
return avatarInfo.build();
|
||||
}
|
||||
|
||||
@PostLoad
|
||||
private void onLoad() {
|
||||
|
||||
}
|
||||
|
||||
@PrePersist
|
||||
private void prePersist() {
|
||||
this.currentHp = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package emu.grasscutter.game.dungeons;
|
||||
|
||||
import emu.grasscutter.server.game.GameServer;
|
||||
|
||||
public class DungeonManager {
|
||||
private final GameServer server;
|
||||
|
||||
public DungeonManager(GameServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public GameServer getServer() {
|
||||
return server;
|
||||
}
|
||||
}
|
||||
239
src/main/java/emu/grasscutter/game/entity/EntityAvatar.java
Normal file
239
src/main/java/emu/grasscutter/game/entity/EntityAvatar.java
Normal file
@@ -0,0 +1,239 @@
|
||||
package emu.grasscutter.game.entity;
|
||||
|
||||
import emu.grasscutter.GenshinConstants;
|
||||
import emu.grasscutter.data.GenshinData;
|
||||
import emu.grasscutter.data.def.AvatarData;
|
||||
import emu.grasscutter.data.def.AvatarSkillDepotData;
|
||||
import emu.grasscutter.game.GenshinPlayer;
|
||||
import emu.grasscutter.game.World;
|
||||
import emu.grasscutter.game.avatar.GenshinAvatar;
|
||||
import emu.grasscutter.game.inventory.EquipType;
|
||||
import emu.grasscutter.game.inventory.GenshinItem;
|
||||
import emu.grasscutter.game.props.EntityIdType;
|
||||
import emu.grasscutter.game.props.FightProperty;
|
||||
import emu.grasscutter.game.props.PlayerProperty;
|
||||
import emu.grasscutter.net.proto.AbilityControlBlockOuterClass.AbilityControlBlock;
|
||||
import emu.grasscutter.net.proto.AbilityEmbryoOuterClass.AbilityEmbryo;
|
||||
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.PlayerDieTypeOuterClass.PlayerDieType;
|
||||
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
|
||||
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
|
||||
import emu.grasscutter.net.proto.SceneAvatarInfoOuterClass.SceneAvatarInfo;
|
||||
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
|
||||
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
|
||||
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
|
||||
import emu.grasscutter.utils.Position;
|
||||
import emu.grasscutter.utils.ProtoHelper;
|
||||
import emu.grasscutter.utils.Utils;
|
||||
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
|
||||
|
||||
public class EntityAvatar extends GenshinEntity {
|
||||
private final GenshinAvatar avatar;
|
||||
|
||||
private PlayerDieType killedType;
|
||||
private int killedBy;
|
||||
|
||||
public EntityAvatar(World world, GenshinAvatar avatar) {
|
||||
super(world);
|
||||
this.avatar = avatar;
|
||||
this.id = world.getNextEntityId(EntityIdType.AVATAR);
|
||||
|
||||
GenshinItem weapon = this.getAvatar().getWeapon();
|
||||
if (weapon != null) {
|
||||
weapon.setWeaponEntityId(world.getNextEntityId(EntityIdType.WEAPON));
|
||||
}
|
||||
}
|
||||
|
||||
public EntityAvatar(GenshinAvatar avatar) {
|
||||
super(null);
|
||||
this.avatar = avatar;
|
||||
}
|
||||
|
||||
public GenshinPlayer getPlayer() {
|
||||
return avatar.getPlayer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position getPosition() {
|
||||
return getPlayer().getPos();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position getRotation() {
|
||||
return getPlayer().getRotation();
|
||||
}
|
||||
|
||||
public GenshinAvatar getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
public int getKilledBy() {
|
||||
return killedBy;
|
||||
}
|
||||
|
||||
public PlayerDieType getKilledType() {
|
||||
return killedType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAlive() {
|
||||
return this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) > 0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Int2FloatOpenHashMap getFightProperties() {
|
||||
return getAvatar().getFightProperties();
|
||||
}
|
||||
|
||||
public int getWeaponEntityId() {
|
||||
if (getAvatar().getWeapon() != null) {
|
||||
return getAvatar().getWeapon().getWeaponEntityId();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeath(int killerId) {
|
||||
this.killedType = PlayerDieType.PlayerDieKillByMonster;
|
||||
this.killedBy = killerId;
|
||||
}
|
||||
|
||||
public SceneAvatarInfo getSceneAvatarInfo() {
|
||||
SceneAvatarInfo.Builder avatarInfo = SceneAvatarInfo.newBuilder()
|
||||
.setPlayerId(this.getPlayer().getId())
|
||||
.setAvatarId(this.getAvatar().getAvatarId())
|
||||
.setGuid(this.getAvatar().getGuid())
|
||||
.setPeerId(this.getPlayer().getPeerId())
|
||||
.addAllTalentIdList(this.getAvatar().getTalentIdList())
|
||||
.setCoreProudSkillLevel(this.getAvatar().getCoreProudSkillLevel())
|
||||
.putAllSkillLevelMap(this.getAvatar().getSkillLevelMap())
|
||||
.setSkillDepotId(this.getAvatar().getSkillDepotId())
|
||||
.addAllInherentProudSkillList(this.getAvatar().getProudSkillList())
|
||||
.putAllProudSkillExtraLevelMap(this.getAvatar().getProudSkillBonusMap())
|
||||
.addAllTeamResonanceList(this.getAvatar().getPlayer().getTeamManager().getTeamResonances())
|
||||
.setWearingFlycloakId(this.getAvatar().getFlyCloak())
|
||||
.setCostumeId(this.getAvatar().getCostume())
|
||||
.setBornTime(this.getAvatar().getBornTime());
|
||||
|
||||
for (GenshinItem item : avatar.getEquips().values()) {
|
||||
if (item.getItemData().getEquipType() == EquipType.EQUIP_WEAPON) {
|
||||
avatarInfo.setWeapon(item.createSceneWeaponInfo());
|
||||
} else {
|
||||
avatarInfo.addReliquaryList(item.createSceneReliquaryInfo());
|
||||
}
|
||||
avatarInfo.addEquipIdList(item.getItemId());
|
||||
}
|
||||
|
||||
return avatarInfo.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SceneEntityInfo toProto() {
|
||||
EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder()
|
||||
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
|
||||
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
|
||||
.setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder()))
|
||||
.setBornPos(Vector.newBuilder())
|
||||
.build();
|
||||
|
||||
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
|
||||
.setEntityId(getId())
|
||||
.setEntityType(ProtEntityType.ProtEntityAvatar)
|
||||
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
|
||||
.setEntityClientData(EntityClientData.newBuilder())
|
||||
.setEntityAuthorityInfo(authority)
|
||||
.setLastMoveSceneTimeMs(this.getLastMoveSceneTimeMs())
|
||||
.setLastMoveReliableSeq(this.getLastMoveReliableSeq())
|
||||
.setLifeState(this.getLifeState().getValue());
|
||||
|
||||
if (this.getWorld() != null) {
|
||||
entityInfo.setMotionInfo(this.getMotionInfo());
|
||||
}
|
||||
|
||||
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, getAvatar().getLevel()))
|
||||
.build();
|
||||
entityInfo.addPropList(pair);
|
||||
|
||||
entityInfo.setAvatar(this.getSceneAvatarInfo());
|
||||
|
||||
return entityInfo.build();
|
||||
}
|
||||
|
||||
public AbilityControlBlock getAbilityControlBlock() {
|
||||
AvatarData data = this.getAvatar().getAvatarData();
|
||||
AbilityControlBlock.Builder abilityControlBlock = AbilityControlBlock.newBuilder();
|
||||
int embryoId = 0;
|
||||
|
||||
// Add avatar abilities
|
||||
if (data.getAbilities() != null) {
|
||||
for (int id : data.getAbilities()) {
|
||||
AbilityEmbryo emb = AbilityEmbryo.newBuilder()
|
||||
.setAbilityId(++embryoId)
|
||||
.setAbilityNameHash(id)
|
||||
.setAbilityOverrideNameHash(GenshinConstants.DEFAULT_ABILITY_NAME)
|
||||
.build();
|
||||
abilityControlBlock.addAbilityEmbryoList(emb);
|
||||
}
|
||||
}
|
||||
// Add default abilities
|
||||
for (int id : GenshinConstants.DEFAULT_ABILITY_HASHES) {
|
||||
AbilityEmbryo emb = AbilityEmbryo.newBuilder()
|
||||
.setAbilityId(++embryoId)
|
||||
.setAbilityNameHash(id)
|
||||
.setAbilityOverrideNameHash(GenshinConstants.DEFAULT_ABILITY_NAME)
|
||||
.build();
|
||||
abilityControlBlock.addAbilityEmbryoList(emb);
|
||||
}
|
||||
// Add team resonances
|
||||
for (int id : this.getPlayer().getTeamManager().getTeamResonancesConfig()) {
|
||||
AbilityEmbryo emb = AbilityEmbryo.newBuilder()
|
||||
.setAbilityId(++embryoId)
|
||||
.setAbilityNameHash(id)
|
||||
.setAbilityOverrideNameHash(GenshinConstants.DEFAULT_ABILITY_NAME)
|
||||
.build();
|
||||
abilityControlBlock.addAbilityEmbryoList(emb);
|
||||
}
|
||||
// Add skill depot abilities
|
||||
AvatarSkillDepotData skillDepot = GenshinData.getAvatarSkillDepotDataMap().get(this.getAvatar().getSkillDepotId());
|
||||
if (skillDepot != null && skillDepot.getAbilities() != null) {
|
||||
for (int id : skillDepot.getAbilities()) {
|
||||
AbilityEmbryo emb = AbilityEmbryo.newBuilder()
|
||||
.setAbilityId(++embryoId)
|
||||
.setAbilityNameHash(id)
|
||||
.setAbilityOverrideNameHash(GenshinConstants.DEFAULT_ABILITY_NAME)
|
||||
.build();
|
||||
abilityControlBlock.addAbilityEmbryoList(emb);
|
||||
}
|
||||
}
|
||||
// Add equip abilities
|
||||
if (this.getAvatar().getBonusAbilityList().size() > 0) {
|
||||
for (String skill : this.getAvatar().getBonusAbilityList()) {
|
||||
AbilityEmbryo emb = AbilityEmbryo.newBuilder()
|
||||
.setAbilityId(++embryoId)
|
||||
.setAbilityNameHash(Utils.abilityHash(skill))
|
||||
.setAbilityOverrideNameHash(GenshinConstants.DEFAULT_ABILITY_NAME)
|
||||
.build();
|
||||
abilityControlBlock.addAbilityEmbryoList(emb);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
return abilityControlBlock.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package emu.grasscutter.game.entity;
|
||||
|
||||
import emu.grasscutter.game.GenshinPlayer;
|
||||
import emu.grasscutter.game.World;
|
||||
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.EvtCreateGadgetNotifyOuterClass.EvtCreateGadgetNotify;
|
||||
import emu.grasscutter.net.proto.GadgetClientParamOuterClass.GadgetClientParam;
|
||||
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
|
||||
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.SceneGadgetInfoOuterClass.SceneGadgetInfo;
|
||||
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
|
||||
import emu.grasscutter.utils.Position;
|
||||
import emu.grasscutter.utils.ProtoHelper;
|
||||
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
|
||||
|
||||
public class EntityClientGadget extends EntityGadget {
|
||||
private final GenshinPlayer owner;
|
||||
|
||||
private final Position pos;
|
||||
private final Position rot;
|
||||
|
||||
private int configId;
|
||||
private int campId;
|
||||
private int campType;
|
||||
private int ownerEntityId;
|
||||
private int targetEntityId;
|
||||
private boolean asyncLoad;
|
||||
|
||||
public EntityClientGadget(World world, GenshinPlayer player, EvtCreateGadgetNotify notify) {
|
||||
super(world);
|
||||
this.owner = player;
|
||||
this.id = notify.getEntityId();
|
||||
this.pos = new Position(notify.getInitPos());
|
||||
this.rot = new Position(notify.getInitEulerAngles());
|
||||
this.configId = notify.getConfigId();
|
||||
this.campId = notify.getCampId();
|
||||
this.campType = notify.getCampType();
|
||||
this.ownerEntityId = notify.getPropOwnerEntityId();
|
||||
this.targetEntityId = notify.getTargetEntityId();
|
||||
this.asyncLoad = notify.getIsAsyncLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGadgetId() {
|
||||
return configId;
|
||||
}
|
||||
|
||||
public GenshinPlayer getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public int getCampId() {
|
||||
return campId;
|
||||
}
|
||||
|
||||
public int getCampType() {
|
||||
return campType;
|
||||
}
|
||||
|
||||
public int getOwnerEntityId() {
|
||||
return ownerEntityId;
|
||||
}
|
||||
|
||||
public int getTargetEntityId() {
|
||||
return targetEntityId;
|
||||
}
|
||||
|
||||
public boolean isAsyncLoad() {
|
||||
return this.asyncLoad;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeath(int killerId) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Int2FloatOpenHashMap getFightProperties() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position getPosition() {
|
||||
// TODO Auto-generated method stub
|
||||
return this.pos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position getRotation() {
|
||||
// TODO Auto-generated method stub
|
||||
return this.rot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SceneEntityInfo toProto() {
|
||||
EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder()
|
||||
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
|
||||
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
|
||||
.setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder()))
|
||||
.setBornPos(Vector.newBuilder())
|
||||
.build();
|
||||
|
||||
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
|
||||
.setEntityId(getId())
|
||||
.setEntityType(ProtEntityType.ProtEntityGadget)
|
||||
.setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder()))
|
||||
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
|
||||
.setEntityClientData(EntityClientData.newBuilder())
|
||||
.setEntityAuthorityInfo(authority)
|
||||
.setLifeState(1);
|
||||
|
||||
PropPair pair = PropPair.newBuilder()
|
||||
.setType(PlayerProperty.PROP_LEVEL.getId())
|
||||
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 1))
|
||||
.build();
|
||||
entityInfo.addPropList(pair);
|
||||
|
||||
GadgetClientParam clientGadget = GadgetClientParam.newBuilder()
|
||||
.setCampId(this.getCampId())
|
||||
.setCampType(this.getCampType())
|
||||
.setOwnerEntityId(this.getOwnerEntityId())
|
||||
.setTargetEntityId(this.getTargetEntityId())
|
||||
.setAsyncLoad(this.isAsyncLoad())
|
||||
.build();
|
||||
|
||||
SceneGadgetInfo.Builder gadgetInfo = SceneGadgetInfo.newBuilder()
|
||||
.setGadgetId(this.getGadgetId())
|
||||
.setOwnerEntityId(this.getOwnerEntityId())
|
||||
.setIsEnableInteract(true)
|
||||
.setClientGadget(clientGadget)
|
||||
.setPropOwnerEntityId(this.getOwnerEntityId())
|
||||
.setAuthorityPeerId(this.getOwner().getPeerId());
|
||||
|
||||
entityInfo.setGadget(gadgetInfo);
|
||||
|
||||
return entityInfo.build();
|
||||
}
|
||||
}
|
||||
17
src/main/java/emu/grasscutter/game/entity/EntityGadget.java
Normal file
17
src/main/java/emu/grasscutter/game/entity/EntityGadget.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package emu.grasscutter.game.entity;
|
||||
|
||||
import emu.grasscutter.game.World;
|
||||
|
||||
public abstract class EntityGadget extends GenshinEntity {
|
||||
|
||||
public EntityGadget(World world) {
|
||||
super(world);
|
||||
}
|
||||
|
||||
public abstract int getGadgetId();
|
||||
|
||||
@Override
|
||||
public void onDeath(int killerId) {
|
||||
|
||||
}
|
||||
}
|
||||
118
src/main/java/emu/grasscutter/game/entity/EntityItem.java
Normal file
118
src/main/java/emu/grasscutter/game/entity/EntityItem.java
Normal file
@@ -0,0 +1,118 @@
|
||||
package emu.grasscutter.game.entity;
|
||||
|
||||
import emu.grasscutter.data.def.ItemData;
|
||||
import emu.grasscutter.game.GenshinPlayer;
|
||||
import emu.grasscutter.game.World;
|
||||
import emu.grasscutter.game.inventory.GenshinItem;
|
||||
import emu.grasscutter.game.props.EntityIdType;
|
||||
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.GadgetBornTypeOuterClass.GadgetBornType;
|
||||
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
|
||||
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.SceneGadgetInfoOuterClass.SceneGadgetInfo;
|
||||
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
|
||||
import emu.grasscutter.utils.Position;
|
||||
import emu.grasscutter.utils.ProtoHelper;
|
||||
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
|
||||
|
||||
public class EntityItem extends EntityGadget {
|
||||
private final Position pos;
|
||||
private final Position rot;
|
||||
|
||||
private final GenshinItem item;
|
||||
private final long guid;
|
||||
|
||||
public EntityItem(World world, GenshinPlayer player, ItemData itemData, Position pos, int count) {
|
||||
super(world);
|
||||
this.id = world.getNextEntityId(EntityIdType.GADGET);
|
||||
this.pos = new Position(pos);
|
||||
this.rot = new Position();
|
||||
this.guid = player.getNextGuid();
|
||||
this.item = new GenshinItem(itemData, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
private GenshinItem getItem() {
|
||||
return this.item;
|
||||
}
|
||||
|
||||
public ItemData getItemData() {
|
||||
return this.getItem().getItemData();
|
||||
}
|
||||
|
||||
public long getGuid() {
|
||||
return guid;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return this.getItem().getCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGadgetId() {
|
||||
return this.getItemData().getGadgetId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position getPosition() {
|
||||
return this.pos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position getRotation() {
|
||||
return this.rot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Int2FloatOpenHashMap getFightProperties() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SceneEntityInfo toProto() {
|
||||
EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder()
|
||||
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
|
||||
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
|
||||
.setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder()))
|
||||
.setBornPos(Vector.newBuilder())
|
||||
.build();
|
||||
|
||||
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
|
||||
.setEntityId(getId())
|
||||
.setEntityType(ProtEntityType.ProtEntityGadget)
|
||||
.setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder()))
|
||||
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
|
||||
.setEntityClientData(EntityClientData.newBuilder())
|
||||
.setEntityAuthorityInfo(authority)
|
||||
.setLifeState(1);
|
||||
|
||||
PropPair pair = PropPair.newBuilder()
|
||||
.setType(PlayerProperty.PROP_LEVEL.getId())
|
||||
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 1))
|
||||
.build();
|
||||
entityInfo.addPropList(pair);
|
||||
|
||||
SceneGadgetInfo.Builder gadgetInfo = SceneGadgetInfo.newBuilder()
|
||||
.setGadgetId(this.getItemData().getGadgetId())
|
||||
.setTrifleItem(this.getItem().toProto())
|
||||
.setBornType(GadgetBornType.GadgetBornInAir)
|
||||
.setAuthorityPeerId(this.getWorld().getHostPeerId())
|
||||
.setIsEnableInteract(true);
|
||||
|
||||
entityInfo.setGadget(gadgetInfo);
|
||||
|
||||
return entityInfo.build();
|
||||
}
|
||||
}
|
||||
219
src/main/java/emu/grasscutter/game/entity/EntityMonster.java
Normal file
219
src/main/java/emu/grasscutter/game/entity/EntityMonster.java
Normal file
@@ -0,0 +1,219 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
102
src/main/java/emu/grasscutter/game/entity/GenshinEntity.java
Normal file
102
src/main/java/emu/grasscutter/game/entity/GenshinEntity.java
Normal file
@@ -0,0 +1,102 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
262
src/main/java/emu/grasscutter/game/friends/FriendsList.java
Normal file
262
src/main/java/emu/grasscutter/game/friends/FriendsList.java
Normal file
@@ -0,0 +1,262 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
108
src/main/java/emu/grasscutter/game/friends/Friendship.java
Normal file
108
src/main/java/emu/grasscutter/game/friends/Friendship.java
Normal file
@@ -0,0 +1,108 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
150
src/main/java/emu/grasscutter/game/gacha/GachaBanner.java
Normal file
150
src/main/java/emu/grasscutter/game/gacha/GachaBanner.java
Normal 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;
|
||||
}
|
||||
}
|
||||
287
src/main/java/emu/grasscutter/game/gacha/GachaManager.java
Normal file
287
src/main/java/emu/grasscutter/game/gacha/GachaManager.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
45
src/main/java/emu/grasscutter/game/inventory/EquipType.java
Normal file
45
src/main/java/emu/grasscutter/game/inventory/EquipType.java
Normal file
@@ -0,0 +1,45 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
430
src/main/java/emu/grasscutter/game/inventory/GenshinItem.java
Normal file
430
src/main/java/emu/grasscutter/game/inventory/GenshinItem.java
Normal file
@@ -0,0 +1,430 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
353
src/main/java/emu/grasscutter/game/inventory/Inventory.java
Normal file
353
src/main/java/emu/grasscutter/game/inventory/Inventory.java
Normal file
@@ -0,0 +1,353 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
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();
|
||||
}
|
||||
27
src/main/java/emu/grasscutter/game/inventory/ItemDef.java
Normal file
27
src/main/java/emu/grasscutter/game/inventory/ItemDef.java
Normal file
@@ -0,0 +1,27 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
45
src/main/java/emu/grasscutter/game/inventory/ItemType.java
Normal file
45
src/main/java/emu/grasscutter/game/inventory/ItemType.java
Normal file
@@ -0,0 +1,45 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
82
src/main/java/emu/grasscutter/game/managers/ChatManager.java
Normal file
82
src/main/java/emu/grasscutter/game/managers/ChatManager.java
Normal file
@@ -0,0 +1,82 @@
|
||||
package emu.grasscutter.game.managers;
|
||||
|
||||
import emu.grasscutter.commands.PlayerCommands;
|
||||
import emu.grasscutter.game.GenshinPlayer;
|
||||
import emu.grasscutter.net.packet.GenshinPacket;
|
||||
import emu.grasscutter.server.game.GameServer;
|
||||
import emu.grasscutter.server.packet.send.PacketPlayerChatNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketPrivateChatNotify;
|
||||
|
||||
public class ChatManager {
|
||||
private final GameServer server;
|
||||
|
||||
public ChatManager(GameServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public GameServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public void sendPrivChat(GenshinPlayer player, int targetUid, String message) {
|
||||
// Sanity checks
|
||||
if (message == null || message.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if command
|
||||
if (message.charAt(0) == '!') {
|
||||
PlayerCommands.handle(player, message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get target
|
||||
GenshinPlayer target = getServer().getPlayerById(targetUid);
|
||||
|
||||
if (target == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create chat packet
|
||||
GenshinPacket packet = new PacketPrivateChatNotify(player.getId(), target.getId(), message);
|
||||
|
||||
player.sendPacket(packet);
|
||||
target.sendPacket(packet);
|
||||
}
|
||||
|
||||
public void sendPrivChat(GenshinPlayer player, int targetUid, int emote) {
|
||||
// Get target
|
||||
GenshinPlayer target = getServer().getPlayerById(targetUid);
|
||||
|
||||
if (target == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create chat packet
|
||||
GenshinPacket packet = new PacketPrivateChatNotify(player.getId(), target.getId(), emote);
|
||||
|
||||
player.sendPacket(packet);
|
||||
target.sendPacket(packet);
|
||||
}
|
||||
|
||||
public void sendTeamChat(GenshinPlayer player, int channel, String message) {
|
||||
// Sanity checks
|
||||
if (message == null || message.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if command
|
||||
if (message.charAt(0) == '!') {
|
||||
PlayerCommands.handle(player, message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create and send chat packet
|
||||
player.getWorld().broadcastPacket(new PacketPlayerChatNotify(player, channel, message));
|
||||
}
|
||||
|
||||
public void sendTeamChat(GenshinPlayer player, int channel, int icon) {
|
||||
// Create and send chat packet
|
||||
player.getWorld().broadcastPacket(new PacketPlayerChatNotify(player, channel, icon));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,897 @@
|
||||
package emu.grasscutter.game.managers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import emu.grasscutter.data.GenshinData;
|
||||
import emu.grasscutter.data.common.ItemParamData;
|
||||
import emu.grasscutter.data.custom.OpenConfigEntry;
|
||||
import emu.grasscutter.data.def.AvatarPromoteData;
|
||||
import emu.grasscutter.data.def.AvatarSkillData;
|
||||
import emu.grasscutter.data.def.AvatarSkillDepotData;
|
||||
import emu.grasscutter.data.def.WeaponPromoteData;
|
||||
import emu.grasscutter.data.def.AvatarSkillDepotData.InherentProudSkillOpens;
|
||||
import emu.grasscutter.data.def.AvatarTalentData;
|
||||
import emu.grasscutter.data.def.ProudSkillData;
|
||||
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.ItemParamOuterClass.ItemParam;
|
||||
import emu.grasscutter.net.proto.MaterialInfoOuterClass.MaterialInfo;
|
||||
import emu.grasscutter.server.game.GameServer;
|
||||
import emu.grasscutter.server.packet.send.PacketAbilityChangeNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketAvatarPromoteRsp;
|
||||
import emu.grasscutter.server.packet.send.PacketAvatarPropNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketAvatarSkillChangeNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketAvatarSkillUpgradeRsp;
|
||||
import emu.grasscutter.server.packet.send.PacketAvatarUnlockTalentNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketAvatarUpgradeRsp;
|
||||
import emu.grasscutter.server.packet.send.PacketDestroyMaterialRsp;
|
||||
import emu.grasscutter.server.packet.send.PacketProudSkillChangeNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketProudSkillExtraLevelNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketReliquaryUpgradeRsp;
|
||||
import emu.grasscutter.server.packet.send.PacketSetEquipLockStateRsp;
|
||||
import emu.grasscutter.server.packet.send.PacketStoreItemChangeNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketUnlockAvatarTalentRsp;
|
||||
import emu.grasscutter.server.packet.send.PacketWeaponAwakenRsp;
|
||||
import emu.grasscutter.server.packet.send.PacketWeaponPromoteRsp;
|
||||
import emu.grasscutter.server.packet.send.PacketWeaponUpgradeRsp;
|
||||
import emu.grasscutter.utils.Utils;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
|
||||
public class InventoryManager {
|
||||
private final GameServer server;
|
||||
|
||||
private final static int RELIC_MATERIAL_1 = 105002; // Sanctifying Unction
|
||||
private final static int RELIC_MATERIAL_2 = 105003; // Sanctifying Essence
|
||||
|
||||
private final static int WEAPON_ORE_1 = 104011; // Enhancement Ore
|
||||
private final static int WEAPON_ORE_2 = 104012; // Fine Enhancement Ore
|
||||
private final static int WEAPON_ORE_3 = 104013; // Mystic Enhancement Ore
|
||||
private final static int WEAPON_ORE_EXP_1 = 400; // Enhancement Ore
|
||||
private final static int WEAPON_ORE_EXP_2 = 2000; // Fine Enhancement Ore
|
||||
private final static int WEAPON_ORE_EXP_3 = 10000; // Mystic Enhancement Ore
|
||||
|
||||
private final static int AVATAR_BOOK_1 = 104001; // Wanderer's Advice
|
||||
private final static int AVATAR_BOOK_2 = 104002; // Adventurer's Experience
|
||||
private final static int AVATAR_BOOK_3 = 104003; // Hero's Wit
|
||||
private final static int AVATAR_BOOK_EXP_1 = 1000; // Wanderer's Advice
|
||||
private final static int AVATAR_BOOK_EXP_2 = 5000; // Adventurer's Experience
|
||||
private final static int AVATAR_BOOK_EXP_3 = 20000; // Hero's Wit
|
||||
|
||||
public InventoryManager(GameServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public GameServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public void lockEquip(GenshinPlayer player, long targetEquipGuid, boolean isLocked) {
|
||||
GenshinItem equip = player.getInventory().getItemByGuid(targetEquipGuid);
|
||||
|
||||
if (equip == null || !equip.getItemData().isEquip()) {
|
||||
return;
|
||||
}
|
||||
|
||||
equip.setLocked(isLocked);
|
||||
equip.save();
|
||||
|
||||
player.sendPacket(new PacketStoreItemChangeNotify(equip));
|
||||
player.sendPacket(new PacketSetEquipLockStateRsp(equip));
|
||||
}
|
||||
|
||||
public void upgradeRelic(GenshinPlayer player, long targetGuid, List<Long> foodRelicList, List<ItemParam> list) {
|
||||
GenshinItem relic = player.getInventory().getItemByGuid(targetGuid);
|
||||
|
||||
if (relic == null || relic.getItemType() != ItemType.ITEM_RELIQUARY) {
|
||||
return;
|
||||
}
|
||||
|
||||
int moraCost = 0;
|
||||
int expGain = 0;
|
||||
|
||||
for (long guid : foodRelicList) {
|
||||
// Add to delete queue
|
||||
GenshinItem food = player.getInventory().getItemByGuid(guid);
|
||||
if (food == null || !food.isDestroyable()) {
|
||||
continue;
|
||||
}
|
||||
// Calculate mora cost
|
||||
moraCost += food.getItemData().getBaseConvExp();
|
||||
expGain += food.getItemData().getBaseConvExp();
|
||||
// Feeding artifact with exp already
|
||||
if (food.getTotalExp() > 0) {
|
||||
expGain += (int) Math.floor(food.getTotalExp() * .8f);
|
||||
}
|
||||
}
|
||||
for (ItemParam itemParam : list) {
|
||||
GenshinItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemParam.getItemId());
|
||||
if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_RELIQUARY_MATERIAL) {
|
||||
continue;
|
||||
}
|
||||
int amount = Math.min(food.getCount(), itemParam.getCount());
|
||||
int gain = 0;
|
||||
if (food.getItemId() == RELIC_MATERIAL_2) {
|
||||
gain = 10000 * amount;
|
||||
} else if (food.getItemId() == RELIC_MATERIAL_1) {
|
||||
gain = 2500 * amount;
|
||||
}
|
||||
expGain += gain;
|
||||
moraCost += gain;
|
||||
}
|
||||
|
||||
// Make sure exp gain is valid
|
||||
if (expGain <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check mora
|
||||
if (player.getMora() < moraCost) {
|
||||
return;
|
||||
}
|
||||
player.setMora(player.getMora() - moraCost);
|
||||
|
||||
// Consume food items
|
||||
for (long guid : foodRelicList) {
|
||||
GenshinItem food = player.getInventory().getItemByGuid(guid);
|
||||
if (food == null || !food.isDestroyable()) {
|
||||
continue;
|
||||
}
|
||||
player.getInventory().removeItem(food);
|
||||
}
|
||||
for (ItemParam itemParam : list) {
|
||||
GenshinItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemParam.getItemId());
|
||||
if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_RELIQUARY_MATERIAL) {
|
||||
continue;
|
||||
}
|
||||
int amount = Math.min(food.getCount(), itemParam.getCount());
|
||||
player.getInventory().removeItem(food, amount);
|
||||
}
|
||||
|
||||
// Implement random rate boost
|
||||
int rate = 1;
|
||||
int boost = Utils.randomRange(1, 100);
|
||||
if (boost == 100) {
|
||||
rate = 5;
|
||||
} else if (boost <= 9) {
|
||||
rate = 2;
|
||||
}
|
||||
expGain *= rate;
|
||||
|
||||
// Now we upgrade
|
||||
int level = relic.getLevel();
|
||||
int oldLevel = level;
|
||||
int exp = relic.getExp();
|
||||
int totalExp = relic.getTotalExp();
|
||||
int reqExp = GenshinData.getRelicExpRequired(relic.getItemData().getRankLevel(), level);
|
||||
int upgrades = 0;
|
||||
List<Integer> oldAppendPropIdList = relic.getAppendPropIdList();
|
||||
|
||||
while (expGain > 0 && reqExp > 0 && level < relic.getItemData().getMaxLevel()) {
|
||||
// Do calculations
|
||||
int toGain = Math.min(expGain, reqExp - exp);
|
||||
exp += toGain;
|
||||
totalExp += toGain;
|
||||
expGain -= toGain;
|
||||
// Level up
|
||||
if (exp >= reqExp) {
|
||||
// Exp
|
||||
exp = 0;
|
||||
level += 1;
|
||||
// On relic levelup
|
||||
if (relic.getItemData().getAddPropLevelSet() != null && relic.getItemData().getAddPropLevelSet().contains(level)) {
|
||||
upgrades += 1;
|
||||
}
|
||||
// Set req exp
|
||||
reqExp = GenshinData.getRelicExpRequired(relic.getItemData().getRankLevel(), level);
|
||||
}
|
||||
}
|
||||
|
||||
if (upgrades > 0) {
|
||||
oldAppendPropIdList = new ArrayList<>(relic.getAppendPropIdList());
|
||||
while (upgrades > 0) {
|
||||
relic.addAppendProp();
|
||||
upgrades -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Save
|
||||
relic.setLevel(level);
|
||||
relic.setExp(exp);
|
||||
relic.setTotalExp(totalExp);
|
||||
relic.save();
|
||||
|
||||
// Avatar
|
||||
if (oldLevel != level) {
|
||||
GenshinAvatar avatar = relic.getEquipCharacter() > 0 ? player.getAvatars().getAvatarById(relic.getEquipCharacter()) : null;
|
||||
if (avatar != null) {
|
||||
avatar.recalcStats();
|
||||
}
|
||||
}
|
||||
|
||||
// Packet
|
||||
player.sendPacket(new PacketStoreItemChangeNotify(relic));
|
||||
player.sendPacket(new PacketReliquaryUpgradeRsp(relic, rate, oldLevel, oldAppendPropIdList));
|
||||
}
|
||||
|
||||
public List<ItemParam> calcWeaponUpgradeReturnItems(GenshinPlayer player, long targetGuid, List<Long> foodWeaponGuidList, List<ItemParam> itemParamList) {
|
||||
GenshinItem weapon = player.getInventory().getItemByGuid(targetGuid);
|
||||
|
||||
// Sanity checks
|
||||
if (weapon == null || weapon.getItemType() != ItemType.ITEM_WEAPON) {
|
||||
return null;
|
||||
}
|
||||
|
||||
WeaponPromoteData promoteData = GenshinData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), weapon.getPromoteLevel());
|
||||
if (promoteData == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get exp gain
|
||||
int expGain = 0;
|
||||
for (long guid : foodWeaponGuidList) {
|
||||
GenshinItem food = player.getInventory().getItemByGuid(guid);
|
||||
if (food == null) {
|
||||
continue;
|
||||
}
|
||||
expGain += food.getItemData().getWeaponBaseExp();
|
||||
if (food.getTotalExp() > 0) {
|
||||
expGain += (int) Math.floor(food.getTotalExp() * .8f);
|
||||
}
|
||||
}
|
||||
for (ItemParam param : itemParamList) {
|
||||
GenshinItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(param.getItemId());
|
||||
if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_WEAPON_EXP_STONE) {
|
||||
continue;
|
||||
}
|
||||
int amount = Math.min(param.getCount(), food.getCount());
|
||||
if (food.getItemId() == WEAPON_ORE_3) {
|
||||
expGain += 10000 * amount;
|
||||
} else if (food.getItemId() == WEAPON_ORE_2) {
|
||||
expGain += 2000 * amount;
|
||||
} else if (food.getItemId() == WEAPON_ORE_1) {
|
||||
expGain += 400 * amount;
|
||||
}
|
||||
}
|
||||
|
||||
// Try
|
||||
int maxLevel = promoteData.getUnlockMaxLevel();
|
||||
int level = weapon.getLevel();
|
||||
int exp = weapon.getExp();
|
||||
int reqExp = GenshinData.getWeaponExpRequired(weapon.getItemData().getRankLevel(), level);
|
||||
|
||||
while (expGain > 0 && reqExp > 0 && level < maxLevel) {
|
||||
// Do calculations
|
||||
int toGain = Math.min(expGain, reqExp - exp);
|
||||
exp += toGain;
|
||||
expGain -= toGain;
|
||||
// Level up
|
||||
if (exp >= reqExp) {
|
||||
// Exp
|
||||
exp = 0;
|
||||
level += 1;
|
||||
// Set req exp
|
||||
reqExp = GenshinData.getWeaponExpRequired(weapon.getItemData().getRankLevel(), level);
|
||||
}
|
||||
}
|
||||
|
||||
return getLeftoverOres(expGain);
|
||||
}
|
||||
|
||||
|
||||
public void upgradeWeapon(GenshinPlayer player, long targetGuid, List<Long> foodWeaponGuidList, List<ItemParam> itemParamList) {
|
||||
GenshinItem weapon = player.getInventory().getItemByGuid(targetGuid);
|
||||
|
||||
// Sanity checks
|
||||
if (weapon == null || weapon.getItemType() != ItemType.ITEM_WEAPON) {
|
||||
return;
|
||||
}
|
||||
|
||||
WeaponPromoteData promoteData = GenshinData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), weapon.getPromoteLevel());
|
||||
if (promoteData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get exp gain
|
||||
int expGain = 0, moraCost = 0;
|
||||
|
||||
for (long guid : foodWeaponGuidList) {
|
||||
GenshinItem food = player.getInventory().getItemByGuid(guid);
|
||||
if (food == null || !food.isDestroyable()) {
|
||||
continue;
|
||||
}
|
||||
expGain += food.getItemData().getWeaponBaseExp();
|
||||
moraCost += (int) Math.floor(food.getItemData().getWeaponBaseExp() * .1f);
|
||||
if (food.getTotalExp() > 0) {
|
||||
expGain += (int) Math.floor(food.getTotalExp() * .8f);
|
||||
}
|
||||
}
|
||||
for (ItemParam param : itemParamList) {
|
||||
GenshinItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(param.getItemId());
|
||||
if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_WEAPON_EXP_STONE) {
|
||||
continue;
|
||||
}
|
||||
int amount = Math.min(param.getCount(), food.getCount());
|
||||
int gain = 0;
|
||||
if (food.getItemId() == WEAPON_ORE_3) {
|
||||
gain = 10000 * amount;
|
||||
} else if (food.getItemId() == WEAPON_ORE_2) {
|
||||
gain = 2000 * amount;
|
||||
} else if (food.getItemId() == WEAPON_ORE_1) {
|
||||
gain = 400 * amount;
|
||||
}
|
||||
expGain += gain;
|
||||
moraCost += (int) Math.floor(gain * .1f);
|
||||
}
|
||||
|
||||
// Make sure exp gain is valid
|
||||
if (expGain <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mora check
|
||||
if (player.getMora() >= moraCost) {
|
||||
player.setMora(player.getMora() - moraCost);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Consume weapon/items used to feed
|
||||
for (long guid : foodWeaponGuidList) {
|
||||
GenshinItem food = player.getInventory().getItemByGuid(guid);
|
||||
if (food == null || !food.isDestroyable()) {
|
||||
continue;
|
||||
}
|
||||
player.getInventory().removeItem(food);
|
||||
}
|
||||
for (ItemParam param : itemParamList) {
|
||||
GenshinItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(param.getItemId());
|
||||
if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_WEAPON_EXP_STONE) {
|
||||
continue;
|
||||
}
|
||||
int amount = Math.min(param.getCount(), food.getCount());
|
||||
player.getInventory().removeItem(food, amount);
|
||||
}
|
||||
|
||||
// Level up
|
||||
int maxLevel = promoteData.getUnlockMaxLevel();
|
||||
int level = weapon.getLevel();
|
||||
int oldLevel = level;
|
||||
int exp = weapon.getExp();
|
||||
int totalExp = weapon.getTotalExp();
|
||||
int reqExp = GenshinData.getWeaponExpRequired(weapon.getItemData().getRankLevel(), level);
|
||||
|
||||
while (expGain > 0 && reqExp > 0 && level < maxLevel) {
|
||||
// Do calculations
|
||||
int toGain = Math.min(expGain, reqExp - exp);
|
||||
exp += toGain;
|
||||
totalExp += toGain;
|
||||
expGain -= toGain;
|
||||
// Level up
|
||||
if (exp >= reqExp) {
|
||||
// Exp
|
||||
exp = 0;
|
||||
level += 1;
|
||||
// Set req exp
|
||||
reqExp = GenshinData.getWeaponExpRequired(weapon.getItemData().getRankLevel(), level);
|
||||
}
|
||||
}
|
||||
|
||||
List<ItemParam> leftovers = getLeftoverOres(expGain);
|
||||
player.getInventory().addItemParams(leftovers);
|
||||
|
||||
weapon.setLevel(level);
|
||||
weapon.setExp(exp);
|
||||
weapon.setTotalExp(totalExp);
|
||||
weapon.save();
|
||||
|
||||
// Avatar
|
||||
if (oldLevel != level) {
|
||||
GenshinAvatar avatar = weapon.getEquipCharacter() > 0 ? player.getAvatars().getAvatarById(weapon.getEquipCharacter()) : null;
|
||||
if (avatar != null) {
|
||||
avatar.recalcStats();
|
||||
}
|
||||
}
|
||||
|
||||
// Packets
|
||||
player.sendPacket(new PacketStoreItemChangeNotify(weapon));
|
||||
player.sendPacket(new PacketWeaponUpgradeRsp(weapon, oldLevel, leftovers));
|
||||
}
|
||||
|
||||
private List<ItemParam> getLeftoverOres(float leftover) {
|
||||
List<ItemParam> leftoverOreList = new ArrayList<>(3);
|
||||
|
||||
if (leftover < WEAPON_ORE_EXP_1) {
|
||||
return leftoverOreList;
|
||||
}
|
||||
|
||||
// Get leftovers
|
||||
int ore3 = (int) Math.floor(leftover / WEAPON_ORE_EXP_3);
|
||||
leftover = leftover % WEAPON_ORE_EXP_3;
|
||||
int ore2 = (int) Math.floor(leftover / WEAPON_ORE_EXP_2);
|
||||
leftover = leftover % WEAPON_ORE_EXP_2;
|
||||
int ore1 = (int) Math.floor(leftover / WEAPON_ORE_EXP_1);
|
||||
|
||||
if (ore3 > 0) {
|
||||
leftoverOreList.add(ItemParam.newBuilder().setItemId(WEAPON_ORE_3).setCount(ore3).build());
|
||||
} if (ore2 > 0) {
|
||||
leftoverOreList.add(ItemParam.newBuilder().setItemId(WEAPON_ORE_2).setCount(ore2).build());
|
||||
} if (ore1 > 0) {
|
||||
leftoverOreList.add(ItemParam.newBuilder().setItemId(WEAPON_ORE_1).setCount(ore1).build());
|
||||
}
|
||||
|
||||
return leftoverOreList;
|
||||
}
|
||||
|
||||
public void refineWeapon(GenshinPlayer player, long targetGuid, long feedGuid) {
|
||||
GenshinItem weapon = player.getInventory().getItemByGuid(targetGuid);
|
||||
GenshinItem feed = player.getInventory().getItemByGuid(feedGuid);
|
||||
|
||||
// Sanity checks
|
||||
if (weapon == null || feed == null || !feed.isDestroyable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (weapon.getItemType() != ItemType.ITEM_WEAPON || weapon.getItemId() != feed.getItemId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (weapon.getRefinement() >= 4 || weapon.getAffixes() == null || weapon.getAffixes().size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate
|
||||
int oldRefineLevel = weapon.getRefinement();
|
||||
int targetRefineLevel = Math.min(oldRefineLevel + feed.getRefinement() + 1, 4);
|
||||
int moraCost = 0;
|
||||
|
||||
try {
|
||||
moraCost = weapon.getItemData().getAwakenCosts()[weapon.getRefinement()];
|
||||
} catch (Exception e) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mora check
|
||||
if (player.getMora() >= moraCost) {
|
||||
player.setMora(player.getMora() - moraCost);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Consume weapon
|
||||
player.getInventory().removeItem(feed);
|
||||
|
||||
// Get
|
||||
weapon.setRefinement(targetRefineLevel);
|
||||
weapon.save();
|
||||
|
||||
// Avatar
|
||||
GenshinAvatar avatar = weapon.getEquipCharacter() > 0 ? player.getAvatars().getAvatarById(weapon.getEquipCharacter()) : null;
|
||||
if (avatar != null) {
|
||||
avatar.recalcStats();
|
||||
}
|
||||
|
||||
// Packets
|
||||
player.sendPacket(new PacketStoreItemChangeNotify(weapon));
|
||||
player.sendPacket(new PacketWeaponAwakenRsp(avatar, weapon, feed, oldRefineLevel));
|
||||
}
|
||||
|
||||
public void promoteWeapon(GenshinPlayer player, long targetGuid) {
|
||||
GenshinItem weapon = player.getInventory().getItemByGuid(targetGuid);
|
||||
|
||||
if (weapon == null || weapon.getItemType() != ItemType.ITEM_WEAPON) {
|
||||
return;
|
||||
}
|
||||
|
||||
int nextPromoteLevel = weapon.getPromoteLevel() + 1;
|
||||
WeaponPromoteData currentPromoteData = GenshinData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), weapon.getPromoteLevel());
|
||||
WeaponPromoteData nextPromoteData = GenshinData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), nextPromoteLevel);
|
||||
if (currentPromoteData == null || nextPromoteData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Level check
|
||||
if (weapon.getLevel() != currentPromoteData.getUnlockMaxLevel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure player has promote items
|
||||
for (ItemParamData cost : nextPromoteData.getCostItems()) {
|
||||
GenshinItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId());
|
||||
if (feedItem == null || feedItem.getCount() < cost.getCount()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Mora check
|
||||
if (player.getMora() >= nextPromoteData.getCoinCost()) {
|
||||
player.setMora(player.getMora() - nextPromoteData.getCoinCost());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Consume promote filler items
|
||||
for (ItemParamData cost : nextPromoteData.getCostItems()) {
|
||||
GenshinItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId());
|
||||
player.getInventory().removeItem(feedItem, cost.getCount());
|
||||
}
|
||||
|
||||
int oldPromoteLevel = weapon.getPromoteLevel();
|
||||
weapon.setPromoteLevel(nextPromoteLevel);
|
||||
weapon.save();
|
||||
|
||||
// Avatar
|
||||
GenshinAvatar avatar = weapon.getEquipCharacter() > 0 ? player.getAvatars().getAvatarById(weapon.getEquipCharacter()) : null;
|
||||
if (avatar != null) {
|
||||
avatar.recalcStats();
|
||||
}
|
||||
|
||||
// Packets
|
||||
player.sendPacket(new PacketStoreItemChangeNotify(weapon));
|
||||
player.sendPacket(new PacketWeaponPromoteRsp(weapon, oldPromoteLevel));
|
||||
}
|
||||
|
||||
public void promoteAvatar(GenshinPlayer player, long guid) {
|
||||
GenshinAvatar avatar = player.getAvatars().getAvatarByGuid(guid);
|
||||
|
||||
// Sanity checks
|
||||
if (avatar == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int nextPromoteLevel = avatar.getPromoteLevel() + 1;
|
||||
AvatarPromoteData currentPromoteData = GenshinData.getAvatarPromoteData(avatar.getAvatarData().getAvatarPromoteId(), avatar.getPromoteLevel());
|
||||
AvatarPromoteData nextPromoteData = GenshinData.getAvatarPromoteData(avatar.getAvatarData().getAvatarPromoteId(), nextPromoteLevel);
|
||||
if (currentPromoteData == null || nextPromoteData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Level check
|
||||
if (avatar.getLevel() != currentPromoteData.getUnlockMaxLevel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure player has cost items
|
||||
for (ItemParamData cost : nextPromoteData.getCostItems()) {
|
||||
GenshinItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId());
|
||||
if (feedItem == null || feedItem.getCount() < cost.getCount()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Mora check
|
||||
if (player.getMora() >= nextPromoteData.getCoinCost()) {
|
||||
player.setMora(player.getMora() - nextPromoteData.getCoinCost());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Consume promote filler items
|
||||
for (ItemParamData cost : nextPromoteData.getCostItems()) {
|
||||
GenshinItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId());
|
||||
player.getInventory().removeItem(feedItem, cost.getCount());
|
||||
}
|
||||
|
||||
// Update promote level
|
||||
avatar.setPromoteLevel(nextPromoteLevel);
|
||||
|
||||
// Update proud skills
|
||||
AvatarSkillDepotData skillDepot = GenshinData.getAvatarSkillDepotDataMap().get(avatar.getSkillDepotId());
|
||||
boolean hasAddedProudSkill = false;
|
||||
|
||||
if (skillDepot != null && skillDepot.getInherentProudSkillOpens() != null) {
|
||||
for (InherentProudSkillOpens openData : skillDepot.getInherentProudSkillOpens()) {
|
||||
if (openData.getProudSkillGroupId() == 0) {
|
||||
continue;
|
||||
}
|
||||
if (openData.getNeedAvatarPromoteLevel() == avatar.getPromoteLevel()) {
|
||||
int proudSkillId = (openData.getProudSkillGroupId() * 100) + 1;
|
||||
if (GenshinData.getProudSkillDataMap().containsKey(proudSkillId)) {
|
||||
hasAddedProudSkill = true;
|
||||
avatar.getProudSkillList().add(proudSkillId);
|
||||
player.sendPacket(new PacketProudSkillChangeNotify(avatar));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Racalc stats and save avatar
|
||||
avatar.recalcStats();
|
||||
avatar.save();
|
||||
|
||||
// Resend ability embryos if proud skill has been added
|
||||
if (hasAddedProudSkill && avatar.getAsEntity() != null) {
|
||||
player.sendPacket(new PacketAbilityChangeNotify(avatar.getAsEntity()));
|
||||
}
|
||||
|
||||
// TODO Send entity prop update packet to world
|
||||
|
||||
// Packets
|
||||
player.sendPacket(new PacketAvatarPropNotify(avatar));
|
||||
player.sendPacket(new PacketAvatarPromoteRsp(avatar));
|
||||
}
|
||||
|
||||
public void upgradeAvatar(GenshinPlayer player, long guid, int itemId, int count) {
|
||||
GenshinAvatar avatar = player.getAvatars().getAvatarByGuid(guid);
|
||||
|
||||
// Sanity checks
|
||||
if (avatar == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
AvatarPromoteData promoteData = GenshinData.getAvatarPromoteData(avatar.getAvatarData().getAvatarPromoteId(), avatar.getPromoteLevel());
|
||||
if (promoteData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
GenshinItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemId);
|
||||
|
||||
if (feedItem == null || feedItem.getItemData().getMaterialType() != MaterialType.MATERIAL_EXP_FRUIT || feedItem.getCount() < count) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calc exp
|
||||
int expGain = 0, moraCost = 0;
|
||||
|
||||
// TODO clean up
|
||||
if (itemId == AVATAR_BOOK_3) {
|
||||
expGain = AVATAR_BOOK_EXP_3 * count;
|
||||
} else if (itemId == AVATAR_BOOK_2) {
|
||||
expGain = AVATAR_BOOK_EXP_2 * count;
|
||||
} else if (itemId == AVATAR_BOOK_1) {
|
||||
expGain = AVATAR_BOOK_EXP_1 * count;
|
||||
}
|
||||
moraCost = (int) Math.floor(expGain * .2f);
|
||||
|
||||
// Mora check
|
||||
if (player.getMora() >= moraCost) {
|
||||
player.setMora(player.getMora() - moraCost);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Consume items
|
||||
player.getInventory().removeItem(feedItem, count);
|
||||
|
||||
// Level up
|
||||
int maxLevel = promoteData.getUnlockMaxLevel();
|
||||
int level = avatar.getLevel();
|
||||
int oldLevel = level;
|
||||
int exp = avatar.getExp();
|
||||
int reqExp = GenshinData.getAvatarLevelExpRequired(level);
|
||||
|
||||
while (expGain > 0 && reqExp > 0 && level < maxLevel) {
|
||||
// Do calculations
|
||||
int toGain = Math.min(expGain, reqExp - exp);
|
||||
exp += toGain;
|
||||
expGain -= toGain;
|
||||
// Level up
|
||||
if (exp >= reqExp) {
|
||||
// Exp
|
||||
exp = 0;
|
||||
level += 1;
|
||||
// Set req exp
|
||||
reqExp = GenshinData.getAvatarLevelExpRequired(level);
|
||||
}
|
||||
}
|
||||
|
||||
// Old map for packet
|
||||
Map<Integer, Float> oldPropMap = avatar.getFightProperties();
|
||||
if (oldLevel != level) {
|
||||
// Deep copy if level has changed
|
||||
oldPropMap = avatar.getFightProperties().int2FloatEntrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
// Done
|
||||
avatar.setLevel(level);
|
||||
avatar.setExp(exp);
|
||||
avatar.recalcStats();
|
||||
avatar.save();
|
||||
|
||||
// TODO Send entity prop update packet to world
|
||||
|
||||
// Packets
|
||||
player.sendPacket(new PacketAvatarPropNotify(avatar));
|
||||
player.sendPacket(new PacketAvatarUpgradeRsp(avatar, oldLevel, oldPropMap));
|
||||
}
|
||||
|
||||
public void upgradeAvatarSkill(GenshinPlayer player, long guid, int skillId) {
|
||||
// Sanity checks
|
||||
GenshinAvatar avatar = player.getAvatars().getAvatarByGuid(guid);
|
||||
if (avatar == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure avatar has skill
|
||||
if (!avatar.getSkillLevelMap().containsKey(skillId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AvatarSkillData skillData = GenshinData.getAvatarSkillDataMap().get(skillId);
|
||||
if (skillData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get data for next skill level
|
||||
int currentLevel = avatar.getSkillLevelMap().get(skillId);
|
||||
int nextLevel = currentLevel + 1;
|
||||
int proudSkillId = (skillData.getProudSkillGroupId() * 100) + nextLevel;
|
||||
|
||||
// Capped at level 10 talent
|
||||
if (nextLevel > 10) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Proud skill data
|
||||
ProudSkillData proudSkill = GenshinData.getProudSkillDataMap().get(proudSkillId);
|
||||
if (proudSkill == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure break level is correct
|
||||
if (avatar.getPromoteLevel() < proudSkill.getBreakLevel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure player has cost items
|
||||
for (ItemParamData cost : proudSkill.getCostItems()) {
|
||||
if (cost.getId() == 0) {
|
||||
continue;
|
||||
}
|
||||
GenshinItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId());
|
||||
if (feedItem == null || feedItem.getCount() < cost.getCount()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Mora check
|
||||
if (player.getMora() >= proudSkill.getCoinCost()) {
|
||||
player.setMora(player.getMora() - proudSkill.getCoinCost());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Consume promote filler items
|
||||
for (ItemParamData cost : proudSkill.getCostItems()) {
|
||||
if (cost.getId() == 0) {
|
||||
continue;
|
||||
}
|
||||
GenshinItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId());
|
||||
player.getInventory().removeItem(feedItem, cost.getCount());
|
||||
}
|
||||
|
||||
// Upgrade skill
|
||||
avatar.getSkillLevelMap().put(skillId, nextLevel);
|
||||
avatar.save();
|
||||
|
||||
// Packet
|
||||
player.sendPacket(new PacketAvatarSkillChangeNotify(avatar, skillId, currentLevel, nextLevel));
|
||||
player.sendPacket(new PacketAvatarSkillUpgradeRsp(avatar, skillId, currentLevel, nextLevel));
|
||||
}
|
||||
|
||||
public void unlockAvatarConstellation(GenshinPlayer player, long guid) {
|
||||
// Sanity checks
|
||||
GenshinAvatar avatar = player.getAvatars().getAvatarByGuid(guid);
|
||||
if (avatar == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get talent
|
||||
int currentTalentLevel = avatar.getCoreProudSkillLevel();
|
||||
int nextTalentId = ((avatar.getAvatarId() % 10000000) * 10) + currentTalentLevel + 1;
|
||||
AvatarTalentData talentData = GenshinData.getAvatarTalentDataMap().get(nextTalentId);
|
||||
|
||||
if (talentData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
GenshinItem costItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(talentData.getMainCostItemId());
|
||||
if (costItem == null || costItem.getCount() < talentData.getMainCostItemCount()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Consume item
|
||||
player.getInventory().removeItem(costItem, talentData.getMainCostItemCount());
|
||||
|
||||
// Apply + recalc
|
||||
avatar.getTalentIdList().add(talentData.getId());
|
||||
avatar.setCoreProudSkillLevel(currentTalentLevel + 1);
|
||||
avatar.recalcStats();
|
||||
|
||||
// Packet
|
||||
player.sendPacket(new PacketAvatarUnlockTalentNotify(avatar, nextTalentId));
|
||||
player.sendPacket(new PacketUnlockAvatarTalentRsp(avatar, nextTalentId));
|
||||
|
||||
// Proud skill bonus map
|
||||
OpenConfigEntry entry = GenshinData.getOpenConfigEntries().get(talentData.getOpenConfig());
|
||||
if (entry != null && entry.getExtraTalentIndex() > 0) {
|
||||
avatar.recalcProudSkillBonusMap();
|
||||
player.sendPacket(new PacketProudSkillExtraLevelNotify(avatar, entry.getExtraTalentIndex()));
|
||||
}
|
||||
|
||||
// Resend ability embryos
|
||||
if (avatar.getAsEntity() != null) {
|
||||
player.sendPacket(new PacketAbilityChangeNotify(avatar.getAsEntity()));
|
||||
}
|
||||
|
||||
// Save avatar
|
||||
avatar.save();
|
||||
}
|
||||
|
||||
public void destroyMaterial(GenshinPlayer player, List<MaterialInfo> list) {
|
||||
// Return materials
|
||||
Int2IntOpenHashMap returnMaterialMap = new Int2IntOpenHashMap();
|
||||
|
||||
for (MaterialInfo info : list) {
|
||||
// Sanity check
|
||||
if (info.getCount() <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
GenshinItem item = player.getInventory().getItemByGuid(info.getGuid());
|
||||
if (item == null || !item.isDestroyable()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove
|
||||
int removeAmount = Math.min(info.getCount(), item.getCount());
|
||||
player.getInventory().removeItem(item, removeAmount);
|
||||
|
||||
// Delete material return items
|
||||
if (item.getItemData().getDestroyReturnMaterial().length > 0) {
|
||||
for (int i = 0; i < item.getItemData().getDestroyReturnMaterial().length; i++) {
|
||||
returnMaterialMap.addTo(item.getItemData().getDestroyReturnMaterial()[i], item.getItemData().getDestroyReturnMaterialCount()[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Give back items
|
||||
if (returnMaterialMap.size() > 0) {
|
||||
for (Int2IntMap.Entry e : returnMaterialMap.int2IntEntrySet()) {
|
||||
player.getInventory().addItem(new GenshinItem(e.getIntKey(), e.getIntValue()));
|
||||
}
|
||||
}
|
||||
|
||||
// Packets
|
||||
player.sendPacket(new PacketDestroyMaterialRsp(returnMaterialMap));
|
||||
}
|
||||
|
||||
public GenshinItem useItem(GenshinPlayer player, long targetGuid, long itemGuid, int count) {
|
||||
GenshinAvatar target = player.getAvatars().getAvatarByGuid(targetGuid);
|
||||
GenshinItem useItem = player.getInventory().getItemByGuid(itemGuid);
|
||||
|
||||
if (useItem == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int used = 0;
|
||||
|
||||
// Use
|
||||
switch (useItem.getItemData().getMaterialType()) {
|
||||
case MATERIAL_FOOD:
|
||||
if (useItem.getItemData().getUseTarget().equals("ITEM_USE_TARGET_SPECIFY_DEAD_AVATAR")) {
|
||||
if (target == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
used = player.getTeamManager().reviveAvatar(target) ? 1 : 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (used > 0) {
|
||||
player.getInventory().removeItem(useItem, used);
|
||||
return useItem;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package emu.grasscutter.game.managers;
|
||||
|
||||
import emu.grasscutter.game.CoopRequest;
|
||||
import emu.grasscutter.game.GenshinPlayer;
|
||||
import emu.grasscutter.game.GenshinPlayer.SceneLoadState;
|
||||
import emu.grasscutter.game.props.EnterReason;
|
||||
import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType;
|
||||
import emu.grasscutter.net.proto.PlayerApplyEnterMpReasonOuterClass.PlayerApplyEnterMpReason;
|
||||
import emu.grasscutter.game.World;
|
||||
import emu.grasscutter.server.game.GameServer;
|
||||
import emu.grasscutter.server.packet.send.PacketPlayerApplyEnterMpNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketPlayerApplyEnterMpResultNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketPlayerEnterSceneNotify;
|
||||
|
||||
public class MultiplayerManager {
|
||||
private final GameServer server;
|
||||
|
||||
public MultiplayerManager(GameServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public GameServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public void applyEnterMp(GenshinPlayer player, int targetUid) {
|
||||
GenshinPlayer target = getServer().getPlayerById(targetUid);
|
||||
if (target == null) {
|
||||
player.sendPacket(new PacketPlayerApplyEnterMpResultNotify(targetUid, "", false, PlayerApplyEnterMpReason.PlayerCannotEnterMp));
|
||||
return;
|
||||
}
|
||||
|
||||
// Sanity checks - Dont let player join if already in multiplayer
|
||||
if (player.getWorld().isMultiplayer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.getWorld().isDungeon()) {
|
||||
player.sendPacket(new PacketPlayerApplyEnterMpResultNotify(targetUid, "", false, PlayerApplyEnterMpReason.SceneCannotEnter));
|
||||
return;
|
||||
}
|
||||
|
||||
// Get request
|
||||
CoopRequest request = target.getCoopRequests().get(player.getId());
|
||||
|
||||
if (request != null && !request.isExpired()) {
|
||||
// Join request already exists
|
||||
return;
|
||||
}
|
||||
|
||||
// Put request in
|
||||
request = new CoopRequest(player);
|
||||
target.getCoopRequests().put(player.getId(), request);
|
||||
|
||||
// Packet
|
||||
target.sendPacket(new PacketPlayerApplyEnterMpNotify(player));
|
||||
}
|
||||
|
||||
public void applyEnterMpReply(GenshinPlayer player, int applyUid, boolean isAgreed) {
|
||||
// Checks
|
||||
CoopRequest request = player.getCoopRequests().get(applyUid);
|
||||
if (request == null || request.isExpired()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove now that we are handling it
|
||||
GenshinPlayer requester = request.getRequester();
|
||||
player.getCoopRequests().remove(applyUid);
|
||||
|
||||
// Sanity checks - Dont let player join if already in multiplayer
|
||||
if (requester.getWorld().isMultiplayer()) {
|
||||
request.getRequester().sendPacket(new PacketPlayerApplyEnterMpResultNotify(player, false, PlayerApplyEnterMpReason.PlayerCannotEnterMp));
|
||||
return;
|
||||
}
|
||||
|
||||
// Response packet
|
||||
request.getRequester().sendPacket(new PacketPlayerApplyEnterMpResultNotify(player, isAgreed, PlayerApplyEnterMpReason.PlayerJudge));
|
||||
|
||||
// Declined
|
||||
if (!isAgreed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Success
|
||||
if (!player.getWorld().isMultiplayer()) {
|
||||
// Player not in multiplayer, create multiplayer world
|
||||
World world = new World(player, true);
|
||||
|
||||
// Add
|
||||
world.addPlayer(player);
|
||||
|
||||
// Rejoin packet
|
||||
player.sendPacket(new PacketPlayerEnterSceneNotify(player, player, EnterType.EnterSelf, EnterReason.HostFromSingleToMp, player.getWorld().getSceneId(), player.getPos()));
|
||||
}
|
||||
|
||||
// Make requester join
|
||||
player.getWorld().addPlayer(requester);
|
||||
|
||||
// Packet
|
||||
requester.sendPacket(new PacketPlayerEnterSceneNotify(requester, player, EnterType.EnterOther, EnterReason.TeamJoin, player.getWorld().getSceneId(), player.getPos()));
|
||||
requester.getPos().set(player.getPos());
|
||||
requester.getRotation().set(player.getRotation());
|
||||
}
|
||||
|
||||
public boolean leaveCoop(GenshinPlayer player) {
|
||||
// Make sure player's world is multiplayer
|
||||
if (!player.getWorld().isMultiplayer()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure everyone's scene is loaded
|
||||
for (GenshinPlayer p : player.getWorld().getPlayers()) {
|
||||
if (p.getSceneLoadState() != SceneLoadState.LOADED) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Create new world for player
|
||||
World world = new World(player);
|
||||
world.addPlayer(player);
|
||||
|
||||
// Packet
|
||||
player.sendPacket(new PacketPlayerEnterSceneNotify(player, EnterType.EnterSelf, EnterReason.TeamBack, player.getWorld().getSceneId(), player.getPos()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean kickPlayer(GenshinPlayer player, int targetUid) {
|
||||
// Make sure player's world is multiplayer and that player is owner
|
||||
if (!player.getWorld().isMultiplayer() || player.getWorld().getHost() != player) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get victim and sanity checks
|
||||
GenshinPlayer victim = player.getServer().getPlayerById(targetUid);
|
||||
|
||||
if (victim == null || victim == player) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure victim's scene has loaded
|
||||
if (victim.getSceneLoadState() != SceneLoadState.LOADED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Kick
|
||||
World world = new World(victim);
|
||||
world.addPlayer(victim);
|
||||
|
||||
victim.sendPacket(new PacketPlayerEnterSceneNotify(victim, EnterType.EnterSelf, EnterReason.TeamKick, victim.getWorld().getSceneId(), victim.getPos()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
211
src/main/java/emu/grasscutter/game/props/ActionReason.java
Normal file
211
src/main/java/emu/grasscutter/game/props/ActionReason.java
Normal file
@@ -0,0 +1,211 @@
|
||||
package emu.grasscutter.game.props;
|
||||
|
||||
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 ActionReason {
|
||||
None(0),
|
||||
QuestItem(1),
|
||||
QuestReward(2),
|
||||
Trifle(3),
|
||||
Shop(4),
|
||||
PlayerUpgradeReward(5),
|
||||
AddAvatar(6),
|
||||
GadgetEnvAnimal(7),
|
||||
MonsterEnvAnimal(8),
|
||||
Compound(9),
|
||||
Cook(10),
|
||||
Gather(11),
|
||||
MailAttachment(12),
|
||||
CityLevelupReturn(15),
|
||||
CityLevelupReward(17),
|
||||
AreaExploreReward(18),
|
||||
UnlockPointReward(19),
|
||||
DungeonFirstPass(20),
|
||||
DungeonPass(21),
|
||||
ChangeElemType(23),
|
||||
FetterOpen(25),
|
||||
DailyTaskScore(26),
|
||||
DailyTaskHost(27),
|
||||
RandTaskHost(28),
|
||||
Expedition(29),
|
||||
Gacha(30),
|
||||
Combine(31),
|
||||
RandTaskGuest(32),
|
||||
DailyTaskGuest(33),
|
||||
ForgeOutput(34),
|
||||
ForgeReturn(35),
|
||||
InitAvatar(36),
|
||||
MonsterDie(37),
|
||||
Gm(38),
|
||||
OpenChest(39),
|
||||
GadgetDie(40),
|
||||
MonsterChangeHp(41),
|
||||
SubfieldDrop(42),
|
||||
PushTipsReward(43),
|
||||
ActivityMonsterDrop(44),
|
||||
ActivityGather(45),
|
||||
ActivitySubfieldDrop(46),
|
||||
TowerScheduleReward(47),
|
||||
TowerFloorStarReward(48),
|
||||
TowerFirstPassReward(49),
|
||||
TowerDailyReward(50),
|
||||
HitClientTrivialEntity(51),
|
||||
OpenWorldBossChest(52),
|
||||
MaterialDeleteReturn(53),
|
||||
SignInReward(54),
|
||||
OpenBlossomChest(55),
|
||||
Recharge(56),
|
||||
BonusActivityReward(57),
|
||||
TowerCommemorativeReward(58),
|
||||
TowerSkipFloorReward(59),
|
||||
RechargeBonus(60),
|
||||
RechargeCard(61),
|
||||
RechargeCardDaily(62),
|
||||
RechargeCardReplace(63),
|
||||
RechargeCardReplaceFree(64),
|
||||
RechargePlayReplace(65),
|
||||
MpPlayTakeReward(66),
|
||||
ActivityWatcher(67),
|
||||
SalesmanDeliverItem(68),
|
||||
SalesmanReward(69),
|
||||
Rebate(70),
|
||||
McoinExchangeHcoin(71),
|
||||
DailyTaskExchangeLegendaryKey(72),
|
||||
UnlockPersonLine(73),
|
||||
FetterLevelReward(74),
|
||||
BuyResin(75),
|
||||
RechargePackage(76),
|
||||
DeliveryDailyReward(77),
|
||||
CityReputationLevel(78),
|
||||
CityReputationQuest(79),
|
||||
CityReputationRequest(80),
|
||||
CityReputationExplore(81),
|
||||
OffergingLevel(82),
|
||||
RoutineHost(83),
|
||||
RoutineGuest(84),
|
||||
TreasureMapSpotToken(89),
|
||||
TreasureMapBonusLevelReward(90),
|
||||
TreasureMapMpReward(91),
|
||||
Convert(92),
|
||||
OverflowTransform(93),
|
||||
ActivityAvatarSelectionReward(96),
|
||||
ActivityWatcherBatch(97),
|
||||
HitTreeDrop(98),
|
||||
GetHomeLevelupReward(99),
|
||||
HomeDefaultFurniture(100),
|
||||
ActivityCond(101),
|
||||
BattlePassNotify(102),
|
||||
PlayerUseItem(1001),
|
||||
DropItem(1002),
|
||||
WeaponUpgrade(1011),
|
||||
WeaponPromote(1012),
|
||||
WeaponAwaken(1013),
|
||||
RelicUpgrade(1014),
|
||||
Ability(1015),
|
||||
DungeonStatueDrop(1016),
|
||||
OfflineMsg(1017),
|
||||
AvatarUpgrade(1018),
|
||||
AvatarPromote(1019),
|
||||
QuestAction(1021),
|
||||
CityLevelup(1022),
|
||||
UpgradeSkill(1024),
|
||||
UnlockTalent(1025),
|
||||
UpgradeProudSkill(1026),
|
||||
PlayerLevelLimitUp(1027),
|
||||
DungeonDaily(1028),
|
||||
ItemGiving(1030),
|
||||
ForgeCost(1031),
|
||||
InvestigationReward(1032),
|
||||
InvestigationTargetReward(1033),
|
||||
GadgetInteract(1034),
|
||||
SeaLampCiMaterial(1036),
|
||||
SeaLampContributionReward(1037),
|
||||
SeaLampPhaseReward(1038),
|
||||
SeaLampFlyLamp(1039),
|
||||
AutoRecover(1040),
|
||||
ActivityExpireItem(1041),
|
||||
SubCoinNegative(1042),
|
||||
BargainDeduct(1043),
|
||||
BattlePassPaidReward(1044),
|
||||
BattlePassLevelReward(1045),
|
||||
TrialAvatarActivityFirstPassReward(1046),
|
||||
BuyBattlePassLevel(1047),
|
||||
GrantBirthdayBenefit(1048),
|
||||
AchievementReward(1049),
|
||||
AchievementGoalReward(1050),
|
||||
FirstShareToSocialNetwork(1051),
|
||||
DestroyMaterial(1052),
|
||||
CodexLevelupReward(1053),
|
||||
HuntingOfferReward(1054),
|
||||
UseWidgetAnchorPoint(1055),
|
||||
UseWidgetBonfire(1056),
|
||||
UngradeWeaponReturnMaterial(1057),
|
||||
UseWidgetOneoffGatherPointDetector(1058),
|
||||
UseWidgetClientCollector(1059),
|
||||
UseWidgetClientDetector(1060),
|
||||
TakeGeneralReward(1061),
|
||||
AsterTakeSpecialReward(1062),
|
||||
RemoveCodexBook(1063),
|
||||
OfferingItem(1064),
|
||||
UseWidgetGadgetBuilder(1065),
|
||||
EffigyFirstPassReward(1066),
|
||||
EffigyReward(1067),
|
||||
ReunionFirstGiftReward(1068),
|
||||
ReunionSignInReward(1069),
|
||||
ReunionWatcherReward(1070),
|
||||
SalesmanMpReward(1071),
|
||||
ActionReasionAvatarPromoteReward(1072),
|
||||
BlessingRedeemReward(1073),
|
||||
ActionMiracleRingReward(1074),
|
||||
ExpeditionReward(1075),
|
||||
TreasureMapRemoveDetector(1076),
|
||||
MechanicusDungeonTicket(1077),
|
||||
MechanicusLevelupGear(1078),
|
||||
MechanicusBattleSettle(1079),
|
||||
RegionSearchReward(1080),
|
||||
UnlockCoopChapter(1081),
|
||||
TakeCoopReward(1082),
|
||||
FleurFairDungeonReward(1083),
|
||||
ActivityScore(1084),
|
||||
ChannellerSlabOneoffDungeonReward(1085),
|
||||
FurnitureMakeStart(1086),
|
||||
FurnitureMakeTake(1087),
|
||||
FurnitureMakeCancel(1088),
|
||||
FurnitureMakeFastFinish(1089),
|
||||
ChannellerSlabLoopDungeonFirstPassReward(1090),
|
||||
ChannellerSlabLoopDungeonScoreReward(1091),
|
||||
HomeLimitedShopBuy(1092),
|
||||
HomeCoinCollect(1093);
|
||||
|
||||
private final int value;
|
||||
private static final Int2ObjectMap<ActionReason> map = new Int2ObjectOpenHashMap<>();
|
||||
private static final Map<String, ActionReason> stringMap = new HashMap<>();
|
||||
|
||||
static {
|
||||
Stream.of(values()).forEach(e -> {
|
||||
map.put(e.getValue(), e);
|
||||
stringMap.put(e.name(), e);
|
||||
});
|
||||
}
|
||||
|
||||
private ActionReason(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static ActionReason getTypeByValue(int value) {
|
||||
return map.getOrDefault(value, None);
|
||||
}
|
||||
|
||||
public static ActionReason getTypeByName(String name) {
|
||||
return stringMap.getOrDefault(name, None);
|
||||
}
|
||||
}
|
||||
45
src/main/java/emu/grasscutter/game/props/ClimateType.java
Normal file
45
src/main/java/emu/grasscutter/game/props/ClimateType.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package emu.grasscutter.game.props;
|
||||
|
||||
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 ClimateType {
|
||||
CLIMATE_NONE(0),
|
||||
CLIMATE_SUNNY(1),
|
||||
CLIMATE_CLOUDY(2),
|
||||
CLIMATE_RAIN(3),
|
||||
CLIMATE_THUNDERSTORM(4),
|
||||
CLIMATE_SNOW(5),
|
||||
CLIMATE_MIST(6);
|
||||
|
||||
private final int value;
|
||||
private static final Int2ObjectMap<ClimateType> map = new Int2ObjectOpenHashMap<>();
|
||||
private static final Map<String, ClimateType> stringMap = new HashMap<>();
|
||||
|
||||
static {
|
||||
Stream.of(values()).forEach(e -> {
|
||||
map.put(e.getValue(), e);
|
||||
stringMap.put(e.name(), e);
|
||||
});
|
||||
}
|
||||
|
||||
private ClimateType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static ClimateType getTypeByValue(int value) {
|
||||
return map.getOrDefault(value, CLIMATE_NONE);
|
||||
}
|
||||
|
||||
public static ClimateType getTypeByName(String name) {
|
||||
return stringMap.getOrDefault(name, CLIMATE_NONE);
|
||||
}
|
||||
}
|
||||
76
src/main/java/emu/grasscutter/game/props/ElementType.java
Normal file
76
src/main/java/emu/grasscutter/game/props/ElementType.java
Normal file
@@ -0,0 +1,76 @@
|
||||
package emu.grasscutter.game.props;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import emu.grasscutter.utils.Utils;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
public enum ElementType {
|
||||
None (0, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY),
|
||||
Fire (1, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY, 10101, "TeamResonance_Fire_Lv2"),
|
||||
Water (2, FightProperty.FIGHT_PROP_MAX_WATER_ENERGY, 10201, "TeamResonance_Water_Lv2"),
|
||||
Grass (3, FightProperty.FIGHT_PROP_MAX_GRASS_ENERGY),
|
||||
Electric (4, FightProperty.FIGHT_PROP_MAX_ELEC_ENERGY, 10401, "TeamResonance_Electric_Lv2"),
|
||||
Ice (5, FightProperty.FIGHT_PROP_MAX_ICE_ENERGY, 10601, "TeamResonance_Ice_Lv2"),
|
||||
Frozen (6, FightProperty.FIGHT_PROP_MAX_ICE_ENERGY),
|
||||
Wind (7, FightProperty.FIGHT_PROP_MAX_WIND_ENERGY, 10301, "TeamResonance_Wind_Lv2"),
|
||||
Rock (8, FightProperty.FIGHT_PROP_MAX_ROCK_ENERGY, 10701, "TeamResonance_Rock_Lv2"),
|
||||
AntiFire (9, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY),
|
||||
Default (255, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY, 10801, "TeamResonance_AllDifferent");
|
||||
|
||||
private final int value;
|
||||
private final int teamResonanceId;
|
||||
private final FightProperty energyProperty;
|
||||
private final int configHash;
|
||||
private static final Int2ObjectMap<ElementType> map = new Int2ObjectOpenHashMap<>();
|
||||
private static final Map<String, ElementType> stringMap = new HashMap<>();
|
||||
|
||||
static {
|
||||
Stream.of(values()).forEach(e -> {
|
||||
map.put(e.getValue(), e);
|
||||
stringMap.put(e.name(), e);
|
||||
});
|
||||
}
|
||||
|
||||
private ElementType(int value, FightProperty energyProperty) {
|
||||
this(value, energyProperty, 0, null);
|
||||
}
|
||||
|
||||
private ElementType(int value, FightProperty energyProperty, int teamResonanceId, String configName) {
|
||||
this.value = value;
|
||||
this.energyProperty = energyProperty;
|
||||
this.teamResonanceId = teamResonanceId;
|
||||
if (configName != null) {
|
||||
this.configHash = Utils.abilityHash(configName);
|
||||
} else {
|
||||
this.configHash = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public FightProperty getEnergyProperty() {
|
||||
return energyProperty;
|
||||
}
|
||||
|
||||
public int getTeamResonanceId() {
|
||||
return teamResonanceId;
|
||||
}
|
||||
|
||||
public int getConfigHash() {
|
||||
return configHash;
|
||||
}
|
||||
|
||||
public static ElementType getTypeByValue(int value) {
|
||||
return map.getOrDefault(value, None);
|
||||
}
|
||||
|
||||
public static ElementType getTypeByName(String name) {
|
||||
return stringMap.getOrDefault(name, None);
|
||||
}
|
||||
}
|
||||
70
src/main/java/emu/grasscutter/game/props/EnterReason.java
Normal file
70
src/main/java/emu/grasscutter/game/props/EnterReason.java
Normal file
@@ -0,0 +1,70 @@
|
||||
package emu.grasscutter.game.props;
|
||||
|
||||
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 EnterReason {
|
||||
None(0),
|
||||
Login(1),
|
||||
DungeonReplay(11),
|
||||
DungeonReviveOnWaypoint(12),
|
||||
DungeonEnter(13),
|
||||
DungeonQuit(14),
|
||||
Gm(21),
|
||||
QuestRollback(31),
|
||||
Revival(32),
|
||||
PersonalScene(41),
|
||||
TransPoint(42),
|
||||
ClientTransmit(43),
|
||||
ForceDragBack(44),
|
||||
TeamKick(51),
|
||||
TeamJoin(52),
|
||||
TeamBack(53),
|
||||
Muip(54),
|
||||
DungeonInviteAccept(55),
|
||||
Lua(56),
|
||||
ActivityLoadTerrain(57),
|
||||
HostFromSingleToMp(58),
|
||||
MpPlay(59),
|
||||
AnchorPoint(60),
|
||||
LuaSkipUi(61),
|
||||
ReloadTerrain(62),
|
||||
DraftTransfer(63),
|
||||
EnterHome(64),
|
||||
ExitHome(65),
|
||||
ChangeHomeModule(66),
|
||||
Gallery(67),
|
||||
HomeSceneJump(68),
|
||||
HideAndSeek(69);
|
||||
|
||||
private final int value;
|
||||
private static final Int2ObjectMap<EnterReason> map = new Int2ObjectOpenHashMap<>();
|
||||
private static final Map<String, EnterReason> stringMap = new HashMap<>();
|
||||
|
||||
static {
|
||||
Stream.of(values()).forEach(e -> {
|
||||
map.put(e.getValue(), e);
|
||||
stringMap.put(e.name(), e);
|
||||
});
|
||||
}
|
||||
|
||||
private EnterReason(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static EnterReason getTypeByValue(int value) {
|
||||
return map.getOrDefault(value, None);
|
||||
}
|
||||
|
||||
public static EnterReason getTypeByName(String name) {
|
||||
return stringMap.getOrDefault(name, None);
|
||||
}
|
||||
}
|
||||
21
src/main/java/emu/grasscutter/game/props/EntityIdType.java
Normal file
21
src/main/java/emu/grasscutter/game/props/EntityIdType.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package emu.grasscutter.game.props;
|
||||
|
||||
public enum EntityIdType {
|
||||
AVATAR (0x01),
|
||||
MONSTER (0x02),
|
||||
NPC (0x03),
|
||||
GADGET (0x04),
|
||||
WEAPON (0x06),
|
||||
TEAM (0x09),
|
||||
MPLEVEL (0x0b);
|
||||
|
||||
private final int id;
|
||||
|
||||
private EntityIdType(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
136
src/main/java/emu/grasscutter/game/props/FightProperty.java
Normal file
136
src/main/java/emu/grasscutter/game/props/FightProperty.java
Normal file
@@ -0,0 +1,136 @@
|
||||
package emu.grasscutter.game.props;
|
||||
|
||||
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 FightProperty {
|
||||
FIGHT_PROP_NONE(0),
|
||||
FIGHT_PROP_BASE_HP(1),
|
||||
FIGHT_PROP_HP(2),
|
||||
FIGHT_PROP_HP_PERCENT(3),
|
||||
FIGHT_PROP_BASE_ATTACK(4),
|
||||
FIGHT_PROP_ATTACK(5),
|
||||
FIGHT_PROP_ATTACK_PERCENT(6),
|
||||
FIGHT_PROP_BASE_DEFENSE(7),
|
||||
FIGHT_PROP_DEFENSE(8),
|
||||
FIGHT_PROP_DEFENSE_PERCENT(9),
|
||||
FIGHT_PROP_BASE_SPEED(10),
|
||||
FIGHT_PROP_SPEED_PERCENT(11),
|
||||
FIGHT_PROP_HP_MP_PERCENT(12),
|
||||
FIGHT_PROP_ATTACK_MP_PERCENT(13),
|
||||
FIGHT_PROP_CRITICAL(20),
|
||||
FIGHT_PROP_ANTI_CRITICAL(21),
|
||||
FIGHT_PROP_CRITICAL_HURT(22),
|
||||
FIGHT_PROP_CHARGE_EFFICIENCY(23),
|
||||
FIGHT_PROP_ADD_HURT(24),
|
||||
FIGHT_PROP_SUB_HURT(25),
|
||||
FIGHT_PROP_HEAL_ADD(26),
|
||||
FIGHT_PROP_HEALED_ADD(27),
|
||||
FIGHT_PROP_ELEMENT_MASTERY(28),
|
||||
FIGHT_PROP_PHYSICAL_SUB_HURT(29),
|
||||
FIGHT_PROP_PHYSICAL_ADD_HURT(30),
|
||||
FIGHT_PROP_DEFENCE_IGNORE_RATIO(31),
|
||||
FIGHT_PROP_DEFENCE_IGNORE_DELTA(32),
|
||||
FIGHT_PROP_FIRE_ADD_HURT(40),
|
||||
FIGHT_PROP_ELEC_ADD_HURT(41),
|
||||
FIGHT_PROP_WATER_ADD_HURT(42),
|
||||
FIGHT_PROP_GRASS_ADD_HURT(43),
|
||||
FIGHT_PROP_WIND_ADD_HURT(44),
|
||||
FIGHT_PROP_ROCK_ADD_HURT(45),
|
||||
FIGHT_PROP_ICE_ADD_HURT(46),
|
||||
FIGHT_PROP_HIT_HEAD_ADD_HURT(47),
|
||||
FIGHT_PROP_FIRE_SUB_HURT(50),
|
||||
FIGHT_PROP_ELEC_SUB_HURT(51),
|
||||
FIGHT_PROP_WATER_SUB_HURT(52),
|
||||
FIGHT_PROP_GRASS_SUB_HURT(53),
|
||||
FIGHT_PROP_WIND_SUB_HURT(54),
|
||||
FIGHT_PROP_ROCK_SUB_HURT(55),
|
||||
FIGHT_PROP_ICE_SUB_HURT(56),
|
||||
FIGHT_PROP_EFFECT_HIT(60),
|
||||
FIGHT_PROP_EFFECT_RESIST(61),
|
||||
FIGHT_PROP_FREEZE_RESIST(62),
|
||||
FIGHT_PROP_TORPOR_RESIST(63),
|
||||
FIGHT_PROP_DIZZY_RESIST(64),
|
||||
FIGHT_PROP_FREEZE_SHORTEN(65),
|
||||
FIGHT_PROP_TORPOR_SHORTEN(66),
|
||||
FIGHT_PROP_DIZZY_SHORTEN(67),
|
||||
FIGHT_PROP_MAX_FIRE_ENERGY(70),
|
||||
FIGHT_PROP_MAX_ELEC_ENERGY(71),
|
||||
FIGHT_PROP_MAX_WATER_ENERGY(72),
|
||||
FIGHT_PROP_MAX_GRASS_ENERGY(73),
|
||||
FIGHT_PROP_MAX_WIND_ENERGY(74),
|
||||
FIGHT_PROP_MAX_ICE_ENERGY(75),
|
||||
FIGHT_PROP_MAX_ROCK_ENERGY(76),
|
||||
FIGHT_PROP_SKILL_CD_MINUS_RATIO(80),
|
||||
FIGHT_PROP_SHIELD_COST_MINUS_RATIO(81),
|
||||
FIGHT_PROP_CUR_FIRE_ENERGY(1000),
|
||||
FIGHT_PROP_CUR_ELEC_ENERGY(1001),
|
||||
FIGHT_PROP_CUR_WATER_ENERGY(1002),
|
||||
FIGHT_PROP_CUR_GRASS_ENERGY(1003),
|
||||
FIGHT_PROP_CUR_WIND_ENERGY(1004),
|
||||
FIGHT_PROP_CUR_ICE_ENERGY(1005),
|
||||
FIGHT_PROP_CUR_ROCK_ENERGY(1006),
|
||||
FIGHT_PROP_CUR_HP(1010),
|
||||
FIGHT_PROP_MAX_HP(2000),
|
||||
FIGHT_PROP_CUR_ATTACK(2001),
|
||||
FIGHT_PROP_CUR_DEFENSE(2002),
|
||||
FIGHT_PROP_CUR_SPEED(2003),
|
||||
FIGHT_PROP_NONEXTRA_ATTACK(3000),
|
||||
FIGHT_PROP_NONEXTRA_DEFENSE(3001),
|
||||
FIGHT_PROP_NONEXTRA_CRITICAL(3002),
|
||||
FIGHT_PROP_NONEXTRA_ANTI_CRITICAL(3003),
|
||||
FIGHT_PROP_NONEXTRA_CRITICAL_HURT(3004),
|
||||
FIGHT_PROP_NONEXTRA_CHARGE_EFFICIENCY(3005),
|
||||
FIGHT_PROP_NONEXTRA_ELEMENT_MASTERY(3006),
|
||||
FIGHT_PROP_NONEXTRA_PHYSICAL_SUB_HURT(3007),
|
||||
FIGHT_PROP_NONEXTRA_FIRE_ADD_HURT(3008),
|
||||
FIGHT_PROP_NONEXTRA_ELEC_ADD_HURT(3009),
|
||||
FIGHT_PROP_NONEXTRA_WATER_ADD_HURT(3010),
|
||||
FIGHT_PROP_NONEXTRA_GRASS_ADD_HURT(3011),
|
||||
FIGHT_PROP_NONEXTRA_WIND_ADD_HURT(3012),
|
||||
FIGHT_PROP_NONEXTRA_ROCK_ADD_HURT(3013),
|
||||
FIGHT_PROP_NONEXTRA_ICE_ADD_HURT(3014),
|
||||
FIGHT_PROP_NONEXTRA_FIRE_SUB_HURT(3015),
|
||||
FIGHT_PROP_NONEXTRA_ELEC_SUB_HURT(3016),
|
||||
FIGHT_PROP_NONEXTRA_WATER_SUB_HURT(3017),
|
||||
FIGHT_PROP_NONEXTRA_GRASS_SUB_HURT(3018),
|
||||
FIGHT_PROP_NONEXTRA_WIND_SUB_HURT(3019),
|
||||
FIGHT_PROP_NONEXTRA_ROCK_SUB_HURT(3020),
|
||||
FIGHT_PROP_NONEXTRA_ICE_SUB_HURT(3021),
|
||||
FIGHT_PROP_NONEXTRA_SKILL_CD_MINUS_RATIO(3022),
|
||||
FIGHT_PROP_NONEXTRA_SHIELD_COST_MINUS_RATIO(3023),
|
||||
FIGHT_PROP_NONEXTRA_PHYSICAL_ADD_HURT(3024);
|
||||
|
||||
private final int id;
|
||||
private static final Int2ObjectMap<FightProperty> map = new Int2ObjectOpenHashMap<>();
|
||||
private static final Map<String, FightProperty> stringMap = new HashMap<>();
|
||||
|
||||
public static final int[] fightProps = new int[] {1, 4, 7, 20, 21, 22, 23, 26, 27, 28, 29, 30, 40, 41, 42, 43, 44, 45, 46, 50, 51, 52, 53, 54, 55, 56, 2000, 2001, 2002, 2003, 1010};
|
||||
|
||||
static {
|
||||
Stream.of(values()).forEach(e -> {
|
||||
map.put(e.getId(), e);
|
||||
stringMap.put(e.name(), e);
|
||||
});
|
||||
}
|
||||
|
||||
private FightProperty(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public static FightProperty getPropById(int value) {
|
||||
return map.getOrDefault(value, FIGHT_PROP_NONE);
|
||||
}
|
||||
|
||||
public static FightProperty getPropByName(String name) {
|
||||
return stringMap.getOrDefault(name, FIGHT_PROP_NONE);
|
||||
}
|
||||
}
|
||||
100
src/main/java/emu/grasscutter/game/props/GrowCurve.java
Normal file
100
src/main/java/emu/grasscutter/game/props/GrowCurve.java
Normal file
@@ -0,0 +1,100 @@
|
||||
package emu.grasscutter.game.props;
|
||||
|
||||
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 GrowCurve {
|
||||
GROW_CURVE_NONE(0),
|
||||
GROW_CURVE_HP(1),
|
||||
GROW_CURVE_ATTACK(2),
|
||||
GROW_CURVE_STAMINA(3),
|
||||
GROW_CURVE_STRIKE(4),
|
||||
GROW_CURVE_ANTI_STRIKE(5),
|
||||
GROW_CURVE_ANTI_STRIKE1(6),
|
||||
GROW_CURVE_ANTI_STRIKE2(7),
|
||||
GROW_CURVE_ANTI_STRIKE3(8),
|
||||
GROW_CURVE_STRIKE_HURT(9),
|
||||
GROW_CURVE_ELEMENT(10),
|
||||
GROW_CURVE_KILL_EXP(11),
|
||||
GROW_CURVE_DEFENSE(12),
|
||||
GROW_CURVE_ATTACK_BOMB(13),
|
||||
GROW_CURVE_HP_LITTLEMONSTER(14),
|
||||
GROW_CURVE_ELEMENT_MASTERY(15),
|
||||
GROW_CURVE_PROGRESSION(16),
|
||||
GROW_CURVE_DEFENDING(17),
|
||||
GROW_CURVE_MHP(18),
|
||||
GROW_CURVE_MATK(19),
|
||||
GROW_CURVE_TOWERATK(20),
|
||||
GROW_CURVE_HP_S5(21),
|
||||
GROW_CURVE_HP_S4(22),
|
||||
GROW_CURVE_HP_2(23),
|
||||
GROW_CURVE_ATTACK_S5(31),
|
||||
GROW_CURVE_ATTACK_S4(32),
|
||||
GROW_CURVE_ATTACK_S3(33),
|
||||
GROW_CURVE_STRIKE_S5(34),
|
||||
GROW_CURVE_DEFENSE_S5(41),
|
||||
GROW_CURVE_DEFENSE_S4(42),
|
||||
GROW_CURVE_ATTACK_101(1101),
|
||||
GROW_CURVE_ATTACK_102(1102),
|
||||
GROW_CURVE_ATTACK_103(1103),
|
||||
GROW_CURVE_ATTACK_104(1104),
|
||||
GROW_CURVE_ATTACK_105(1105),
|
||||
GROW_CURVE_ATTACK_201(1201),
|
||||
GROW_CURVE_ATTACK_202(1202),
|
||||
GROW_CURVE_ATTACK_203(1203),
|
||||
GROW_CURVE_ATTACK_204(1204),
|
||||
GROW_CURVE_ATTACK_205(1205),
|
||||
GROW_CURVE_ATTACK_301(1301),
|
||||
GROW_CURVE_ATTACK_302(1302),
|
||||
GROW_CURVE_ATTACK_303(1303),
|
||||
GROW_CURVE_ATTACK_304(1304),
|
||||
GROW_CURVE_ATTACK_305(1305),
|
||||
GROW_CURVE_CRITICAL_101(2101),
|
||||
GROW_CURVE_CRITICAL_102(2102),
|
||||
GROW_CURVE_CRITICAL_103(2103),
|
||||
GROW_CURVE_CRITICAL_104(2104),
|
||||
GROW_CURVE_CRITICAL_105(2105),
|
||||
GROW_CURVE_CRITICAL_201(2201),
|
||||
GROW_CURVE_CRITICAL_202(2202),
|
||||
GROW_CURVE_CRITICAL_203(2203),
|
||||
GROW_CURVE_CRITICAL_204(2204),
|
||||
GROW_CURVE_CRITICAL_205(2205),
|
||||
GROW_CURVE_CRITICAL_301(2301),
|
||||
GROW_CURVE_CRITICAL_302(2302),
|
||||
GROW_CURVE_CRITICAL_303(2303),
|
||||
GROW_CURVE_CRITICAL_304(2304),
|
||||
GROW_CURVE_CRITICAL_305(2305);
|
||||
|
||||
private final int id;
|
||||
private static final Int2ObjectMap<GrowCurve> map = new Int2ObjectOpenHashMap<>();
|
||||
private static final Map<String, GrowCurve> stringMap = new HashMap<>();
|
||||
|
||||
public static final int[] fightProps = new int[] {1, 4, 7, 20, 21, 22, 23, 26, 27, 28, 29, 30, 40, 41, 42, 43, 44, 45, 46, 50, 51, 52, 53, 54, 55, 56, 2000, 2001, 2002, 2003, 1010};
|
||||
|
||||
static {
|
||||
Stream.of(values()).forEach(e -> {
|
||||
map.put(e.getId(), e);
|
||||
stringMap.put(e.name(), e);
|
||||
});
|
||||
}
|
||||
|
||||
private GrowCurve(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public static GrowCurve getPropById(int value) {
|
||||
return map.getOrDefault(value, GROW_CURVE_NONE);
|
||||
}
|
||||
|
||||
public static GrowCurve getPropByName(String name) {
|
||||
return stringMap.getOrDefault(name, GROW_CURVE_NONE);
|
||||
}
|
||||
}
|
||||
42
src/main/java/emu/grasscutter/game/props/LifeState.java
Normal file
42
src/main/java/emu/grasscutter/game/props/LifeState.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package emu.grasscutter.game.props;
|
||||
|
||||
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 LifeState {
|
||||
LIFE_NONE(0),
|
||||
LIFE_ALIVE(1),
|
||||
LIFE_DEAD(2),
|
||||
LIFE_REVIVE(3);
|
||||
|
||||
private final int value;
|
||||
private static final Int2ObjectMap<LifeState> map = new Int2ObjectOpenHashMap<>();
|
||||
private static final Map<String, LifeState> stringMap = new HashMap<>();
|
||||
|
||||
static {
|
||||
Stream.of(values()).forEach(e -> {
|
||||
map.put(e.getValue(), e);
|
||||
stringMap.put(e.name(), e);
|
||||
});
|
||||
}
|
||||
|
||||
private LifeState(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static LifeState getTypeByValue(int value) {
|
||||
return map.getOrDefault(value, LIFE_NONE);
|
||||
}
|
||||
|
||||
public static LifeState getTypeByName(String name) {
|
||||
return stringMap.getOrDefault(name, LIFE_NONE);
|
||||
}
|
||||
}
|
||||
204
src/main/java/emu/grasscutter/game/props/OpenState.java
Normal file
204
src/main/java/emu/grasscutter/game/props/OpenState.java
Normal file
@@ -0,0 +1,204 @@
|
||||
package emu.grasscutter.game.props;
|
||||
|
||||
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 OpenState {
|
||||
OPEN_STATE_NONE(0),
|
||||
OPEN_STATE_PAIMON(1),
|
||||
OPEN_STATE_PAIMON_NAVIGATION(2),
|
||||
OPEN_STATE_AVATAR_PROMOTE(3),
|
||||
OPEN_STATE_AVATAR_TALENT(4),
|
||||
OPEN_STATE_WEAPON_PROMOTE(5),
|
||||
OPEN_STATE_WEAPON_AWAKEN(6),
|
||||
OPEN_STATE_QUEST_REMIND(7),
|
||||
OPEN_STATE_GAME_GUIDE(8),
|
||||
OPEN_STATE_COOK(9),
|
||||
OPEN_STATE_WEAPON_UPGRADE(10),
|
||||
OPEN_STATE_RELIQUARY_UPGRADE(11),
|
||||
OPEN_STATE_RELIQUARY_PROMOTE(12),
|
||||
OPEN_STATE_WEAPON_PROMOTE_GUIDE(13),
|
||||
OPEN_STATE_WEAPON_CHANGE_GUIDE(14),
|
||||
OPEN_STATE_PLAYER_LVUP_GUIDE(15),
|
||||
OPEN_STATE_FRESHMAN_GUIDE(16),
|
||||
OPEN_STATE_SKIP_FRESHMAN_GUIDE(17),
|
||||
OPEN_STATE_GUIDE_MOVE_CAMERA(18),
|
||||
OPEN_STATE_GUIDE_SCALE_CAMERA(19),
|
||||
OPEN_STATE_GUIDE_KEYBOARD(20),
|
||||
OPEN_STATE_GUIDE_MOVE(21),
|
||||
OPEN_STATE_GUIDE_JUMP(22),
|
||||
OPEN_STATE_GUIDE_SPRINT(23),
|
||||
OPEN_STATE_GUIDE_MAP(24),
|
||||
OPEN_STATE_GUIDE_ATTACK(25),
|
||||
OPEN_STATE_GUIDE_FLY(26),
|
||||
OPEN_STATE_GUIDE_TALENT(27),
|
||||
OPEN_STATE_GUIDE_RELIC(28),
|
||||
OPEN_STATE_GUIDE_RELIC_PROM(29),
|
||||
OPEN_STATE_COMBINE(30),
|
||||
OPEN_STATE_GACHA(31),
|
||||
OPEN_STATE_GUIDE_GACHA(32),
|
||||
OPEN_STATE_GUIDE_TEAM(33),
|
||||
OPEN_STATE_GUIDE_PROUD(34),
|
||||
OPEN_STATE_GUIDE_AVATAR_PROMOTE(35),
|
||||
OPEN_STATE_GUIDE_ADVENTURE_CARD(36),
|
||||
OPEN_STATE_FORGE(37),
|
||||
OPEN_STATE_GUIDE_BAG(38),
|
||||
OPEN_STATE_EXPEDITION(39),
|
||||
OPEN_STATE_GUIDE_ADVENTURE_DAILYTASK(40),
|
||||
OPEN_STATE_GUIDE_ADVENTURE_DUNGEON(41),
|
||||
OPEN_STATE_TOWER(42),
|
||||
OPEN_STATE_WORLD_STAMINA(43),
|
||||
OPEN_STATE_TOWER_FIRST_ENTER(44),
|
||||
OPEN_STATE_RESIN(45),
|
||||
OPEN_STATE_LIMIT_REGION_FRESHMEAT(47),
|
||||
OPEN_STATE_LIMIT_REGION_GLOBAL(48),
|
||||
OPEN_STATE_MULTIPLAYER(49),
|
||||
OPEN_STATE_GUIDE_MOUSEPC(50),
|
||||
OPEN_STATE_GUIDE_MULTIPLAYER(51),
|
||||
OPEN_STATE_GUIDE_DUNGEONREWARD(52),
|
||||
OPEN_STATE_GUIDE_BLOSSOM(53),
|
||||
OPEN_STATE_AVATAR_FASHION(54),
|
||||
OPEN_STATE_PHOTOGRAPH(55),
|
||||
OPEN_STATE_GUIDE_KSLQUEST(56),
|
||||
OPEN_STATE_PERSONAL_LINE(57),
|
||||
OPEN_STATE_GUIDE_PERSONAL_LINE(58),
|
||||
OPEN_STATE_GUIDE_APPEARANCE(59),
|
||||
OPEN_STATE_GUIDE_PROCESS(60),
|
||||
OPEN_STATE_GUIDE_PERSONAL_LINE_KEY(61),
|
||||
OPEN_STATE_GUIDE_WIDGET(62),
|
||||
OPEN_STATE_GUIDE_ACTIVITY_SKILL_ASTER(63),
|
||||
OPEN_STATE_GUIDE_COLDCLIMATE(64),
|
||||
OPEN_STATE_DERIVATIVE_MALL(65),
|
||||
OPEN_STATE_GUIDE_EXITMULTIPLAYER(66),
|
||||
OPEN_STATE_GUIDE_THEATREMACHANICUS_BUILD(67),
|
||||
OPEN_STATE_GUIDE_THEATREMACHANICUS_REBUILD(68),
|
||||
OPEN_STATE_GUIDE_THEATREMACHANICUS_CARD(69),
|
||||
OPEN_STATE_GUIDE_THEATREMACHANICUS_MONSTER(70),
|
||||
OPEN_STATE_GUIDE_THEATREMACHANICUS_MISSION_CHECK(71),
|
||||
OPEN_STATE_GUIDE_THEATREMACHANICUS_BUILD_SELECT(72),
|
||||
OPEN_STATE_GUIDE_THEATREMACHANICUS_CHALLENGE_START(73),
|
||||
OPEN_STATE_GUIDE_CONVERT(74),
|
||||
OPEN_STATE_GUIDE_THEATREMACHANICUS_MULTIPLAYER(75),
|
||||
OPEN_STATE_GUIDE_COOP_TASK(76),
|
||||
OPEN_STATE_GUIDE_HOMEWORLD_ADEPTIABODE(77),
|
||||
OPEN_STATE_GUIDE_HOMEWORLD_DEPLOY(78),
|
||||
OPEN_STATE_GUIDE_CHANNELLERSLAB_EQUIP(79),
|
||||
OPEN_STATE_GUIDE_CHANNELLERSLAB_MP_SOLUTION(80),
|
||||
OPEN_STATE_GUIDE_CHANNELLERSLAB_POWER(81),
|
||||
OPEN_STATE_GUIDE_HIDEANDSEEK_SKILL(82),
|
||||
OPEN_STATE_GUIDE_HOMEWORLD_MAPLIST(83),
|
||||
OPEN_STATE_GUIDE_RELICRESOLVE(84),
|
||||
OPEN_STATE_GUIDE_GGUIDE(85),
|
||||
OPEN_STATE_GUIDE_GGUIDE_HINT(86),
|
||||
OPEN_STATE_CITY_REPUATION_MENGDE(800),
|
||||
OPEN_STATE_CITY_REPUATION_LIYUE(801),
|
||||
OPEN_STATE_CITY_REPUATION_UI_HINT(802),
|
||||
OPEN_STATE_CITY_REPUATION_INAZUMA(803),
|
||||
OPEN_STATE_SHOP_TYPE_MALL(900),
|
||||
OPEN_STATE_SHOP_TYPE_RECOMMANDED(901),
|
||||
OPEN_STATE_SHOP_TYPE_GENESISCRYSTAL(902),
|
||||
OPEN_STATE_SHOP_TYPE_GIFTPACKAGE(903),
|
||||
OPEN_STATE_SHOP_TYPE_PAIMON(1001),
|
||||
OPEN_STATE_SHOP_TYPE_CITY(1002),
|
||||
OPEN_STATE_SHOP_TYPE_BLACKSMITH(1003),
|
||||
OPEN_STATE_SHOP_TYPE_GROCERY(1004),
|
||||
OPEN_STATE_SHOP_TYPE_FOOD(1005),
|
||||
OPEN_STATE_SHOP_TYPE_SEA_LAMP(1006),
|
||||
OPEN_STATE_SHOP_TYPE_VIRTUAL_SHOP(1007),
|
||||
OPEN_STATE_SHOP_TYPE_LIYUE_GROCERY(1008),
|
||||
OPEN_STATE_SHOP_TYPE_LIYUE_SOUVENIR(1009),
|
||||
OPEN_STATE_SHOP_TYPE_LIYUE_RESTAURANT(1010),
|
||||
OPEN_STATE_SHOP_TYPE_INAZUMA_SOUVENIR(1011),
|
||||
OPEN_STATE_SHOP_TYPE_NPC_TOMOKI(1012),
|
||||
OPEN_ADVENTURE_MANUAL(1100),
|
||||
OPEN_ADVENTURE_MANUAL_CITY_MENGDE(1101),
|
||||
OPEN_ADVENTURE_MANUAL_CITY_LIYUE(1102),
|
||||
OPEN_ADVENTURE_MANUAL_MONSTER(1103),
|
||||
OPEN_ADVENTURE_MANUAL_BOSS_DUNGEON(1104),
|
||||
OPEN_STATE_ACTIVITY_SEALAMP(1200),
|
||||
OPEN_STATE_ACTIVITY_SEALAMP_TAB2(1201),
|
||||
OPEN_STATE_ACTIVITY_SEALAMP_TAB3(1202),
|
||||
OPEN_STATE_BATTLE_PASS(1300),
|
||||
OPEN_STATE_BATTLE_PASS_ENTRY(1301),
|
||||
OPEN_STATE_ACTIVITY_CRUCIBLE(1400),
|
||||
OPEN_STATE_ACTIVITY_NEWBEEBOUNS_OPEN(1401),
|
||||
OPEN_STATE_ACTIVITY_NEWBEEBOUNS_CLOSE(1402),
|
||||
OPEN_STATE_ACTIVITY_ENTRY_OPEN(1403),
|
||||
OPEN_STATE_MENGDE_INFUSEDCRYSTAL(1404),
|
||||
OPEN_STATE_LIYUE_INFUSEDCRYSTAL(1405),
|
||||
OPEN_STATE_SNOW_MOUNTAIN_ELDER_TREE(1406),
|
||||
OPEN_STATE_MIRACLE_RING(1407),
|
||||
OPEN_STATE_COOP_LINE(1408),
|
||||
OPEN_STATE_INAZUMA_INFUSEDCRYSTAL(1409),
|
||||
OPEN_STATE_FISH(1410),
|
||||
OPEN_STATE_GUIDE_SUMO_TEAM_SKILL(1411),
|
||||
OPEN_STATE_GUIDE_FISH_RECIPE(1412),
|
||||
OPEN_STATE_HOME(1500),
|
||||
OPEN_STATE_ACTIVITY_HOMEWORLD(1501),
|
||||
OPEN_STATE_ADEPTIABODE(1502),
|
||||
OPEN_STATE_HOME_AVATAR(1503),
|
||||
OPEN_STATE_HOME_EDIT(1504),
|
||||
OPEN_STATE_HOME_EDIT_TIPS(1505),
|
||||
OPEN_STATE_RELIQUARY_DECOMPOSE(1600),
|
||||
OPEN_STATE_ACTIVITY_H5(1700),
|
||||
OPEN_STATE_ORAIONOKAMI(2000),
|
||||
OPEN_STATE_GUIDE_CHESS_MISSION_CHECK(2001),
|
||||
OPEN_STATE_GUIDE_CHESS_BUILD(2002),
|
||||
OPEN_STATE_GUIDE_CHESS_WIND_TOWER_CIRCLE(2003),
|
||||
OPEN_STATE_GUIDE_CHESS_CARD_SELECT(2004),
|
||||
OPEN_STATE_INAZUMA_MAINQUEST_FINISHED(2005),
|
||||
OPEN_STATE_PAIMON_LVINFO(2100),
|
||||
OPEN_STATE_TELEPORT_HUD(2101),
|
||||
OPEN_STATE_GUIDE_MAP_UNLOCK(2102),
|
||||
OPEN_STATE_GUIDE_PAIMON_LVINFO(2103),
|
||||
OPEN_STATE_GUIDE_AMBORTRANSPORT(2104),
|
||||
OPEN_STATE_GUIDE_FLY_SECOND(2105),
|
||||
OPEN_STATE_GUIDE_KAEYA_CLUE(2106),
|
||||
OPEN_STATE_CAPTURE_CODEX(2107),
|
||||
OPEN_STATE_ACTIVITY_FISH_OPEN(2200),
|
||||
OPEN_STATE_ACTIVITY_FISH_CLOSE(2201),
|
||||
OPEN_STATE_GUIDE_ROGUE_MAP(2205),
|
||||
OPEN_STATE_GUIDE_ROGUE_RUNE(2206),
|
||||
OPEN_STATE_GUIDE_BARTENDER_FORMULA(2210),
|
||||
OPEN_STATE_GUIDE_BARTENDER_MIX(2211),
|
||||
OPEN_STATE_GUIDE_BARTENDER_CUP(2212),
|
||||
OPEN_STATE_GUIDE_MAIL_FAVORITES(2400),
|
||||
OPEN_STATE_GUIDE_POTION_CONFIGURE(2401),
|
||||
OPEN_STATE_GUIDE_LANV2_FIREWORK(2402),
|
||||
OPEN_STATE_LOADINGTIPS_ENKANOMIYA(2403),
|
||||
OPEN_STATE_MICHIAE_CASKET(2500),
|
||||
OPEN_STATE_MAIL_COLLECT_UNLOCK_RED_POINT(2501),
|
||||
OPEN_STATE_LUMEN_STONE(2600),
|
||||
OPEN_STATE_GUIDE_CRYSTALLINK_BUFF(2601);
|
||||
|
||||
private final int value;
|
||||
private static final Int2ObjectMap<OpenState> map = new Int2ObjectOpenHashMap<>();
|
||||
private static final Map<String, OpenState> stringMap = new HashMap<>();
|
||||
|
||||
static {
|
||||
Stream.of(values()).forEach(e -> {
|
||||
map.put(e.getValue(), e);
|
||||
stringMap.put(e.name(), e);
|
||||
});
|
||||
}
|
||||
|
||||
private OpenState(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static OpenState getTypeByValue(int value) {
|
||||
return map.getOrDefault(value, OPEN_STATE_NONE);
|
||||
}
|
||||
|
||||
public static OpenState getTypeByName(String name) {
|
||||
return stringMap.getOrDefault(name, OPEN_STATE_NONE);
|
||||
}
|
||||
}
|
||||
70
src/main/java/emu/grasscutter/game/props/PlayerProperty.java
Normal file
70
src/main/java/emu/grasscutter/game/props/PlayerProperty.java
Normal file
@@ -0,0 +1,70 @@
|
||||
package emu.grasscutter.game.props;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
public enum PlayerProperty {
|
||||
PROP_EXP (1001),
|
||||
PROP_BREAK_LEVEL (1002),
|
||||
PROP_SATIATION_VAL (1003),
|
||||
PROP_SATIATION_PENALTY_TIME (1004),
|
||||
PROP_LEVEL (4001),
|
||||
PROP_LAST_CHANGE_AVATAR_TIME (10001),
|
||||
PROP_MAX_SPRING_VOLUME (10002),
|
||||
PROP_CUR_SPRING_VOLUME (10003),
|
||||
PROP_IS_SPRING_AUTO_USE (10004),
|
||||
PROP_SPRING_AUTO_USE_PERCENT (10005),
|
||||
PROP_IS_FLYABLE (10006),
|
||||
PROP_IS_WEATHER_LOCKED (10007),
|
||||
PROP_IS_GAME_TIME_LOCKED (10008),
|
||||
PROP_IS_TRANSFERABLE (10009),
|
||||
PROP_MAX_STAMINA (10010),
|
||||
PROP_CUR_PERSIST_STAMINA (10011),
|
||||
PROP_CUR_TEMPORARY_STAMINA (10012),
|
||||
PROP_PLAYER_LEVEL (10013),
|
||||
PROP_PLAYER_EXP (10014),
|
||||
PROP_PLAYER_HCOIN (10015), // Primogem
|
||||
PROP_PLAYER_SCOIN (10016), // Mora
|
||||
PROP_PLAYER_MP_SETTING_TYPE (10017),
|
||||
PROP_IS_MP_MODE_AVAILABLE (10018),
|
||||
PROP_PLAYER_WORLD_LEVEL (10019),
|
||||
PROP_PLAYER_RESIN (10020),
|
||||
PROP_PLAYER_WAIT_SUB_HCOIN (10022),
|
||||
PROP_PLAYER_WAIT_SUB_SCOIN (10023),
|
||||
PROP_IS_ONLY_MP_WITH_PS_PLAYER (10024),
|
||||
PROP_PLAYER_MCOIN (10025), // Genesis Crystal
|
||||
PROP_PLAYER_WAIT_SUB_MCOIN (10026),
|
||||
PROP_PLAYER_LEGENDARY_KEY (10027),
|
||||
PROP_IS_HAS_FIRST_SHARE (10028),
|
||||
PROP_PLAYER_FORGE_POINT (10029),
|
||||
PROP_CUR_CLIMATE_METER (10035),
|
||||
PROP_CUR_CLIMATE_TYPE (10036),
|
||||
PROP_CUR_CLIMATE_AREA_ID (10037),
|
||||
PROP_CUR_CLIMATE_AREA_CLIMATE_TYPE (10038),
|
||||
PROP_PLAYER_WORLD_LEVEL_LIMIT (10039),
|
||||
PROP_PLAYER_WORLD_LEVEL_ADJUST_CD (10040),
|
||||
PROP_PLAYER_LEGENDARY_DAILY_TASK_NUM (10041),
|
||||
PROP_PLAYER_HOME_COIN (10042),
|
||||
PROP_PLAYER_WAIT_SUB_HOME_COIN (10043);
|
||||
|
||||
private final int id;
|
||||
private static final Int2ObjectMap<PlayerProperty> map = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
static {
|
||||
Stream.of(values()).forEach(e -> map.put(e.getId(), e));
|
||||
}
|
||||
|
||||
private PlayerProperty(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public static PlayerProperty getPropById(int value) {
|
||||
return map.getOrDefault(value, null);
|
||||
}
|
||||
}
|
||||
5
src/main/java/emu/grasscutter/game/shop/ShopInfo.java
Normal file
5
src/main/java/emu/grasscutter/game/shop/ShopInfo.java
Normal file
@@ -0,0 +1,5 @@
|
||||
package emu.grasscutter.game.shop;
|
||||
|
||||
public class ShopInfo {
|
||||
|
||||
}
|
||||
15
src/main/java/emu/grasscutter/game/shop/ShopManager.java
Normal file
15
src/main/java/emu/grasscutter/game/shop/ShopManager.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package emu.grasscutter.game.shop;
|
||||
|
||||
import emu.grasscutter.server.game.GameServer;
|
||||
|
||||
public class ShopManager {
|
||||
private final GameServer server;
|
||||
|
||||
public ShopManager(GameServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public GameServer getServer() {
|
||||
return server;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user