mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2026-05-15 00:42:34 +08:00
ccf182d692
* Update InventorySystem.java -:[fix] Medicine revival * Update GameMainQuest.java -:[fix] Login white screen
837 lines
32 KiB
Java
837 lines
32 KiB
Java
package emu.grasscutter.game.systems;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.Optional;
|
|
import java.util.stream.Stream;
|
|
|
|
import emu.grasscutter.Grasscutter;
|
|
import emu.grasscutter.data.GameData;
|
|
import emu.grasscutter.data.common.ItemParamData;
|
|
import emu.grasscutter.data.excels.AvatarPromoteData;
|
|
import emu.grasscutter.data.excels.AvatarSkillDepotData;
|
|
import emu.grasscutter.data.excels.ItemData;
|
|
import emu.grasscutter.data.excels.WeaponPromoteData;
|
|
import emu.grasscutter.game.avatar.Avatar;
|
|
import emu.grasscutter.game.inventory.GameItem;
|
|
import emu.grasscutter.game.inventory.ItemType;
|
|
import emu.grasscutter.game.inventory.MaterialType;
|
|
import emu.grasscutter.game.player.Player;
|
|
import emu.grasscutter.game.props.FightProperty;
|
|
import emu.grasscutter.game.props.ItemUseOp;
|
|
import emu.grasscutter.game.props.ItemUseAction.ItemUseAddExp;
|
|
import emu.grasscutter.game.props.ItemUseAction.ItemUseAddReliquaryExp;
|
|
import emu.grasscutter.game.props.ItemUseAction.ItemUseAddWeaponExp;
|
|
import emu.grasscutter.game.props.ItemUseAction.UseItemParams;
|
|
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
|
|
import emu.grasscutter.net.proto.MaterialInfoOuterClass.MaterialInfo;
|
|
import emu.grasscutter.server.event.player.PlayerUseFoodEvent;
|
|
import emu.grasscutter.server.game.BaseGameSystem;
|
|
import emu.grasscutter.server.game.GameServer;
|
|
import emu.grasscutter.server.packet.send.*;
|
|
import emu.grasscutter.utils.Utils;
|
|
import it.unimi.dsi.fastutil.ints.Int2FloatArrayMap;
|
|
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
|
|
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
|
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
|
import it.unimi.dsi.fastutil.ints.Int2IntRBTreeMap;
|
|
import lombok.val;
|
|
|
|
public class InventorySystem extends BaseGameSystem {
|
|
private static final Int2IntMap weaponRefundMaterials = new Int2IntArrayMap();
|
|
{
|
|
// Use a sorted map, use exp as key to sort by exp
|
|
// We want to have weaponRefundMaterials as (id, exp) in descending exp order
|
|
var temp = new Int2IntRBTreeMap(Collections.reverseOrder());
|
|
GameData.getItemDataMap().forEach((id, data) -> {
|
|
if (data == null) return;
|
|
if (data.getMaterialType() != MaterialType.MATERIAL_WEAPON_EXP_STONE) return;
|
|
var actions = data.getItemUseActions();
|
|
if (actions == null) return;
|
|
for (var action : actions) {
|
|
if (action.getItemUseOp() == ItemUseOp.ITEM_USE_ADD_WEAPON_EXP) {
|
|
temp.putIfAbsent((int) ((ItemUseAddWeaponExp) action).getExp(), (int) id);
|
|
return;
|
|
}
|
|
}
|
|
});
|
|
temp.forEach((exp, id) -> weaponRefundMaterials.putIfAbsent((int) id, (int) exp));
|
|
}
|
|
|
|
public InventorySystem(GameServer server) {
|
|
super(server);
|
|
}
|
|
|
|
public void lockEquip(Player player, long targetEquipGuid, boolean isLocked) {
|
|
GameItem equip = player.getInventory().getItemByGuid(targetEquipGuid);
|
|
|
|
if (equip == null || !equip.getItemData().isEquip()) {
|
|
return;
|
|
}
|
|
|
|
equip.setLocked(isLocked);
|
|
equip.save();
|
|
|
|
player.sendPacket(new PacketStoreItemChangeNotify(equip));
|
|
player.sendPacket(new PacketSetEquipLockStateRsp(equip));
|
|
}
|
|
|
|
public void upgradeRelic(Player player, long targetGuid, List<Long> foodRelicList, List<ItemParam> list) {
|
|
GameItem relic = player.getInventory().getItemByGuid(targetGuid);
|
|
|
|
if (relic == null || relic.getItemType() != ItemType.ITEM_RELIQUARY) {
|
|
return;
|
|
}
|
|
|
|
int moraCost = 0;
|
|
int expGain = 0;
|
|
|
|
List<GameItem> foodRelics = new ArrayList<GameItem>();
|
|
for (long guid : foodRelicList) {
|
|
// Add to delete queue
|
|
GameItem food = player.getInventory().getItemByGuid(guid);
|
|
if (food == null || !food.isDestroyable()) {
|
|
continue;
|
|
}
|
|
// Calculate mora cost
|
|
int exp = food.getItemData().getBaseConvExp();
|
|
moraCost += exp;
|
|
expGain += exp;
|
|
// Feeding artifact with exp already
|
|
if (food.getTotalExp() > 0) {
|
|
expGain += (food.getTotalExp() * 4) / 5;
|
|
}
|
|
foodRelics.add(food);
|
|
}
|
|
List<ItemParamData> payList = new ArrayList<ItemParamData>();
|
|
for (ItemParam itemParam : list) {
|
|
int amount = itemParam.getCount(); // Previously this capped to inventory amount, but rejecting the payment makes more sense for an invalid order
|
|
int gain = 0;
|
|
var data = GameData.getItemDataMap().get(itemParam.getItemId());
|
|
if (data != null) {
|
|
var actions = data.getItemUseActions();
|
|
if (actions != null) {
|
|
for (var action : actions) {
|
|
if (action.getItemUseOp() == ItemUseOp.ITEM_USE_ADD_RELIQUARY_EXP) {
|
|
gain += ((ItemUseAddReliquaryExp) action).getExp();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
gain *= amount;
|
|
expGain += gain;
|
|
moraCost += gain;
|
|
payList.add(new ItemParamData(itemParam.getItemId(), itemParam.getCount()));
|
|
}
|
|
|
|
// Make sure exp gain is valid
|
|
if (expGain <= 0) {
|
|
return;
|
|
}
|
|
|
|
// Confirm payment of materials and mora (assume food relics are payable afterwards)
|
|
payList.add(new ItemParamData(202, moraCost));
|
|
if (!player.getInventory().payItems(payList)) {
|
|
return;
|
|
}
|
|
|
|
// Consume food relics
|
|
player.getInventory().removeItems(foodRelics);
|
|
|
|
// Implement random rate boost
|
|
int rate = 1;
|
|
int boost = Utils.randomRange(1, 100);
|
|
if (boost == 100) {
|
|
rate = 5;
|
|
} else if (boost <= 9) {
|
|
rate = 2;
|
|
}
|
|
expGain *= rate;
|
|
|
|
// Now we upgrade
|
|
int level = relic.getLevel();
|
|
int oldLevel = level;
|
|
int exp = relic.getExp();
|
|
int totalExp = relic.getTotalExp();
|
|
int reqExp = GameData.getRelicExpRequired(relic.getItemData().getRankLevel(), level);
|
|
int upgrades = 0;
|
|
List<Integer> oldAppendPropIdList = new ArrayList<>(relic.getAppendPropIdList());
|
|
|
|
while (expGain > 0 && reqExp > 0 && level < relic.getItemData().getMaxLevel()) {
|
|
// Do calculations
|
|
int toGain = Math.min(expGain, reqExp - exp);
|
|
exp += toGain;
|
|
totalExp += toGain;
|
|
expGain -= toGain;
|
|
// Level up
|
|
if (exp >= reqExp) {
|
|
// Exp
|
|
exp = 0;
|
|
level += 1;
|
|
// On relic levelup
|
|
if (relic.getItemData().getAddPropLevelSet() != null && relic.getItemData().getAddPropLevelSet().contains(level)) {
|
|
upgrades += 1;
|
|
}
|
|
// Set req exp
|
|
reqExp = GameData.getRelicExpRequired(relic.getItemData().getRankLevel(), level);
|
|
}
|
|
}
|
|
|
|
relic.addAppendProps(upgrades);
|
|
|
|
// Save
|
|
relic.setLevel(level);
|
|
relic.setExp(exp);
|
|
relic.setTotalExp(totalExp);
|
|
relic.save();
|
|
|
|
// Avatar
|
|
if (oldLevel != level) {
|
|
Avatar avatar = relic.getEquipCharacter() > 0 ? player.getAvatars().getAvatarById(relic.getEquipCharacter()) : null;
|
|
if (avatar != null) {
|
|
avatar.recalcStats();
|
|
}
|
|
}
|
|
|
|
// Packet
|
|
player.sendPacket(new PacketStoreItemChangeNotify(relic));
|
|
player.sendPacket(new PacketReliquaryUpgradeRsp(relic, rate, oldLevel, oldAppendPropIdList));
|
|
}
|
|
|
|
public List<ItemParam> calcWeaponUpgradeReturnItems(Player player, long targetGuid, List<Long> foodWeaponGuidList, List<ItemParam> itemParamList) {
|
|
GameItem weapon = player.getInventory().getItemByGuid(targetGuid);
|
|
|
|
// Sanity checks
|
|
if (weapon == null || weapon.getItemType() != ItemType.ITEM_WEAPON) {
|
|
return null;
|
|
}
|
|
|
|
WeaponPromoteData promoteData = GameData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), weapon.getPromoteLevel());
|
|
if (promoteData == null) {
|
|
return null;
|
|
}
|
|
|
|
// Get exp gain
|
|
int expGain = foodWeaponGuidList.stream()
|
|
.map(player.getInventory()::getItemByGuid)
|
|
.filter(Objects::nonNull)
|
|
.mapToInt(food -> food.getItemData().getWeaponBaseExp() + ((food.getTotalExp() * 4) / 5))
|
|
.sum();
|
|
// Stream::ofNullable version
|
|
expGain += itemParamList.stream()
|
|
.mapToInt(param -> {
|
|
int exp = Stream.ofNullable(GameData.getItemDataMap().get(param.getItemId()))
|
|
.map(ItemData::getItemUseActions)
|
|
.filter(Objects::nonNull)
|
|
.flatMap(Collection::stream)
|
|
.filter(action -> action.getItemUseOp() == ItemUseOp.ITEM_USE_ADD_WEAPON_EXP)
|
|
.mapToInt(action -> ((ItemUseAddWeaponExp) action).getExp())
|
|
.sum();
|
|
return exp * param.getCount();
|
|
})
|
|
.sum();
|
|
// Optional::ofNullable version
|
|
// expGain += itemParamList.stream()
|
|
// .mapToInt(param -> {
|
|
// int exp = Optional.ofNullable(GameData.getItemDataMap().get(param.getItemId()))
|
|
// .map(ItemData::getItemUseActions)
|
|
// .map(actions -> {
|
|
// return actions.stream()
|
|
// .filter(action -> action.getItemUseOp() == ItemUseOp.ITEM_USE_ADD_WEAPON_EXP)
|
|
// .mapToInt(action -> ((ItemUseAddWeaponExp) action).getExp())
|
|
// .sum();
|
|
// })
|
|
// .orElse(0);
|
|
// return exp * param.getCount();
|
|
// })
|
|
// .sum();
|
|
|
|
// Try
|
|
int maxLevel = promoteData.getUnlockMaxLevel();
|
|
int level = weapon.getLevel();
|
|
int exp = weapon.getExp();
|
|
int reqExp = GameData.getWeaponExpRequired(weapon.getItemData().getRankLevel(), level);
|
|
|
|
while (expGain > 0 && reqExp > 0 && level < maxLevel) {
|
|
// Do calculations
|
|
int toGain = Math.min(expGain, reqExp - exp);
|
|
exp += toGain;
|
|
expGain -= toGain;
|
|
// Level up
|
|
if (exp >= reqExp) {
|
|
// Exp
|
|
exp = 0;
|
|
level += 1;
|
|
// Set req exp
|
|
reqExp = GameData.getWeaponExpRequired(weapon.getItemData().getRankLevel(), level);
|
|
}
|
|
}
|
|
|
|
return getLeftoverOres(expGain);
|
|
}
|
|
|
|
|
|
public void upgradeWeapon(Player player, long targetGuid, List<Long> foodWeaponGuidList, List<ItemParam> itemParamList) {
|
|
GameItem weapon = player.getInventory().getItemByGuid(targetGuid);
|
|
|
|
// Sanity checks
|
|
if (weapon == null || weapon.getItemType() != ItemType.ITEM_WEAPON) {
|
|
return;
|
|
}
|
|
|
|
WeaponPromoteData promoteData = GameData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), weapon.getPromoteLevel());
|
|
if (promoteData == null) {
|
|
return;
|
|
}
|
|
|
|
// Get exp gain
|
|
int expGain = 0, expGainFree = 0;
|
|
List<GameItem> foodWeapons = new ArrayList<GameItem>();
|
|
for (long guid : foodWeaponGuidList) {
|
|
GameItem food = player.getInventory().getItemByGuid(guid);
|
|
if (food == null || !food.isDestroyable()) {
|
|
continue;
|
|
}
|
|
expGain += food.getItemData().getWeaponBaseExp();
|
|
if (food.getTotalExp() > 0) {
|
|
expGainFree += (food.getTotalExp() * 4) / 5; // No tax :D
|
|
}
|
|
foodWeapons.add(food);
|
|
}
|
|
List<ItemParamData> payList = new ArrayList<ItemParamData>();
|
|
for (ItemParam param : itemParamList) {
|
|
int amount = param.getCount(); // Previously this capped to inventory amount, but rejecting the payment makes more sense for an invalid order
|
|
|
|
var data = GameData.getItemDataMap().get(param.getItemId());
|
|
if (data != null) {
|
|
var actions = data.getItemUseActions();
|
|
if (actions != null) {
|
|
for (var action : actions) {
|
|
if (action.getItemUseOp() == ItemUseOp.ITEM_USE_ADD_WEAPON_EXP) {
|
|
expGain += ((ItemUseAddWeaponExp) action).getExp() * amount;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
payList.add(new ItemParamData(param.getItemId(), amount));
|
|
}
|
|
|
|
// Make sure exp gain is valid
|
|
int moraCost = expGain / 10;
|
|
expGain += expGainFree;
|
|
if (expGain <= 0) {
|
|
return;
|
|
}
|
|
|
|
// Confirm payment of materials and mora (assume food weapons are payable afterwards)
|
|
payList.add(new ItemParamData(202, moraCost));
|
|
if (!player.getInventory().payItems(payList)) {
|
|
return;
|
|
}
|
|
player.getInventory().removeItems(foodWeapons);
|
|
|
|
// Level up
|
|
int maxLevel = promoteData.getUnlockMaxLevel();
|
|
int level = weapon.getLevel();
|
|
int oldLevel = level;
|
|
int exp = weapon.getExp();
|
|
int totalExp = weapon.getTotalExp();
|
|
int reqExp = GameData.getWeaponExpRequired(weapon.getItemData().getRankLevel(), level);
|
|
|
|
while (expGain > 0 && reqExp > 0 && level < maxLevel) {
|
|
// Do calculations
|
|
int toGain = Math.min(expGain, reqExp - exp);
|
|
exp += toGain;
|
|
totalExp += toGain;
|
|
expGain -= toGain;
|
|
// Level up
|
|
if (exp >= reqExp) {
|
|
// Exp
|
|
exp = 0;
|
|
level += 1;
|
|
// Set req exp
|
|
reqExp = GameData.getWeaponExpRequired(weapon.getItemData().getRankLevel(), level);
|
|
}
|
|
}
|
|
|
|
List<ItemParam> leftovers = getLeftoverOres(expGain);
|
|
player.getInventory().addItemParams(leftovers);
|
|
|
|
weapon.setLevel(level);
|
|
weapon.setExp(exp);
|
|
weapon.setTotalExp(totalExp);
|
|
weapon.save();
|
|
|
|
// Avatar
|
|
if (oldLevel != level) {
|
|
Avatar avatar = weapon.getEquipCharacter() > 0 ? player.getAvatars().getAvatarById(weapon.getEquipCharacter()) : null;
|
|
if (avatar != null) {
|
|
avatar.recalcStats();
|
|
}
|
|
}
|
|
|
|
// Packets
|
|
player.sendPacket(new PacketStoreItemChangeNotify(weapon));
|
|
player.sendPacket(new PacketWeaponUpgradeRsp(weapon, oldLevel, leftovers));
|
|
}
|
|
|
|
private List<ItemParam> getLeftoverOres(int leftover) {
|
|
List<ItemParam> leftoverOreList = new ArrayList<>(3);
|
|
|
|
for (var e : weaponRefundMaterials.int2IntEntrySet()) {
|
|
int exp = e.getIntValue();
|
|
int ores = leftover / exp;
|
|
leftover = leftover % exp;
|
|
|
|
if (ores > 0)
|
|
leftoverOreList.add(ItemParam.newBuilder().setItemId(e.getIntKey()).setCount(ores).build());
|
|
}
|
|
|
|
return leftoverOreList;
|
|
}
|
|
|
|
public void refineWeapon(Player player, long targetGuid, long feedGuid) {
|
|
GameItem weapon = player.getInventory().getItemByGuid(targetGuid);
|
|
GameItem feed = player.getInventory().getItemByGuid(feedGuid);
|
|
|
|
// Sanity checks
|
|
if (weapon == null || feed == null || !feed.isDestroyable()) {
|
|
return;
|
|
}
|
|
|
|
if (weapon.getItemData().getAwakenMaterial() == 0) {
|
|
if (weapon.getItemType() != ItemType.ITEM_WEAPON || weapon.getItemId() != feed.getItemId()) {
|
|
return;
|
|
}
|
|
} else {
|
|
if (weapon.getItemType() != ItemType.ITEM_WEAPON || weapon.getItemData().getAwakenMaterial() != feed.getItemId()) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (weapon.getRefinement() >= 4 || weapon.getAffixes() == null || weapon.getAffixes().size() == 0) {
|
|
return;
|
|
}
|
|
|
|
// Calculate
|
|
int oldRefineLevel = weapon.getRefinement();
|
|
int targetRefineLevel = Math.min(oldRefineLevel + feed.getRefinement() + 1, 4);
|
|
int moraCost = 0;
|
|
|
|
try {
|
|
moraCost = weapon.getItemData().getAwakenCosts()[weapon.getRefinement()];
|
|
} catch (Exception e) {
|
|
return;
|
|
}
|
|
|
|
// Mora check
|
|
if (player.getMora() >= moraCost) {
|
|
player.setMora(player.getMora() - moraCost);
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
// Consume weapon
|
|
player.getInventory().removeItem(feed, 1);
|
|
|
|
// Get
|
|
weapon.setRefinement(targetRefineLevel);
|
|
weapon.save();
|
|
|
|
// Avatar
|
|
Avatar avatar = weapon.getEquipCharacter() > 0 ? player.getAvatars().getAvatarById(weapon.getEquipCharacter()) : null;
|
|
if (avatar != null) {
|
|
avatar.recalcStats();
|
|
}
|
|
|
|
// Packets
|
|
player.sendPacket(new PacketStoreItemChangeNotify(weapon));
|
|
player.sendPacket(new PacketWeaponAwakenRsp(avatar, weapon, feed, oldRefineLevel));
|
|
}
|
|
|
|
public void promoteWeapon(Player player, long targetGuid) {
|
|
GameItem weapon = player.getInventory().getItemByGuid(targetGuid);
|
|
|
|
if (weapon == null || weapon.getItemType() != ItemType.ITEM_WEAPON) {
|
|
return;
|
|
}
|
|
|
|
int nextPromoteLevel = weapon.getPromoteLevel() + 1;
|
|
WeaponPromoteData currentPromoteData = GameData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), weapon.getPromoteLevel());
|
|
WeaponPromoteData nextPromoteData = GameData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), nextPromoteLevel);
|
|
if (currentPromoteData == null || nextPromoteData == null) {
|
|
return;
|
|
}
|
|
|
|
// Level check
|
|
if (weapon.getLevel() != currentPromoteData.getUnlockMaxLevel()) {
|
|
return;
|
|
}
|
|
|
|
// Pay materials and mora if possible
|
|
ItemParamData[] costs = nextPromoteData.getCostItems(); // Can this be null?
|
|
if (nextPromoteData.getCoinCost() > 0) {
|
|
costs = Arrays.copyOf(costs, costs.length + 1);
|
|
costs[costs.length-1] = new ItemParamData(202, nextPromoteData.getCoinCost());
|
|
}
|
|
if (!player.getInventory().payItems(costs)) {
|
|
return;
|
|
}
|
|
|
|
int oldPromoteLevel = weapon.getPromoteLevel();
|
|
weapon.setPromoteLevel(nextPromoteLevel);
|
|
weapon.save();
|
|
|
|
// Avatar
|
|
Avatar avatar = weapon.getEquipCharacter() > 0 ? player.getAvatars().getAvatarById(weapon.getEquipCharacter()) : null;
|
|
if (avatar != null) {
|
|
avatar.recalcStats();
|
|
}
|
|
|
|
// Packets
|
|
player.sendPacket(new PacketStoreItemChangeNotify(weapon));
|
|
player.sendPacket(new PacketWeaponPromoteRsp(weapon, oldPromoteLevel));
|
|
}
|
|
|
|
public void promoteAvatar(Player player, long guid) {
|
|
Avatar avatar = player.getAvatars().getAvatarByGuid(guid);
|
|
|
|
// Sanity checks
|
|
if (avatar == null) {
|
|
return;
|
|
}
|
|
|
|
int nextPromoteLevel = avatar.getPromoteLevel() + 1;
|
|
AvatarPromoteData currentPromoteData = GameData.getAvatarPromoteData(avatar.getAvatarData().getAvatarPromoteId(), avatar.getPromoteLevel());
|
|
AvatarPromoteData nextPromoteData = GameData.getAvatarPromoteData(avatar.getAvatarData().getAvatarPromoteId(), nextPromoteLevel);
|
|
if (currentPromoteData == null || nextPromoteData == null) {
|
|
return;
|
|
}
|
|
|
|
// Level check
|
|
if (avatar.getLevel() != currentPromoteData.getUnlockMaxLevel()) {
|
|
return;
|
|
}
|
|
|
|
// Pay materials and mora if possible
|
|
ItemParamData[] costs = nextPromoteData.getCostItems(); // Can this be null?
|
|
if (nextPromoteData.getCoinCost() > 0) {
|
|
costs = Arrays.copyOf(costs, costs.length + 1);
|
|
costs[costs.length-1] = new ItemParamData(202, nextPromoteData.getCoinCost());
|
|
}
|
|
if (!player.getInventory().payItems(costs)) {
|
|
return;
|
|
}
|
|
|
|
// Update promote level
|
|
avatar.setPromoteLevel(nextPromoteLevel);
|
|
|
|
// Update proud skills
|
|
Optional.ofNullable(GameData.getAvatarSkillDepotDataMap().get(avatar.getSkillDepotId()))
|
|
.map(AvatarSkillDepotData::getInherentProudSkillOpens)
|
|
.ifPresent(d -> d.stream()
|
|
.filter(openData -> openData.getProudSkillGroupId() > 0)
|
|
.filter(openData -> openData.getNeedAvatarPromoteLevel() == avatar.getPromoteLevel())
|
|
.mapToInt(openData -> (openData.getProudSkillGroupId() * 100) + 1)
|
|
.filter(GameData.getProudSkillDataMap()::containsKey)
|
|
.forEach(proudSkillId -> {
|
|
avatar.getProudSkillList().add(proudSkillId);
|
|
player.sendPacket(new PacketProudSkillChangeNotify(avatar));
|
|
})
|
|
);
|
|
|
|
// Packets
|
|
player.sendPacket(new PacketAvatarPropNotify(avatar));
|
|
player.sendPacket(new PacketAvatarPromoteRsp(avatar));
|
|
|
|
// TODO Send entity prop update packet to world
|
|
avatar.recalcStats(true);
|
|
avatar.save();
|
|
}
|
|
|
|
public void upgradeAvatar(Player player, long guid, int itemId, int count) {
|
|
Avatar avatar = player.getAvatars().getAvatarByGuid(guid);
|
|
|
|
// Sanity checks
|
|
if (avatar == null) {
|
|
return;
|
|
}
|
|
|
|
AvatarPromoteData promoteData = GameData.getAvatarPromoteData(avatar.getAvatarData().getAvatarPromoteId(), avatar.getPromoteLevel());
|
|
if (promoteData == null) {
|
|
return;
|
|
}
|
|
|
|
// Calc exp
|
|
int expGain = 0;
|
|
|
|
var data = GameData.getItemDataMap().get(itemId);
|
|
if (data != null) {
|
|
var actions = data.getItemUseActions();
|
|
if (actions != null) {
|
|
for (var action : actions) {
|
|
if (action.getItemUseOp() == ItemUseOp.ITEM_USE_ADD_EXP) {
|
|
expGain += ((ItemUseAddExp) action).getExp() * count;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sanity check
|
|
if (expGain <= 0) {
|
|
return;
|
|
}
|
|
|
|
// Payment check
|
|
int moraCost = expGain / 5;
|
|
ItemParamData[] costItems = new ItemParamData[] {new ItemParamData(itemId, count), new ItemParamData(202, moraCost)};
|
|
if (!player.getInventory().payItems(costItems)) {
|
|
return;
|
|
}
|
|
|
|
// Level up
|
|
upgradeAvatar(player, avatar, promoteData, expGain);
|
|
}
|
|
|
|
public void upgradeAvatar(Player player, Avatar avatar, int expGain) {
|
|
AvatarPromoteData promoteData = GameData.getAvatarPromoteData(avatar.getAvatarData().getAvatarPromoteId(), avatar.getPromoteLevel());
|
|
if (promoteData == null) {
|
|
return;
|
|
}
|
|
|
|
upgradeAvatar(player, avatar, promoteData, expGain);
|
|
}
|
|
|
|
public void upgradeAvatar(Player player, Avatar avatar, AvatarPromoteData promoteData, int expGain) {
|
|
int maxLevel = promoteData.getUnlockMaxLevel();
|
|
int level = avatar.getLevel();
|
|
int oldLevel = level;
|
|
int exp = avatar.getExp();
|
|
int reqExp = GameData.getAvatarLevelExpRequired(level);
|
|
|
|
while (expGain > 0 && reqExp > 0 && level < maxLevel) {
|
|
// Do calculations
|
|
int toGain = Math.min(expGain, reqExp - exp);
|
|
exp += toGain;
|
|
expGain -= toGain;
|
|
// Level up
|
|
if (exp >= reqExp) {
|
|
// Exp
|
|
exp = 0;
|
|
level += 1;
|
|
// Set req exp
|
|
reqExp = GameData.getAvatarLevelExpRequired(level);
|
|
}
|
|
}
|
|
|
|
// Old map for packet
|
|
Map<Integer, Float> oldPropMap = avatar.getFightProperties();
|
|
if (oldLevel != level) {
|
|
// Deep copy if level has changed
|
|
oldPropMap = new Int2FloatArrayMap(avatar.getFightProperties());
|
|
}
|
|
|
|
// Done
|
|
avatar.setLevel(level);
|
|
avatar.setExp(exp);
|
|
avatar.recalcStats();
|
|
avatar.save();
|
|
|
|
// TODO Send entity prop update packet to world
|
|
|
|
// Packets
|
|
player.sendPacket(new PacketAvatarPropNotify(avatar));
|
|
player.sendPacket(new PacketAvatarUpgradeRsp(avatar, oldLevel, oldPropMap));
|
|
}
|
|
|
|
public void 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));
|
|
}
|
|
|
|
@Deprecated(forRemoval = true)
|
|
public void upgradeAvatarSkill(Player player, long guid, int skillId) {
|
|
// Sanity checks
|
|
Avatar avatar = player.getAvatars().getAvatarByGuid(guid);
|
|
if (avatar == null) return;
|
|
|
|
avatar.upgradeSkill(skillId);
|
|
}
|
|
|
|
@Deprecated(forRemoval = true)
|
|
public void unlockAvatarConstellation(Player player, long guid) {
|
|
// Sanity check
|
|
Avatar avatar = player.getAvatars().getAvatarByGuid(guid);
|
|
if (avatar == null) return;
|
|
|
|
avatar.unlockConstellation();
|
|
}
|
|
|
|
public void destroyMaterial(Player player, List<MaterialInfo> list) {
|
|
// Return materials
|
|
val returnMaterialMap = new Int2IntOpenHashMap();
|
|
val inventory = player.getInventory();
|
|
|
|
for (MaterialInfo info : list) {
|
|
// Sanity check
|
|
if (info.getCount() <= 0) {
|
|
continue;
|
|
}
|
|
|
|
GameItem item = inventory.getItemByGuid(info.getGuid());
|
|
if (item == null || !item.isDestroyable()) {
|
|
continue;
|
|
}
|
|
|
|
// Remove
|
|
int removeAmount = Math.min(info.getCount(), item.getCount());
|
|
inventory.removeItem(item, removeAmount);
|
|
|
|
// Delete material return items
|
|
val data = item.getItemData();
|
|
if (data.getDestroyReturnMaterial().length > 0) {
|
|
for (int i = 0; i < data.getDestroyReturnMaterial().length; i++) {
|
|
returnMaterialMap.addTo(data.getDestroyReturnMaterial()[i], data.getDestroyReturnMaterialCount()[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Give back items
|
|
if (returnMaterialMap.size() > 0) {
|
|
returnMaterialMap.forEach((id, count) -> inventory.addItem(new GameItem(id, count)));
|
|
}
|
|
|
|
// Packets
|
|
player.sendPacket(new PacketDestroyMaterialRsp(returnMaterialMap));
|
|
}
|
|
|
|
// Uses an item from the player's inventory.
|
|
public synchronized GameItem useItem(Player player, long targetGuid, long itemGuid, int count, int optionId, boolean isEnterMpDungeonTeam) {
|
|
Grasscutter.getLogger().debug("Attempting to use item from inventory");
|
|
Avatar target = player.getAvatars().getAvatarByGuid(targetGuid);
|
|
GameItem item = player.getInventory().getItemByGuid(itemGuid);
|
|
if (item == null) return null;
|
|
if (item.getCount() < count) return null;
|
|
ItemData itemData = item.getItemData();
|
|
if (itemData == null) return null;
|
|
|
|
var params = new UseItemParams(player, itemData.getUseTarget(), target, count, optionId, isEnterMpDungeonTeam);
|
|
if (useItemDirect(itemData, params)) {
|
|
player.getInventory().removeItem(item, count);
|
|
var actions = itemData.getItemUseActions();
|
|
if (actions != null)
|
|
actions.forEach(use -> use.postUseItem(params));
|
|
Grasscutter.getLogger().debug("Item use succeeded!");
|
|
return item;
|
|
} else {
|
|
Grasscutter.getLogger().debug("Item use failed!");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Uses an item without checking the player's inventory.
|
|
public synchronized boolean useItemDirect(ItemData itemData, UseItemParams params) {
|
|
if (itemData == null) return false;
|
|
|
|
// Ensure targeting conditions are satisfied
|
|
val target = Optional.ofNullable(params.targetAvatar);
|
|
switch (params.itemUseTarget) {
|
|
case ITEM_USE_TARGET_NONE -> {}
|
|
case ITEM_USE_TARGET_SPECIFY_AVATAR -> {
|
|
if (target.isEmpty()) return false;
|
|
}
|
|
case ITEM_USE_TARGET_SPECIFY_ALIVE_AVATAR -> {
|
|
if (target.map(a -> !a.getAsEntity().isAlive()).orElse(true)) return false;
|
|
}
|
|
case ITEM_USE_TARGET_SPECIFY_DEAD_AVATAR -> {
|
|
if (target.map(a -> a.getAsEntity().isAlive()).orElse(true)) return false;
|
|
}
|
|
case ITEM_USE_TARGET_CUR_AVATAR -> {}
|
|
case ITEM_USE_TARGET_CUR_TEAM -> {}
|
|
}
|
|
|
|
int[] satiationParams = itemData.getSatiationParams();
|
|
if (satiationParams != null && satiationParams.length > 0 && target.isPresent()) {
|
|
// Invoke and call player use food event.
|
|
var event = new PlayerUseFoodEvent(params.player, itemData, params.targetAvatar.getAsEntity()); event.call();
|
|
if (event.isCanceled()) return false;
|
|
|
|
float satiationIncrease = satiationParams[0] + ((float)satiationParams[1])/params.targetAvatar.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
|
|
if (!params.targetAvatar.addSatiation(satiationIncrease)) { // Make sure avatar can eat
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Use
|
|
var actions = itemData.getItemUseActions();
|
|
Grasscutter.getLogger().debug("Using - actions - {}", actions);
|
|
if (actions == null) return true; // Maybe returning false would be more appropriate?
|
|
return actions.stream()
|
|
.map(use -> use.useItem(params))
|
|
.reduce(false, (a,b) -> a || b); // Don't short-circuit!!!
|
|
}
|
|
|
|
public static synchronized int checkPlayerAvatarConstellationLevel(Player player, int id) {
|
|
// Try to accept itemId OR avatarId
|
|
int avatarId = 0;
|
|
if (GameData.getAvatarDataMap().containsKey(id)) {
|
|
avatarId = id;
|
|
} else {
|
|
avatarId = Optional.ofNullable(GameData.getItemDataMap().get(id))
|
|
.map(itemData -> itemData.getItemUseActions())
|
|
.flatMap(actions ->
|
|
actions.stream()
|
|
.filter(action -> action.getItemUseOp() == ItemUseOp.ITEM_USE_GAIN_AVATAR)
|
|
.map(action -> ((emu.grasscutter.game.props.ItemUseAction.ItemUseGainAvatar) action).getI())
|
|
.findFirst())
|
|
.orElse(0);
|
|
}
|
|
|
|
if (avatarId == 0)
|
|
return -2; // Not an Avatar
|
|
|
|
Avatar avatar = player.getAvatars().getAvatarById(avatarId);
|
|
if (avatar == null)
|
|
return -1; // Doesn't have
|
|
|
|
// Constellation
|
|
int constLevel = avatar.getCoreProudSkillLevel();
|
|
val avatarData = avatar.getSkillDepot();
|
|
if (avatarData == null) {
|
|
Grasscutter.getLogger().error("Attempted to check constellation level for UID"+player.getUid()+"'s avatar "+avatarId+" but avatar has no skillDepot!");
|
|
return 0;
|
|
}
|
|
int constItemId = avatarData.getTalentCostItemId();
|
|
GameItem constItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(constItemId);
|
|
constLevel += Optional.ofNullable(constItem).map(GameItem::getCount).orElse(0);
|
|
return constLevel;
|
|
}
|
|
}
|