Initial commit

This commit is contained in:
Melledy
2022-04-17 05:43:07 -07:00
commit 7925d1cda3
354 changed files with 20869 additions and 0 deletions

View 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);
}
}

View 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();
}
}

View 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;
}
}
}

View 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();
}
}
}

View 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);
}
}
}

View 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();
}
}
}

View 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();
}
}

View File

@@ -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();
}
}

View 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);
}
}

View 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();
}
}

View 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);
}
}

View File

@@ -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;
}
}

View 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();
}
}

View File

@@ -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();
}
}

View 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) {
}
}

View 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();
}
}

View 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();
}
}

View 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;
}
}

View 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();
}
}
}

View 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;
}
}

View File

@@ -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();
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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;
}
}

View 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);
}
}

View 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();
}
}

View 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();
}
}

View File

@@ -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();
}

View 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;
}
}

View 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 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);
}
}

View 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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View 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));
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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;
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View File

@@ -0,0 +1,5 @@
package emu.grasscutter.game.shop;
public class ShopInfo {
}

View 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;
}
}