Commit 01b190bc authored by Magix's avatar Magix Committed by GitHub
Browse files

UPGRADE TO 1.1.0 POG

Merge `development` into `stable`
parents 6b81b888 1beddf16
package emu.grasscutter.game.mail;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import dev.morphia.annotations.Transient;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import org.bson.types.ObjectId;
@Entity(value = "mail", useDiscriminator = false)
public class Mail {
@Id private ObjectId id;
@Indexed private int ownerUid;
public MailContent mailContent;
public List<MailItem> itemList;
public long sendTime;
public long expireTime;
public int importance;
public boolean isRead;
public boolean isAttachmentGot;
public int stateValue;
@Transient private boolean shouldDelete;
public Mail() {
this(new MailContent(), new ArrayList<MailItem>(), (int) Instant.now().getEpochSecond() + 604800); // TODO: add expire time to send mail command
}
public Mail(MailContent mailContent, List<MailItem> itemList, long expireTime) {
this(mailContent, itemList, expireTime, 0);
}
public Mail(MailContent mailContent, List<MailItem> itemList, long expireTime, int importance) {
this(mailContent, itemList, expireTime, importance, 1);
}
public Mail(MailContent mailContent, List<MailItem> itemList, long expireTime, int importance, int state) {
this.mailContent = mailContent;
this.itemList = itemList;
this.sendTime = (int) Instant.now().getEpochSecond();
this.expireTime = expireTime;
this.importance = importance; // Starred mail, 0 = No star, 1 = Star.
this.isRead = false;
this.isAttachmentGot = false;
this.stateValue = state; // Different mailboxes, 1 = Default, 3 = Gift-box.
}
public ObjectId getId() {
return id;
}
public int getOwnerUid() {
return ownerUid;
}
public void setOwnerUid(int ownerUid) {
this.ownerUid = ownerUid;
}
@Entity
public static class MailContent {
public String title;
public String content;
public String sender;
public MailContent() {
this.title = "";
this.content = "loading...";
this.sender = "loading";
}
public MailContent(String title, String content) {
this(title, content, "Server");
}
public MailContent(String title, String content, Player sender) {
this(title, content, sender.getNickname());
}
public MailContent(String title, String content, String sender) {
this.title = title;
this.content = content;
this.sender = sender;
}
}
@Entity
public static class MailItem {
public int itemId;
public int itemCount;
public int itemLevel;
public MailItem() {
this.itemId = 11101;
this.itemCount = 1;
this.itemLevel = 1;
}
public MailItem(int itemId) {
this(itemId, 1);
}
public MailItem(int itemId, int itemCount) { this(itemId, itemCount, 1); }
public MailItem(int itemId, int itemCount, int itemLevel) {
this.itemId = itemId;
this.itemCount = itemCount;
this.itemLevel = itemLevel;
}
}
public void save() {
if (this.expireTime * 1000 < System.currentTimeMillis()) {
DatabaseHelper.deleteMail(this);
} else {
DatabaseHelper.saveMail(this);
}
}
}
package emu.grasscutter.game.mail;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.event.player.PlayerReceiveMailEvent;
import emu.grasscutter.server.packet.send.PacketDelMailRsp;
import emu.grasscutter.server.packet.send.PacketMailChangeNotify;
public class MailHandler {
private final Player player;
private final List<Mail> mail;
public MailHandler(Player player) {
this.player = player;
this.mail = new ArrayList<>();
}
public Player getPlayer() {
return player;
}
public List<Mail> getMail() {
return mail;
}
// ---------------------MAIL------------------------
public void sendMail(Mail message) {
// Call mail receive event.
PlayerReceiveMailEvent event = new PlayerReceiveMailEvent(this.getPlayer(), message); event.call();
if(event.isCanceled()) return; message = event.getMessage();
message.setOwnerUid(this.getPlayer().getUid());
message.save();
this.mail.add(message);
Grasscutter.getLogger().debug("Mail sent to user [" + this.getPlayer().getUid() + ":" + this.getPlayer().getNickname() + "]!");
if (this.getPlayer().isOnline()) {
this.getPlayer().sendPacket(new PacketMailChangeNotify(this.getPlayer(), message));
} // TODO: setup a way for the mail notification to show up when someone receives mail when they were offline
}
public boolean deleteMail(int mailId) {
Mail message = getMailById(mailId);
if (message != null) {
this.getMail().remove(mailId);
message.expireTime = 0;
message.save();
return true;
}
return false;
}
public void deleteMail(List<Integer> mailList) {
List<Integer> sortedMailList = new ArrayList<>();
sortedMailList.addAll(mailList);
Collections.sort(sortedMailList, Collections.reverseOrder());
List<Integer> deleted = new ArrayList<>();
for (int id : sortedMailList) {
if (this.deleteMail(id)) {
deleted.add(id);
}
}
player.getSession().send(new PacketDelMailRsp(player, deleted));
player.getSession().send(new PacketMailChangeNotify(player, null, deleted));
}
public Mail getMailById(int index) { return this.mail.get(index); }
public int getMailIndex(Mail message) {
return this.mail.indexOf(message);
}
public boolean replaceMailByIndex(int index, Mail message) {
if(getMailById(index) != null) {
this.mail.set(index, message);
message.save();
return true;
} else {
return false;
}
}
public void loadFromDatabase() {
List<Mail> mailList = DatabaseHelper.getAllMail(this.getPlayer());
for (Mail mail : mailList) {
this.getMail().add(mail);
}
}
}
package emu.grasscutter.game.managers; package emu.grasscutter.game.managers;
import emu.grasscutter.command.CommandMap; import emu.grasscutter.command.CommandMap;
import emu.grasscutter.game.GenshinPlayer; import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.GenshinPacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketPlayerChatNotify; import emu.grasscutter.server.packet.send.PacketPlayerChatNotify;
import emu.grasscutter.server.packet.send.PacketPrivateChatNotify; import emu.grasscutter.server.packet.send.PacketPrivateChatNotify;
...@@ -23,48 +23,48 @@ public class ChatManager { ...@@ -23,48 +23,48 @@ public class ChatManager {
return server; return server;
} }
public void sendPrivateMessage(GenshinPlayer player, int targetUid, String message) { public void sendPrivateMessage(Player player, int targetUid, String message) {
// Sanity checks // Sanity checks
if (message == null || message.length() == 0) { if (message == null || message.length() == 0) {
return; return;
} }
// Get target
Player target = getServer().getPlayerByUid(targetUid);
// Check if command // Check if command
if (PREFIXES.contains(message.charAt(0))) { if (PREFIXES.contains(message.charAt(0))) {
CommandMap.getInstance().invoke(player, message); CommandMap.getInstance().invoke(player, target, message.substring(1));
return; return;
} }
// Get target
GenshinPlayer target = getServer().getPlayerByUid(targetUid);
if (target == null) { if (target == null) {
return; return;
} }
// Create chat packet // Create chat packet
GenshinPacket packet = new PacketPrivateChatNotify(player.getUid(), target.getUid(), message); BasePacket packet = new PacketPrivateChatNotify(player.getUid(), target.getUid(), message);
player.sendPacket(packet); player.sendPacket(packet);
target.sendPacket(packet); target.sendPacket(packet);
} }
public void sendPrivateMessage(GenshinPlayer player, int targetUid, int emote) { public void sendPrivateMessage(Player player, int targetUid, int emote) {
// Get target // Get target
GenshinPlayer target = getServer().getPlayerByUid(targetUid); Player target = getServer().getPlayerByUid(targetUid);
if (target == null) { if (target == null) {
return; return;
} }
// Create chat packet // Create chat packet
GenshinPacket packet = new PacketPrivateChatNotify(player.getUid(), target.getUid(), emote); BasePacket packet = new PacketPrivateChatNotify(player.getUid(), target.getUid(), emote);
player.sendPacket(packet); player.sendPacket(packet);
target.sendPacket(packet); target.sendPacket(packet);
} }
public void sendTeamMessage(GenshinPlayer player, int channel, String message) { public void sendTeamMessage(Player player, int channel, String message) {
// Sanity checks // Sanity checks
if (message == null || message.length() == 0) { if (message == null || message.length() == 0) {
return; return;
...@@ -72,7 +72,7 @@ public class ChatManager { ...@@ -72,7 +72,7 @@ public class ChatManager {
// Check if command // Check if command
if (PREFIXES.contains(message.charAt(0))) { if (PREFIXES.contains(message.charAt(0))) {
CommandMap.getInstance().invoke(player, message); CommandMap.getInstance().invoke(player, null, message);
return; return;
} }
...@@ -80,7 +80,7 @@ public class ChatManager { ...@@ -80,7 +80,7 @@ public class ChatManager {
player.getWorld().broadcastPacket(new PacketPlayerChatNotify(player, channel, message)); player.getWorld().broadcastPacket(new PacketPlayerChatNotify(player, channel, message));
} }
public void sendTeamMessage(GenshinPlayer player, int channel, int icon) { public void sendTeamMessage(Player player, int channel, int icon) {
// Create and send chat packet // Create and send chat packet
player.getWorld().broadcastPacket(new PacketPlayerChatNotify(player, channel, icon)); player.getWorld().broadcastPacket(new PacketPlayerChatNotify(player, channel, icon));
} }
......
...@@ -5,41 +5,30 @@ import java.util.List; ...@@ -5,41 +5,30 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import emu.grasscutter.data.GenshinData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.custom.OpenConfigEntry; import emu.grasscutter.data.custom.OpenConfigEntry;
import emu.grasscutter.data.custom.OpenConfigEntry.SkillPointModifier;
import emu.grasscutter.data.def.AvatarPromoteData; import emu.grasscutter.data.def.AvatarPromoteData;
import emu.grasscutter.data.def.AvatarSkillData; import emu.grasscutter.data.def.AvatarSkillData;
import emu.grasscutter.data.def.AvatarSkillDepotData; import emu.grasscutter.data.def.AvatarSkillDepotData;
import emu.grasscutter.data.def.ItemData;
import emu.grasscutter.data.def.WeaponPromoteData; import emu.grasscutter.data.def.WeaponPromoteData;
import emu.grasscutter.data.def.AvatarSkillDepotData.InherentProudSkillOpens; import emu.grasscutter.data.def.AvatarSkillDepotData.InherentProudSkillOpens;
import emu.grasscutter.data.def.AvatarTalentData; import emu.grasscutter.data.def.AvatarTalentData;
import emu.grasscutter.data.def.ProudSkillData; import emu.grasscutter.data.def.ProudSkillData;
import emu.grasscutter.game.GenshinPlayer; import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.avatar.GenshinAvatar; import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.GenshinItem;
import emu.grasscutter.game.inventory.ItemType; import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.inventory.MaterialType; import emu.grasscutter.game.inventory.MaterialType;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.shop.ShopChestBatchUseTable;
import emu.grasscutter.game.shop.ShopChestTable;
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam; import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
import emu.grasscutter.net.proto.MaterialInfoOuterClass.MaterialInfo; import emu.grasscutter.net.proto.MaterialInfoOuterClass.MaterialInfo;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketAbilityChangeNotify; import emu.grasscutter.server.packet.send.*;
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 emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
...@@ -72,8 +61,8 @@ public class InventoryManager { ...@@ -72,8 +61,8 @@ public class InventoryManager {
return server; return server;
} }
public void lockEquip(GenshinPlayer player, long targetEquipGuid, boolean isLocked) { public void lockEquip(Player player, long targetEquipGuid, boolean isLocked) {
GenshinItem equip = player.getInventory().getItemByGuid(targetEquipGuid); GameItem equip = player.getInventory().getItemByGuid(targetEquipGuid);
if (equip == null || !equip.getItemData().isEquip()) { if (equip == null || !equip.getItemData().isEquip()) {
return; return;
...@@ -86,8 +75,8 @@ public class InventoryManager { ...@@ -86,8 +75,8 @@ public class InventoryManager {
player.sendPacket(new PacketSetEquipLockStateRsp(equip)); player.sendPacket(new PacketSetEquipLockStateRsp(equip));
} }
public void upgradeRelic(GenshinPlayer player, long targetGuid, List<Long> foodRelicList, List<ItemParam> list) { public void upgradeRelic(Player player, long targetGuid, List<Long> foodRelicList, List<ItemParam> list) {
GenshinItem relic = player.getInventory().getItemByGuid(targetGuid); GameItem relic = player.getInventory().getItemByGuid(targetGuid);
if (relic == null || relic.getItemType() != ItemType.ITEM_RELIQUARY) { if (relic == null || relic.getItemType() != ItemType.ITEM_RELIQUARY) {
return; return;
...@@ -98,7 +87,7 @@ public class InventoryManager { ...@@ -98,7 +87,7 @@ public class InventoryManager {
for (long guid : foodRelicList) { for (long guid : foodRelicList) {
// Add to delete queue // Add to delete queue
GenshinItem food = player.getInventory().getItemByGuid(guid); GameItem food = player.getInventory().getItemByGuid(guid);
if (food == null || !food.isDestroyable()) { if (food == null || !food.isDestroyable()) {
continue; continue;
} }
...@@ -111,7 +100,7 @@ public class InventoryManager { ...@@ -111,7 +100,7 @@ public class InventoryManager {
} }
} }
for (ItemParam itemParam : list) { for (ItemParam itemParam : list) {
GenshinItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemParam.getItemId()); GameItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemParam.getItemId());
if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_RELIQUARY_MATERIAL) { if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_RELIQUARY_MATERIAL) {
continue; continue;
} }
...@@ -139,14 +128,14 @@ public class InventoryManager { ...@@ -139,14 +128,14 @@ public class InventoryManager {
// Consume food items // Consume food items
for (long guid : foodRelicList) { for (long guid : foodRelicList) {
GenshinItem food = player.getInventory().getItemByGuid(guid); GameItem food = player.getInventory().getItemByGuid(guid);
if (food == null || !food.isDestroyable()) { if (food == null || !food.isDestroyable()) {
continue; continue;
} }
player.getInventory().removeItem(food); player.getInventory().removeItem(food);
} }
for (ItemParam itemParam : list) { for (ItemParam itemParam : list) {
GenshinItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemParam.getItemId()); GameItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemParam.getItemId());
if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_RELIQUARY_MATERIAL) { if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_RELIQUARY_MATERIAL) {
continue; continue;
} }
...@@ -169,7 +158,7 @@ public class InventoryManager { ...@@ -169,7 +158,7 @@ public class InventoryManager {
int oldLevel = level; int oldLevel = level;
int exp = relic.getExp(); int exp = relic.getExp();
int totalExp = relic.getTotalExp(); int totalExp = relic.getTotalExp();
int reqExp = GenshinData.getRelicExpRequired(relic.getItemData().getRankLevel(), level); int reqExp = GameData.getRelicExpRequired(relic.getItemData().getRankLevel(), level);
int upgrades = 0; int upgrades = 0;
List<Integer> oldAppendPropIdList = relic.getAppendPropIdList(); List<Integer> oldAppendPropIdList = relic.getAppendPropIdList();
...@@ -189,7 +178,7 @@ public class InventoryManager { ...@@ -189,7 +178,7 @@ public class InventoryManager {
upgrades += 1; upgrades += 1;
} }
// Set req exp // Set req exp
reqExp = GenshinData.getRelicExpRequired(relic.getItemData().getRankLevel(), level); reqExp = GameData.getRelicExpRequired(relic.getItemData().getRankLevel(), level);
} }
} }
...@@ -209,7 +198,7 @@ public class InventoryManager { ...@@ -209,7 +198,7 @@ public class InventoryManager {
// Avatar // Avatar
if (oldLevel != level) { if (oldLevel != level) {
GenshinAvatar avatar = relic.getEquipCharacter() > 0 ? player.getAvatars().getAvatarById(relic.getEquipCharacter()) : null; Avatar avatar = relic.getEquipCharacter() > 0 ? player.getAvatars().getAvatarById(relic.getEquipCharacter()) : null;
if (avatar != null) { if (avatar != null) {
avatar.recalcStats(); avatar.recalcStats();
} }
...@@ -220,15 +209,15 @@ public class InventoryManager { ...@@ -220,15 +209,15 @@ public class InventoryManager {
player.sendPacket(new PacketReliquaryUpgradeRsp(relic, rate, oldLevel, oldAppendPropIdList)); player.sendPacket(new PacketReliquaryUpgradeRsp(relic, rate, oldLevel, oldAppendPropIdList));
} }
public List<ItemParam> calcWeaponUpgradeReturnItems(GenshinPlayer player, long targetGuid, List<Long> foodWeaponGuidList, List<ItemParam> itemParamList) { public List<ItemParam> calcWeaponUpgradeReturnItems(Player player, long targetGuid, List<Long> foodWeaponGuidList, List<ItemParam> itemParamList) {
GenshinItem weapon = player.getInventory().getItemByGuid(targetGuid); GameItem weapon = player.getInventory().getItemByGuid(targetGuid);
// Sanity checks // Sanity checks
if (weapon == null || weapon.getItemType() != ItemType.ITEM_WEAPON) { if (weapon == null || weapon.getItemType() != ItemType.ITEM_WEAPON) {
return null; return null;
} }
WeaponPromoteData promoteData = GenshinData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), weapon.getPromoteLevel()); WeaponPromoteData promoteData = GameData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), weapon.getPromoteLevel());
if (promoteData == null) { if (promoteData == null) {
return null; return null;
} }
...@@ -236,7 +225,7 @@ public class InventoryManager { ...@@ -236,7 +225,7 @@ public class InventoryManager {
// Get exp gain // Get exp gain
int expGain = 0; int expGain = 0;
for (long guid : foodWeaponGuidList) { for (long guid : foodWeaponGuidList) {
GenshinItem food = player.getInventory().getItemByGuid(guid); GameItem food = player.getInventory().getItemByGuid(guid);
if (food == null) { if (food == null) {
continue; continue;
} }
...@@ -246,7 +235,7 @@ public class InventoryManager { ...@@ -246,7 +235,7 @@ public class InventoryManager {
} }
} }
for (ItemParam param : itemParamList) { for (ItemParam param : itemParamList) {
GenshinItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(param.getItemId()); GameItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(param.getItemId());
if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_WEAPON_EXP_STONE) { if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_WEAPON_EXP_STONE) {
continue; continue;
} }
...@@ -264,7 +253,7 @@ public class InventoryManager { ...@@ -264,7 +253,7 @@ public class InventoryManager {
int maxLevel = promoteData.getUnlockMaxLevel(); int maxLevel = promoteData.getUnlockMaxLevel();
int level = weapon.getLevel(); int level = weapon.getLevel();
int exp = weapon.getExp(); int exp = weapon.getExp();
int reqExp = GenshinData.getWeaponExpRequired(weapon.getItemData().getRankLevel(), level); int reqExp = GameData.getWeaponExpRequired(weapon.getItemData().getRankLevel(), level);
while (expGain > 0 && reqExp > 0 && level < maxLevel) { while (expGain > 0 && reqExp > 0 && level < maxLevel) {
// Do calculations // Do calculations
...@@ -277,7 +266,7 @@ public class InventoryManager { ...@@ -277,7 +266,7 @@ public class InventoryManager {
exp = 0; exp = 0;
level += 1; level += 1;
// Set req exp // Set req exp
reqExp = GenshinData.getWeaponExpRequired(weapon.getItemData().getRankLevel(), level); reqExp = GameData.getWeaponExpRequired(weapon.getItemData().getRankLevel(), level);
} }
} }
...@@ -285,15 +274,15 @@ public class InventoryManager { ...@@ -285,15 +274,15 @@ public class InventoryManager {
} }
public void upgradeWeapon(GenshinPlayer player, long targetGuid, List<Long> foodWeaponGuidList, List<ItemParam> itemParamList) { public void upgradeWeapon(Player player, long targetGuid, List<Long> foodWeaponGuidList, List<ItemParam> itemParamList) {
GenshinItem weapon = player.getInventory().getItemByGuid(targetGuid); GameItem weapon = player.getInventory().getItemByGuid(targetGuid);
// Sanity checks // Sanity checks
if (weapon == null || weapon.getItemType() != ItemType.ITEM_WEAPON) { if (weapon == null || weapon.getItemType() != ItemType.ITEM_WEAPON) {
return; return;
} }
WeaponPromoteData promoteData = GenshinData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), weapon.getPromoteLevel()); WeaponPromoteData promoteData = GameData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), weapon.getPromoteLevel());
if (promoteData == null) { if (promoteData == null) {
return; return;
} }
...@@ -302,7 +291,7 @@ public class InventoryManager { ...@@ -302,7 +291,7 @@ public class InventoryManager {
int expGain = 0, moraCost = 0; int expGain = 0, moraCost = 0;
for (long guid : foodWeaponGuidList) { for (long guid : foodWeaponGuidList) {
GenshinItem food = player.getInventory().getItemByGuid(guid); GameItem food = player.getInventory().getItemByGuid(guid);
if (food == null || !food.isDestroyable()) { if (food == null || !food.isDestroyable()) {
continue; continue;
} }
...@@ -313,7 +302,7 @@ public class InventoryManager { ...@@ -313,7 +302,7 @@ public class InventoryManager {
} }
} }
for (ItemParam param : itemParamList) { for (ItemParam param : itemParamList) {
GenshinItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(param.getItemId()); GameItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(param.getItemId());
if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_WEAPON_EXP_STONE) { if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_WEAPON_EXP_STONE) {
continue; continue;
} }
...@@ -344,14 +333,14 @@ public class InventoryManager { ...@@ -344,14 +333,14 @@ public class InventoryManager {
// Consume weapon/items used to feed // Consume weapon/items used to feed
for (long guid : foodWeaponGuidList) { for (long guid : foodWeaponGuidList) {
GenshinItem food = player.getInventory().getItemByGuid(guid); GameItem food = player.getInventory().getItemByGuid(guid);
if (food == null || !food.isDestroyable()) { if (food == null || !food.isDestroyable()) {
continue; continue;
} }
player.getInventory().removeItem(food); player.getInventory().removeItem(food);
} }
for (ItemParam param : itemParamList) { for (ItemParam param : itemParamList) {
GenshinItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(param.getItemId()); GameItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(param.getItemId());
if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_WEAPON_EXP_STONE) { if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_WEAPON_EXP_STONE) {
continue; continue;
} }
...@@ -365,7 +354,7 @@ public class InventoryManager { ...@@ -365,7 +354,7 @@ public class InventoryManager {
int oldLevel = level; int oldLevel = level;
int exp = weapon.getExp(); int exp = weapon.getExp();
int totalExp = weapon.getTotalExp(); int totalExp = weapon.getTotalExp();
int reqExp = GenshinData.getWeaponExpRequired(weapon.getItemData().getRankLevel(), level); int reqExp = GameData.getWeaponExpRequired(weapon.getItemData().getRankLevel(), level);
while (expGain > 0 && reqExp > 0 && level < maxLevel) { while (expGain > 0 && reqExp > 0 && level < maxLevel) {
// Do calculations // Do calculations
...@@ -379,7 +368,7 @@ public class InventoryManager { ...@@ -379,7 +368,7 @@ public class InventoryManager {
exp = 0; exp = 0;
level += 1; level += 1;
// Set req exp // Set req exp
reqExp = GenshinData.getWeaponExpRequired(weapon.getItemData().getRankLevel(), level); reqExp = GameData.getWeaponExpRequired(weapon.getItemData().getRankLevel(), level);
} }
} }
...@@ -393,7 +382,7 @@ public class InventoryManager { ...@@ -393,7 +382,7 @@ public class InventoryManager {
// Avatar // Avatar
if (oldLevel != level) { if (oldLevel != level) {
GenshinAvatar avatar = weapon.getEquipCharacter() > 0 ? player.getAvatars().getAvatarById(weapon.getEquipCharacter()) : null; Avatar avatar = weapon.getEquipCharacter() > 0 ? player.getAvatars().getAvatarById(weapon.getEquipCharacter()) : null;
if (avatar != null) { if (avatar != null) {
avatar.recalcStats(); avatar.recalcStats();
} }
...@@ -429,9 +418,9 @@ public class InventoryManager { ...@@ -429,9 +418,9 @@ public class InventoryManager {
return leftoverOreList; return leftoverOreList;
} }
public void refineWeapon(GenshinPlayer player, long targetGuid, long feedGuid) { public void refineWeapon(Player player, long targetGuid, long feedGuid) {
GenshinItem weapon = player.getInventory().getItemByGuid(targetGuid); GameItem weapon = player.getInventory().getItemByGuid(targetGuid);
GenshinItem feed = player.getInventory().getItemByGuid(feedGuid); GameItem feed = player.getInventory().getItemByGuid(feedGuid);
// Sanity checks // Sanity checks
if (weapon == null || feed == null || !feed.isDestroyable()) { if (weapon == null || feed == null || !feed.isDestroyable()) {
...@@ -478,7 +467,7 @@ public class InventoryManager { ...@@ -478,7 +467,7 @@ public class InventoryManager {
weapon.save(); weapon.save();
// Avatar // Avatar
GenshinAvatar avatar = weapon.getEquipCharacter() > 0 ? player.getAvatars().getAvatarById(weapon.getEquipCharacter()) : null; Avatar avatar = weapon.getEquipCharacter() > 0 ? player.getAvatars().getAvatarById(weapon.getEquipCharacter()) : null;
if (avatar != null) { if (avatar != null) {
avatar.recalcStats(); avatar.recalcStats();
} }
...@@ -488,16 +477,16 @@ public class InventoryManager { ...@@ -488,16 +477,16 @@ public class InventoryManager {
player.sendPacket(new PacketWeaponAwakenRsp(avatar, weapon, feed, oldRefineLevel)); player.sendPacket(new PacketWeaponAwakenRsp(avatar, weapon, feed, oldRefineLevel));
} }
public void promoteWeapon(GenshinPlayer player, long targetGuid) { public void promoteWeapon(Player player, long targetGuid) {
GenshinItem weapon = player.getInventory().getItemByGuid(targetGuid); GameItem weapon = player.getInventory().getItemByGuid(targetGuid);
if (weapon == null || weapon.getItemType() != ItemType.ITEM_WEAPON) { if (weapon == null || weapon.getItemType() != ItemType.ITEM_WEAPON) {
return; return;
} }
int nextPromoteLevel = weapon.getPromoteLevel() + 1; int nextPromoteLevel = weapon.getPromoteLevel() + 1;
WeaponPromoteData currentPromoteData = GenshinData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), weapon.getPromoteLevel()); WeaponPromoteData currentPromoteData = GameData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), weapon.getPromoteLevel());
WeaponPromoteData nextPromoteData = GenshinData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), nextPromoteLevel); WeaponPromoteData nextPromoteData = GameData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), nextPromoteLevel);
if (currentPromoteData == null || nextPromoteData == null) { if (currentPromoteData == null || nextPromoteData == null) {
return; return;
} }
...@@ -509,7 +498,7 @@ public class InventoryManager { ...@@ -509,7 +498,7 @@ public class InventoryManager {
// Make sure player has promote items // Make sure player has promote items
for (ItemParamData cost : nextPromoteData.getCostItems()) { for (ItemParamData cost : nextPromoteData.getCostItems()) {
GenshinItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()); GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId());
if (feedItem == null || feedItem.getCount() < cost.getCount()) { if (feedItem == null || feedItem.getCount() < cost.getCount()) {
return; return;
} }
...@@ -524,7 +513,7 @@ public class InventoryManager { ...@@ -524,7 +513,7 @@ public class InventoryManager {
// Consume promote filler items // Consume promote filler items
for (ItemParamData cost : nextPromoteData.getCostItems()) { for (ItemParamData cost : nextPromoteData.getCostItems()) {
GenshinItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()); GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId());
player.getInventory().removeItem(feedItem, cost.getCount()); player.getInventory().removeItem(feedItem, cost.getCount());
} }
...@@ -533,7 +522,7 @@ public class InventoryManager { ...@@ -533,7 +522,7 @@ public class InventoryManager {
weapon.save(); weapon.save();
// Avatar // Avatar
GenshinAvatar avatar = weapon.getEquipCharacter() > 0 ? player.getAvatars().getAvatarById(weapon.getEquipCharacter()) : null; Avatar avatar = weapon.getEquipCharacter() > 0 ? player.getAvatars().getAvatarById(weapon.getEquipCharacter()) : null;
if (avatar != null) { if (avatar != null) {
avatar.recalcStats(); avatar.recalcStats();
} }
...@@ -543,8 +532,8 @@ public class InventoryManager { ...@@ -543,8 +532,8 @@ public class InventoryManager {
player.sendPacket(new PacketWeaponPromoteRsp(weapon, oldPromoteLevel)); player.sendPacket(new PacketWeaponPromoteRsp(weapon, oldPromoteLevel));
} }
public void promoteAvatar(GenshinPlayer player, long guid) { public void promoteAvatar(Player player, long guid) {
GenshinAvatar avatar = player.getAvatars().getAvatarByGuid(guid); Avatar avatar = player.getAvatars().getAvatarByGuid(guid);
// Sanity checks // Sanity checks
if (avatar == null) { if (avatar == null) {
...@@ -552,8 +541,8 @@ public class InventoryManager { ...@@ -552,8 +541,8 @@ public class InventoryManager {
} }
int nextPromoteLevel = avatar.getPromoteLevel() + 1; int nextPromoteLevel = avatar.getPromoteLevel() + 1;
AvatarPromoteData currentPromoteData = GenshinData.getAvatarPromoteData(avatar.getAvatarData().getAvatarPromoteId(), avatar.getPromoteLevel()); AvatarPromoteData currentPromoteData = GameData.getAvatarPromoteData(avatar.getAvatarData().getAvatarPromoteId(), avatar.getPromoteLevel());
AvatarPromoteData nextPromoteData = GenshinData.getAvatarPromoteData(avatar.getAvatarData().getAvatarPromoteId(), nextPromoteLevel); AvatarPromoteData nextPromoteData = GameData.getAvatarPromoteData(avatar.getAvatarData().getAvatarPromoteId(), nextPromoteLevel);
if (currentPromoteData == null || nextPromoteData == null) { if (currentPromoteData == null || nextPromoteData == null) {
return; return;
} }
...@@ -565,7 +554,7 @@ public class InventoryManager { ...@@ -565,7 +554,7 @@ public class InventoryManager {
// Make sure player has cost items // Make sure player has cost items
for (ItemParamData cost : nextPromoteData.getCostItems()) { for (ItemParamData cost : nextPromoteData.getCostItems()) {
GenshinItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()); GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId());
if (feedItem == null || feedItem.getCount() < cost.getCount()) { if (feedItem == null || feedItem.getCount() < cost.getCount()) {
return; return;
} }
...@@ -580,7 +569,7 @@ public class InventoryManager { ...@@ -580,7 +569,7 @@ public class InventoryManager {
// Consume promote filler items // Consume promote filler items
for (ItemParamData cost : nextPromoteData.getCostItems()) { for (ItemParamData cost : nextPromoteData.getCostItems()) {
GenshinItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()); GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId());
player.getInventory().removeItem(feedItem, cost.getCount()); player.getInventory().removeItem(feedItem, cost.getCount());
} }
...@@ -588,7 +577,7 @@ public class InventoryManager { ...@@ -588,7 +577,7 @@ public class InventoryManager {
avatar.setPromoteLevel(nextPromoteLevel); avatar.setPromoteLevel(nextPromoteLevel);
// Update proud skills // Update proud skills
AvatarSkillDepotData skillDepot = GenshinData.getAvatarSkillDepotDataMap().get(avatar.getSkillDepotId()); AvatarSkillDepotData skillDepot = GameData.getAvatarSkillDepotDataMap().get(avatar.getSkillDepotId());
if (skillDepot != null && skillDepot.getInherentProudSkillOpens() != null) { if (skillDepot != null && skillDepot.getInherentProudSkillOpens() != null) {
for (InherentProudSkillOpens openData : skillDepot.getInherentProudSkillOpens()) { for (InherentProudSkillOpens openData : skillDepot.getInherentProudSkillOpens()) {
...@@ -597,7 +586,7 @@ public class InventoryManager { ...@@ -597,7 +586,7 @@ public class InventoryManager {
} }
if (openData.getNeedAvatarPromoteLevel() == avatar.getPromoteLevel()) { if (openData.getNeedAvatarPromoteLevel() == avatar.getPromoteLevel()) {
int proudSkillId = (openData.getProudSkillGroupId() * 100) + 1; int proudSkillId = (openData.getProudSkillGroupId() * 100) + 1;
if (GenshinData.getProudSkillDataMap().containsKey(proudSkillId)) { if (GameData.getProudSkillDataMap().containsKey(proudSkillId)) {
avatar.getProudSkillList().add(proudSkillId); avatar.getProudSkillList().add(proudSkillId);
player.sendPacket(new PacketProudSkillChangeNotify(avatar)); player.sendPacket(new PacketProudSkillChangeNotify(avatar));
} }
...@@ -614,20 +603,20 @@ public class InventoryManager { ...@@ -614,20 +603,20 @@ public class InventoryManager {
avatar.save(); avatar.save();
} }
public void upgradeAvatar(GenshinPlayer player, long guid, int itemId, int count) { public void upgradeAvatar(Player player, long guid, int itemId, int count) {
GenshinAvatar avatar = player.getAvatars().getAvatarByGuid(guid); Avatar avatar = player.getAvatars().getAvatarByGuid(guid);
// Sanity checks // Sanity checks
if (avatar == null) { if (avatar == null) {
return; return;
} }
AvatarPromoteData promoteData = GenshinData.getAvatarPromoteData(avatar.getAvatarData().getAvatarPromoteId(), avatar.getPromoteLevel()); AvatarPromoteData promoteData = GameData.getAvatarPromoteData(avatar.getAvatarData().getAvatarPromoteId(), avatar.getPromoteLevel());
if (promoteData == null) { if (promoteData == null) {
return; return;
} }
GenshinItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemId); GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemId);
if (feedItem == null || feedItem.getItemData().getMaterialType() != MaterialType.MATERIAL_EXP_FRUIT || feedItem.getCount() < count) { if (feedItem == null || feedItem.getItemData().getMaterialType() != MaterialType.MATERIAL_EXP_FRUIT || feedItem.getCount() < count) {
return; return;
...@@ -659,23 +648,23 @@ public class InventoryManager { ...@@ -659,23 +648,23 @@ public class InventoryManager {
// Level up // Level up
upgradeAvatar(player, avatar, promoteData, expGain); upgradeAvatar(player, avatar, promoteData, expGain);
} }
public void upgradeAvatar(GenshinPlayer player, GenshinAvatar avatar, int expGain) { public void upgradeAvatar(Player player, Avatar avatar, int expGain) {
AvatarPromoteData promoteData = GenshinData.getAvatarPromoteData(avatar.getAvatarData().getAvatarPromoteId(), avatar.getPromoteLevel()); AvatarPromoteData promoteData = GameData.getAvatarPromoteData(avatar.getAvatarData().getAvatarPromoteId(), avatar.getPromoteLevel());
if (promoteData == null) { if (promoteData == null) {
return; return;
} }
upgradeAvatar(player, avatar, promoteData, expGain); upgradeAvatar(player, avatar, promoteData, expGain);
} }
public void upgradeAvatar(GenshinPlayer player, GenshinAvatar avatar, AvatarPromoteData promoteData, int expGain) { public void upgradeAvatar(Player player, Avatar avatar, AvatarPromoteData promoteData, int expGain) {
int maxLevel = promoteData.getUnlockMaxLevel(); int maxLevel = promoteData.getUnlockMaxLevel();
int level = avatar.getLevel(); int level = avatar.getLevel();
int oldLevel = level; int oldLevel = level;
int exp = avatar.getExp(); int exp = avatar.getExp();
int reqExp = GenshinData.getAvatarLevelExpRequired(level); int reqExp = GameData.getAvatarLevelExpRequired(level);
while (expGain > 0 && reqExp > 0 && level < maxLevel) { while (expGain > 0 && reqExp > 0 && level < maxLevel) {
// Do calculations // Do calculations
int toGain = Math.min(expGain, reqExp - exp); int toGain = Math.min(expGain, reqExp - exp);
...@@ -687,10 +676,10 @@ public class InventoryManager { ...@@ -687,10 +676,10 @@ public class InventoryManager {
exp = 0; exp = 0;
level += 1; level += 1;
// Set req exp // Set req exp
reqExp = GenshinData.getAvatarLevelExpRequired(level); reqExp = GameData.getAvatarLevelExpRequired(level);
} }
} }
// Old map for packet // Old map for packet
Map<Integer, Float> oldPropMap = avatar.getFightProperties(); Map<Integer, Float> oldPropMap = avatar.getFightProperties();
if (oldLevel != level) { if (oldLevel != level) {
...@@ -711,9 +700,35 @@ public class InventoryManager { ...@@ -711,9 +700,35 @@ public class InventoryManager {
player.sendPacket(new PacketAvatarUpgradeRsp(avatar, oldLevel, oldPropMap)); player.sendPacket(new PacketAvatarUpgradeRsp(avatar, oldLevel, oldPropMap));
} }
public void upgradeAvatarSkill(GenshinPlayer player, long guid, int skillId) { public void upgradeAvatarFetterLevel(Player player, Avatar avatar, int expGain) {
// May work. Not test.
int maxLevel = 10; // Keep it until I think of a more "elegant" way
int level = avatar.getFetterLevel();
int exp = avatar.getFetterExp();
int reqExp = GameData.getAvatarFetterLevelExpRequired(level);
while (expGain > 0 && reqExp > 0 && level < maxLevel) {
int toGain = Math.min(expGain, reqExp - exp);
exp += toGain;
expGain -= toGain;
if (exp >= reqExp) {
exp = 0;
level += 1;
reqExp = GameData.getAvatarFetterLevelExpRequired(level);
}
}
avatar.setFetterLevel(level);
avatar.setFetterExp(exp);
avatar.save();
player.sendPacket(new PacketAvatarPropNotify(avatar));
player.sendPacket(new PacketAvatarFetterDataNotify(avatar));
}
public void upgradeAvatarSkill(Player player, long guid, int skillId) {
// Sanity checks // Sanity checks
GenshinAvatar avatar = player.getAvatars().getAvatarByGuid(guid); Avatar avatar = player.getAvatars().getAvatarByGuid(guid);
if (avatar == null) { if (avatar == null) {
return; return;
} }
...@@ -723,7 +738,7 @@ public class InventoryManager { ...@@ -723,7 +738,7 @@ public class InventoryManager {
return; return;
} }
AvatarSkillData skillData = GenshinData.getAvatarSkillDataMap().get(skillId); AvatarSkillData skillData = GameData.getAvatarSkillDataMap().get(skillId);
if (skillData == null) { if (skillData == null) {
return; return;
} }
...@@ -739,7 +754,7 @@ public class InventoryManager { ...@@ -739,7 +754,7 @@ public class InventoryManager {
} }
// Proud skill data // Proud skill data
ProudSkillData proudSkill = GenshinData.getProudSkillDataMap().get(proudSkillId); ProudSkillData proudSkill = GameData.getProudSkillDataMap().get(proudSkillId);
if (proudSkill == null) { if (proudSkill == null) {
return; return;
} }
...@@ -754,7 +769,7 @@ public class InventoryManager { ...@@ -754,7 +769,7 @@ public class InventoryManager {
if (cost.getId() == 0) { if (cost.getId() == 0) {
continue; continue;
} }
GenshinItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()); GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId());
if (feedItem == null || feedItem.getCount() < cost.getCount()) { if (feedItem == null || feedItem.getCount() < cost.getCount()) {
return; return;
} }
...@@ -772,7 +787,7 @@ public class InventoryManager { ...@@ -772,7 +787,7 @@ public class InventoryManager {
if (cost.getId() == 0) { if (cost.getId() == 0) {
continue; continue;
} }
GenshinItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()); GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId());
player.getInventory().removeItem(feedItem, cost.getCount()); player.getInventory().removeItem(feedItem, cost.getCount());
} }
...@@ -785,9 +800,9 @@ public class InventoryManager { ...@@ -785,9 +800,9 @@ public class InventoryManager {
player.sendPacket(new PacketAvatarSkillUpgradeRsp(avatar, skillId, currentLevel, nextLevel)); player.sendPacket(new PacketAvatarSkillUpgradeRsp(avatar, skillId, currentLevel, nextLevel));
} }
public void unlockAvatarConstellation(GenshinPlayer player, long guid) { public void unlockAvatarConstellation(Player player, long guid) {
// Sanity checks // Sanity checks
GenshinAvatar avatar = player.getAvatars().getAvatarByGuid(guid); Avatar avatar = player.getAvatars().getAvatarByGuid(guid);
if (avatar == null) { if (avatar == null) {
return; return;
} }
...@@ -801,13 +816,13 @@ public class InventoryManager { ...@@ -801,13 +816,13 @@ public class InventoryManager {
nextTalentId = 40 + currentTalentLevel + 1; nextTalentId = 40 + currentTalentLevel + 1;
} }
AvatarTalentData talentData = GenshinData.getAvatarTalentDataMap().get(nextTalentId); AvatarTalentData talentData = GameData.getAvatarTalentDataMap().get(nextTalentId);
if (talentData == null) { if (talentData == null) {
return; return;
} }
GenshinItem costItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(talentData.getMainCostItemId()); GameItem costItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(talentData.getMainCostItemId());
if (costItem == null || costItem.getCount() < talentData.getMainCostItemCount()) { if (costItem == null || costItem.getCount() < talentData.getMainCostItemCount()) {
return; return;
} }
...@@ -824,10 +839,23 @@ public class InventoryManager { ...@@ -824,10 +839,23 @@ public class InventoryManager {
player.sendPacket(new PacketUnlockAvatarTalentRsp(avatar, nextTalentId)); player.sendPacket(new PacketUnlockAvatarTalentRsp(avatar, nextTalentId));
// Proud skill bonus map (Extra skills) // Proud skill bonus map (Extra skills)
OpenConfigEntry entry = GenshinData.getOpenConfigEntries().get(talentData.getOpenConfig()); OpenConfigEntry entry = GameData.getOpenConfigEntries().get(talentData.getOpenConfig());
if (entry != null && entry.getExtraTalentIndex() > 0) { if (entry != null) {
avatar.recalcProudSkillBonusMap(); if (entry.getExtraTalentIndex() > 0) {
player.sendPacket(new PacketProudSkillExtraLevelNotify(avatar, entry.getExtraTalentIndex())); // Check if new constellation adds +3 to a skill level
avatar.recalcConstellations();
// Packet
player.sendPacket(new PacketProudSkillExtraLevelNotify(avatar, entry.getExtraTalentIndex()));
} else if (entry.getSkillPointModifiers() != null) {
// Check if new constellation adds skill charges
avatar.recalcConstellations();
// Packet
for (SkillPointModifier mod : entry.getSkillPointModifiers()) {
player.sendPacket(
new PacketAvatarSkillMaxChargeCountNotify(avatar, mod.getSkillId(), avatar.getSkillExtraChargeMap().getOrDefault(mod.getSkillId(), 0))
);
}
}
} }
// Recalc + save avatar // Recalc + save avatar
...@@ -835,7 +863,7 @@ public class InventoryManager { ...@@ -835,7 +863,7 @@ public class InventoryManager {
avatar.save(); avatar.save();
} }
public void destroyMaterial(GenshinPlayer player, List<MaterialInfo> list) { public void destroyMaterial(Player player, List<MaterialInfo> list) {
// Return materials // Return materials
Int2IntOpenHashMap returnMaterialMap = new Int2IntOpenHashMap(); Int2IntOpenHashMap returnMaterialMap = new Int2IntOpenHashMap();
...@@ -845,7 +873,7 @@ public class InventoryManager { ...@@ -845,7 +873,7 @@ public class InventoryManager {
continue; continue;
} }
GenshinItem item = player.getInventory().getItemByGuid(info.getGuid()); GameItem item = player.getInventory().getItemByGuid(info.getGuid());
if (item == null || !item.isDestroyable()) { if (item == null || !item.isDestroyable()) {
continue; continue;
} }
...@@ -865,7 +893,7 @@ public class InventoryManager { ...@@ -865,7 +893,7 @@ public class InventoryManager {
// Give back items // Give back items
if (returnMaterialMap.size() > 0) { if (returnMaterialMap.size() > 0) {
for (Int2IntMap.Entry e : returnMaterialMap.int2IntEntrySet()) { for (Int2IntMap.Entry e : returnMaterialMap.int2IntEntrySet()) {
player.getInventory().addItem(new GenshinItem(e.getIntKey(), e.getIntValue())); player.getInventory().addItem(new GameItem(e.getIntKey(), e.getIntValue()));
} }
} }
...@@ -873,9 +901,9 @@ public class InventoryManager { ...@@ -873,9 +901,9 @@ public class InventoryManager {
player.sendPacket(new PacketDestroyMaterialRsp(returnMaterialMap)); player.sendPacket(new PacketDestroyMaterialRsp(returnMaterialMap));
} }
public GenshinItem useItem(GenshinPlayer player, long targetGuid, long itemGuid, int count) { public GameItem useItem(Player player, long targetGuid, long itemGuid, int count, int optionId) {
GenshinAvatar target = player.getAvatars().getAvatarByGuid(targetGuid); Avatar target = player.getAvatars().getAvatarByGuid(targetGuid);
GenshinItem useItem = player.getInventory().getItemByGuid(itemGuid); GameItem useItem = player.getInventory().getItemByGuid(itemGuid);
if (useItem == null) { if (useItem == null) {
return null; return null;
...@@ -890,19 +918,89 @@ public class InventoryManager { ...@@ -890,19 +918,89 @@ public class InventoryManager {
if (target == null) { if (target == null) {
break; break;
} }
used = player.getTeamManager().reviveAvatar(target) ? 1 : 0; used = player.getTeamManager().reviveAvatar(target) ? 1 : 0;
} }
break; break;
case MATERIAL_NOTICE_ADD_HP:
if (useItem.getItemData().getUseTarget().equals("ITEM_USE_TARGET_SPECIFY_ALIVE_AVATAR")) {
if (target == null) {
break;
}
int[] SatiationParams = useItem.getItemData().getSatiationParams();
used = player.getTeamManager().healAvatar(target, SatiationParams[0], SatiationParams[1]) ? 1 : 0;
}
break;
case MATERIAL_CHEST:
List<ShopChestTable> shopChestTableList = player.getServer().getShopManager().getShopChestData();
List<GameItem> rewardItemList = new ArrayList<>();
for (ShopChestTable shopChestTable : shopChestTableList) {
if (shopChestTable.getItemId() != useItem.getItemId()) {
continue;
}
if (shopChestTable.getContainsItem() == null) {
break;
}
for (ItemParamData itemParamData : shopChestTable.getContainsItem()) {
ItemData itemData = GameData.getItemDataMap().get(itemParamData.getId());
if (itemData == null) {
continue;
}
rewardItemList.add(new GameItem(itemData, itemParamData.getCount()));
}
if (!rewardItemList.isEmpty()) {
player.getInventory().addItems(rewardItemList, ActionReason.Shop);
}
used = 1;
break;
}
break;
case MATERIAL_CHEST_BATCH_USE:
if (optionId < 1) {
break;
}
List<ShopChestBatchUseTable> shopChestBatchUseTableList = player.getServer().getShopManager().getShopChestBatchUseData();
for (ShopChestBatchUseTable shopChestBatchUseTable : shopChestBatchUseTableList) {
if (shopChestBatchUseTable.getItemId() != useItem.getItemId()) {
continue;
}
if (shopChestBatchUseTable.getOptionItem() == null || optionId > shopChestBatchUseTable.getOptionItem().size()) {
break;
}
int optionItemId = shopChestBatchUseTable.getOptionItem().get(optionId - 1);
ItemData itemData = GameData.getItemDataMap().get(optionItemId);
if (itemData == null) {
break;
}
player.getInventory().addItem(new GameItem(itemData, count), ActionReason.Shop);
used = count;
break;
}
break;
default: default:
break; break;
} }
// Welkin
if (useItem.getItemId() == 1202) {
player.rechargeMoonCard();
used = 1;
}
if (used > 0) { if (used > 0) {
player.getInventory().removeItem(useItem, used); player.getInventory().removeItem(useItem, used);
return useItem; return useItem;
} }
return null; return null;
} }
} }
package emu.grasscutter.game.managers.MapMarkManager;
import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.MapMarkFromTypeOuterClass;
import emu.grasscutter.net.proto.MapMarkPointOuterClass;
import emu.grasscutter.net.proto.MapMarkPointTypeOuterClass;
import emu.grasscutter.utils.Position;
@Entity
public class MapMark {
private int sceneId;
private String name;
private Position position;
private MapMarkPointTypeOuterClass.MapMarkPointType pointType;
private int monsterId = 0;
private MapMarkFromTypeOuterClass.MapMarkFromType fromType;
private int questId = 7;
public MapMark(Position position, MapMarkPointTypeOuterClass.MapMarkPointType type) {
this.position = position;
}
public MapMark(MapMarkPointOuterClass.MapMarkPoint mapMarkPoint) {
this.sceneId = mapMarkPoint.getSceneId();
this.name = mapMarkPoint.getName();
this.position = new Position(mapMarkPoint.getPos().getX(), mapMarkPoint.getPos().getY(), mapMarkPoint.getPos().getZ());
this.pointType = mapMarkPoint.getPointType();
this.monsterId = mapMarkPoint.getMonsterId();
this.fromType = mapMarkPoint.getFromType();
this.questId = mapMarkPoint.getQuestId();
}
public int getSceneId() {
return this.sceneId;
}
public String getName() {
return this.name;
}
public Position getPosition() {
return this.position;
}
public MapMarkPointTypeOuterClass.MapMarkPointType getMapMarkPointType() {
return this.pointType;
}
public void setMapMarkPointType(MapMarkPointTypeOuterClass.MapMarkPointType pointType) {
this.pointType = pointType;
}
public int getMonsterId() {
return this.monsterId;
}
public void setMonsterId(int monsterId) {
this.monsterId = monsterId;
}
public MapMarkFromTypeOuterClass.MapMarkFromType getMapMarkFromType() {
return this.fromType;
}
public int getQuestId() {
return this.questId;
}
public void setQuestId(int questId) {
this.questId = questId;
}
}
package emu.grasscutter.game.managers.MapMarkManager;
import dev.morphia.annotations.Entity;
import emu.grasscutter.utils.Position;
import java.util.HashMap;
@Entity
public class MapMarksManager {
static final int mapMarkMaxCount = 150;
private HashMap<String, MapMark> mapMarks;
public MapMarksManager() {
mapMarks = new HashMap<String, MapMark>();
}
public MapMarksManager(HashMap<String, MapMark> mapMarks) {
this.mapMarks = mapMarks;
}
public HashMap<String, MapMark> getAllMapMarks() {
return mapMarks;
}
public MapMark getMapMark(Position position) {
String key = getMapMarkKey(position);
if (mapMarks.containsKey(key)) {
return mapMarks.get(key);
} else {
return null;
}
}
public String getMapMarkKey(Position position) {
return "x" + (int)position.getX()+ "z" + (int)position.getZ();
}
public boolean removeMapMark(Position position) {
String key = getMapMarkKey(position);
if (mapMarks.containsKey(key)) {
mapMarks.remove(key);
return true;
}
return false;
}
public boolean addMapMark(MapMark mapMark) {
if (mapMarks.size() < mapMarkMaxCount) {
if (!mapMarks.containsKey(mapMark.getPosition())) {
mapMarks.put(getMapMarkKey(mapMark.getPosition()), mapMark);
return true;
}
}
return false;
}
public void setMapMarks(HashMap<String, MapMark> mapMarks) {
this.mapMarks = mapMarks;
}
}
package emu.grasscutter.game.managers.MovementManager;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.LifeState;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.proto.EntityMoveInfoOuterClass;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType;
import emu.grasscutter.net.proto.VectorOuterClass;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.Position;
import org.jetbrains.annotations.NotNull;
import java.lang.Math;
import java.util.*;
public class MovementManager {
public HashMap<String, HashSet<MotionState>> MotionStatesCategorized = new HashMap<>();
private enum ConsumptionType {
None(0),
// consume
CLIMB_START(-500),
CLIMBING(-150),
CLIMB_JUMP(-2500),
DASH(-1800),
SPRINT(-360),
FLY(-60),
SWIM_DASH_START(-200),
SWIM_DASH(-200),
SWIMMING(-80),
FIGHT(0),
// restore
STANDBY(500),
RUN(500),
WALK(500),
STANDBY_MOVE(500),
POWERED_FLY(500);
public final int amount;
ConsumptionType(int amount) {
this.amount = amount;
}
}
private class Consumption {
public ConsumptionType consumptionType;
public int amount;
public Consumption(ConsumptionType ct, int a) {
consumptionType = ct;
amount = a;
}
public Consumption(ConsumptionType ct) {
this(ct, ct.amount);
}
}
private MotionState previousState = MotionState.MOTION_STANDBY;
private MotionState currentState = MotionState.MOTION_STANDBY;
private Position previousCoordinates = new Position(0, 0, 0);
private Position currentCoordinates = new Position(0, 0, 0);
private final Player player;
private float landSpeed = 0;
private long landTimeMillisecond = 0;
private Timer movementManagerTickTimer;
private GameSession cachedSession = null;
private GameEntity cachedEntity = null;
private int staminaRecoverDelay = 0;
private int skillCaster = 0;
private int skillCasting = 0;
public MovementManager(Player player) {
previousCoordinates.add(new Position(0,0,0));
this.player = player;
MotionStatesCategorized.put("SWIM", new HashSet<>(Arrays.asList(
MotionState.MOTION_SWIM_MOVE,
MotionState.MOTION_SWIM_IDLE,
MotionState.MOTION_SWIM_DASH,
MotionState.MOTION_SWIM_JUMP
)));
MotionStatesCategorized.put("STANDBY", new HashSet<>(Arrays.asList(
MotionState.MOTION_STANDBY,
MotionState.MOTION_STANDBY_MOVE,
MotionState.MOTION_DANGER_STANDBY,
MotionState.MOTION_DANGER_STANDBY_MOVE,
MotionState.MOTION_LADDER_TO_STANDBY,
MotionState.MOTION_JUMP_UP_WALL_FOR_STANDBY
)));
MotionStatesCategorized.put("CLIMB", new HashSet<>(Arrays.asList(
MotionState.MOTION_CLIMB,
MotionState.MOTION_CLIMB_JUMP,
MotionState.MOTION_STANDBY_TO_CLIMB,
MotionState.MOTION_LADDER_IDLE,
MotionState.MOTION_LADDER_MOVE,
MotionState.MOTION_LADDER_SLIP,
MotionState.MOTION_STANDBY_TO_LADDER
)));
MotionStatesCategorized.put("FLY", new HashSet<>(Arrays.asList(
MotionState.MOTION_FLY,
MotionState.MOTION_FLY_IDLE,
MotionState.MOTION_FLY_SLOW,
MotionState.MOTION_FLY_FAST,
MotionState.MOTION_POWERED_FLY
)));
MotionStatesCategorized.put("RUN", new HashSet<>(Arrays.asList(
MotionState.MOTION_DASH,
MotionState.MOTION_DANGER_DASH,
MotionState.MOTION_DASH_BEFORE_SHAKE,
MotionState.MOTION_RUN,
MotionState.MOTION_DANGER_RUN,
MotionState.MOTION_WALK,
MotionState.MOTION_DANGER_WALK
)));
MotionStatesCategorized.put("FIGHT", new HashSet<>(Arrays.asList(
MotionState.MOTION_FIGHT
)));
}
public void handle(GameSession session, EntityMoveInfoOuterClass.EntityMoveInfo moveInfo, GameEntity entity) {
if (movementManagerTickTimer == null) {
movementManagerTickTimer = new Timer();
movementManagerTickTimer.scheduleAtFixedRate(new MotionManagerTick(), 0, 200);
}
// cache info for later use in tick
cachedSession = session;
cachedEntity = entity;
MotionInfo motionInfo = moveInfo.getMotionInfo();
moveEntity(entity, moveInfo);
VectorOuterClass.Vector posVector = motionInfo.getPos();
Position newPos = new Position(posVector.getX(),
posVector.getY(), posVector.getZ());;
if (newPos.getX() != 0 && newPos.getY() != 0 && newPos.getZ() != 0) {
currentCoordinates = newPos;
}
currentState = motionInfo.getState();
Grasscutter.getLogger().debug("" + currentState + "\t" + (moveInfo.getIsReliable() ? "reliable" : ""));
handleFallOnGround(motionInfo);
}
public void resetTimer() {
Grasscutter.getLogger().debug("MovementManager ticker stopped");
movementManagerTickTimer.cancel();
movementManagerTickTimer = null;
}
private void moveEntity(GameEntity entity, EntityMoveInfoOuterClass.EntityMoveInfo moveInfo) {
entity.getPosition().set(moveInfo.getMotionInfo().getPos());
entity.getRotation().set(moveInfo.getMotionInfo().getRot());
entity.setLastMoveSceneTimeMs(moveInfo.getSceneTime());
entity.setLastMoveReliableSeq(moveInfo.getReliableSeq());
entity.setMotionState(moveInfo.getMotionInfo().getState());
}
private boolean isPlayerMoving() {
float diffX = currentCoordinates.getX() - previousCoordinates.getX();
float diffY = currentCoordinates.getY() - previousCoordinates.getY();
float diffZ = currentCoordinates.getZ() - previousCoordinates.getZ();
// Grasscutter.getLogger().debug("isPlayerMoving: " + previousCoordinates + ", " + currentCoordinates + ", " + diffX + ", " + diffY + ", " + diffZ);
return Math.abs(diffX) > 0.2 || Math.abs(diffY) > 0.1 || Math.abs(diffZ) > 0.2;
}
private int getCurrentStamina() {
return player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA);
}
private int getMaximumStamina() {
return player.getProperty(PlayerProperty.PROP_MAX_STAMINA);
}
// Returns new stamina
public int updateStamina(GameSession session, int amount) {
int currentStamina = session.getPlayer().getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA);
if (amount == 0) {
return currentStamina;
}
int playerMaxStamina = session.getPlayer().getProperty(PlayerProperty.PROP_MAX_STAMINA);
int newStamina = currentStamina + amount;
if (newStamina < 0) {
newStamina = 0;
}
if (newStamina > playerMaxStamina) {
newStamina = playerMaxStamina;
}
session.getPlayer().setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina);
session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA));
return newStamina;
}
private void handleFallOnGround(@NotNull MotionInfo motionInfo) {
MotionState state = motionInfo.getState();
// land speed and fall on ground event arrive in different packets
// cache land speed
if (state == MotionState.MOTION_LAND_SPEED) {
landSpeed = motionInfo.getSpeed().getY();
landTimeMillisecond = System.currentTimeMillis();
}
if (state == MotionState.MOTION_FALL_ON_GROUND) {
// if not received immediately after MOTION_LAND_SPEED, discard this packet.
// TODO: Test in high latency.
int maxDelay = 200;
if ((System.currentTimeMillis() - landTimeMillisecond) > maxDelay) {
Grasscutter.getLogger().debug("MOTION_FALL_ON_GROUND received after " + maxDelay + "ms, discard.");
return;
}
float currentHP = cachedEntity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
float maxHP = cachedEntity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
float damage = 0;
Grasscutter.getLogger().debug("LandSpeed: " + landSpeed);
if (landSpeed < -23.5) {
damage = (float)(maxHP * 0.33);
}
if (landSpeed < -25) {
damage = (float)(maxHP * 0.5);
}
if (landSpeed < -26.5) {
damage = (float)(maxHP * 0.66);
}
if (landSpeed < -28) {
damage = (maxHP * 1);
}
float newHP = currentHP - damage;
if (newHP < 0) {
newHP = 0;
}
Grasscutter.getLogger().debug("Max: " + maxHP + "\tCurr: " + currentHP + "\tDamage: " + damage + "\tnewHP: " + newHP);
cachedEntity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP);
cachedEntity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(cachedEntity, FightProperty.FIGHT_PROP_CUR_HP));
if (newHP == 0) {
killAvatar(cachedSession, cachedEntity, PlayerDieType.PLAYER_DIE_FALL);
}
landSpeed = 0;
}
}
private void handleDrowning() {
int stamina = getCurrentStamina();
if (stamina < 10) {
boolean isSwimming = MotionStatesCategorized.get("SWIM").contains(currentState);
Grasscutter.getLogger().debug(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" + player.getProperty(PlayerProperty.PROP_MAX_STAMINA) + "\t" + currentState + "\t" + isSwimming);
if (isSwimming && currentState != MotionState.MOTION_SWIM_IDLE) {
killAvatar(cachedSession, cachedEntity, PlayerDieType.PLAYER_DIE_DRAWN);
}
}
}
public void killAvatar(GameSession session, GameEntity entity, PlayerDieType dieType) {
cachedSession.send(new PacketAvatarLifeStateChangeNotify(
cachedSession.getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar(),
LifeState.LIFE_DEAD,
dieType
));
cachedSession.send(new PacketLifeStateChangeNotify(
cachedEntity,
LifeState.LIFE_DEAD,
dieType
));
cachedEntity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0);
cachedEntity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(cachedEntity, FightProperty.FIGHT_PROP_CUR_HP));
entity.getWorld().broadcastPacket(new PacketLifeStateChangeNotify(0, entity, LifeState.LIFE_DEAD));
session.getPlayer().getScene().removeEntity(entity);
((EntityAvatar)entity).onDeath(dieType, 0);
}
private class MotionManagerTick extends TimerTask
{
public void run() {
if (Grasscutter.getConfig().OpenStamina) {
boolean moving = isPlayerMoving();
if (moving || (getCurrentStamina() < getMaximumStamina())) {
// Grasscutter.getLogger().debug("Player moving: " + moving + ", stamina full: " + (getCurrentStamina() >= getMaximumStamina()) + ", recalculate stamina");
Consumption consumption = new Consumption(ConsumptionType.None);
// TODO: refactor these conditions.
if (MotionStatesCategorized.get("CLIMB").contains(currentState)) {
consumption = getClimbConsumption();
} else if (MotionStatesCategorized.get("SWIM").contains((currentState))) {
consumption = getSwimConsumptions();
} else if (MotionStatesCategorized.get("RUN").contains(currentState)) {
consumption = getRunWalkDashConsumption();
} else if (MotionStatesCategorized.get("FLY").contains(currentState)) {
consumption = getFlyConsumption();
} else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) {
consumption = getStandConsumption();
} else if (MotionStatesCategorized.get("FIGHT").contains(currentState)) {
consumption = getFightConsumption();
}
// delay 2 seconds before start recovering - as official server does.
if (cachedSession != null) {
if (consumption.amount < 0) {
staminaRecoverDelay = 0;
}
if (consumption.amount > 0 && consumption.consumptionType != ConsumptionType.POWERED_FLY) {
if (staminaRecoverDelay < 10) {
staminaRecoverDelay++;
consumption = new Consumption(ConsumptionType.None);
}
}
// Grasscutter.getLogger().debug(getCurrentStamina() + "/" + getMaximumStamina() + "\t" + currentState + "\t" + "isMoving: " + isPlayerMoving() + "\t(" + consumption.consumptionType + "," + consumption.amount + ")");
updateStamina(cachedSession, consumption.amount);
}
// tick triggered
handleDrowning();
}
}
previousState = currentState;
previousCoordinates = new Position(currentCoordinates.getX(),
currentCoordinates.getY(), currentCoordinates.getZ());;
}
}
private Consumption getClimbConsumption() {
Consumption consumption = new Consumption(ConsumptionType.None);
if (currentState == MotionState.MOTION_CLIMB) {
consumption = new Consumption(ConsumptionType.CLIMBING);
if (previousState != MotionState.MOTION_CLIMB && previousState != MotionState.MOTION_CLIMB_JUMP) {
consumption = new Consumption(ConsumptionType.CLIMB_START);
}
if (!isPlayerMoving()) {
consumption = new Consumption(ConsumptionType.None);
}
}
if (currentState == MotionState.MOTION_CLIMB_JUMP) {
if (previousState != MotionState.MOTION_CLIMB_JUMP) {
consumption = new Consumption(ConsumptionType.CLIMB_JUMP);
}
}
return consumption;
}
private Consumption getSwimConsumptions() {
Consumption consumption = new Consumption(ConsumptionType.None);
if (currentState == MotionState.MOTION_SWIM_MOVE) {
consumption = new Consumption(ConsumptionType.SWIMMING);
}
if (currentState == MotionState.MOTION_SWIM_DASH) {
consumption = new Consumption(ConsumptionType.SWIM_DASH_START);
if (previousState == MotionState.MOTION_SWIM_DASH) {
consumption = new Consumption(ConsumptionType.SWIM_DASH);
}
}
return consumption;
}
private Consumption getRunWalkDashConsumption() {
Consumption consumption = new Consumption(ConsumptionType.None);
if (currentState == MotionState.MOTION_DASH_BEFORE_SHAKE) {
consumption = new Consumption(ConsumptionType.DASH);
if (previousState == MotionState.MOTION_DASH_BEFORE_SHAKE) {
// only charge once
consumption = new Consumption(ConsumptionType.SPRINT);
}
}
if (currentState == MotionState.MOTION_DASH) {
consumption = new Consumption(ConsumptionType.SPRINT);
}
if (currentState == MotionState.MOTION_RUN) {
consumption = new Consumption(ConsumptionType.RUN);
}
if (currentState == MotionState.MOTION_WALK) {
consumption = new Consumption(ConsumptionType.WALK);
}
return consumption;
}
private Consumption getFlyConsumption() {
Consumption consumption = new Consumption(ConsumptionType.FLY);
HashMap<Integer, Float> glidingCostReduction = new HashMap<>() {{
put(212301, 0.8f); // Amber
put(222301, 0.8f); // Venti
}};
float reduction = 1;
for (EntityAvatar entity: cachedSession.getPlayer().getTeamManager().getActiveTeam()) {
for (int skillId: entity.getAvatar().getProudSkillList()) {
if (glidingCostReduction.containsKey(skillId)) {
reduction = glidingCostReduction.get(skillId);
}
}
}
consumption.amount *= reduction;
// POWERED_FLY, e.g. wind tunnel
if (currentState == MotionState.MOTION_POWERED_FLY) {
consumption = new Consumption(ConsumptionType.POWERED_FLY);
}
return consumption;
}
private Consumption getStandConsumption() {
Consumption consumption = new Consumption(ConsumptionType.None);
if (currentState == MotionState.MOTION_STANDBY) {
consumption = new Consumption(ConsumptionType.STANDBY);
}
if (currentState == MotionState.MOTION_STANDBY_MOVE) {
consumption = new Consumption(ConsumptionType.STANDBY_MOVE);
}
return consumption;
}
private Consumption getFightConsumption() {
Consumption consumption = new Consumption(ConsumptionType.None);
HashMap<Integer, Integer> fightingCost = new HashMap<>() {{
put(10013, -1000); // Kamisato Ayaka
put(10413, -1000); // Mona
}};
if (fightingCost.containsKey(skillCasting)) {
consumption = new Consumption(ConsumptionType.FIGHT, fightingCost.get(skillCasting));
// only handle once, so reset.
skillCasting = 0;
skillCaster = 0;
}
return consumption;
}
public void notifySkill(int caster, int skillId) {
skillCaster = caster;
skillCasting = skillId;
}
}
package emu.grasscutter.game.managers; package emu.grasscutter.game.managers;
import emu.grasscutter.game.CoopRequest; 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.game.props.EnterReason;
import emu.grasscutter.game.world.World;
import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType; import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType;
import emu.grasscutter.net.proto.PlayerApplyEnterMpReasonOuterClass.PlayerApplyEnterMpReason; import emu.grasscutter.net.proto.PlayerApplyEnterMpReasonOuterClass.PlayerApplyEnterMpReason;
import emu.grasscutter.game.World; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.player.Player.SceneLoadState;
import emu.grasscutter.net.proto.PlayerApplyEnterMpResultNotifyOuterClass;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketPlayerApplyEnterMpNotify; import emu.grasscutter.server.packet.send.PacketPlayerApplyEnterMpNotify;
import emu.grasscutter.server.packet.send.PacketPlayerApplyEnterMpResultNotify; import emu.grasscutter.server.packet.send.PacketPlayerApplyEnterMpResultNotify;
...@@ -23,10 +24,10 @@ public class MultiplayerManager { ...@@ -23,10 +24,10 @@ public class MultiplayerManager {
return server; return server;
} }
public void applyEnterMp(GenshinPlayer player, int targetUid) { public void applyEnterMp(Player player, int targetUid) {
GenshinPlayer target = getServer().getPlayerByUid(targetUid); Player target = getServer().getPlayerByUid(targetUid);
if (target == null) { if (target == null) {
player.sendPacket(new PacketPlayerApplyEnterMpResultNotify(targetUid, "", false, PlayerApplyEnterMpReason.PlayerCannotEnterMp)); player.sendPacket(new PacketPlayerApplyEnterMpResultNotify(targetUid, "", false, PlayerApplyEnterMpResultNotifyOuterClass.PlayerApplyEnterMpResultNotify.Reason.PLAYER_CANNOT_ENTER_MP));
return; return;
} }
...@@ -58,7 +59,7 @@ public class MultiplayerManager { ...@@ -58,7 +59,7 @@ public class MultiplayerManager {
target.sendPacket(new PacketPlayerApplyEnterMpNotify(player)); target.sendPacket(new PacketPlayerApplyEnterMpNotify(player));
} }
public void applyEnterMpReply(GenshinPlayer hostPlayer, int applyUid, boolean isAgreed) { public void applyEnterMpReply(Player hostPlayer, int applyUid, boolean isAgreed) {
// Checks // Checks
CoopRequest request = hostPlayer.getCoopRequests().get(applyUid); CoopRequest request = hostPlayer.getCoopRequests().get(applyUid);
if (request == null || request.isExpired()) { if (request == null || request.isExpired()) {
...@@ -66,17 +67,17 @@ public class MultiplayerManager { ...@@ -66,17 +67,17 @@ public class MultiplayerManager {
} }
// Remove now that we are handling it // Remove now that we are handling it
GenshinPlayer requester = request.getRequester(); Player requester = request.getRequester();
hostPlayer.getCoopRequests().remove(applyUid); hostPlayer.getCoopRequests().remove(applyUid);
// Sanity checks - Dont let the requesting player join if they are already in multiplayer // Sanity checks - Dont let the requesting player join if they are already in multiplayer
if (requester.getWorld().isMultiplayer()) { if (requester.getWorld().isMultiplayer()) {
request.getRequester().sendPacket(new PacketPlayerApplyEnterMpResultNotify(hostPlayer, false, PlayerApplyEnterMpReason.PlayerCannotEnterMp)); request.getRequester().sendPacket(new PacketPlayerApplyEnterMpResultNotify(hostPlayer, false, PlayerApplyEnterMpResultNotifyOuterClass.PlayerApplyEnterMpResultNotify.Reason.PLAYER_CANNOT_ENTER_MP));
return; return;
} }
// Response packet // Response packet
request.getRequester().sendPacket(new PacketPlayerApplyEnterMpResultNotify(hostPlayer, isAgreed, PlayerApplyEnterMpReason.PlayerJudge)); request.getRequester().sendPacket(new PacketPlayerApplyEnterMpResultNotify(hostPlayer, isAgreed, PlayerApplyEnterMpResultNotifyOuterClass.PlayerApplyEnterMpResultNotify.Reason.PLAYER_JUDGE));
// Declined // Declined
if (!isAgreed) { if (!isAgreed) {
...@@ -92,7 +93,7 @@ public class MultiplayerManager { ...@@ -92,7 +93,7 @@ public class MultiplayerManager {
world.addPlayer(hostPlayer); world.addPlayer(hostPlayer);
// Rejoin packet // Rejoin packet
hostPlayer.sendPacket(new PacketPlayerEnterSceneNotify(hostPlayer, hostPlayer, EnterType.EnterSelf, EnterReason.HostFromSingleToMp, hostPlayer.getScene().getId(), hostPlayer.getPos())); hostPlayer.sendPacket(new PacketPlayerEnterSceneNotify(hostPlayer, hostPlayer, EnterType.ENTER_SELF, EnterReason.HostFromSingleToMp, hostPlayer.getScene().getId(), hostPlayer.getPos()));
} }
// Set scene pos and id of requester to the host player's // Set scene pos and id of requester to the host player's
...@@ -104,17 +105,17 @@ public class MultiplayerManager { ...@@ -104,17 +105,17 @@ public class MultiplayerManager {
hostPlayer.getWorld().addPlayer(requester); hostPlayer.getWorld().addPlayer(requester);
// Packet // Packet
requester.sendPacket(new PacketPlayerEnterSceneNotify(requester, hostPlayer, EnterType.EnterOther, EnterReason.TeamJoin, hostPlayer.getScene().getId(), hostPlayer.getPos())); requester.sendPacket(new PacketPlayerEnterSceneNotify(requester, hostPlayer, EnterType.ENTER_OTHER, EnterReason.TeamJoin, hostPlayer.getScene().getId(), hostPlayer.getPos()));
} }
public boolean leaveCoop(GenshinPlayer player) { public boolean leaveCoop(Player player) {
// Make sure player's world is multiplayer // Make sure player's world is multiplayer
if (!player.getWorld().isMultiplayer()) { if (!player.getWorld().isMultiplayer()) {
return false; return false;
} }
// Make sure everyone's scene is loaded // Make sure everyone's scene is loaded
for (GenshinPlayer p : player.getWorld().getPlayers()) { for (Player p : player.getWorld().getPlayers()) {
if (p.getSceneLoadState() != SceneLoadState.LOADED) { if (p.getSceneLoadState() != SceneLoadState.LOADED) {
return false; return false;
} }
...@@ -125,19 +126,19 @@ public class MultiplayerManager { ...@@ -125,19 +126,19 @@ public class MultiplayerManager {
world.addPlayer(player); world.addPlayer(player);
// Packet // Packet
player.sendPacket(new PacketPlayerEnterSceneNotify(player, EnterType.EnterSelf, EnterReason.TeamBack, player.getScene().getId(), player.getPos())); player.sendPacket(new PacketPlayerEnterSceneNotify(player, EnterType.ENTER_SELF, EnterReason.TeamBack, player.getScene().getId(), player.getPos()));
return true; return true;
} }
public boolean kickPlayer(GenshinPlayer player, int targetUid) { public boolean kickPlayer(Player player, int targetUid) {
// Make sure player's world is multiplayer and that player is owner // Make sure player's world is multiplayer and that player is owner
if (!player.getWorld().isMultiplayer() || player.getWorld().getHost() != player) { if (!player.getWorld().isMultiplayer() || player.getWorld().getHost() != player) {
return false; return false;
} }
// Get victim and sanity checks // Get victim and sanity checks
GenshinPlayer victim = player.getServer().getPlayerByUid(targetUid); Player victim = player.getServer().getPlayerByUid(targetUid);
if (victim == null || victim == player) { if (victim == null || victim == player) {
return false; return false;
...@@ -152,7 +153,7 @@ public class MultiplayerManager { ...@@ -152,7 +153,7 @@ public class MultiplayerManager {
World world = new World(victim); World world = new World(victim);
world.addPlayer(victim); world.addPlayer(victim);
victim.sendPacket(new PacketPlayerEnterSceneNotify(victim, EnterType.EnterSelf, EnterReason.TeamKick, victim.getScene().getId(), victim.getPos())); victim.sendPacket(new PacketPlayerEnterSceneNotify(victim, EnterType.ENTER_SELF, EnterReason.TeamKick, victim.getScene().getId(), victim.getPos()));
return true; return true;
} }
} }
package emu.grasscutter.game.managers.SotSManager;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.managers.MovementManager.MovementManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.world.World;
import emu.grasscutter.net.proto.ChangeHpReasonOuterClass;
import emu.grasscutter.net.proto.PropChangeReasonOuterClass;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
// Statue of the Seven Manager
public class SotSManager {
// NOTE: Spring volume balance *1 = fight prop HP *100
private final Player player;
private Timer autoRecoverTimer;
public SotSManager(Player player) {
this.player = player;
}
public boolean getIsAutoRecoveryEnabled() {
return player.getProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE) == 1;
}
public void setIsAutoRecoveryEnabled(boolean enabled) {
player.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, enabled ? 1 : 0);
}
public int getAutoRecoveryPercentage() {
return player.getProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT);
}
public void setAutoRecoveryPercentage(int percentage) {
player.setProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, percentage);
}
// autoRevive automatically revives all team members.
public void autoRevive(GameSession session) {
player.getTeamManager().getActiveTeam().forEach(entity -> {
boolean isAlive = entity.isAlive();
float currentHP = entity.getAvatar().getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
float maxHP = entity.getAvatar().getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
// Grasscutter.getLogger().debug("" + entity.getAvatar().getAvatarData().getName() + "\t" + currentHP + "/" + maxHP + "\t" + (isAlive ? "ALIVE":"DEAD"));
float newHP = (float)(maxHP * 0.3);
if (currentHP < newHP) {
updateAvatarCurHP(session, entity, newHP);
}
if (!isAlive) {
entity.getWorld().broadcastPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar()));
}
});
}
public void scheduleAutoRecover(GameSession session) {
if (autoRecoverTimer == null) {
autoRecoverTimer = new Timer();
autoRecoverTimer.schedule(new AutoRecoverTimerTick(session), 2500);
}
}
public void cancelAutoRecover() {
if (autoRecoverTimer != null) {
autoRecoverTimer.cancel();
autoRecoverTimer = null;
}
}
private class AutoRecoverTimerTick extends TimerTask
{
private GameSession session;
public AutoRecoverTimerTick(GameSession session) {
this.session = session;
}
public void run() {
autoRecover(session);
cancelAutoRecover();
}
}
public void refillSpringVolume() {
// TODO: max spring volume depends on level of the statues in Mondstadt and Liyue.
// https://genshin-impact.fandom.com/wiki/Statue_of_The_Seven#:~:text=region%20of%20Inazuma.-,Statue%20Levels,-Upon%20first%20unlocking
player.setProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME, 8500000);
long now = System.currentTimeMillis() / 1000;
long secondsSinceLastUsed = now - player.getSpringLastUsed();
float percentageRefilled = (float)secondsSinceLastUsed / 15 / 100; // 15s = 1% max volume
int maxVolume = player.getProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME);
int currentVolume = player.getProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME);
if (currentVolume < maxVolume) {
int volumeRefilled = (int)(percentageRefilled * maxVolume);
int newVolume = currentVolume + volumeRefilled;
if (currentVolume + volumeRefilled > maxVolume) {
newVolume = maxVolume;
}
player.setProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME, newVolume);
}
player.setSpringLastUsed(now);
player.save();
}
// autoRecover checks player setting to see if auto recover is enabled, and refill HP to the predefined level.
public void autoRecover(GameSession session) {
// TODO: In MP, respect SotS settings from the HOST.
boolean isAutoRecoveryEnabled = getIsAutoRecoveryEnabled();
int autoRecoverPercentage = getAutoRecoveryPercentage();
Grasscutter.getLogger().debug("isAutoRecoveryEnabled: " + isAutoRecoveryEnabled + "\tautoRecoverPercentage: " + autoRecoverPercentage);
if (isAutoRecoveryEnabled) {
player.getTeamManager().getActiveTeam().forEach(entity -> {
float maxHP = entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
float currentHP = entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
if (currentHP == maxHP) {
return;
}
float targetHP = maxHP * autoRecoverPercentage / 100;
if (targetHP > currentHP) {
float needHP = targetHP - currentHP;
float needSV = needHP * 100; // convert HP needed to Spring Volume needed
int sotsSVBalance = player.getProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME);
if (sotsSVBalance >= needSV) {
// sufficient
sotsSVBalance -= needSV;
} else {
// insufficient balance
needSV = sotsSVBalance;
sotsSVBalance = 0;
}
player.setProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME, sotsSVBalance);
player.setSpringLastUsed(System.currentTimeMillis() / 1000);
float newHP = currentHP + needSV / 100; // convert SV to HP
updateAvatarCurHP(session, entity, newHP);
}
});
}
}
private void updateAvatarCurHP(GameSession session, EntityAvatar entity, float newHP) {
// TODO: Figure out why client shows current HP instead of added HP.
// Say an avatar had 12000 and now has 14000, it should show "2000".
// The client always show "+14000" which is incorrect.
entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP);
session.send(new PacketEntityFightPropChangeReasonNotify(entity, FightProperty.FIGHT_PROP_CUR_HP,
newHP, List.of(3), PropChangeReasonOuterClass.PropChangeReason.PROP_CHANGE_STATUE_RECOVER,
ChangeHpReasonOuterClass.ChangeHpReason.ChangeHpAddStatue));
session.send(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP));
Avatar avatar = entity.getAvatar();
avatar.setCurrentHp(newHP);
session.send(new PacketAvatarFightPropUpdateNotify(avatar, FightProperty.FIGHT_PROP_CUR_HP));
player.save();
}
}
package emu.grasscutter.game; package emu.grasscutter.game.player;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import emu.grasscutter.net.packet.GenshinPacket;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.proto.ForwardTypeOuterClass.ForwardType; import emu.grasscutter.net.proto.ForwardTypeOuterClass.ForwardType;
public class InvokeHandler<T> { public class InvokeHandler<T> {
private final List<T> entryListForwardAll; private final List<T> entryListForwardAll;
private final List<T> entryListForwardAllExceptCur; private final List<T> entryListForwardAllExceptCur;
private final List<T> entryListForwardHost; private final List<T> entryListForwardHost;
private final Class<? extends GenshinPacket> packetClass; private final Class<? extends BasePacket> packetClass;
public InvokeHandler(Class<? extends GenshinPacket> packetClass) { public InvokeHandler(Class<? extends BasePacket> packetClass) {
this.entryListForwardAll = new ArrayList<>(); this.entryListForwardAll = new ArrayList<>();
this.entryListForwardAllExceptCur = new ArrayList<>(); this.entryListForwardAllExceptCur = new ArrayList<>();
this.entryListForwardHost = new ArrayList<>(); this.entryListForwardHost = new ArrayList<>();
...@@ -20,22 +21,15 @@ public class InvokeHandler<T> { ...@@ -20,22 +21,15 @@ public class InvokeHandler<T> {
public synchronized void addEntry(ForwardType forward, T entry) { public synchronized void addEntry(ForwardType forward, T entry) {
switch (forward) { switch (forward) {
case ForwardToAll: case FORWARD_TO_ALL -> entryListForwardAll.add(entry);
entryListForwardAll.add(entry); case FORWARD_TO_ALL_EXCEPT_CUR, FORWARD_TO_ALL_EXIST_EXCEPT_CUR -> entryListForwardAllExceptCur.add(entry);
break; case FORWARD_TO_HOST -> entryListForwardHost.add(entry);
case ForwardToAllExceptCur: default -> {
case ForwardToAllExistExceptCur: }
entryListForwardAllExceptCur.add(entry);
break;
case ForwardToHost:
entryListForwardHost.add(entry);
break;
default:
break;
} }
} }
public synchronized void update(GenshinPlayer player) { public synchronized void update(Player player) {
if (player.getWorld() == null) { if (player.getWorld() == null) {
this.entryListForwardAll.clear(); this.entryListForwardAll.clear();
this.entryListForwardAllExceptCur.clear(); this.entryListForwardAllExceptCur.clear();
...@@ -45,17 +39,17 @@ public class InvokeHandler<T> { ...@@ -45,17 +39,17 @@ public class InvokeHandler<T> {
try { try {
if (entryListForwardAll.size() > 0) { if (entryListForwardAll.size() > 0) {
GenshinPacket packet = packetClass.getDeclaredConstructor(List.class).newInstance(this.entryListForwardAll); BasePacket packet = packetClass.getDeclaredConstructor(List.class).newInstance(this.entryListForwardAll);
player.getScene().broadcastPacket(packet); player.getScene().broadcastPacket(packet);
this.entryListForwardAll.clear(); this.entryListForwardAll.clear();
} }
if (entryListForwardAllExceptCur.size() > 0) { if (entryListForwardAllExceptCur.size() > 0) {
GenshinPacket packet = packetClass.getDeclaredConstructor(List.class).newInstance(this.entryListForwardAllExceptCur); BasePacket packet = packetClass.getDeclaredConstructor(List.class).newInstance(this.entryListForwardAllExceptCur);
player.getScene().broadcastPacketToOthers(player, packet); player.getScene().broadcastPacketToOthers(player, packet);
this.entryListForwardAllExceptCur.clear(); this.entryListForwardAllExceptCur.clear();
} }
if (entryListForwardHost.size() > 0) { if (entryListForwardHost.size() > 0) {
GenshinPacket packet = packetClass.getDeclaredConstructor(List.class).newInstance(this.entryListForwardHost); BasePacket packet = packetClass.getDeclaredConstructor(List.class).newInstance(this.entryListForwardHost);
player.getWorld().getHost().sendPacket(packet); player.getWorld().getHost().sendPacket(packet);
this.entryListForwardHost.clear(); this.entryListForwardHost.clear();
} }
......
package emu.grasscutter.game; package emu.grasscutter.game.player;
import java.util.*;
import dev.morphia.annotations.*; import dev.morphia.annotations.*;
import emu.grasscutter.GenshinConstants; import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GenshinData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.def.PlayerLevelData; import emu.grasscutter.data.def.PlayerLevelData;
import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.CoopRequest;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.avatar.AvatarProfileData; import emu.grasscutter.game.avatar.AvatarProfileData;
import emu.grasscutter.game.avatar.AvatarStorage; import emu.grasscutter.game.avatar.AvatarStorage;
import emu.grasscutter.game.avatar.GenshinAvatar; import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityItem; import emu.grasscutter.game.entity.EntityItem;
import emu.grasscutter.game.entity.GenshinEntity; import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.expedition.ExpeditionInfo;
import emu.grasscutter.game.friends.FriendsList; import emu.grasscutter.game.friends.FriendsList;
import emu.grasscutter.game.friends.PlayerProfile; import emu.grasscutter.game.friends.PlayerProfile;
import emu.grasscutter.game.gacha.PlayerGachaInfo; import emu.grasscutter.game.gacha.PlayerGachaInfo;
import emu.grasscutter.game.inventory.GenshinItem; import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.Inventory; import emu.grasscutter.game.inventory.Inventory;
import emu.grasscutter.game.mail.Mail;
import emu.grasscutter.game.mail.MailHandler;
import emu.grasscutter.game.managers.MovementManager.MovementManager;
import emu.grasscutter.game.managers.SotSManager.SotSManager;
import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.EntityType;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.packet.GenshinPacket; import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.shop.ShopLimit;
import emu.grasscutter.game.managers.MapMarkManager.*;
import emu.grasscutter.game.tower.TowerManager;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.World;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry; import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry;
import emu.grasscutter.net.proto.BirthdayOuterClass.Birthday; import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry; 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.InteractTypeOuterClass.InteractType;
import emu.grasscutter.net.proto.MpSettingTypeOuterClass.MpSettingType; import emu.grasscutter.net.proto.MpSettingTypeOuterClass.MpSettingType;
import emu.grasscutter.net.proto.OnlinePlayerInfoOuterClass.OnlinePlayerInfo; 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.PlayerLocationInfoOuterClass.PlayerLocationInfo;
import emu.grasscutter.net.proto.ProfilePictureOuterClass.ProfilePicture;
import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail; import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail;
import emu.grasscutter.net.proto.WorldPlayerLocationInfoOuterClass.WorldPlayerLocationInfo; import emu.grasscutter.server.event.player.PlayerJoinEvent;
import emu.grasscutter.server.event.player.PlayerQuitEvent;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketAbilityInvocationsNotify; import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.server.packet.send.PacketAvatarAddNotify; import emu.grasscutter.utils.DateHelper;
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.PacketClientAbilityInitFinishNotify;
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.PacketScenePlayerLocationNotify;
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.PacketWorldPlayerLocationNotify;
import emu.grasscutter.server.packet.send.PacketWorldPlayerRTTNotify;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.MessageHandler;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@Entity(value = "players", noClassnameStored = true) import java.util.*;
public class GenshinPlayer { import java.util.concurrent.LinkedBlockingQueue;
@Entity(value = "players", useDiscriminator = false)
public class Player {
@Transient private static int GlobalMaximumSpringVolume = 8500000;
@Transient private static int GlobalMaximumStamina = 24000;
@Id private int id; @Id private int id;
@Indexed(options = @IndexOptions(unique = true)) private String accountId; @Indexed(options = @IndexOptions(unique = true)) private String accountId;
@Transient private Account account; @Transient private Account account;
private String nickname; private String nickname;
private String signature; private String signature;
...@@ -73,49 +75,76 @@ public class GenshinPlayer { ...@@ -73,49 +75,76 @@ public class GenshinPlayer {
private int nameCardId = 210001; private int nameCardId = 210001;
private Position pos; private Position pos;
private Position rotation; private Position rotation;
private PlayerBirthday birthday;
private Map<Integer, Integer> properties; private Map<Integer, Integer> properties;
private Set<Integer> nameCardList; private Set<Integer> nameCardList;
private Set<Integer> flyCloakList; private Set<Integer> flyCloakList;
private Set<Integer> costumeList; private Set<Integer> costumeList;
@Transient private long nextGuid = 0; @Transient private long nextGuid = 0;
@Transient private int peerId; @Transient private int peerId;
@Transient private World world; @Transient private World world;
@Transient private GenshinScene scene; @Transient private Scene scene;
@Transient private GameSession session; @Transient private GameSession session;
@Transient private AvatarStorage avatars; @Transient private AvatarStorage avatars;
@Transient private Inventory inventory; @Transient private Inventory inventory;
@Transient private FriendsList friendsList; @Transient private FriendsList friendsList;
@Transient private MailHandler mailHandler;
@Transient private MessageHandler messageHandler;
@Transient private SotSManager sotsManager;
private TeamManager teamManager; private TeamManager teamManager;
private TowerManager towerManager;
private PlayerGachaInfo gachaInfo; private PlayerGachaInfo gachaInfo;
private PlayerProfile playerProfile; private PlayerProfile playerProfile;
private MpSettingType mpSetting = MpSettingType.MpSettingEnterAfterApply;
private boolean showAvatar; private boolean showAvatar;
private ArrayList<AvatarProfileData> shownAvatars; private ArrayList<AvatarProfileData> shownAvatars;
private Set<Integer> rewardedLevels;
private ArrayList<ShopLimit> shopLimit;
private Map<Long, ExpeditionInfo> expeditionInfo;
private int sceneId; private int sceneId;
private int regionId; private int regionId;
private int mainCharacterId; private int mainCharacterId;
private boolean godmode; private boolean godmode;
private boolean moonCard;
private Date moonCardStartTime;
private int moonCardDuration;
private Set<Date> moonCardGetTimes;
private List<Integer> showAvatarList;
private boolean showAvatars;
@Transient private boolean paused; @Transient private boolean paused;
@Transient private int enterSceneToken; @Transient private int enterSceneToken;
@Transient private SceneLoadState sceneState; @Transient private SceneLoadState sceneState;
@Transient private boolean hasSentAvatarDataNotify; @Transient private boolean hasSentAvatarDataNotify;
@Transient private long nextSendPlayerLocTime = 0; @Transient private long nextSendPlayerLocTime = 0;
@Transient private final Int2ObjectMap<CoopRequest> coopRequests; @Transient private final Int2ObjectMap<CoopRequest> coopRequests;
@Transient private final Queue<AttackResult> attackResults;
@Transient private final InvokeHandler<CombatInvokeEntry> combatInvokeHandler; @Transient private final InvokeHandler<CombatInvokeEntry> combatInvokeHandler;
@Transient private final InvokeHandler<AbilityInvokeEntry> abilityInvokeHandler; @Transient private final InvokeHandler<AbilityInvokeEntry> abilityInvokeHandler;
@Transient private final InvokeHandler<AbilityInvokeEntry> clientAbilityInitFinishHandler; @Transient private final InvokeHandler<AbilityInvokeEntry> clientAbilityInitFinishHandler;
@Deprecated @SuppressWarnings({ "rawtypes", "unchecked" }) // Morphia only! private MapMarksManager mapMarksManager;
public GenshinPlayer() { @Transient private MovementManager movementManager;
private long springLastUsed;
@Deprecated
@SuppressWarnings({"rawtypes", "unchecked"}) // Morphia only!
public Player() {
this.inventory = new Inventory(this); this.inventory = new Inventory(this);
this.avatars = new AvatarStorage(this); this.avatars = new AvatarStorage(this);
this.friendsList = new FriendsList(this); this.friendsList = new FriendsList(this);
this.mailHandler = new MailHandler(this);
this.towerManager = new TowerManager(this);
this.pos = new Position(); this.pos = new Position();
this.rotation = new Position(); this.rotation = new Position();
this.properties = new HashMap<>(); this.properties = new HashMap<>();
...@@ -125,24 +154,36 @@ public class GenshinPlayer { ...@@ -125,24 +154,36 @@ public class GenshinPlayer {
} }
this.properties.put(prop.getId(), 0); this.properties.put(prop.getId(), 0);
} }
this.gachaInfo = new PlayerGachaInfo(); this.gachaInfo = new PlayerGachaInfo();
this.nameCardList = new HashSet<>(); this.nameCardList = new HashSet<>();
this.flyCloakList = new HashSet<>(); this.flyCloakList = new HashSet<>();
this.costumeList = new HashSet<>(); this.costumeList = new HashSet<>();
this.setSceneId(3); this.setSceneId(3);
this.setRegionId(1); this.setRegionId(1);
this.sceneState = SceneLoadState.NONE; this.sceneState = SceneLoadState.NONE;
this.attackResults = new LinkedBlockingQueue<>();
this.coopRequests = new Int2ObjectOpenHashMap<>(); this.coopRequests = new Int2ObjectOpenHashMap<>();
this.combatInvokeHandler = new InvokeHandler(PacketCombatInvocationsNotify.class); this.combatInvokeHandler = new InvokeHandler(PacketCombatInvocationsNotify.class);
this.abilityInvokeHandler = new InvokeHandler(PacketAbilityInvocationsNotify.class); this.abilityInvokeHandler = new InvokeHandler(PacketAbilityInvocationsNotify.class);
this.clientAbilityInitFinishHandler = new InvokeHandler(PacketClientAbilityInitFinishNotify.class); this.clientAbilityInitFinishHandler = new InvokeHandler(PacketClientAbilityInitFinishNotify.class);
this.birthday = new PlayerBirthday();
this.rewardedLevels = new HashSet<>();
this.moonCardGetTimes = new HashSet<>();
this.shopLimit = new ArrayList<>();
this.expeditionInfo = new HashMap<>();
this.messageHandler = null;
this.mapMarksManager = new MapMarksManager();
this.movementManager = new MovementManager(this);
this.sotsManager = new SotSManager(this);
} }
// On player creation // On player creation
public GenshinPlayer(GameSession session) { public Player(GameSession session) {
this(); this();
this.account = session.getAccount(); this.account = session.getAccount();
this.accountId = this.getAccount().getId(); this.accountId = this.getAccount().getId();
...@@ -150,6 +191,7 @@ public class GenshinPlayer { ...@@ -150,6 +191,7 @@ public class GenshinPlayer {
this.nickname = "Traveler"; this.nickname = "Traveler";
this.signature = ""; this.signature = "";
this.teamManager = new TeamManager(this); this.teamManager = new TeamManager(this);
this.birthday = new PlayerBirthday();
this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, 1); this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, 1);
this.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, 1); this.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, 1);
this.setProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, 50); this.setProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, 50);
...@@ -160,8 +202,12 @@ public class GenshinPlayer { ...@@ -160,8 +202,12 @@ public class GenshinPlayer {
this.setProperty(PlayerProperty.PROP_PLAYER_RESIN, 160); this.setProperty(PlayerProperty.PROP_PLAYER_RESIN, 160);
this.getFlyCloakList().add(140001); this.getFlyCloakList().add(140001);
this.getNameCardList().add(210001); this.getNameCardList().add(210001);
this.getPos().set(GenshinConstants.START_POSITION); this.getPos().set(GameConstants.START_POSITION);
this.getRotation().set(0, 307, 0); this.getRotation().set(0, 307, 0);
this.messageHandler = null;
this.mapMarksManager = new MapMarksManager();
this.movementManager = new MovementManager(this);
this.sotsManager = new SotSManager(this);
} }
public int getUid() { public int getUid() {
...@@ -171,8 +217,8 @@ public class GenshinPlayer { ...@@ -171,8 +217,8 @@ public class GenshinPlayer {
public void setUid(int id) { public void setUid(int id) {
this.id = id; this.id = id;
} }
public long getNextGenshinGuid() { public long getNextGameGuid() {
long nextId = ++this.nextGuid; long nextId = ++this.nextGuid;
return ((long) this.getUid() << 32) + nextId; return ((long) this.getUid() << 32) + nextId;
} }
...@@ -193,28 +239,28 @@ public class GenshinPlayer { ...@@ -193,28 +239,28 @@ public class GenshinPlayer {
public void setSession(GameSession session) { public void setSession(GameSession session) {
this.session = session; this.session = session;
} }
public boolean isOnline() { public boolean isOnline() {
return this.getSession() != null && this.getSession().isActive(); return this.getSession() != null && this.getSession().isActive();
} }
public GameServer getServer() { public GameServer getServer() {
return this.getSession().getServer(); return this.getSession().getServer();
} }
public synchronized World getWorld() { public synchronized World getWorld() {
return this.world; return this.world;
} }
public synchronized void setWorld(World world) { public synchronized void setWorld(World world) {
this.world = world; this.world = world;
} }
public GenshinScene getScene() { public synchronized Scene getScene() {
return scene; return scene;
} }
public void setScene(GenshinScene scene) { public synchronized void setScene(Scene scene) {
this.scene = scene; this.scene = scene;
} }
...@@ -230,7 +276,7 @@ public class GenshinPlayer { ...@@ -230,7 +276,7 @@ public class GenshinPlayer {
this.nickname = nickName; this.nickname = nickName;
this.updateProfile(); this.updateProfile();
} }
public int getHeadImage() { public int getHeadImage() {
return headImage; return headImage;
} }
...@@ -252,71 +298,85 @@ public class GenshinPlayer { ...@@ -252,71 +298,85 @@ public class GenshinPlayer {
public Position getPos() { public Position getPos() {
return pos; return pos;
} }
public Position getRotation() { public Position getRotation() {
return rotation; return rotation;
} }
public int getLevel() { public int getLevel() {
return this.getProperty(PlayerProperty.PROP_PLAYER_LEVEL); return this.getProperty(PlayerProperty.PROP_PLAYER_LEVEL);
} }
public int getExp() { public int getExp() {
return this.getProperty(PlayerProperty.PROP_PLAYER_EXP); return this.getProperty(PlayerProperty.PROP_PLAYER_EXP);
} }
public int getWorldLevel() { public int getWorldLevel() {
return this.getProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL); return this.getProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL);
} }
public void setWorldLevel(int level) {
this.setProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL, level);
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_WORLD_LEVEL));
}
public int getPrimogems() { public int getPrimogems() {
return this.getProperty(PlayerProperty.PROP_PLAYER_HCOIN); return this.getProperty(PlayerProperty.PROP_PLAYER_HCOIN);
} }
public void setPrimogems(int primogem) { public void setPrimogems(int primogem) {
this.setProperty(PlayerProperty.PROP_PLAYER_HCOIN, primogem); this.setProperty(PlayerProperty.PROP_PLAYER_HCOIN, primogem);
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_HCOIN)); this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_HCOIN));
} }
public int getMora() { public int getMora() {
return this.getProperty(PlayerProperty.PROP_PLAYER_SCOIN); return this.getProperty(PlayerProperty.PROP_PLAYER_SCOIN);
} }
public void setMora(int mora) { public void setMora(int mora) {
this.setProperty(PlayerProperty.PROP_PLAYER_SCOIN, mora); this.setProperty(PlayerProperty.PROP_PLAYER_SCOIN, mora);
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_SCOIN)); this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_SCOIN));
} }
public int getCrystals() {
return this.getProperty(PlayerProperty.PROP_PLAYER_MCOIN);
}
public void setCrystals(int crystals) {
this.setProperty(PlayerProperty.PROP_PLAYER_MCOIN, crystals);
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_MCOIN));
}
private int getExpRequired(int level) { private int getExpRequired(int level) {
PlayerLevelData levelData = GenshinData.getPlayerLevelDataMap().get(level); PlayerLevelData levelData = GameData.getPlayerLevelDataMap().get(level);
return levelData != null ? levelData.getExp() : 0; return levelData != null ? levelData.getExp() : 0;
} }
private float getExpModifier() { private float getExpModifier() {
return Grasscutter.getConfig().getGameServerOptions().getGameRates().ADVENTURE_EXP_RATE; return Grasscutter.getConfig().getGameServerOptions().getGameRates().ADVENTURE_EXP_RATE;
} }
// Affected by exp rate // Affected by exp rate
public void earnExp(int exp) { public void earnExp(int exp) {
addExpDirectly((int) (exp * getExpModifier())); addExpDirectly((int) (exp * getExpModifier()));
} }
// Directly give player exp // Directly give player exp
public void addExpDirectly(int gain) { public void addExpDirectly(int gain) {
boolean hasLeveledUp = false; boolean hasLeveledUp = false;
int level = getLevel(); int level = getLevel();
int exp = getExp(); int exp = getExp();
int reqExp = getExpRequired(level); int reqExp = getExpRequired(level);
exp += gain; exp += gain;
while (exp >= reqExp && reqExp > 0) { while (exp >= reqExp && reqExp > 0) {
exp -= reqExp; exp -= reqExp;
level += 1; level += 1;
reqExp = getExpRequired(level); reqExp = getExpRequired(level);
hasLeveledUp = true; hasLeveledUp = true;
} }
if (hasLeveledUp) { if (hasLeveledUp) {
// Set level property // Set level property
this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, level); this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, level);
...@@ -325,18 +385,18 @@ public class GenshinPlayer { ...@@ -325,18 +385,18 @@ public class GenshinPlayer {
// Update player with packet // Update player with packet
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_LEVEL)); this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_LEVEL));
} }
// Set exp // Set exp
this.setProperty(PlayerProperty.PROP_PLAYER_EXP, exp); this.setProperty(PlayerProperty.PROP_PLAYER_EXP, exp);
// Update player with packet // Update player with packet
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_EXP)); this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_EXP));
} }
private void updateProfile() { private void updateProfile() {
getProfile().syncWithCharacter(this); getProfile().syncWithCharacter(this);
} }
public boolean isFirstLoginEnterScene() { public boolean isFirstLoginEnterScene() {
return !this.hasSentAvatarDataNotify; return !this.hasSentAvatarDataNotify;
} }
...@@ -345,6 +405,10 @@ public class GenshinPlayer { ...@@ -345,6 +405,10 @@ public class GenshinPlayer {
return this.teamManager; return this.teamManager;
} }
public TowerManager getTowerManager() {
return towerManager;
}
public PlayerGachaInfo getGachaInfo() { public PlayerGachaInfo getGachaInfo() {
return gachaInfo; return gachaInfo;
} }
...@@ -356,14 +420,16 @@ public class GenshinPlayer { ...@@ -356,14 +420,16 @@ public class GenshinPlayer {
return playerProfile; return playerProfile;
} }
// TODO: Based on the proto, property value could be int or float.
// Although there's no float value at this moment, our code should be prepared for float values.
public Map<Integer, Integer> getProperties() { public Map<Integer, Integer> getProperties() {
return properties; return properties;
} }
public void setProperty(PlayerProperty prop, int value) { public boolean setProperty(PlayerProperty prop, int value) {
getProperties().put(prop.getId(), value); return setPropertyWithSanityCheck(prop, value);
} }
public int getProperty(PlayerProperty prop) { public int getProperty(PlayerProperty prop) {
return getProperties().get(prop.getId()); return getProperties().get(prop.getId());
} }
...@@ -371,19 +437,23 @@ public class GenshinPlayer { ...@@ -371,19 +437,23 @@ public class GenshinPlayer {
public Set<Integer> getFlyCloakList() { public Set<Integer> getFlyCloakList() {
return flyCloakList; return flyCloakList;
} }
public Set<Integer> getCostumeList() { public Set<Integer> getCostumeList() {
return costumeList; return costumeList;
} }
public Set<Integer> getNameCardList() { public Set<Integer> getNameCardList() {
return this.nameCardList; return this.nameCardList;
} }
public MpSettingType getMpSetting() { public MpSettingType getMpSetting() {
return mpSetting; return MpSettingType.MP_SETTING_ENTER_AFTER_APPLY; // TEMP
} }
public Queue<AttackResult> getAttackResults() {
return this.attackResults;
}
public synchronized Int2ObjectMap<CoopRequest> getCoopRequests() { public synchronized Int2ObjectMap<CoopRequest> getCoopRequests() {
return coopRequests; return coopRequests;
} }
...@@ -391,7 +461,7 @@ public class GenshinPlayer { ...@@ -391,7 +461,7 @@ public class GenshinPlayer {
public InvokeHandler<CombatInvokeEntry> getCombatInvokeHandler() { public InvokeHandler<CombatInvokeEntry> getCombatInvokeHandler() {
return this.combatInvokeHandler; return this.combatInvokeHandler;
} }
public InvokeHandler<AbilityInvokeEntry> getAbilityInvokeHandler() { public InvokeHandler<AbilityInvokeEntry> getAbilityInvokeHandler() {
return this.abilityInvokeHandler; return this.abilityInvokeHandler;
} }
...@@ -400,22 +470,22 @@ public class GenshinPlayer { ...@@ -400,22 +470,22 @@ public class GenshinPlayer {
return clientAbilityInitFinishHandler; return clientAbilityInitFinishHandler;
} }
public void setMpSetting(MpSettingType mpSetting) {
this.mpSetting = mpSetting;
}
public AvatarStorage getAvatars() { public AvatarStorage getAvatars() {
return avatars; return avatars;
} }
public Inventory getInventory() { public Inventory getInventory() {
return inventory; return inventory;
} }
public FriendsList getFriendsList() { public FriendsList getFriendsList() {
return this.friendsList; return this.friendsList;
} }
public MailHandler getMailHandler() {
return mailHandler;
}
public int getEnterSceneToken() { public int getEnterSceneToken() {
return enterSceneToken; return enterSceneToken;
} }
...@@ -464,7 +534,7 @@ public class GenshinPlayer { ...@@ -464,7 +534,7 @@ public class GenshinPlayer {
public void setPaused(boolean newPauseState) { public void setPaused(boolean newPauseState) {
boolean oldPauseState = this.paused; boolean oldPauseState = this.paused;
this.paused = newPauseState; this.paused = newPauseState;
if (newPauseState && !oldPauseState) { if (newPauseState && !oldPauseState) {
this.onPause(); this.onPause();
} else if (oldPauseState && !newPauseState) { } else if (oldPauseState && !newPauseState) {
...@@ -472,6 +542,14 @@ public class GenshinPlayer { ...@@ -472,6 +542,14 @@ public class GenshinPlayer {
} }
} }
public long getSpringLastUsed() {
return springLastUsed;
}
public void setSpringLastUsed(long val) {
springLastUsed = val;
}
public SceneLoadState getSceneLoadState() { public SceneLoadState getSceneLoadState() {
return sceneState; return sceneState;
} }
...@@ -479,7 +557,7 @@ public class GenshinPlayer { ...@@ -479,7 +557,7 @@ public class GenshinPlayer {
public void setSceneLoadState(SceneLoadState sceneState) { public void setSceneLoadState(SceneLoadState sceneState) {
this.sceneState = sceneState; this.sceneState = sceneState;
} }
public boolean isInMultiplayer() { public boolean isInMultiplayer() {
return this.getWorld() != null && this.getWorld().isMultiplayer(); return this.getWorld() != null && this.getWorld().isMultiplayer();
} }
...@@ -499,7 +577,157 @@ public class GenshinPlayer { ...@@ -499,7 +577,157 @@ public class GenshinPlayer {
public void setRegionId(int regionId) { public void setRegionId(int regionId) {
this.regionId = regionId; this.regionId = regionId;
} }
public void setShowAvatars(boolean showAvatars) {
this.showAvatars = showAvatars;
}
public boolean isShowAvatars() {
return showAvatars;
}
public void setShowAvatarList(List<Integer> showAvatarList) {
this.showAvatarList = showAvatarList;
}
public List<Integer> getShowAvatarList() {
return showAvatarList;
}
public boolean inMoonCard() {
return moonCard;
}
public void setMoonCard(boolean moonCard) {
this.moonCard = moonCard;
}
public void addMoonCardDays(int days) {
this.moonCardDuration += days;
}
public int getMoonCardDuration() {
return moonCardDuration;
}
public void setMoonCardDuration(int moonCardDuration) {
this.moonCardDuration = moonCardDuration;
}
public Date getMoonCardStartTime() {
return moonCardStartTime;
}
public void setMoonCardStartTime(Date moonCardStartTime) {
this.moonCardStartTime = moonCardStartTime;
}
public Set<Date> getMoonCardGetTimes() {
return moonCardGetTimes;
}
public void setMoonCardGetTimes(Set<Date> moonCardGetTimes) {
this.moonCardGetTimes = moonCardGetTimes;
}
public int getMoonCardRemainDays() {
Calendar remainCalendar = Calendar.getInstance();
remainCalendar.setTime(moonCardStartTime);
remainCalendar.add(Calendar.DATE, moonCardDuration);
Date theLastDay = remainCalendar.getTime();
Date now = DateHelper.onlyYearMonthDay(new Date());
return (int) ((theLastDay.getTime() - now.getTime()) / (24 * 60 * 60 * 1000)); // By copilot
}
public void rechargeMoonCard() {
inventory.addItem(new GameItem(203, 300));
if (!moonCard) {
moonCard = true;
Date now = new Date();
moonCardStartTime = DateHelper.onlyYearMonthDay(now);
moonCardDuration = 30;
} else {
moonCardDuration += 30;
}
if (!moonCardGetTimes.contains(moonCardStartTime)) {
moonCardGetTimes.add(moonCardStartTime);
}
}
public void getTodayMoonCard() {
if (!moonCard) {
return;
}
Date now = DateHelper.onlyYearMonthDay(new Date());
if (moonCardGetTimes.contains(now)) {
return;
}
Date stopTime = new Date();
Calendar stopCalendar = Calendar.getInstance();
stopCalendar.setTime(stopTime);
stopCalendar.add(Calendar.DATE, moonCardDuration);
stopTime = stopCalendar.getTime();
if (now.after(stopTime)) {
moonCard = false;
return;
}
moonCardGetTimes.add(now);
addMoonCardDays(1);
GameItem item = new GameItem(201, 90);
getInventory().addItem(item, ActionReason.BlessingRedeemReward);
session.send(new PacketCardProductRewardNotify(getMoonCardRemainDays()));
}
public Map<Long, ExpeditionInfo> getExpeditionInfo() {
return expeditionInfo;
}
public void addExpeditionInfo(long avaterGuid, int expId, int hourTime, int startTime){
ExpeditionInfo exp = new ExpeditionInfo();
exp.setExpId(expId);
exp.setHourTime(hourTime);
exp.setState(1);
exp.setStartTime(startTime);
expeditionInfo.put(avaterGuid, exp);
}
public void removeExpeditionInfo(long avaterGuid){
expeditionInfo.remove(avaterGuid);
}
public ExpeditionInfo getExpeditionInfo(long avaterGuid){
return expeditionInfo.get(avaterGuid);
}
public List<ShopLimit> getShopLimit() {
return shopLimit;
}
public ShopLimit getGoodsLimit(int goodsId) {
Optional<ShopLimit> shopLimit = this.shopLimit.stream().filter(x -> x.getShopGoodId() == goodsId).findFirst();
if (shopLimit.isEmpty())
return null;
return shopLimit.get();
}
public void addShopLimit(int goodsId, int boughtCount, int nextRefreshTime) {
ShopLimit target = getGoodsLimit(goodsId);
if (target != null) {
target.setHasBought(target.getHasBought() + boughtCount);
target.setHasBoughtInPeriod(target.getHasBoughtInPeriod() + boughtCount);
target.setNextRefreshTime(nextRefreshTime);
} else {
ShopLimit sl = new ShopLimit();
sl.setShopGoodId(goodsId);
sl.setHasBought(boughtCount);
sl.setHasBoughtInPeriod(boughtCount);
sl.setNextRefreshTime(nextRefreshTime);
getShopLimit().add(sl);
}
this.save();
}
public boolean inGodmode() { public boolean inGodmode() {
return godmode; return godmode;
} }
...@@ -516,113 +744,150 @@ public class GenshinPlayer { ...@@ -516,113 +744,150 @@ public class GenshinPlayer {
this.hasSentAvatarDataNotify = hasSentAvatarDataNotify; this.hasSentAvatarDataNotify = hasSentAvatarDataNotify;
} }
public void addAvatar(GenshinAvatar avatar) { public void addAvatar(Avatar avatar) {
boolean result = getAvatars().addAvatar(avatar); boolean result = getAvatars().addAvatar(avatar);
if (result) { if (result) {
// Add starting weapon // Add starting weapon
getAvatars().addStartingWeapon(avatar); 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 // Done
if (hasSentAvatarDataNotify()) { if (hasSentAvatarDataNotify()) {
// Recalc stats // Recalc stats
avatar.recalcStats(); avatar.recalcStats();
// Packet // Packet
sendPacket(new PacketAvatarAddNotify(avatar, addedToTeam)); sendPacket(new PacketAvatarAddNotify(avatar, false));
} }
} else { } else {
// Failed adding avatar // Failed adding avatar
} }
} }
public void addFlycloak(int flycloakId) { public void addFlycloak(int flycloakId) {
this.getFlyCloakList().add(flycloakId); this.getFlyCloakList().add(flycloakId);
this.sendPacket(new PacketAvatarGainFlycloakNotify(flycloakId)); this.sendPacket(new PacketAvatarGainFlycloakNotify(flycloakId));
} }
public void addCostume(int costumeId) { public void addCostume(int costumeId) {
this.getCostumeList().add(costumeId); this.getCostumeList().add(costumeId);
this.sendPacket(new PacketAvatarGainCostumeNotify(costumeId)); this.sendPacket(new PacketAvatarGainCostumeNotify(costumeId));
} }
public void addNameCard(int nameCardId) { public void addNameCard(int nameCardId) {
this.getNameCardList().add(nameCardId); this.getNameCardList().add(nameCardId);
this.sendPacket(new PacketUnlockNameCardNotify(nameCardId)); this.sendPacket(new PacketUnlockNameCardNotify(nameCardId));
} }
public void setNameCard(int nameCardId) { public void setNameCard(int nameCardId) {
if (!this.getNameCardList().contains(nameCardId)) { if (!this.getNameCardList().contains(nameCardId)) {
return; return;
} }
this.setNameCardId(nameCardId); this.setNameCardId(nameCardId);
this.sendPacket(new PacketSetNameCardRsp(nameCardId)); this.sendPacket(new PacketSetNameCardRsp(nameCardId));
} }
public void dropMessage(Object message) { public void dropMessage(Object message) {
this.sendPacket(new PacketPrivateChatNotify(GenshinConstants.SERVER_CONSOLE_UID, getUid(), message.toString())); if (this.messageHandler != null) {
this.messageHandler.append(message.toString());
return;
}
this.sendPacket(new PacketPrivateChatNotify(GameConstants.SERVER_CONSOLE_UID, getUid(), message.toString()));
} }
/** /**
* Sends a message to another player. * Sends a message to another player.
* @param sender The sender of the message. *
* @param sender The sender of the message.
* @param message The message to send. * @param message The message to send.
*/ */
public void sendMessage(GenshinPlayer sender, Object message) { public void sendMessage(Player sender, Object message) {
this.sendPacket(new PacketPrivateChatNotify(sender.getUid(), this.getUid(), message.toString())); this.sendPacket(new PacketPrivateChatNotify(sender.getUid(), this.getUid(), message.toString()));
} }
// ---------------------MAIL------------------------
public List<Mail> getAllMail() { return this.getMailHandler().getMail(); }
public void sendMail(Mail message) {
this.getMailHandler().sendMail(message);
}
public boolean deleteMail(int mailId) {
return this.getMailHandler().deleteMail(mailId);
}
public Mail getMail(int index) { return this.getMailHandler().getMailById(index); }
public int getMailId(Mail message) {
return this.getMailHandler().getMailIndex(message);
}
public boolean replaceMailByIndex(int index, Mail message) {
return this.getMailHandler().replaceMailByIndex(index, message);
}
public void interactWith(int gadgetEntityId) { public void interactWith(int gadgetEntityId) {
GenshinEntity entity = getScene().getEntityById(gadgetEntityId); GameEntity entity = getScene().getEntityById(gadgetEntityId);
if (entity == null) { if (entity == null) {
return; return;
} }
// Delete
entity.getScene().removeEntity(entity);
// Handle // Handle
if (entity instanceof EntityItem) { if (entity instanceof EntityItem) {
// Pick item // Pick item
EntityItem drop = (EntityItem) entity; EntityItem drop = (EntityItem) entity;
GenshinItem item = new GenshinItem(drop.getItemData(), drop.getCount()); if (!drop.isShare()) // check drop owner to avoid someone picked up item in others' world
{
int dropOwner = (int)(drop.getGuid() >> 32);
if (dropOwner != getUid())
return;
}
entity.getScene().removeEntity(entity);
GameItem item = new GameItem(drop.getItemData(), drop.getCount());
// Add to inventory // Add to inventory
boolean success = getInventory().addItem(item); boolean success = getInventory().addItem(item, ActionReason.SubfieldDrop);
if (success) { if (success) {
this.sendPacket(new PacketGadgetInteractRsp(drop, InteractType.InteractPickItem));
this.sendPacket(new PacketItemAddHintNotify(item, ActionReason.SubfieldDrop)); if (!drop.isShare()) // not shared drop
this.sendPacket(new PacketGadgetInteractRsp(drop, InteractType.INTERACT_PICK_ITEM));
else
this.getScene().broadcastPacket(new PacketGadgetInteractRsp(drop, InteractType.INTERACT_PICK_ITEM));
}
} else if (entity instanceof EntityGadget) {
EntityGadget gadget = (EntityGadget) entity;
if (gadget.getGadgetData().getType() == EntityType.RewardStatue) {
if (scene.getChallenge() != null) {
scene.getChallenge().getStatueDrops(this);
}
this.sendPacket(new PacketGadgetInteractRsp(gadget, InteractType.INTERACT_OPEN_STATUE));
} }
} else {
// Delete directly
entity.getScene().removeEntity(entity);
} }
return; return;
} }
public void onPause() { public void onPause() {
} }
public void onUnpause() { public void onUnpause() {
} }
public void sendPacket(GenshinPacket packet) { public void sendPacket(BasePacket packet) {
if (this.hasSentAvatarDataNotify) { if (this.hasSentAvatarDataNotify) {
this.getSession().send(packet); this.getSession().send(packet);
} }
} }
public OnlinePlayerInfo getOnlinePlayerInfo() { public OnlinePlayerInfo getOnlinePlayerInfo() {
OnlinePlayerInfo.Builder onlineInfo = OnlinePlayerInfo.newBuilder() OnlinePlayerInfo.Builder onlineInfo = OnlinePlayerInfo.newBuilder()
.setUid(this.getUid()) .setUid(this.getUid())
...@@ -631,48 +896,138 @@ public class GenshinPlayer { ...@@ -631,48 +896,138 @@ public class GenshinPlayer {
.setMpSettingType(this.getMpSetting()) .setMpSettingType(this.getMpSetting())
.setNameCardId(this.getNameCardId()) .setNameCardId(this.getNameCardId())
.setSignature(this.getSignature()) .setSignature(this.getSignature())
.setAvatar(HeadImage.newBuilder().setAvatarId(this.getHeadImage())); .setProfilePicture(ProfilePicture.newBuilder().setAvatarId(this.getHeadImage()));
if (this.getWorld() != null) { if (this.getWorld() != null) {
onlineInfo.setCurPlayerNumInWorld(this.getWorld().getPlayers().indexOf(this) + 1); onlineInfo.setCurPlayerNumInWorld(getWorld().getPlayerCount());
} else { } else {
onlineInfo.setCurPlayerNumInWorld(1); onlineInfo.setCurPlayerNumInWorld(1);
} }
return onlineInfo.build(); return onlineInfo.build();
} }
public PlayerBirthday getBirthday() {
return this.birthday;
}
public void setBirthday(int d, int m) {
this.birthday = new PlayerBirthday(d, m);
this.updateProfile();
}
public boolean hasBirthday() {
return this.birthday.getDay() > 0;
}
public Set<Integer> getRewardedLevels() {
return rewardedLevels;
}
public void setRewardedLevels(Set<Integer> rewardedLevels) {
this.rewardedLevels = rewardedLevels;
}
public SocialDetail.Builder getSocialDetail() { public SocialDetail.Builder getSocialDetail() {
List<SocialShowAvatarInfoOuterClass.SocialShowAvatarInfo> socialShowAvatarInfoList = new ArrayList<>();
if (this.isOnline()) {
if (this.getShowAvatarList() != null) {
for (int avatarId : this.getShowAvatarList()) {
socialShowAvatarInfoList.add(
socialShowAvatarInfoList.size(),
SocialShowAvatarInfoOuterClass.SocialShowAvatarInfo.newBuilder()
.setAvatarId(avatarId)
.setLevel(getAvatars().getAvatarById(avatarId).getLevel())
.setCostumeId(getAvatars().getAvatarById(avatarId).getCostume())
.build()
);
}
}
} else {
List<Integer> showAvatarList = DatabaseHelper.getPlayerById(id).getShowAvatarList();
AvatarStorage avatars = DatabaseHelper.getPlayerById(id).getAvatars();
avatars.loadFromDatabase();
if (showAvatarList != null) {
for (int avatarId : showAvatarList) {
socialShowAvatarInfoList.add(
socialShowAvatarInfoList.size(),
SocialShowAvatarInfoOuterClass.SocialShowAvatarInfo.newBuilder()
.setAvatarId(avatarId)
.setLevel(avatars.getAvatarById(avatarId).getLevel())
.setCostumeId(avatars.getAvatarById(avatarId).getCostume())
.build()
);
}
}
}
SocialDetail.Builder social = SocialDetail.newBuilder() SocialDetail.Builder social = SocialDetail.newBuilder()
.setUid(this.getUid()) .setUid(this.getUid())
.setAvatar(HeadImage.newBuilder().setAvatarId(this.getHeadImage())) .setProfilePicture(ProfilePicture.newBuilder().setAvatarId(this.getHeadImage()))
.setNickname(this.getNickname()) .setNickname(this.getNickname())
.setSignature(this.getSignature()) .setSignature(this.getSignature())
.setLevel(this.getLevel()) .setLevel(this.getLevel())
.setBirthday(Birthday.newBuilder()) .setBirthday(this.getBirthday().getFilledProtoWhenNotEmpty())
.setWorldLevel(this.getWorldLevel()) .setWorldLevel(this.getWorldLevel())
.setUnk1(1)
.setUnk3(1)
.setNameCardId(this.getNameCardId()) .setNameCardId(this.getNameCardId())
.setIsShowAvatar(this.isShowAvatars())
.addAllShowAvatarInfoList(socialShowAvatarInfoList)
.setFinishAchievementNum(0); .setFinishAchievementNum(0);
return social; return social;
} }
public WorldPlayerLocationInfo getWorldPlayerLocationInfo() { public List<ShowAvatarInfoOuterClass.ShowAvatarInfo> getShowAvatarInfoList() {
return WorldPlayerLocationInfo.newBuilder() List<ShowAvatarInfoOuterClass.ShowAvatarInfo> showAvatarInfoList = new ArrayList<>();
.setSceneId(this.getSceneId())
.setPlayerLoc(this.getPlayerLocationInfo()) Player player;
.build(); boolean shouldRecalc;
if (this.isOnline()) {
player = this;
shouldRecalc = false;
} else {
player = DatabaseHelper.getPlayerById(id);
player.getAvatars().loadFromDatabase();
player.getInventory().loadFromDatabase();
shouldRecalc = true;
}
List<Integer> showAvatarList = player.getShowAvatarList();
AvatarStorage avatars = player.getAvatars();
if (showAvatarList != null) {
for (int avatarId : showAvatarList) {
Avatar avatar = avatars.getAvatarById(avatarId);
if (shouldRecalc) {
avatar.recalcStats();
}
showAvatarInfoList.add(avatar.toShowAvatarInfoProto());
}
}
return showAvatarInfoList;
} }
public PlayerWorldLocationInfoOuterClass.PlayerWorldLocationInfo getWorldPlayerLocationInfo() {
return PlayerWorldLocationInfoOuterClass.PlayerWorldLocationInfo.newBuilder()
.setSceneId(this.getSceneId())
.setPlayerLoc(this.getPlayerLocationInfo())
.build();
}
public PlayerLocationInfo getPlayerLocationInfo() { public PlayerLocationInfo getPlayerLocationInfo() {
return PlayerLocationInfo.newBuilder() return PlayerLocationInfo.newBuilder()
.setUid(this.getUid()) .setUid(this.getUid())
.setPos(this.getPos().toProto()) .setPos(this.getPos().toProto())
.setRot(this.getRotation().toProto()) .setRot(this.getRotation().toProto())
.build(); .build();
} }
public MapMarksManager getMapMarksManager() {
return mapMarksManager;
}
public MovementManager getMovementManager() { return movementManager; }
public SotSManager getSotSManager() { return sotsManager; }
public synchronized void onTick() { public synchronized void onTick() {
// Check ping // Check ping
if (this.getLastPingTime() > System.currentTimeMillis() + 60000) { if (this.getLastPingTime() > System.currentTimeMillis() + 60000) {
...@@ -684,7 +1039,7 @@ public class GenshinPlayer { ...@@ -684,7 +1039,7 @@ public class GenshinPlayer {
while (it.hasNext()) { while (it.hasNext()) {
CoopRequest req = it.next(); CoopRequest req = it.next();
if (req.isExpired()) { if (req.isExpired()) {
req.getRequester().sendPacket(new PacketPlayerApplyEnterMpResultNotify(this, false, PlayerApplyEnterMpReason.SystemJudge)); req.getRequester().sendPacket(new PacketPlayerApplyEnterMpResultNotify(this, false, PlayerApplyEnterMpResultNotifyOuterClass.PlayerApplyEnterMpResultNotify.Reason.SYSTEM_JUDGE));
it.remove(); it.remove();
} }
} }
...@@ -692,7 +1047,7 @@ public class GenshinPlayer { ...@@ -692,7 +1047,7 @@ public class GenshinPlayer {
if (this.getWorld() != null) { if (this.getWorld() != null) {
// RTT notify - very important to send this often // RTT notify - very important to send this often
this.sendPacket(new PacketWorldPlayerRTTNotify(this.getWorld())); this.sendPacket(new PacketWorldPlayerRTTNotify(this.getWorld()));
// Update player locations if in multiplayer every 5 seconds // Update player locations if in multiplayer every 5 seconds
long time = System.currentTimeMillis(); long time = System.currentTimeMillis();
if (this.getWorld().isMultiplayer() && this.getScene() != null && time > nextSendPlayerLocTime) { if (this.getWorld().isMultiplayer() && this.getScene() != null && time > nextSendPlayerLocTime) {
...@@ -701,8 +1056,27 @@ public class GenshinPlayer { ...@@ -701,8 +1056,27 @@ public class GenshinPlayer {
this.resetSendPlayerLocTime(); this.resetSendPlayerLocTime();
} }
} }
// Expedition
var timeNow = Utils.getCurrentSeconds();
var needNotify = false;
for (Long key : expeditionInfo.keySet()) {
ExpeditionInfo e = expeditionInfo.get(key);
if(e.getState() == 1){
if(timeNow - e.getStartTime() >= e.getHourTime() * 60 * 60){
e.setState(2);
needNotify = true;
}
}
}
if(needNotify){
this.save();
this.sendPacket(new PacketAvatarExpeditionDataNotify(this));
}
} }
public void resetSendPlayerLocTime() { public void resetSendPlayerLocTime() {
this.nextSendPlayerLocTime = System.currentTimeMillis() + 5000; this.nextSendPlayerLocTime = System.currentTimeMillis() + 5000;
} }
...@@ -710,8 +1084,9 @@ public class GenshinPlayer { ...@@ -710,8 +1084,9 @@ public class GenshinPlayer {
@PostLoad @PostLoad
private void onLoad() { private void onLoad() {
this.getTeamManager().setPlayer(this); this.getTeamManager().setPlayer(this);
this.getTowerManager().setPlayer(this);
} }
public void save() { public void save() {
DatabaseHelper.savePlayer(this); DatabaseHelper.savePlayer(this);
} }
...@@ -720,80 +1095,202 @@ public class GenshinPlayer { ...@@ -720,80 +1095,202 @@ public class GenshinPlayer {
// Make sure these exist // Make sure these exist
if (this.getTeamManager() == null) { if (this.getTeamManager() == null) {
this.teamManager = new TeamManager(this); this.teamManager = new TeamManager(this);
} if (this.getProfile().getUid() == 0) { }
if (this.getProfile().getUid() == 0) {
this.getProfile().syncWithCharacter(this); this.getProfile().syncWithCharacter(this);
} }
// Check if player object exists in server // Check if player object exists in server
// TODO - optimize // TODO - optimize
GenshinPlayer exists = this.getServer().getPlayerByUid(getUid()); Player exists = this.getServer().getPlayerByUid(getUid());
if (exists != null) { if (exists != null) {
exists.getSession().close(); exists.getSession().close();
} }
// Load from db // Load from db
this.getAvatars().loadFromDatabase(); this.getAvatars().loadFromDatabase();
this.getInventory().loadFromDatabase(); this.getInventory().loadFromDatabase();
this.getAvatars().postLoad(); this.getAvatars().postLoad();
this.getFriendsList().loadFromDatabase(); this.getFriendsList().loadFromDatabase();
this.getMailHandler().loadFromDatabase();
// Create world // Create world
World world = new World(this); World world = new World(this);
world.addPlayer(this); world.addPlayer(this);
// Add to gameserver // Add to gameserver
if (getSession().isActive()) { if (getSession().isActive()) {
getServer().registerPlayer(this); getServer().registerPlayer(this);
getProfile().setPlayer(this); // Set online getProfile().setPlayer(this); // Set online
} }
// Multiplayer setting // Multiplayer setting
this.setProperty(PlayerProperty.PROP_PLAYER_MP_SETTING_TYPE, this.getMpSetting().getNumber()); this.setProperty(PlayerProperty.PROP_PLAYER_MP_SETTING_TYPE, this.getMpSetting().getNumber());
this.setProperty(PlayerProperty.PROP_IS_MP_MODE_AVAILABLE, 1); this.setProperty(PlayerProperty.PROP_IS_MP_MODE_AVAILABLE, 1);
// Packets // Packets
session.send(new PacketPlayerDataNotify(this)); // Player data session.send(new PacketPlayerDataNotify(this)); // Player data
session.send(new PacketStoreWeightLimitNotify()); session.send(new PacketStoreWeightLimitNotify());
session.send(new PacketPlayerStoreNotify(this)); session.send(new PacketPlayerStoreNotify(this));
session.send(new PacketAvatarDataNotify(this)); session.send(new PacketAvatarDataNotify(this));
getTodayMoonCard(); // The timer works at 0:0, some users log in after that, use this method to check if they have received a reward today or not. If not, send the reward.
session.send(new PacketPlayerEnterSceneNotify(this)); // Enter game world session.send(new PacketPlayerEnterSceneNotify(this)); // Enter game world
session.send(new PacketPlayerLevelRewardUpdateNotify(rewardedLevels));
session.send(new PacketOpenStateUpdateNotify()); session.send(new PacketOpenStateUpdateNotify());
// First notify packets sent // First notify packets sent
this.setHasSentAvatarDataNotify(true); this.setHasSentAvatarDataNotify(true);
// Call join event.
PlayerJoinEvent event = new PlayerJoinEvent(this); event.call();
if(event.isCanceled()) // If event is not cancelled, continue.
session.close();
} }
public void onLogout() { public void onLogout() {
// stop stamina calculation
getMovementManager().resetTimer();
// force to leave the dungeon
if (getScene().getSceneType() == SceneType.SCENE_DUNGEON) {
this.getServer().getDungeonManager().exitDungeon(this);
}
// Leave world // Leave world
if (this.getWorld() != null) { if (this.getWorld() != null) {
this.getWorld().removePlayer(this); this.getWorld().removePlayer(this);
} }
// Status stuff // Status stuff
this.getProfile().syncWithCharacter(this); this.getProfile().syncWithCharacter(this);
this.getProfile().setPlayer(null); // Set offline this.getProfile().setPlayer(null); // Set offline
this.getCoopRequests().clear(); this.getCoopRequests().clear();
// Save to db // Save to db
this.save(); this.save();
this.getTeamManager().saveAvatars(); this.getTeamManager().saveAvatars();
this.getFriendsList().save(); this.getFriendsList().save();
// Call quit event.
PlayerQuitEvent event = new PlayerQuitEvent(this); event.call();
} }
public enum SceneLoadState { public enum SceneLoadState {
NONE (0), LOADING (1), INIT (2), LOADED (3); NONE(0), LOADING(1), INIT(2), LOADED(3);
private final int value; private final int value;
private SceneLoadState(int value) { private SceneLoadState(int value) {
this.value = value; this.value = value;
} }
public int getValue() { public int getValue() {
return this.value; return this.value;
} }
} }
public void setMessageHandler(MessageHandler messageHandler) {
this.messageHandler = messageHandler;
}
private void saveSanityCheckedProperty(PlayerProperty prop, int value) {
getProperties().put(prop.getId(), value);
}
private boolean setPropertyWithSanityCheck(PlayerProperty prop, int value) {
if (prop == PlayerProperty.PROP_EXP) { // 1001
if (!(value >= 0)) { return false; }
} else if (prop == PlayerProperty.PROP_BREAK_LEVEL) { // 1002
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_SATIATION_VAL) { // 1003
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_SATIATION_PENALTY_TIME) { // 1004
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_LEVEL) { // 4001
if (!(value >= 0 && value <= 90)) { return false; }
} else if (prop == PlayerProperty.PROP_LAST_CHANGE_AVATAR_TIME) { // 10001
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_MAX_SPRING_VOLUME) { // 10002
if (!(value >= 0 && value <= GlobalMaximumSpringVolume)) { return false; }
} else if (prop == PlayerProperty.PROP_CUR_SPRING_VOLUME) { // 10003
int playerMaximumSpringVolume = getProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME);
if (!(value >= 0 && value <= playerMaximumSpringVolume)) { return false; }
} else if (prop == PlayerProperty.PROP_IS_SPRING_AUTO_USE) { // 10004
if (!(value >= 0 && value <= 1)) { return false; }
} else if (prop == PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT) { // 10005
if (!(value >= 0 && value <= 100)) { return false; }
} else if (prop == PlayerProperty.PROP_IS_FLYABLE) { // 10006
if (!(0 <= value && value <= 1)) { return false; }
} else if (prop == PlayerProperty.PROP_IS_WEATHER_LOCKED) { // 10007
if (!(0 <= value && value <= 1)) { return false; }
} else if (prop == PlayerProperty.PROP_IS_GAME_TIME_LOCKED) { // 10008
if (!(0 <= value && value <= 1)) { return false; }
} else if (prop == PlayerProperty.PROP_IS_TRANSFERABLE) { // 10009
if (!(0 <= value && value <= 1)) { return false; }
} else if (prop == PlayerProperty.PROP_MAX_STAMINA) { // 10010
if (!(value >= 0 && value <= GlobalMaximumStamina)) { return false; }
} else if (prop == PlayerProperty.PROP_CUR_PERSIST_STAMINA) { // 10011
int playerMaximumStamina = getProperty(PlayerProperty.PROP_MAX_STAMINA);
if (!(value >= 0 && value <= playerMaximumStamina)) { return false; }
} else if (prop == PlayerProperty.PROP_CUR_TEMPORARY_STAMINA) { // 10012
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_PLAYER_LEVEL) { // 10013
if (!(0 < value && value <= 90)) { return false; }
} else if (prop == PlayerProperty.PROP_PLAYER_EXP) { // 10014
if (!(0 <= value)) { return false; }
} else if (prop == PlayerProperty.PROP_PLAYER_HCOIN) { // 10015
// see 10015
} else if (prop == PlayerProperty.PROP_PLAYER_SCOIN) { // 10016
// See 10015
} else if (prop == PlayerProperty.PROP_PLAYER_MP_SETTING_TYPE) { // 10017
if (!(0 <= value && value <= 2)) { return false; }
} else if (prop == PlayerProperty.PROP_IS_MP_MODE_AVAILABLE) { // 10018
if (!(0 <= value && value <= 1)) { return false; }
} else if (prop == PlayerProperty.PROP_PLAYER_WORLD_LEVEL) { // 10019
if (!(0 <= value && value <= 8)) { return false; }
} else if (prop == PlayerProperty.PROP_PLAYER_RESIN) { // 10020
// Do not set 160 as a cap, because player can have more than 160 when they use fragile resin.
if (!(0 <= value)) { return false; }
} else if (prop == PlayerProperty.PROP_PLAYER_WAIT_SUB_HCOIN) { // 10022
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_PLAYER_WAIT_SUB_SCOIN) { // 10023
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_IS_ONLY_MP_WITH_PS_PLAYER) { // 10024
if (!(0 <= value && value <= 1)) { return false; }
} else if (prop == PlayerProperty.PROP_PLAYER_MCOIN) { // 10025
// see 10015
} else if (prop == PlayerProperty.PROP_PLAYER_WAIT_SUB_MCOIN) { // 10026
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_PLAYER_LEGENDARY_KEY) { // 10027
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_IS_HAS_FIRST_SHARE) { // 10028
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_PLAYER_FORGE_POINT) { // 10029
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_CUR_CLIMATE_METER) { // 10035
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_CUR_CLIMATE_TYPE) { // 10036
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_CUR_CLIMATE_AREA_ID) { // 10037
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_CUR_CLIMATE_AREA_CLIMATE_TYPE) { // 10038
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_PLAYER_WORLD_LEVEL_LIMIT) { // 10039
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_PLAYER_WORLD_LEVEL_ADJUST_CD) { // 10040
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_PLAYER_LEGENDARY_DAILY_TASK_NUM) { // 10041
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_PLAYER_HOME_COIN) { // 10042
if (!(0 <= value)) { return false; }
} else if (prop == PlayerProperty.PROP_PLAYER_WAIT_SUB_HOME_COIN) { // 10043
// TODO: implement sanity check
}
saveSanityCheckedProperty(prop, value);
return false;
}
} }
package emu.grasscutter.game.player;
import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.BirthdayOuterClass.Birthday;
@Entity
public class PlayerBirthday {
private int day;
private int month;
public PlayerBirthday(){
this.day = 0;
this.month = 0;
}
public PlayerBirthday(int day, int month){
this.day = day;
this.month = month;
}
public PlayerBirthday set(PlayerBirthday birth){
this.day = birth.day;
this.month = birth.month;
return this;
}
public PlayerBirthday set(int d, int m){
this.day = d;
this.month = m;
return this;
}
public PlayerBirthday setDay(int value){
this.day = value;
return this;
}
public PlayerBirthday setMonth(int value){
this.month = value;
return this;
}
public int getDay(){
return this.day;
}
public int getMonth(){
return this.month;
}
public Birthday toProto(){
return Birthday.newBuilder()
.setDay(this.getDay())
.setMonth(this.getMonth())
.build();
}
public Birthday.Builder getFilledProtoWhenNotEmpty(){
if(this.getDay() > 0)
{
return Birthday.newBuilder()
.setDay(this.getDay())
.setMonth(this.getMonth());
}
return Birthday.newBuilder();
}
}
\ No newline at end of file
package emu.grasscutter.game; package emu.grasscutter.game.player;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import emu.grasscutter.GenshinConstants; import dev.morphia.annotations.Entity;
import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.avatar.GenshinAvatar; import emu.grasscutter.game.avatar.Avatar;
@Entity
public class TeamInfo { public class TeamInfo {
private String name; private String name;
private List<Integer> avatars; private List<Integer> avatars;
...@@ -16,6 +18,11 @@ public class TeamInfo { ...@@ -16,6 +18,11 @@ public class TeamInfo {
this.avatars = new ArrayList<>(Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeam); this.avatars = new ArrayList<>(Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeam);
} }
public TeamInfo(List<Integer> avatars) {
this.name = "";
this.avatars = avatars;
}
public String getName() { public String getName() {
return name; return name;
} }
...@@ -32,11 +39,11 @@ public class TeamInfo { ...@@ -32,11 +39,11 @@ public class TeamInfo {
return avatars.size(); return avatars.size();
} }
public boolean contains(GenshinAvatar avatar) { public boolean contains(Avatar avatar) {
return getAvatars().contains(avatar.getAvatarId()); return getAvatars().contains(avatar.getAvatarId());
} }
public boolean addAvatar(GenshinAvatar avatar) { public boolean addAvatar(Avatar avatar) {
if (size() >= Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeam || contains(avatar)) { if (size() >= Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeam || contains(avatar)) {
return false; return false;
} }
......
package emu.grasscutter.game; package emu.grasscutter.game.player;
import java.util.ArrayList; import java.util.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient; import dev.morphia.annotations.Transient;
import emu.grasscutter.GenshinConstants; import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.def.AvatarSkillDepotData; import emu.grasscutter.data.def.AvatarSkillDepotData;
import emu.grasscutter.game.avatar.GenshinAvatar; import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityBaseGadget;
import emu.grasscutter.game.props.ElementType; import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.game.props.EnterReason; import emu.grasscutter.game.props.EnterReason;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.net.packet.GenshinPacket; import emu.grasscutter.game.world.World;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType; import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType;
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType;
import emu.grasscutter.server.packet.send.PacketAvatarDieAnimationEndRsp; import emu.grasscutter.server.packet.send.PacketAvatarDieAnimationEndRsp;
import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify; import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify; import emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify;
import emu.grasscutter.server.packet.send.PacketAvatarSkillInfoNotify;
import emu.grasscutter.server.packet.send.PacketAvatarTeamUpdateNotify; import emu.grasscutter.server.packet.send.PacketAvatarTeamUpdateNotify;
import emu.grasscutter.server.packet.send.PacketChangeAvatarRsp; import emu.grasscutter.server.packet.send.PacketChangeAvatarRsp;
import emu.grasscutter.server.packet.send.PacketChangeMpTeamAvatarRsp; import emu.grasscutter.server.packet.send.PacketChangeMpTeamAvatarRsp;
...@@ -41,8 +39,9 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; ...@@ -41,8 +39,9 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.ints.IntSet;
@Entity
public class TeamManager { public class TeamManager {
@Transient private GenshinPlayer player; @Transient private Player player;
private Map<Integer, TeamInfo> teams; private Map<Integer, TeamInfo> teams;
private int currentTeamIndex; private int currentTeamIndex;
...@@ -51,10 +50,16 @@ public class TeamManager { ...@@ -51,10 +50,16 @@ public class TeamManager {
@Transient private TeamInfo mpTeam; @Transient private TeamInfo mpTeam;
@Transient private int entityId; @Transient private int entityId;
@Transient private final List<EntityAvatar> avatars; @Transient private final List<EntityAvatar> avatars;
@Transient private final Set<EntityGadget> gadgets; @Transient private final Set<EntityBaseGadget> gadgets;
@Transient private final IntSet teamResonances; @Transient private final IntSet teamResonances;
@Transient private final IntSet teamResonancesConfig; @Transient private final IntSet teamResonancesConfig;
@Transient private int useTemporarilyTeamIndex = -1;
/**
* Temporary Team for tower
*/
@Transient private List<TeamInfo> temporaryTeam;
public TeamManager() { public TeamManager() {
this.mpTeam = new TeamInfo(); this.mpTeam = new TeamInfo();
this.avatars = new ArrayList<>(); this.avatars = new ArrayList<>();
...@@ -63,18 +68,18 @@ public class TeamManager { ...@@ -63,18 +68,18 @@ public class TeamManager {
this.teamResonancesConfig = new IntOpenHashSet(); this.teamResonancesConfig = new IntOpenHashSet();
} }
public TeamManager(GenshinPlayer player) { public TeamManager(Player player) {
this(); this();
this.player = player; this.player = player;
this.teams = new HashMap<>(); this.teams = new HashMap<>();
this.currentTeamIndex = 1; this.currentTeamIndex = 1;
for (int i = 1; i <= GenshinConstants.MAX_TEAMS; i++) { for (int i = 1; i <= GameConstants.MAX_TEAMS; i++) {
this.teams.put(i, new TeamInfo()); this.teams.put(i, new TeamInfo());
} }
} }
public GenshinPlayer getPlayer() { public Player getPlayer() {
return player; return player;
} }
...@@ -82,7 +87,7 @@ public class TeamManager { ...@@ -82,7 +87,7 @@ public class TeamManager {
return player.getWorld(); return player.getWorld();
} }
public void setPlayer(GenshinPlayer player) { public void setPlayer(Player player) {
this.player = player; this.player = player;
} }
...@@ -120,6 +125,10 @@ public class TeamManager { ...@@ -120,6 +125,10 @@ public class TeamManager {
} }
public TeamInfo getCurrentTeamInfo() { public TeamInfo getCurrentTeamInfo() {
if (useTemporarilyTeamIndex >= 0 &&
useTemporarilyTeamIndex < temporaryTeam.size()){
return temporaryTeam.get(useTemporarilyTeamIndex);
}
if (this.getPlayer().isInMultiplayer()) { if (this.getPlayer().isInMultiplayer()) {
return this.getMpTeam(); return this.getMpTeam();
} }
...@@ -138,7 +147,7 @@ public class TeamManager { ...@@ -138,7 +147,7 @@ public class TeamManager {
this.entityId = entityId; this.entityId = entityId;
} }
public Set<EntityGadget> getGadgets() { public Set<EntityBaseGadget> getGadgets() {
return gadgets; return gadgets;
} }
...@@ -205,7 +214,7 @@ public class TeamManager { ...@@ -205,7 +214,7 @@ public class TeamManager {
} }
} }
public void updateTeamEntities(GenshinPacket responsePacket) { public void updateTeamEntities(BasePacket responsePacket) {
// Sanity check - Should never happen // Sanity check - Should never happen
if (this.getCurrentTeamInfo().getAvatars().size() <= 0) { if (this.getCurrentTeamInfo().getAvatars().size() <= 0) {
return; return;
...@@ -260,6 +269,13 @@ public class TeamManager { ...@@ -260,6 +269,13 @@ public class TeamManager {
// Packets // Packets
getPlayer().getWorld().broadcastPacket(new PacketSceneTeamUpdateNotify(getPlayer())); getPlayer().getWorld().broadcastPacket(new PacketSceneTeamUpdateNotify(getPlayer()));
// Skill charges packet - Yes, this is official server behavior as of 2.6.0
for (EntityAvatar entity : getActiveTeam()) {
if (entity.getAvatar().getSkillExtraChargeMap().size() > 0) {
getPlayer().sendPacket(new PacketAvatarSkillInfoNotify(entity.getAvatar()));
}
}
// Run callback // Run callback
if (responsePacket != null) { if (responsePacket != null) {
getPlayer().sendPacket(responsePacket); getPlayer().sendPacket(responsePacket);
...@@ -285,9 +301,9 @@ public class TeamManager { ...@@ -285,9 +301,9 @@ public class TeamManager {
} }
// Set team data // Set team data
LinkedHashSet<GenshinAvatar> newTeam = new LinkedHashSet<>(); LinkedHashSet<Avatar> newTeam = new LinkedHashSet<>();
for (int i = 0; i < list.size(); i++) { for (int i = 0; i < list.size(); i++) {
GenshinAvatar avatar = getPlayer().getAvatars().getAvatarByGuid(list.get(i)); Avatar avatar = getPlayer().getAvatars().getAvatarByGuid(list.get(i));
if (avatar == null || newTeam.contains(avatar)) { if (avatar == null || newTeam.contains(avatar)) {
// Should never happen // Should never happen
return; return;
...@@ -297,7 +313,7 @@ public class TeamManager { ...@@ -297,7 +313,7 @@ public class TeamManager {
// Clear current team info and add avatars from our new team // Clear current team info and add avatars from our new team
teamInfo.getAvatars().clear(); teamInfo.getAvatars().clear();
for (GenshinAvatar avatar : newTeam) { for (Avatar avatar : newTeam) {
teamInfo.addAvatar(avatar); teamInfo.addAvatar(avatar);
} }
...@@ -321,9 +337,9 @@ public class TeamManager { ...@@ -321,9 +337,9 @@ public class TeamManager {
TeamInfo teamInfo = this.getMpTeam(); TeamInfo teamInfo = this.getMpTeam();
// Set team data // Set team data
LinkedHashSet<GenshinAvatar> newTeam = new LinkedHashSet<>(); LinkedHashSet<Avatar> newTeam = new LinkedHashSet<>();
for (int i = 0; i < list.size(); i++) { for (int i = 0; i < list.size(); i++) {
GenshinAvatar avatar = getPlayer().getAvatars().getAvatarByGuid(list.get(i)); Avatar avatar = getPlayer().getAvatars().getAvatarByGuid(list.get(i));
if (avatar == null || newTeam.contains(avatar)) { if (avatar == null || newTeam.contains(avatar)) {
// Should never happen // Should never happen
return; return;
...@@ -333,14 +349,58 @@ public class TeamManager { ...@@ -333,14 +349,58 @@ public class TeamManager {
// Clear current team info and add avatars from our new team // Clear current team info and add avatars from our new team
teamInfo.getAvatars().clear(); teamInfo.getAvatars().clear();
for (GenshinAvatar avatar : newTeam) { for (Avatar avatar : newTeam) {
teamInfo.addAvatar(avatar); teamInfo.addAvatar(avatar);
} }
// Packet // Packet
this.updateTeamEntities(new PacketChangeMpTeamAvatarRsp(getPlayer(), teamInfo)); this.updateTeamEntities(new PacketChangeMpTeamAvatarRsp(getPlayer(), teamInfo));
} }
public void setupTemporaryTeam(List<List<Long>> guidList) {
var team = guidList.stream().map(list -> {
// Sanity checks
if (list.size() == 0 || list.size() > getMaxTeamSize()) {
return null;
}
// Set team data
LinkedHashSet<Avatar> newTeam = new LinkedHashSet<>();
for (int i = 0; i < list.size(); i++) {
Avatar avatar = getPlayer().getAvatars().getAvatarByGuid(list.get(i));
if (avatar == null || newTeam.contains(avatar)) {
// Should never happen
return null;
}
newTeam.add(avatar);
}
// convert to avatar ids
return newTeam.stream()
.map(Avatar::getAvatarId)
.toList();
})
.filter(Objects::nonNull)
.map(TeamInfo::new)
.toList();
this.temporaryTeam = team;
}
public void useTemporaryTeam(int index) {
this.useTemporarilyTeamIndex = index;
updateTeamEntities(null);
}
public void cleanTemporaryTeam() {
// check if using temporary team
if(useTemporarilyTeamIndex < 0){
return;
}
this.useTemporarilyTeamIndex = -1;
this.temporaryTeam = null;
updateTeamEntities(null);
}
public synchronized void setCurrentTeam(int teamId) { public synchronized void setCurrentTeam(int teamId) {
// //
if (getPlayer().isInMultiplayer()) { if (getPlayer().isInMultiplayer()) {
...@@ -395,7 +455,7 @@ public class TeamManager { ...@@ -395,7 +455,7 @@ public class TeamManager {
this.setCurrentCharacterIndex(index); this.setCurrentCharacterIndex(index);
// Old entity motion state // Old entity motion state
oldEntity.setMotionState(MotionState.MotionStandby); oldEntity.setMotionState(MotionState.MOTION_STANDBY);
// Remove and Add // Remove and Add
getPlayer().getScene().replaceEntity(oldEntity, newEntity); getPlayer().getScene().replaceEntity(oldEntity, newEntity);
...@@ -408,34 +468,44 @@ public class TeamManager { ...@@ -408,34 +468,44 @@ public class TeamManager {
if (deadAvatar.isAlive() || deadAvatar.getId() != dieGuid) { if (deadAvatar.isAlive() || deadAvatar.getId() != dieGuid) {
return; return;
} }
// Replacement avatar PlayerDieType dieType = deadAvatar.getKilledType();
EntityAvatar replacement = null; int killedBy = deadAvatar.getKilledBy();
int replaceIndex = -1;
if (dieType == PlayerDieType.PLAYER_DIE_DRAWN) {
for (int i = 0; i < this.getActiveTeam().size(); i++) { // Died in water. Do not replace
EntityAvatar entity = this.getActiveTeam().get(i); // The official server has skipped this notify and will just respawn the team immediately after the animation.
if (entity.isAlive()) { // TODO: Perhaps find a way to get vanilla experience?
replaceIndex = i; getPlayer().sendPacket(new PacketWorldPlayerDieNotify(dieType, killedBy));
replacement = entity;
break;
}
}
if (replacement == null) {
// No more living team members...
getPlayer().sendPacket(new PacketWorldPlayerDieNotify(deadAvatar.getKilledType(), deadAvatar.getKilledBy()));
} else { } else {
// Set index and spawn replacement member // Replacement avatar
this.setCurrentCharacterIndex(replaceIndex); EntityAvatar replacement = null;
getPlayer().getScene().addEntity(replacement); 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(dieType, killedBy));
} else {
// Set index and spawn replacement member
this.setCurrentCharacterIndex(replaceIndex);
getPlayer().getScene().addEntity(replacement);
}
} }
// Response packet // Response packet
getPlayer().sendPacket(new PacketAvatarDieAnimationEndRsp(deadAvatar.getId(), 0)); getPlayer().sendPacket(new PacketAvatarDieAnimationEndRsp(deadAvatar.getId(), 0));
} }
public boolean reviveAvatar(GenshinAvatar avatar) { public boolean reviveAvatar(Avatar avatar) {
for (EntityAvatar entity : getActiveTeam()) { for (EntityAvatar entity : getActiveTeam()) {
if (entity.getAvatar() == avatar) { if (entity.getAvatar() == avatar) {
if (entity.isAlive()) { if (entity.isAlive()) {
...@@ -454,14 +524,40 @@ public class TeamManager { ...@@ -454,14 +524,40 @@ public class TeamManager {
return false; return false;
} }
public void respawnTeam() { public boolean healAvatar(Avatar avatar, int healRate, int healAmount) {
// Make sure all team members are dead
for (EntityAvatar entity : getActiveTeam()) { for (EntityAvatar entity : getActiveTeam()) {
if (entity.isAlive()) { if (entity.getAvatar() == avatar) {
return; if (!entity.isAlive()) {
return false;
}
entity.setFightProperty(
FightProperty.FIGHT_PROP_CUR_HP,
(float) Math.min(
(entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) +
entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * (float) healRate / 100.0 +
(float) healAmount / 100.0),
entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP)
)
);
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
// Drowning needs revive when there may be other team members still alive.
// for (EntityAvatar entity : getActiveTeam()) {
// if (entity.isAlive()) {
// return;
// }
// }
player.getMovementManager().resetTimer(); // prevent drowning immediately after respawn
// Revive all team members // Revive all team members
for (EntityAvatar entity : getActiveTeam()) { for (EntityAvatar entity : getActiveTeam()) {
...@@ -474,14 +570,14 @@ public class TeamManager { ...@@ -474,14 +570,14 @@ public class TeamManager {
} }
// Teleport player // Teleport player
getPlayer().sendPacket(new PacketPlayerEnterSceneNotify(getPlayer(), EnterType.EnterSelf, EnterReason.Revival, 3, GenshinConstants.START_POSITION)); getPlayer().sendPacket(new PacketPlayerEnterSceneNotify(getPlayer(), EnterType.ENTER_SELF, EnterReason.Revival, 3, GameConstants.START_POSITION));
// Set player position // Set player position
player.setSceneId(3); player.setSceneId(3);
player.getPos().set(GenshinConstants.START_POSITION); player.getPos().set(GameConstants.START_POSITION);
// Packets // Packets
getPlayer().sendPacket(new GenshinPacket(PacketOpcodes.WorldPlayerReviveRsp)); getPlayer().sendPacket(new BasePacket(PacketOpcodes.WorldPlayerReviveRsp));
} }
public void saveAvatars() { public void saveAvatars() {
......
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 EntityType {
None (0),
Avatar (1),
Monster (2),
Bullet (3),
AttackPhyisicalUnit (4),
AOE (5),
Camera (6),
EnviroArea (7),
Equip (8),
MonsterEquip (9),
Grass (10),
Level (11),
NPC (12),
TransPointFirst (13),
TransPointFirstGadget (14),
TransPointSecond (15),
TransPointSecondGadget (16),
DropItem (17),
Field (18),
Gadget (19),
Water (20),
GatherPoint (21),
GatherObject (22),
AirflowField (23),
SpeedupField (24),
Gear (25),
Chest (26),
EnergyBall (27),
ElemCrystal (28),
Timeline (29),
Worktop (30),
Team (31),
Platform (32),
AmberWind (33),
EnvAnimal (34),
SealGadget (35),
Tree (36),
Bush (37),
QuestGadget (38),
Lightning (39),
RewardPoint (40),
RewardStatue (41),
MPLevel (42),
WindSeed (43),
MpPlayRewardPoint (44),
ViewPoint (45),
RemoteAvatar (46),
GeneralRewardPoint (47),
PlayTeam (48),
OfferingGadget (49),
EyePoint (50),
MiracleRing (51),
Foundation (52),
WidgetGadget (53),
PlaceHolder (99);
private final int value;
private static final Int2ObjectMap<EntityType> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, EntityType> stringMap = new HashMap<>();
static {
Stream.of(values()).forEach(e -> {
map.put(e.getValue(), e);
stringMap.put(e.name(), e);
});
}
private EntityType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static EntityType getTypeByValue(int value) {
return map.getOrDefault(value, None);
}
public static EntityType getTypeByName(String name) {
return stringMap.getOrDefault(name, None);
}
}
...@@ -6,65 +6,67 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; ...@@ -6,65 +6,67 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public enum PlayerProperty { public enum PlayerProperty {
PROP_EXP (1001), PROP_EXP (1001),
PROP_BREAK_LEVEL (1002), PROP_BREAK_LEVEL (1002),
PROP_SATIATION_VAL (1003), PROP_SATIATION_VAL (1003),
PROP_SATIATION_PENALTY_TIME (1004), PROP_SATIATION_PENALTY_TIME (1004),
PROP_LEVEL (4001), PROP_LEVEL (4001),
PROP_LAST_CHANGE_AVATAR_TIME (10001), PROP_LAST_CHANGE_AVATAR_TIME (10001),
PROP_MAX_SPRING_VOLUME (10002), PROP_MAX_SPRING_VOLUME (10002), // Maximum volume of the Statue of the Seven for the player [0, 8500000]
PROP_CUR_SPRING_VOLUME (10003), PROP_CUR_SPRING_VOLUME (10003), // Current volume of the Statue of the Seven [0, PROP_MAX_SPRING_VOLUME]
PROP_IS_SPRING_AUTO_USE (10004), PROP_IS_SPRING_AUTO_USE (10004), // Auto HP recovery when approaching the Statue of the Seven [0, 1]
PROP_SPRING_AUTO_USE_PERCENT (10005), PROP_SPRING_AUTO_USE_PERCENT (10005), // Auto HP recovery percentage [0, 100]
PROP_IS_FLYABLE (10006), PROP_IS_FLYABLE (10006), // Are you in a state that disables your flying ability? e.g. new player [0, 1]
PROP_IS_WEATHER_LOCKED (10007), PROP_IS_WEATHER_LOCKED (10007),
PROP_IS_GAME_TIME_LOCKED (10008), PROP_IS_GAME_TIME_LOCKED (10008),
PROP_IS_TRANSFERABLE (10009), PROP_IS_TRANSFERABLE (10009),
PROP_MAX_STAMINA (10010), PROP_MAX_STAMINA (10010), // Maximum stamina of the player (0 - 24000)
PROP_CUR_PERSIST_STAMINA (10011), PROP_CUR_PERSIST_STAMINA (10011), // Used stamina of the player (0 - PROP_MAX_STAMINA)
PROP_CUR_TEMPORARY_STAMINA (10012), PROP_CUR_TEMPORARY_STAMINA (10012),
PROP_PLAYER_LEVEL (10013), PROP_PLAYER_LEVEL (10013),
PROP_PLAYER_EXP (10014), PROP_PLAYER_EXP (10014),
PROP_PLAYER_HCOIN (10015), // Primogem PROP_PLAYER_HCOIN (10015), // Primogem (-inf, +inf)
PROP_PLAYER_SCOIN (10016), // Mora // It is known that Mihoyo will make Primogem negative in the cases that a player spends
PROP_PLAYER_MP_SETTING_TYPE (10017), // his gems and then got a money refund, so negative is allowed.
PROP_IS_MP_MODE_AVAILABLE (10018), PROP_PLAYER_SCOIN (10016), // Mora [0, +inf)
PROP_PLAYER_WORLD_LEVEL (10019), PROP_PLAYER_MP_SETTING_TYPE (10017), // Do you allow other players to join your game? [0=no 1=direct 2=approval]
PROP_PLAYER_RESIN (10020), PROP_IS_MP_MODE_AVAILABLE (10018), // Are you not in a quest or something that disables MP? [0, 1]
PROP_PLAYER_WAIT_SUB_HCOIN (10022), PROP_PLAYER_WORLD_LEVEL (10019), // [0, 8]
PROP_PLAYER_WAIT_SUB_SCOIN (10023), PROP_PLAYER_RESIN (10020), // Original Resin [0, +inf)
PROP_IS_ONLY_MP_WITH_PS_PLAYER (10024), PROP_PLAYER_WAIT_SUB_HCOIN (10022),
PROP_PLAYER_MCOIN (10025), // Genesis Crystal PROP_PLAYER_WAIT_SUB_SCOIN (10023),
PROP_PLAYER_WAIT_SUB_MCOIN (10026), PROP_IS_ONLY_MP_WITH_PS_PLAYER (10024), // Is only MP with PlayStation players? [0, 1]
PROP_PLAYER_LEGENDARY_KEY (10027), PROP_PLAYER_MCOIN (10025), // Genesis Crystal (-inf, +inf) see 10015
PROP_IS_HAS_FIRST_SHARE (10028), PROP_PLAYER_WAIT_SUB_MCOIN (10026),
PROP_PLAYER_FORGE_POINT (10029), PROP_PLAYER_LEGENDARY_KEY (10027),
PROP_CUR_CLIMATE_METER (10035), PROP_IS_HAS_FIRST_SHARE (10028),
PROP_CUR_CLIMATE_TYPE (10036), PROP_PLAYER_FORGE_POINT (10029),
PROP_CUR_CLIMATE_AREA_ID (10037), PROP_CUR_CLIMATE_METER (10035),
PROP_CUR_CLIMATE_AREA_CLIMATE_TYPE (10038), PROP_CUR_CLIMATE_TYPE (10036),
PROP_PLAYER_WORLD_LEVEL_LIMIT (10039), PROP_CUR_CLIMATE_AREA_ID (10037),
PROP_PLAYER_WORLD_LEVEL_ADJUST_CD (10040), PROP_CUR_CLIMATE_AREA_CLIMATE_TYPE (10038),
PROP_PLAYER_LEGENDARY_DAILY_TASK_NUM (10041), PROP_PLAYER_WORLD_LEVEL_LIMIT (10039),
PROP_PLAYER_HOME_COIN (10042), PROP_PLAYER_WORLD_LEVEL_ADJUST_CD (10040),
PROP_PLAYER_WAIT_SUB_HOME_COIN (10043); PROP_PLAYER_LEGENDARY_DAILY_TASK_NUM (10041),
PROP_PLAYER_HOME_COIN (10042), // Realm currency [0, +inf)
private final int id; PROP_PLAYER_WAIT_SUB_HOME_COIN (10043);
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() { private final int id;
return id; private static final Int2ObjectMap<PlayerProperty> map = new Int2ObjectOpenHashMap<>();
}
static {
public static PlayerProperty getPropById(int value) { Stream.of(values()).forEach(e -> map.put(e.getId(), e));
return map.getOrDefault(value, null); }
}
PlayerProperty(int id) {
this.id = id;
}
public int getId() {
return id;
}
public static PlayerProperty getPropById(int value) {
return map.getOrDefault(value, null);
}
} }
package emu.grasscutter.game.shop;
import java.util.ArrayList;
import java.util.List;
public class ShopChestBatchUseTable {
private int itemId;
private List<Integer> optionItem = new ArrayList<>();
public int getItemId() {
return itemId;
}
public void setItemId(int itemId) {
this.itemId = itemId;
}
public List<Integer> getOptionItem() {
return optionItem;
}
public void setOptionItem(List<Integer> optionItem) {
this.optionItem = optionItem;
}
}
package emu.grasscutter.game.shop;
import emu.grasscutter.data.common.ItemParamData;
import java.util.ArrayList;
import java.util.List;
public class ShopChestTable {
private int itemId;
private List<ItemParamData> containsItem = new ArrayList<>();
public int getItemId() {
return itemId;
}
public void setItemId(int itemId) {
this.itemId = itemId;
}
public List<ItemParamData> getContainsItem() {
return containsItem;
}
public void setContainsItem(List<ItemParamData> containsItem) {
this.containsItem = containsItem;
}
}
package emu.grasscutter.game.shop; package emu.grasscutter.game.shop;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.def.ShopGoodsData;
import java.util.ArrayList;
import java.util.List;
public class ShopInfo { public class ShopInfo {
private int goodsId = 0;
private ItemParamData goodsItem;
private int scoin = 0;
private List<ItemParamData> costItemList;
private int boughtNum = 0;
private int buyLimit = 0;
private int beginTime = 0;
private int endTime = 1924992000;
private int minLevel = 0;
private int maxLevel = 61;
private List<Integer> preGoodsIdList = new ArrayList<>();
private int mcoin = 0;
private int hcoin = 0;
private int disableType = 0;
private int secondarySheetId = 0;
private String refreshType;
public enum ShopRefreshType {
NONE(0),
SHOP_REFRESH_DAILY(1),
SHOP_REFRESH_WEEKLY(2),
SHOP_REFRESH_MONTHLY(3);
private final int value;
ShopRefreshType(int value) {
this.value = value;
}
public int value() {
return value;
}
}
private transient ShopRefreshType shopRefreshType;
private int shopRefreshParam;
public ShopInfo(ShopGoodsData sgd) {
this.goodsId = sgd.getGoodsId();
this.goodsItem = new ItemParamData(sgd.getItemId(), sgd.getItemCount());
this.scoin = sgd.getCostScoin();
this.mcoin = sgd.getCostMcoin();
this.hcoin = sgd.getCostHcoin();
this.buyLimit = sgd.getBuyLimit();
this.minLevel = sgd.getMinPlayerLevel();
this.maxLevel = sgd.getMaxPlayerLevel();
this.costItemList = sgd.getCostItems().stream().filter(x -> x.getId() != 0).map(x -> new ItemParamData(x.getId(), x.getCount())).toList();
this.secondarySheetId = sgd.getSubTabId();
this.shopRefreshType = sgd.getRefreshType();
this.shopRefreshParam = sgd.getRefreshParam();
}
public int getHcoin() {
return hcoin;
}
public void setHcoin(int hcoin) {
this.hcoin = hcoin;
}
public List<Integer> getPreGoodsIdList() {
return preGoodsIdList;
}
public void setPreGoodsIdList(List<Integer> preGoodsIdList) {
this.preGoodsIdList = preGoodsIdList;
}
public int getMcoin() {
return mcoin;
}
public void setMcoin(int mcoin) {
this.mcoin = mcoin;
}
public int getDisableType() {
return disableType;
}
public void setDisableType(int disableType) {
this.disableType = disableType;
}
public int getSecondarySheetId() {
return secondarySheetId;
}
public void setSecondarySheetId(int secondarySheetId) {
this.secondarySheetId = secondarySheetId;
}
public int getGoodsId() {
return goodsId;
}
public void setGoodsId(int goodsId) {
this.goodsId = goodsId;
}
public ItemParamData getGoodsItem() {
return goodsItem;
}
public void setGoodsItem(ItemParamData goodsItem) {
this.goodsItem = goodsItem;
}
public int getScoin() {
return scoin;
}
public void setScoin(int scoin) {
this.scoin = scoin;
}
public List<ItemParamData> getCostItemList() {
return costItemList;
}
public void setCostItemList(List<ItemParamData> costItemList) {
this.costItemList = costItemList;
}
public int getBoughtNum() {
return boughtNum;
}
public void setBoughtNum(int boughtNum) {
this.boughtNum = boughtNum;
}
public int getBuyLimit() {
return buyLimit;
}
public void setBuyLimit(int buyLimit) {
this.buyLimit = buyLimit;
}
public int getBeginTime() {
return beginTime;
}
public void setBeginTime(int beginTime) {
this.beginTime = beginTime;
}
public int getEndTime() {
return endTime;
}
public void setEndTime(int endTime) {
this.endTime = endTime;
}
public int getMinLevel() {
return minLevel;
}
public void setMinLevel(int minLevel) {
this.minLevel = minLevel;
}
public int getMaxLevel() {
return maxLevel;
}
public void setMaxLevel(int maxLevel) {
this.maxLevel = maxLevel;
}
public ShopRefreshType getShopRefreshType() {
if (refreshType == null)
return ShopRefreshType.NONE;
return switch (refreshType) {
case "SHOP_REFRESH_DAILY" -> ShopInfo.ShopRefreshType.SHOP_REFRESH_DAILY;
case "SHOP_REFRESH_WEEKLY" -> ShopInfo.ShopRefreshType.SHOP_REFRESH_WEEKLY;
case "SHOP_REFRESH_MONTHLY" -> ShopInfo.ShopRefreshType.SHOP_REFRESH_MONTHLY;
default -> ShopInfo.ShopRefreshType.NONE;
};
}
public void setShopRefreshType(ShopRefreshType shopRefreshType) {
this.shopRefreshType = shopRefreshType;
}
public int getShopRefreshParam() {
return shopRefreshParam;
}
public void setShopRefreshParam(int shopRefreshParam) {
this.shopRefreshParam = shopRefreshParam;
}
} }
package emu.grasscutter.game.shop;
import dev.morphia.annotations.Entity;
@Entity
public class ShopLimit {
public int getShopGoodId() {
return shopGoodId;
}
public void setShopGoodId(int shopGoodId) {
this.shopGoodId = shopGoodId;
}
public int getHasBought() {
return hasBought;
}
public void setHasBought(int hasBought) {
this.hasBought = hasBought;
}
public int getNextRefreshTime() {
return nextRefreshTime;
}
public void setNextRefreshTime(int nextRefreshTime) {
this.nextRefreshTime = nextRefreshTime;
}
public int getHasBoughtInPeriod() {
return hasBoughtInPeriod;
}
public void setHasBoughtInPeriod(int hasBoughtInPeriod) {
this.hasBoughtInPeriod = hasBoughtInPeriod;
}
private int shopGoodId;
private int hasBought;
private int hasBoughtInPeriod = 0;
private int nextRefreshTime = 0;
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment