diff --git a/src/main/java/emu/grasscutter/DebugConstants.java b/src/main/java/emu/grasscutter/DebugConstants.java new file mode 100644 index 000000000..647e58bf7 --- /dev/null +++ b/src/main/java/emu/grasscutter/DebugConstants.java @@ -0,0 +1,11 @@ +package emu.grasscutter; + +public final class DebugConstants { + public static boolean LOG_ABILITIES = false; + public static boolean LOG_LUA_SCRIPTS = false; + public static boolean LOG_QUEST_START = false; + + private DebugConstants() { + // Prevent instantiation. + } +} diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index c47d0541c..347fd70d5 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -4,58 +4,33 @@ import emu.grasscutter.Grasscutter; import emu.grasscutter.data.binout.*; import emu.grasscutter.data.binout.config.*; import emu.grasscutter.data.binout.routes.Route; -import emu.grasscutter.data.custom.TrialAvatarActivityCustomData; -import emu.grasscutter.data.custom.TrialAvatarCustomData; +import emu.grasscutter.data.custom.*; import emu.grasscutter.data.excels.*; -import emu.grasscutter.data.excels.achievement.AchievementData; -import emu.grasscutter.data.excels.achievement.AchievementGoalData; -import emu.grasscutter.data.excels.activity.ActivityCondExcelConfigData; -import emu.grasscutter.data.excels.activity.ActivityData; -import emu.grasscutter.data.excels.activity.ActivityShopData; -import emu.grasscutter.data.excels.activity.ActivityWatcherData; +import emu.grasscutter.data.excels.achievement.*; +import emu.grasscutter.data.excels.activity.*; import emu.grasscutter.data.excels.avatar.*; import emu.grasscutter.data.excels.codex.*; import emu.grasscutter.data.excels.dungeon.*; -import emu.grasscutter.data.excels.monster.MonsterCurveData; -import emu.grasscutter.data.excels.monster.MonsterData; -import emu.grasscutter.data.excels.monster.MonsterDescribeData; -import emu.grasscutter.data.excels.monster.MonsterSpecialNameData; -import emu.grasscutter.data.excels.quest.QuestData; -import emu.grasscutter.data.excels.quest.QuestGlobalVarData; -import emu.grasscutter.data.excels.reliquary.ReliquaryAffixData; -import emu.grasscutter.data.excels.reliquary.ReliquaryLevelData; -import emu.grasscutter.data.excels.reliquary.ReliquaryMainPropData; -import emu.grasscutter.data.excels.reliquary.ReliquarySetData; -import emu.grasscutter.data.excels.tower.TowerFloorData; -import emu.grasscutter.data.excels.tower.TowerLevelData; -import emu.grasscutter.data.excels.tower.TowerScheduleData; +import emu.grasscutter.data.excels.giving.*; +import emu.grasscutter.data.excels.monster.*; +import emu.grasscutter.data.excels.quest.*; +import emu.grasscutter.data.excels.reliquary.*; +import emu.grasscutter.data.excels.tower.*; import emu.grasscutter.data.excels.trial.*; -import emu.grasscutter.data.excels.weapon.WeaponCurveData; -import emu.grasscutter.data.excels.weapon.WeaponLevelData; -import emu.grasscutter.data.excels.weapon.WeaponPromoteData; -import emu.grasscutter.data.excels.world.WeatherData; -import emu.grasscutter.data.excels.world.WorldAreaData; -import emu.grasscutter.data.excels.world.WorldLevelData; -import emu.grasscutter.data.server.ActivityCondGroup; -import emu.grasscutter.data.server.DropSubfieldMapping; -import emu.grasscutter.data.server.DropTableExcelConfigData; -import emu.grasscutter.data.server.GadgetMapping; -import emu.grasscutter.data.server.MonsterMapping; -import emu.grasscutter.data.server.SubfieldMapping; +import emu.grasscutter.data.excels.weapon.*; +import emu.grasscutter.data.excels.world.*; +import emu.grasscutter.data.server.*; import emu.grasscutter.game.dungeons.DungeonDropEntry; -import emu.grasscutter.game.quest.QuestEncryptionKey; -import emu.grasscutter.game.quest.RewindData; -import emu.grasscutter.game.quest.TeleportData; +import emu.grasscutter.game.quest.*; import emu.grasscutter.game.quest.enums.QuestCond; import emu.grasscutter.game.world.GroupReplacementData; import emu.grasscutter.utils.Utils; import it.unimi.dsi.fastutil.ints.*; +import lombok.*; + +import javax.annotation.Nullable; import java.lang.reflect.Field; import java.util.*; -import javax.annotation.Nullable; -import lombok.Getter; -import lombok.Setter; -import lombok.val; @SuppressWarnings({"unused", "MismatchedQueryAndUpdateOfCollection"}) public final class GameData { @@ -165,6 +140,10 @@ public final class GameData { private static final Int2ObjectMap avatarTalentDataMap = new Int2ObjectOpenHashMap<>(); + @Getter + private static final Int2ObjectMap bargainDataMap + = new Int2ObjectOpenHashMap<>(); + @Getter private static final Int2ObjectMap battlePassMissionDataMap = new Int2ObjectOpenHashMap<>(); @@ -266,6 +245,12 @@ public final class GameData { @Getter private static final Int2ObjectMap gatherDataMap = new Int2ObjectOpenHashMap<>(); + @Getter + private static final Int2ObjectMap givingDataMap = new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap givingGroupDataMap = new Int2ObjectOpenHashMap<>(); + @Getter @Deprecated // This is to prevent people from using this map. This is for the resource loader // only! diff --git a/src/main/java/emu/grasscutter/data/excels/BargainData.java b/src/main/java/emu/grasscutter/data/excels/BargainData.java new file mode 100644 index 000000000..b51912951 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/excels/BargainData.java @@ -0,0 +1,49 @@ +package emu.grasscutter.data.excels; + +import emu.grasscutter.data.*; +import lombok.Getter; + +import java.util.List; + +@Getter +@ResourceType(name = "BargainExcelConfigData.json") +public final class BargainData extends GameResource { + @Getter private int id; + private int questId; + + private List dialogId; + + /** + * This is a list of 2 integers. + * The first integer is the minimum value of the bargain. + * The second integer is the maximum value of the bargain. + */ + private List expectedValue; + private int space; + + private List successTalkId; + private int failTalkId; + private int moodNpcId; + + /** + * This is a list of 2 integers. + * The first integer is the minimum value of the mood. + * The second integer is the maximum value of the mood. + */ + private List randomMood; + private int moodAlertLimit; + private int moodLowLimit; + private int singleFailMoodDeduction; + + private long moodLowLimitTextTextMapHash; + private long titleTextTextMapHash; + private long affordTextTextMapHash; + private long storageTextTextMapHash; + private long moodHintTextTextMapHash; + private long moodDescTextTextMapHash; + + private List singleFailTalkId; + + private boolean deleteItem; + private int itemId; +} diff --git a/src/main/java/emu/grasscutter/data/excels/giving/GivingData.java b/src/main/java/emu/grasscutter/data/excels/giving/GivingData.java new file mode 100644 index 000000000..560be60e6 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/excels/giving/GivingData.java @@ -0,0 +1,46 @@ +package emu.grasscutter.data.excels.giving; + +import com.google.gson.annotations.SerializedName; +import emu.grasscutter.data.*; +import emu.grasscutter.data.common.ItemParamData; +import emu.grasscutter.game.inventory.BagTab; +import lombok.*; + +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = false) +@ResourceType(name = "GivingExcelConfigData.json") +public final class GivingData extends GameResource { + @SerializedName(value = "id", alternate = "Id") + private int id; + private int talkId; + private int mistakeTalkId; + + private BagTab tab; + private GiveMethod givingMethod; + + private List exactItems; + private int exactFinishTalkId; + + private List givingGroupIds; + private int givingGroupCount; + + private boolean isRemoveItem; + private GiveType giveType; + + public enum GiveMethod { + GIVING_METHOD_NONE, + /** All items are required to succeed. */ + GIVING_METHOD_EXACT, + GIVING_METHOD_GROUP, + /** One in the group is required to succeed. */ + GIVING_METHOD_VAGUE_GROUP, + GIVING_METHOD_ANY_NO_FINISH + } + + public enum GiveType { + @SerializedName("GIVING_TYPE_QUEST") QUEST, + @SerializedName("GIVING_TYPE_GROUP") GROUP + } +} diff --git a/src/main/java/emu/grasscutter/data/excels/giving/GivingGroupData.java b/src/main/java/emu/grasscutter/data/excels/giving/GivingGroupData.java new file mode 100644 index 000000000..4c5bb7d1a --- /dev/null +++ b/src/main/java/emu/grasscutter/data/excels/giving/GivingGroupData.java @@ -0,0 +1,21 @@ +package emu.grasscutter.data.excels.giving; + +import com.google.gson.annotations.SerializedName; +import emu.grasscutter.data.*; +import lombok.*; + +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = false) +@ResourceType(name = "GivingGroupExcelConfigData.json") +public final class GivingGroupData extends GameResource { + @SerializedName(value = "id", alternate = "Id") + private int id; + + @SerializedName(value = "itemIds", alternate = "ItemIds") + private List itemIds; + + private int finishTalkId; + private int mistakeTalkId; +} diff --git a/src/main/java/emu/grasscutter/game/ability/AbilityManager.java b/src/main/java/emu/grasscutter/game/ability/AbilityManager.java index 5c2bc526f..5125285a2 100644 --- a/src/main/java/emu/grasscutter/game/ability/AbilityManager.java +++ b/src/main/java/emu/grasscutter/game/ability/AbilityManager.java @@ -1,7 +1,7 @@ package emu.grasscutter.game.ability; import com.google.protobuf.*; -import emu.grasscutter.Grasscutter; +import emu.grasscutter.*; import emu.grasscutter.data.GameData; import emu.grasscutter.data.binout.*; import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; @@ -19,13 +19,13 @@ import emu.grasscutter.net.proto.AbilityScalarTypeOuterClass.AbilityScalarType; import emu.grasscutter.net.proto.AbilityScalarValueEntryOuterClass.AbilityScalarValueEntry; import emu.grasscutter.net.proto.ModifierActionOuterClass.ModifierAction; import io.netty.util.concurrent.FastThreadLocalThread; -import java.util.HashMap; -import java.util.concurrent.*; import lombok.Getter; import org.reflections.Reflections; -public final class AbilityManager extends BasePlayerManager { +import java.util.HashMap; +import java.util.concurrent.*; +public final class AbilityManager extends BasePlayerManager { private static final HashMap actionHandlers = new HashMap<>(); private static final HashMap mixinHandlers = @@ -91,8 +91,11 @@ public final class AbilityManager extends BasePlayerManager { Ability ability, AbilityModifierAction action, ByteString abilityData, GameEntity target) { var handler = actionHandlers.get(action.type); if (handler == null || ability == null) { - Grasscutter.getLogger() + if (DebugConstants.LOG_ABILITIES) { + Grasscutter.getLogger() .debug("Could not execute ability action {} at {}", action.type, ability); + } + return; } @@ -148,7 +151,7 @@ public final class AbilityManager extends BasePlayerManager { invoke.getArgumentType(), invoke.getArgumentTypeValue(), entity.getId()); - } else { + } else if (DebugConstants.LOG_ABILITIES) { Grasscutter.getLogger() .debug( "Invoke type of {} ({}) has no entity. (referring to {})", @@ -375,7 +378,10 @@ public final class AbilityManager extends BasePlayerManager { var entity = this.player.getScene().getEntityById(invoke.getEntityId()); if (entity == null) { - Grasscutter.getLogger().debug("Entity not found: {}", invoke.getEntityId()); + if (DebugConstants.LOG_ABILITIES) { + Grasscutter.getLogger().debug("Entity not found: {}", invoke.getEntityId()); + } + return; } diff --git a/src/main/java/emu/grasscutter/game/ability/actions/ActionGenerateElemBall.java b/src/main/java/emu/grasscutter/game/ability/actions/ActionGenerateElemBall.java index f42018b46..7073e77f2 100644 --- a/src/main/java/emu/grasscutter/game/ability/actions/ActionGenerateElemBall.java +++ b/src/main/java/emu/grasscutter/game/ability/actions/ActionGenerateElemBall.java @@ -2,7 +2,7 @@ package emu.grasscutter.game.ability.actions; import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; -import emu.grasscutter.Grasscutter; +import emu.grasscutter.*; import emu.grasscutter.data.GameData; import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction.DropType; @@ -81,8 +81,11 @@ public final class ActionGenerateElemBall extends AbilityActionHandler { return false; } - Grasscutter.getLogger() - .debug("Generating {} of {} element balls", amountGenerated, action.configID); + if (DebugConstants.LOG_ABILITIES) { + Grasscutter.getLogger() + .debug("Generating {} of {} element balls", amountGenerated, action.configID); + } + for (int i = 0; i < amountGenerated; i++) { EntityItem energyBall = new EntityItem( diff --git a/src/main/java/emu/grasscutter/game/ability/actions/ActionHealHP.java b/src/main/java/emu/grasscutter/game/ability/actions/ActionHealHP.java index baf50f7d6..2a0d22c8c 100644 --- a/src/main/java/emu/grasscutter/game/ability/actions/ActionHealHP.java +++ b/src/main/java/emu/grasscutter/game/ability/actions/ActionHealHP.java @@ -1,7 +1,7 @@ package emu.grasscutter.game.ability.actions; import com.google.protobuf.ByteString; -import emu.grasscutter.Grasscutter; +import emu.grasscutter.*; import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; import emu.grasscutter.game.ability.Ability; import emu.grasscutter.game.entity.EntityClientGadget; @@ -22,12 +22,14 @@ public final class ActionHealHP extends AbilityActionHandler { ownerGadget .getScene() .getEntityById(ownerGadget.getOwnerEntityId()); // Caster for EntityClientGadget - Grasscutter.getLogger() - .debug( - "Owner {} has top owner {}: {}", - ability.getOwner(), - ownerGadget.getOwnerEntityId(), - owner); + if (DebugConstants.LOG_ABILITIES) { + Grasscutter.getLogger() + .debug( + "Owner {} has top owner {}: {}", + ability.getOwner(), + ownerGadget.getOwnerEntityId(), + owner); + } } if (owner == null) return false; @@ -76,7 +78,7 @@ public final class ActionHealHP extends AbilityActionHandler { amountToRegenerate += amountByTargetMaxHPRatio * target.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); - Grasscutter.getLogger().debug(">>> Healing {} without ratios", amountToRegenerate); + Grasscutter.getLogger().trace("Healing {} without ratios", amountToRegenerate); target.heal( amountToRegenerate * abilityRatio * action.healRatio.get(ability, 1f), action.muteHealEffect); diff --git a/src/main/java/emu/grasscutter/game/inventory/BagTab.java b/src/main/java/emu/grasscutter/game/inventory/BagTab.java new file mode 100644 index 000000000..a089912f7 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/inventory/BagTab.java @@ -0,0 +1,55 @@ +package emu.grasscutter.game.inventory; + +import it.unimi.dsi.fastutil.ints.*; +import lombok.*; + +import java.util.*; +import java.util.stream.Stream; + +@RequiredArgsConstructor +public enum BagTab { + TAB_NONE(0), + TAB_WEAPON(1), + TAB_EQUIP(2), + TAB_AVATAR(3), + TAB_FOOD(4), + TAB_MATERIAL(5), + TAB_QUEST(6), + TAB_CONSUME(7), + TAB_WIDGET(8), + TAB_HOMEWORLD(9); + + private static final Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); + private static final Map stringMap = new HashMap<>(); + + static { + Stream.of(BagTab.values()) + .forEach( + entry -> { + map.put(entry.getValue(), entry); + stringMap.put(entry.name(), entry); + }); + } + + @Getter private final int value; + + /** + * Fetches the bag tab by its value. + * + * @param value The name of the bag tab. + * @return The bag tab. + */ + public static BagTab getTypeByValue(int value) { + return map.getOrDefault(value, TAB_NONE); + } + + /** + * Fetches the bag tab by its name. + * + * @param name The name of the bag tab. + * @return The bag tab. + */ + public static BagTab getTypeByName(String name) { + return stringMap.getOrDefault(name, TAB_NONE); + } +} diff --git a/src/main/java/emu/grasscutter/game/inventory/Inventory.java b/src/main/java/emu/grasscutter/game/inventory/Inventory.java index eabda9c82..0da00025e 100644 --- a/src/main/java/emu/grasscutter/game/inventory/Inventory.java +++ b/src/main/java/emu/grasscutter/game/inventory/Inventory.java @@ -1,7 +1,5 @@ package emu.grasscutter.game.inventory; -import static emu.grasscutter.config.Configuration.INVENTORY_LIMITS; - import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; import emu.grasscutter.data.common.ItemParamData; @@ -17,10 +15,13 @@ import emu.grasscutter.server.packet.send.*; import emu.grasscutter.utils.Utils; import it.unimi.dsi.fastutil.ints.*; import it.unimi.dsi.fastutil.longs.*; + import java.util.*; import javax.annotation.Nullable; import lombok.val; +import static emu.grasscutter.config.Configuration.INVENTORY_LIMITS; + public class Inventory extends BasePlayerManager implements Iterable { private final Long2ObjectMap store; private final Int2ObjectMap inventoryTypes; @@ -60,6 +61,19 @@ public class Inventory extends BasePlayerManager implements Iterable { this.getInventoryTypes().put(type.getValue(), tab); } + /** + * Finds the first item in the inventory with the given item id. + * + * @param itemId The item id to search for. + * @return The first item found with the given item id, or null if no item was + */ + public GameItem getFirstItem(int itemId) { + return this.getItems().values().stream() + .filter(item -> item.getItemId() == itemId) + .findFirst() + .orElse(null); + } + public GameItem getItemByGuid(long id) { return this.getItems().get(id); } @@ -170,6 +184,52 @@ public class Inventory extends BasePlayerManager implements Iterable { getPlayer().sendPacket(new PacketStoreItemChangeNotify(changedItems)); } + /** + * Checks to see if the player has the item in their inventory. + * This will succeed if the player has at least the minimum count of the item. + * + * @param itemId The item id to check for. + * @param minCount The minimum count of the item to check for. + * @return True if the player has the item, false otherwise. + */ + public boolean hasItem(int itemId, int minCount) { + return hasItem(itemId, minCount, false); + } + + /** + * Checks to see if the player has the item in their inventory. + * + * @param itemId The item id to check for. + * @param count The count of the item to check for. + * @param enforce If true, the player must have the exact amount. + * If false, the player must have at least the amount. + * @return True if the player has the item, false otherwise. + */ + public boolean hasItem(int itemId, int count, boolean enforce) { + var item = this.getFirstItem(itemId); + if (item == null) return false; + + return enforce ? + item.getCount() == count : + item.getCount() >= count; + } + + /** + * Checks to see if the player has the item in their inventory. + * This is exact. + * + * @param items A map of item game IDs to their count. + * @return True if the player has the items, false otherwise. + */ + public boolean hasAllItems(Collection items) { + for (var item : items) { + if (!this.hasItem(item.getItemId(), item.getCount(), true)) + return false; + } + + return true; + } + private void triggerAddItemEvents(GameItem result) { try { getPlayer() @@ -454,10 +514,40 @@ public class Inventory extends BasePlayerManager implements Iterable { } } + /** + * Performs a bulk delete of items. + * + * @param items A map of item game IDs to the amount of items to remove. + */ + public void removeItems(Collection items) { + for (var entry : items) { + this.removeItem(entry.getItemId(), entry.getCount()); + } + } + public boolean removeItem(long guid) { return removeItem(guid, 1); } + /** + * Removes an item from the player's inventory. + * This uses the item ID to find the first stack of the item's type. + * + * @param itemId The ID of the item to remove. + * @param count The amount of items to remove. + * @return True if the item was removed, false otherwise. + */ + public synchronized boolean removeItem(int itemId, int count) { + var item = this.getItems().values().stream() + .filter(i -> i.getItemId() == itemId) + .findFirst(); + + // Check if the item is in the player's inventory. + return item + .filter(gameItem -> this.removeItem(gameItem, count)) + .isPresent(); + } + public synchronized boolean removeItem(long guid, int count) { var item = this.getItemByGuid(guid); @@ -476,10 +566,14 @@ public class Inventory extends BasePlayerManager implements Iterable { * @return True if the item was removed, false otherwise. */ public synchronized boolean removeItemById(int itemId, int count) { - var item = this.getItems().values().stream().filter(i -> i.getItemId() == itemId).findFirst(); + var item = this.getItems().values().stream() + .filter(i -> i.getItemId() == itemId) + .findFirst(); // Check if the item is in the player's inventory. - return item.filter(gameItem -> this.removeItem(gameItem, count)).isPresent(); + return item + .filter(gameItem -> this.removeItem(gameItem, count)) + .isPresent(); } public synchronized boolean removeItem(GameItem item) { diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 6fce17436..d0f6e3597 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -160,7 +160,7 @@ public class Player implements PlayerHook, FieldFetch { @Getter private transient FriendsList friendsList; @Getter private transient MailHandler mailHandler; @Getter private transient AbilityManager abilityManager; - @Getter @Setter private transient QuestManager questManager; + @Getter private transient QuestManager questManager; @Getter private transient TowerManager towerManager; @Getter private transient SotSManager sotsManager; @Getter private transient MapMarksManager mapMarksManager; diff --git a/src/main/java/emu/grasscutter/game/player/PlayerProgress.java b/src/main/java/emu/grasscutter/game/player/PlayerProgress.java index 5ac575f5a..638d8c5da 100644 --- a/src/main/java/emu/grasscutter/game/player/PlayerProgress.java +++ b/src/main/java/emu/grasscutter/game/player/PlayerProgress.java @@ -1,40 +1,42 @@ package emu.grasscutter.game.player; -import dev.morphia.annotations.Entity; -import dev.morphia.annotations.Transient; +import dev.morphia.annotations.*; import emu.grasscutter.Grasscutter; +import emu.grasscutter.game.quest.*; import emu.grasscutter.game.quest.enums.QuestContent; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.*; +import lombok.*; + import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.val; /** Tracks progress the player made in the world, like obtained items, seen characters and more */ +@Getter @Entity public class PlayerProgress { - @Getter @Setter @Transient private Player player; - - @Getter private Map itemHistory; + @Setter @Transient private Player player; + private Map itemHistory; /* * A list of dungeon IDs which have been completed. * This only applies to one-time dungeons. */ - @Getter private IntArrayList completedDungeons; + private IntArrayList completedDungeons; // keep track of EXEC_ADD_QUEST_PROGRESS count, will be used in CONTENT_ADD_QUEST_PROGRESS // not sure where to put this, this should be saved to DB but not to individual quest, since // it will be hard to loop and compare private Map questProgressCountMap; + private Map itemGivings; + private Map bargains; + public PlayerProgress() { this.questProgressCountMap = new ConcurrentHashMap<>(); this.completedDungeons = new IntArrayList(); this.itemHistory = new Int2ObjectOpenHashMap<>(); + this.itemGivings = new Int2ObjectOpenHashMap<>(); + this.bargains = new Int2ObjectOpenHashMap<>(); } /** diff --git a/src/main/java/emu/grasscutter/game/quest/BargainRecord.java b/src/main/java/emu/grasscutter/game/quest/BargainRecord.java new file mode 100644 index 000000000..d7a74c9b3 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/BargainRecord.java @@ -0,0 +1,109 @@ +package emu.grasscutter.game.quest; + +import dev.morphia.annotations.Entity; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.excels.BargainData; +import emu.grasscutter.net.proto.BargainResultTypeOuterClass.BargainResultType; +import emu.grasscutter.net.proto.BargainSnapshotOuterClass.BargainSnapshot; +import emu.grasscutter.utils.Utils; +import lombok.*; + +@Data +@Entity +@Builder +public final class BargainRecord { + /** + * Provides an instance of a bargain record. + * Uses information from game resources. + * + * @param bargainId The ID of the bargain. + * @return An instance of a bargain record. + */ + public static BargainRecord resolve(int bargainId) { + var bargainData = GameData.getBargainDataMap().get(bargainId); + if (bargainData == null) throw new RuntimeException("No bargain data found for " + bargainId + "."); + + return BargainRecord.builder() + .bargainId(bargainId) + .build() + .determineBase(bargainData); + } + + private int bargainId; + private int lowestPrice; + private int expectedPrice; + + private int currentMood; + + private boolean finished; + private BargainResultType result; + + /** + * Determines the price of the bargain. + */ + public BargainRecord determineBase(BargainData data) { + // Set the expected price. + var price = data.getExpectedValue(); + this.setExpectedPrice(Utils.randomRange( + price.get(0), price.get(1))); + // Set the lowest price. + this.setLowestPrice(price.get(0)); + + // Set the base mood. + var mood = data.getRandomMood(); + this.setCurrentMood(Utils.randomRange( + mood.get(0), mood.get(1))); + + return this; + } + + /** + * Computes an offer's validity. + * + * @param offer The offer to compute. + * @return The result of the offer. + */ + public BargainResultType applyOffer(int offer) { + if (offer < this.getLowestPrice()) { + // Decrease the mood. + this.currentMood -= Utils.randomRange(1, 3); + // Return a failure. + return this.result = BargainResultType.BARGAIN_SINGLE_FAIL; + } + + if (offer > this.getExpectedPrice()) { + // Complete the bargain. + this.setFinished(true); + // Return a success. + return this.result = BargainResultType.BARGAIN_COMPLETE_SUCC; + } + + // Compare the offer against the mood & expected price. + // The mood is out of 100; 1 mood should decrease the price by 100. + var moodAdjustment = (int) Math.floor(this.getCurrentMood() / 100.0); + var expectedPrice = this.getExpectedPrice() - moodAdjustment; + if (offer < expectedPrice) { + // Decrease the mood. + this.currentMood -= Utils.randomRange(1, 3); + // Return a failure. + return this.result = BargainResultType.BARGAIN_SINGLE_FAIL; + } else { + // Complete the bargain. + this.setFinished(true); + // Return a success. + return this.result = BargainResultType.BARGAIN_COMPLETE_SUCC; + } + } + + /** + * @return A snapshot of this bargain record. + */ + public BargainSnapshot toSnapshot() { + return BargainSnapshot.newBuilder() + .setBargainId(this.getBargainId()) + .setCurMood(this.getCurrentMood()) + .setPJHMEHGELGC(this.getExpectedPrice()) + .setHADMOPEJFIC(this.getLowestPrice()) + .build(); + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/GameQuest.java b/src/main/java/emu/grasscutter/game/quest/GameQuest.java index b69367c3d..4241d05a1 100644 --- a/src/main/java/emu/grasscutter/game/quest/GameQuest.java +++ b/src/main/java/emu/grasscutter/game/quest/GameQuest.java @@ -1,7 +1,7 @@ package emu.grasscutter.game.quest; import dev.morphia.annotations.*; -import emu.grasscutter.Grasscutter; +import emu.grasscutter.*; import emu.grasscutter.data.GameData; import emu.grasscutter.data.excels.*; import emu.grasscutter.data.excels.quest.QuestData; @@ -15,10 +15,11 @@ import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.server.packet.send.*; import emu.grasscutter.utils.Utils; import it.unimi.dsi.fastutil.ints.IntIntImmutablePair; -import java.util.*; -import javax.script.Bindings; import lombok.*; +import javax.script.Bindings; +import java.util.*; + @Entity public class GameQuest { @Transient @Getter @Setter private GameMainQuest mainQuest; @@ -104,7 +105,10 @@ public class GameQuest { .forEach(e -> getOwner().getServer().getQuestSystem().triggerExec(this, e, e.getParam())); this.getOwner().getQuestManager().checkQuestAlreadyFulfilled(this); - Grasscutter.getLogger().debug("Quest {} is started", subQuestId); + if (DebugConstants.LOG_QUEST_START) { + Grasscutter.getLogger().debug("Quest {} is started", subQuestId); + } + this.save(); } diff --git a/src/main/java/emu/grasscutter/game/quest/ItemGiveRecord.java b/src/main/java/emu/grasscutter/game/quest/ItemGiveRecord.java new file mode 100644 index 000000000..1000eac82 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/ItemGiveRecord.java @@ -0,0 +1,75 @@ +package emu.grasscutter.game.quest; + +import dev.morphia.annotations.Entity; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.excels.giving.GivingData.GiveMethod; +import emu.grasscutter.net.proto.GivingRecordOuterClass.GivingRecord; +import lombok.*; + +import java.util.*; + +@Data +@Entity +@Builder +public final class ItemGiveRecord { + /** + * Provides a builder for an item give record. + * Uses information from game resources. + * + * @param givingId The ID of the giving action. + * @return A builder for an item give record. + */ + public static ItemGiveRecord resolve(int givingId) { + var givingData = GameData.getGivingDataMap().get(givingId); + if (givingData == null) throw new RuntimeException("No giving data found for " + givingId + "."); + + var builder = ItemGiveRecord.builder() + .givingId(givingId) + .finished(false); + + // Create a map. + var givenItems = new HashMap(); + if (givingData.getGivingMethod() == GiveMethod.GIVING_METHOD_EXACT) { + givingData.getExactItems().forEach(item -> + givenItems.put(item.getItemId(), 0)); + } else { + givingData.getGivingGroupIds().forEach(groupId -> { + var groupData = GameData.getGivingGroupDataMap().get((int) groupId); + if (groupData == null) return; + + // Add all items in the group. + groupData.getItemIds().forEach(itemId -> + givenItems.put(itemId, 0)); + builder.groupId(groupId); + }); + } + + return builder + .givenItems(givenItems) + .build(); + } + + private int givingId; + private int configId; + + private int groupId; + private int lastGroupId; + + private boolean finished; + private Map givenItems; + + /** + * @return A serialized protobuf object. + */ + public GivingRecord toProto() { + return GivingRecord.newBuilder() + .setGivingId(this.getGivingId()) + .setConfigId(this.getConfigId()) + .setGroupId(this.getGroupId()) + .setLastGroupId(this.getLastGroupId()) + .setIsFinished(this.isFinished()) + .setIsGadgetGiving(false) + .putAllMaterialCntMap(this.getGivenItems()) + .build(); + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/QuestManager.java b/src/main/java/emu/grasscutter/game/quest/QuestManager.java index 46418e500..ab1ee3f30 100644 --- a/src/main/java/emu/grasscutter/game/quest/QuestManager.java +++ b/src/main/java/emu/grasscutter/game/quest/QuestManager.java @@ -1,84 +1,41 @@ package emu.grasscutter.game.quest; -import dev.morphia.annotations.Transient; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; -import emu.grasscutter.data.binout.MainQuestData; -import emu.grasscutter.data.binout.ScenePointEntry; +import emu.grasscutter.data.binout.*; import emu.grasscutter.data.excels.quest.QuestData; import emu.grasscutter.database.DatabaseHelper; -import emu.grasscutter.game.player.BasePlayerManager; -import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.player.*; import emu.grasscutter.game.quest.enums.*; -import emu.grasscutter.server.packet.send.PacketFinishedParentQuestUpdateNotify; -import emu.grasscutter.server.packet.send.PacketQuestGlobalVarNotify; import emu.grasscutter.game.world.Position; +import emu.grasscutter.net.proto.GivingRecordOuterClass.GivingRecord; +import emu.grasscutter.server.packet.send.*; import io.netty.util.concurrent.FastThreadLocalThread; import it.unimi.dsi.fastutil.ints.*; -import lombok.Getter; -import lombok.val; +import lombok.*; import javax.annotation.Nonnull; import java.util.*; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.LinkedBlockingDeque; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.function.Consumer; import java.util.stream.Collectors; import static emu.grasscutter.GameConstants.DEBUG; -import static emu.grasscutter.config.Configuration.GAME_OPTIONS; -import static emu.grasscutter.config.Configuration.SERVER; +import static emu.grasscutter.config.Configuration.*; -public class QuestManager extends BasePlayerManager { +public final class QuestManager extends BasePlayerManager { @Getter private final Player player; + @Getter private final Int2ObjectMap mainQuests; - @Transient @Getter private final List loggedQuests; + @Getter private final List loggedQuests; private long lastHourCheck = 0; private long lastDayCheck = 0; - public static final ExecutorService eventExecutor; - static { - eventExecutor = new ThreadPoolExecutor(4, 4, - 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1000), - FastThreadLocalThread::new, new ThreadPoolExecutor.AbortPolicy()); - } - /* - On SetPlayerBornDataReq, the server sends FinishedParentQuestNotify, with this exact - parentQuestList. Captured on Game version 2.7 - Note: quest 40063 is already set to finished, with childQuest 4006406's state set to 3 - */ - - private static Set newPlayerMainQuests = Set.of(303,318,348,349,350,351,416,500, - 501,502,503,504,505,506,507,508,509,20000,20507,20509,21004,21005,21010,21011,21016,21017, - 21020,21021,21025,40063,70121,70124,70511,71010,71012,71013,71015,71016,71017,71555); - - /* - On SetPlayerBornDataReq, the server sends ServerCondMeetQuestListUpdateNotify, with this exact - addQuestIdList. Captured on Game version 2.7 - Total of 161... - */ - /* - private static Set newPlayerServerCondMeetQuestListUpdateNotify = Set.of(3100101, 7104405, 2201601, - 7100801, 1907002, 7293301, 7193801, 7293401, 7193901, 7091001, 7190501, 7090901, 7190401, 7090801, 7190301, - 7195301, 7294801, 7195201, 7293001, 7094001, 7193501, 7293501, 7194001, 7293701, 7194201, 7194301, 7293801, - 7194901, 7194101, 7195001, 7294501, 7294101, 7194601, 7294301, 7194801, 7091301, 7290301, 2102401, 7216801, - 7190201, 7090701, 7093801, 7193301, 7292801, 7227828, 7093901, 7193401, 7292901, 7093701, 7193201, 7292701, - 7082402, 7093601, 7292601, 7193101, 2102301, 7093501, 7292501, 7193001, 7093401, 7292401, 7192901, 7093301, - 7292301, 7192801, 7294201, 7194701, 2100301, 7093201, 7212402, 7292201, 7192701, 7280001, 7293901, 7194401, - 7093101, 7212302, 7292101, 7192601, 7093001, 7292001, 7192501, 7216001, 7195101, 7294601, 2100900, 7092901, - 7291901, 7192401, 7092801, 7291801, 7192301, 2101501, 7092701, 7291701, 7192201, 7106401, 2100716, 7091801, - 7290801, 7191301, 7293201, 7193701, 7094201, 7294001, 7194501, 2102290, 7227829, 7193601, 7094101, 7091401, - 7290401, 7190901, 7106605, 7291601, 7192101, 7092601, 7291501, 7192001, 7092501, 7291401, 7191901, 7092401, - 7291301, 7191801, 7092301, 7211402, 7291201, 7191701, 7092201, 7291101, 7191601, 7092101, 7291001, 7191501, - 7092001, 7290901, 7191401, 7091901, 7290701, 7191201, 7091701, 7290601, 7191101, 7091601, 7290501, 7191001, - 7091501, 7290201, 7190701, 7091201, 7190601, 7091101, 7190101, 7090601, 7090501, 7090401, 7010701, 7090301, - 7090201, 7010103, 7090101 - ); - - */ + public static final ExecutorService eventExecutor + = new ThreadPoolExecutor(4, 4, + 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1000), + FastThreadLocalThread::new, new ThreadPoolExecutor.AbortPolicy()); public static long getQuestKey(int mainQuestId) { QuestEncryptionKey questEncryptionKey = GameData.getMainQuestEncryptionMap().get(mainQuestId); @@ -134,20 +91,146 @@ public class QuestManager extends BasePlayerManager { return GAME_OPTIONS.questing.enabled; } - public void onPlayerBorn() { - // TODO scan the quest and start the quest with acceptCond fulfilled - // The off send 3 request in that order: - // 1. FinishedParentQuestNotify - // 2. QuestListNotify - // 3. ServerCondMeetQuestListUpdateNotify + /** + * Attempts to add the giving action. + * + * @param givingId The giving action ID. + * @throws IllegalStateException If the giving action is already active. + */ + public void addGiveItemAction(int givingId) + throws IllegalStateException { + var progress = this.player.getPlayerProgress(); + var givings = progress.getItemGivings(); - if (this.isQuestingEnabled()) { - this.enableQuests(); + // Check if the action is already present. + if (givings.containsKey(givingId)) { + throw new IllegalStateException("Giving action " + givingId + " is already active."); } - // this.getPlayer().sendPacket(new PacketFinishedParentQuestUpdateNotify(newQuests)); - // this.getPlayer().sendPacket(new PacketQuestListNotify(subQuests)); - // this.getPlayer().sendPacket(new PacketServerCondMeetQuestListUpdateNotify(newPlayerServerCondMeetQuestListUpdateNotify)); + // Add the action. + givings.put(givingId, ItemGiveRecord.resolve(givingId)); + // Save the givings. + player.save(); + + this.sendGivingRecords(); + } + + /** + * Marks a giving action as completed. + * + * @param givingId The giving action ID. + */ + public void markCompleted(int givingId) { + var progress = this.player.getPlayerProgress(); + var givings = progress.getItemGivings(); + + // Check if the action is already present. + if (!givings.containsKey(givingId)) { + throw new IllegalStateException("Giving action " + givingId + " is not active."); + } + + // Mark the action as finished. + givings.get(givingId).setFinished(true); + // Save the givings. + player.save(); + + this.sendGivingRecords(); + } + + /** + * Attempts to remove the giving action. + * + * @param givingId The giving action ID. + */ + public void removeGivingItemAction(int givingId) { + var progress = this.player.getPlayerProgress(); + var givings = progress.getItemGivings(); + + // Check if the action is already present. + if (!givings.containsKey(givingId)) { + throw new IllegalStateException("Giving action " + givingId + " is not active."); + } + + // Remove the action. + givings.remove(givingId); + // Save the givings. + player.save(); + + this.sendGivingRecords(); + } + + /** + * @return Serialized giving records to be used in a packet. + */ + public Collection getGivingRecords() { + return this.getPlayer().getPlayerProgress() + .getItemGivings().values().stream() + .map(ItemGiveRecord::toProto) + .toList(); + } + + /** + * Attempts to start the bargain. + * + * @param bargainId The bargain ID. + */ + public void startBargain(int bargainId) { + var progress = this.player.getPlayerProgress(); + var bargains = progress.getBargains(); + + // Check if the bargain is already present. + if (bargains.containsKey(bargainId)) { + throw new IllegalStateException("Bargain " + bargainId + " is already active."); + } + + // Add the action. + var bargain = BargainRecord.resolve(bargainId); + bargains.put(bargainId, bargain); + // Save the bargains. + this.player.save(); + + // Send the player the start packet. + this.player.sendPacket(new PacketBargainStartNotify(bargain)); + } + + /** + * Attempts to stop the bargain. + * + * @param bargainId The bargain ID. + */ + public void stopBargain(int bargainId) { + var progress = this.player.getPlayerProgress(); + var bargains = progress.getBargains(); + + // Check if the bargain is already present. + if (!bargains.containsKey(bargainId)) { + throw new IllegalStateException("Bargain " + bargainId + " is not active."); + } + + // Remove the action. + bargains.remove(bargainId); + // Save the bargains. + this.player.save(); + + // Send the player the stop packet. + this.player.sendPacket(new PacketBargainTerminateNotify(bargainId)); + } + + /** + * Sends the giving records to the player. + */ + public void sendGivingRecords() { + // Send the record to the player. + this.player.sendPacket( + new PacketGivingRecordNotify( + this.getGivingRecords())); + } + + public void onPlayerBorn() { + if (this.isQuestingEnabled()) { + this.enableQuests(); + this.sendGivingRecords(); + } } public void onLogin() { diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentBargainFail.java b/src/main/java/emu/grasscutter/game/quest/content/ContentBargainFail.java new file mode 100644 index 000000000..3f88e0143 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentBargainFail.java @@ -0,0 +1,15 @@ +package emu.grasscutter.game.quest.content; + +import emu.grasscutter.data.excels.quest.QuestData; +import emu.grasscutter.game.quest.*; +import emu.grasscutter.game.quest.enums.QuestContent; +import emu.grasscutter.net.proto.BargainResultTypeOuterClass.BargainResultType; + +@QuestValueContent(QuestContent.QUEST_CONTENT_BARGAIN_FAIL) +public final class ContentBargainFail extends BaseContent { + @Override + public boolean execute(GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) { + return condition.getParam()[0] == params[0] && + condition.getParam()[1] == params[1]; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentBargainLessThan.java b/src/main/java/emu/grasscutter/game/quest/content/ContentBargainLessThan.java new file mode 100644 index 000000000..88ee00356 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentBargainLessThan.java @@ -0,0 +1,15 @@ +package emu.grasscutter.game.quest.content; + +import emu.grasscutter.data.excels.quest.QuestData; +import emu.grasscutter.game.quest.*; +import emu.grasscutter.game.quest.enums.QuestContent; +import emu.grasscutter.net.proto.BargainResultTypeOuterClass.BargainResultType; + +@QuestValueContent(QuestContent.QUEST_CONTENT_ITEM_LESS_THAN_BARGAIN) +public final class ContentBargainLessThan extends BaseContent { + @Override + public boolean execute(GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) { + return condition.getParam()[0] == params[0] && + condition.getParam()[1] == params[1]; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentBargainSuccess.java b/src/main/java/emu/grasscutter/game/quest/content/ContentBargainSuccess.java new file mode 100644 index 000000000..8200355d1 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentBargainSuccess.java @@ -0,0 +1,14 @@ +package emu.grasscutter.game.quest.content; + +import emu.grasscutter.data.excels.quest.QuestData; +import emu.grasscutter.game.quest.*; +import emu.grasscutter.game.quest.enums.QuestContent; + +@QuestValueContent(QuestContent.QUEST_CONTENT_BARGAIN_SUCC) +public final class ContentBargainSuccess extends BaseContent { + @Override + public boolean execute(GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) { + return condition.getParam()[0] == params[0] && + condition.getParam()[1] == params[1]; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentFinishGivingItem.java b/src/main/java/emu/grasscutter/game/quest/content/ContentFinishGivingItem.java new file mode 100644 index 000000000..61f6605c9 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentFinishGivingItem.java @@ -0,0 +1,14 @@ +package emu.grasscutter.game.quest.content; + +import emu.grasscutter.data.excels.quest.QuestData; +import emu.grasscutter.game.quest.*; +import emu.grasscutter.game.quest.enums.QuestContent; + +@QuestValueContent(QuestContent.QUEST_CONTENT_FINISH_ITEM_GIVING) +public final class ContentFinishGivingItem extends BaseContent { + @Override + public boolean execute(GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) { + return condition.getParam()[0] == params[0] && + condition.getParam()[1] == params[1]; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/enums/QuestContent.java b/src/main/java/emu/grasscutter/game/quest/enums/QuestContent.java index 3c3f378a3..ac81fa623 100644 --- a/src/main/java/emu/grasscutter/game/quest/enums/QuestContent.java +++ b/src/main/java/emu/grasscutter/game/quest/enums/QuestContent.java @@ -34,7 +34,7 @@ public enum QuestContent implements QuestTrigger { QUEST_CONTENT_ADD_QUEST_PROGRESS(24), QUEST_CONTENT_INTERACT_GADGET(25), QUEST_CONTENT_DAILY_TASK_COMP_FINISH(26), // missing, currently unused - QUEST_CONTENT_FINISH_ITEM_GIVING(27), // missing, finish + QUEST_CONTENT_FINISH_ITEM_GIVING(27), QUEST_CONTENT_SKILL(107), QUEST_CONTENT_CITY_LEVEL_UP(109), // missing, finish QUEST_CONTENT_PATTERN_GROUP_CLEAR_MONSTER(110), // missing, finish, for random quests @@ -51,9 +51,9 @@ public enum QuestContent implements QuestTrigger { QUEST_CONTENT_QUEST_VAR_LESS(121), QUEST_CONTENT_OBTAIN_VARIOUS_ITEM(122), // missing, finish QUEST_CONTENT_FINISH_TOWER_LEVEL(123), // missing, currently unused - QUEST_CONTENT_BARGAIN_SUCC(124), // missing, finish - QUEST_CONTENT_BARGAIN_FAIL(125), // missing, fail - QUEST_CONTENT_ITEM_LESS_THAN_BARGAIN(126), // missing, fail + QUEST_CONTENT_BARGAIN_SUCC(124), + QUEST_CONTENT_BARGAIN_FAIL(125), + QUEST_CONTENT_ITEM_LESS_THAN_BARGAIN(126), QUEST_CONTENT_ACTIVITY_TRIGGER_FAILED(127), // missing, fail QUEST_CONTENT_MAIN_COOP_ENTER_SAVE_POINT(128), // missing, finish QUEST_CONTENT_ANY_MANUAL_TRANSPORT(129), diff --git a/src/main/java/emu/grasscutter/game/quest/enums/QuestExec.java b/src/main/java/emu/grasscutter/game/quest/enums/QuestExec.java index 07c01a8b4..a04fd9ab3 100644 --- a/src/main/java/emu/grasscutter/game/quest/enums/QuestExec.java +++ b/src/main/java/emu/grasscutter/game/quest/enums/QuestExec.java @@ -35,7 +35,7 @@ public enum QuestExec implements QuestTrigger { QUEST_EXEC_CREATE_PATTERN_GROUP(25), // missing, used for random quests QUEST_EXEC_REMOVE_PATTERN_GROUP(26), // missing, used for random quests QUEST_EXEC_REFRESH_GROUP_SUITE_RANDOM(27), // missing - QUEST_EXEC_ACTIVE_ITEM_GIVING(28), // missing + QUEST_EXEC_ACTIVE_ITEM_GIVING(28), QUEST_EXEC_DEL_ALL_SPECIFIC_PACK_ITEM(29), // missing QUEST_EXEC_ROLLBACK_PARENT_QUEST(30), QUEST_EXEC_LOCK_AVATAR_TEAM(31), // missing @@ -47,8 +47,8 @@ public enum QuestExec implements QuestTrigger { QUEST_EXEC_ACTIVE_ACTIVITY_COND_STATE(37), // missing QUEST_EXEC_INACTIVE_ACTIVITY_COND_STATE(38), // missing QUEST_EXEC_ADD_CUR_AVATAR_ENERGY(39), - QUEST_EXEC_START_BARGAIN(41), // missing - QUEST_EXEC_STOP_BARGAIN(42), // missing + QUEST_EXEC_START_BARGAIN(41), + QUEST_EXEC_STOP_BARGAIN(42), QUEST_EXEC_SET_QUEST_GLOBAL_VAR(43), QUEST_EXEC_INC_QUEST_GLOBAL_VAR(44), QUEST_EXEC_DEC_QUEST_GLOBAL_VAR(45), @@ -71,7 +71,7 @@ public enum QuestExec implements QuestTrigger { QUEST_EXEC_MODIFY_CLIMATE_AREA(60), // missing QUEST_EXEC_GRANT_TRIAL_AVATAR_AND_LOCK_TEAM(61), // missing QUEST_EXEC_CHANGE_MAP_AREA_STATE(62), // missing - QUEST_EXEC_DEACTIVE_ITEM_GIVING(63), // missing + QUEST_EXEC_DEACTIVE_ITEM_GIVING(63), QUEST_EXEC_CHANGE_SCENE_LEVEL_TAG(64), // missing QUEST_EXEC_UNLOCK_PLAYER_WORLD_SCENE(65), // missing QUEST_EXEC_LOCK_PLAYER_WORLD_SCENE(66), // missing diff --git a/src/main/java/emu/grasscutter/game/quest/exec/ExecActiveItemGiving.java b/src/main/java/emu/grasscutter/game/quest/exec/ExecActiveItemGiving.java new file mode 100644 index 000000000..c2f073967 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/exec/ExecActiveItemGiving.java @@ -0,0 +1,29 @@ +package emu.grasscutter.game.quest.exec; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.excels.quest.QuestData.QuestExecParam; +import emu.grasscutter.game.quest.*; +import emu.grasscutter.game.quest.enums.QuestExec; +import emu.grasscutter.game.quest.handlers.QuestExecHandler; + +@QuestValueExec(QuestExec.QUEST_EXEC_ACTIVE_ITEM_GIVING) +public final class ExecActiveItemGiving extends QuestExecHandler { + @Override + public boolean execute(GameQuest quest, QuestExecParam condition, String... paramStr) { + var player = quest.getOwner(); + var questManager = player.getQuestManager(); + + var givingId = Integer.parseInt(condition.getParam()[0]); + try { + questManager.addGiveItemAction(givingId); + + Grasscutter.getLogger().debug("Quest {} added give action {}.", + quest.getSubQuestId(), givingId); + return true; + } catch (IllegalStateException ignored) { + Grasscutter.getLogger().warn("Quest {} attempted to add give action {} twice.", + quest.getSubQuestId(), givingId); + return false; + } + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/exec/ExecDeactivateItemGiving.java b/src/main/java/emu/grasscutter/game/quest/exec/ExecDeactivateItemGiving.java new file mode 100644 index 000000000..9f9f8d5ba --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/exec/ExecDeactivateItemGiving.java @@ -0,0 +1,25 @@ +package emu.grasscutter.game.quest.exec; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.excels.quest.QuestData; +import emu.grasscutter.game.quest.*; +import emu.grasscutter.game.quest.enums.QuestExec; +import emu.grasscutter.game.quest.handlers.QuestExecHandler; + +@QuestValueExec(QuestExec.QUEST_EXEC_DEACTIVE_ITEM_GIVING) +public final class ExecDeactivateItemGiving extends QuestExecHandler { + @Override + public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) { + var questManager = quest.getOwner().getQuestManager(); + + var givingId = Integer.parseInt(condition.getParam()[0]); + try { + questManager.removeGivingItemAction(givingId); + return true; + } catch (IllegalStateException ignored) { + Grasscutter.getLogger().warn("Quest {} attempted to remove give action {} twice.", + quest.getSubQuestId(), givingId); + return false; + } + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/exec/ExecStartBargain.java b/src/main/java/emu/grasscutter/game/quest/exec/ExecStartBargain.java new file mode 100644 index 000000000..87b03ec23 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/exec/ExecStartBargain.java @@ -0,0 +1,27 @@ +package emu.grasscutter.game.quest.exec; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.excels.quest.QuestData; +import emu.grasscutter.game.quest.*; +import emu.grasscutter.game.quest.enums.QuestExec; +import emu.grasscutter.game.quest.handlers.QuestExecHandler; + +@QuestValueExec(QuestExec.QUEST_EXEC_START_BARGAIN) +public final class ExecStartBargain extends QuestExecHandler { + @Override + public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) { + // Get the bargain data from the quest parameters. + var bargainId = Integer.parseInt(condition.getParam()[0]); + + try { + // Start the bargain. + quest.getOwner().getQuestManager() + .startBargain(bargainId); + Grasscutter.getLogger().debug("Bargain {} started.", bargainId); + return true; + } catch (RuntimeException ignored) { + Grasscutter.getLogger().debug("Bargain {} does not exist.", bargainId); + return false; + } + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/exec/ExecStopBargain.java b/src/main/java/emu/grasscutter/game/quest/exec/ExecStopBargain.java new file mode 100644 index 000000000..0b5d09531 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/exec/ExecStopBargain.java @@ -0,0 +1,27 @@ +package emu.grasscutter.game.quest.exec; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.excels.quest.QuestData; +import emu.grasscutter.game.quest.*; +import emu.grasscutter.game.quest.enums.QuestExec; +import emu.grasscutter.game.quest.handlers.QuestExecHandler; + +@QuestValueExec(QuestExec.QUEST_EXEC_STOP_BARGAIN) +public final class ExecStopBargain extends QuestExecHandler { + @Override + public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) { + // Get the bargain data from the quest parameters. + var bargainId = Integer.parseInt(condition.getParam()[0]); + + try { + // Start the bargain. + quest.getOwner().getQuestManager() + .stopBargain(bargainId); + Grasscutter.getLogger().debug("Bargain {} stopped.", bargainId); + return true; + } catch (RuntimeException ignored) { + Grasscutter.getLogger().debug("Bargain {} does not exist.", bargainId); + return false; + } + } +} diff --git a/src/main/java/emu/grasscutter/scripts/data/controller/EntityController.java b/src/main/java/emu/grasscutter/scripts/data/controller/EntityController.java index 0435fe44f..4897910b9 100644 --- a/src/main/java/emu/grasscutter/scripts/data/controller/EntityController.java +++ b/src/main/java/emu/grasscutter/scripts/data/controller/EntityController.java @@ -1,14 +1,12 @@ package emu.grasscutter.scripts.data.controller; -import emu.grasscutter.Grasscutter; +import emu.grasscutter.*; import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.props.ElementType; -import emu.grasscutter.scripts.ScriptLib; -import emu.grasscutter.scripts.ScriptLoader; -import javax.script.Bindings; -import javax.script.CompiledScript; -import org.luaj.vm2.LuaError; -import org.luaj.vm2.LuaValue; +import emu.grasscutter.scripts.*; +import org.luaj.vm2.*; + +import javax.script.*; public class EntityController { private transient CompiledScript entityController; @@ -38,9 +36,11 @@ public class EntityController { } public int onClientExecuteRequest(GameEntity entity, int param1, int param2, int param3) { - Grasscutter.getLogger() + if (DebugConstants.LOG_LUA_SCRIPTS) { + Grasscutter.getLogger() .debug( - "Request on {}, {}: {}", entity.getGroupId(), param1, entity.getPosition().toString()); + "Request on {}, {}: {}", entity.getGroupId(), param1, entity.getPosition().toString()); + } LuaValue value = callControllerScriptFunc( entity, diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerBargainOfferPriceReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerBargainOfferPriceReq.java new file mode 100644 index 000000000..fbc776154 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerBargainOfferPriceReq.java @@ -0,0 +1,43 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.game.quest.enums.QuestContent; +import emu.grasscutter.net.packet.*; +import emu.grasscutter.net.proto.BargainOfferPriceReqOuterClass.BargainOfferPriceReq; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketBargainOfferPriceRsp; + +@Opcodes(PacketOpcodes.BargainOfferPriceReq) +public final class HandlerBargainOfferPriceReq extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) + throws Exception { + var packet = BargainOfferPriceReq.parseFrom(payload); + var player = session.getPlayer(); + + // Fetch the active bargain. + var bargainId = packet.getBargainId(); + var progress = player.getPlayerProgress(); + var bargain = progress.getBargains().get(bargainId); + if (bargain == null) return; + + // Apply the offer. + var result = bargain.applyOffer(packet.getPrice()); + + // Queue the quest content event. + var questManager = player.getQuestManager(); + switch (result) { + case BARGAIN_COMPLETE_SUCC -> questManager.queueEvent( + QuestContent.QUEST_CONTENT_BARGAIN_SUCC, + bargainId, 0); + case BARGAIN_SINGLE_FAIL -> questManager.queueEvent( + QuestContent.QUEST_CONTENT_ITEM_LESS_THAN_BARGAIN, + bargainId, 0); + case BARGAIN_COMPLETE_FAIL -> questManager.queueEvent( + QuestContent.QUEST_CONTENT_BARGAIN_FAIL, + bargainId, 0); + } + + // Return the resulting packet. + session.send(new PacketBargainOfferPriceRsp(result, bargain)); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetAllActivatedBargainDataReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetAllActivatedBargainDataReq.java new file mode 100644 index 000000000..9955f356e --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetAllActivatedBargainDataReq.java @@ -0,0 +1,17 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.*; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketGetAllActivatedBargainDataRsp; + +@Opcodes(PacketOpcodes.GetAllActivatedBargainDataReq) +public final class HandlerGetAllActivatedBargainDataReq extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) { + session.send(new PacketGetAllActivatedBargainDataRsp( + session.getPlayer() + .getPlayerProgress() + .getBargains() + .values())); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetBargainDataReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetBargainDataReq.java new file mode 100644 index 000000000..aed038239 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetBargainDataReq.java @@ -0,0 +1,29 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.*; +import emu.grasscutter.net.proto.GetBargainDataReqOuterClass.GetBargainDataReq; +import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketGetBargainDataRsp; + +@Opcodes(PacketOpcodes.GetBargainDataReq) +public final class HandlerGetBargainDataReq extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) + throws Exception { + var packet = GetBargainDataReq.parseFrom(payload); + + var bargainId = packet.getBargainId(); + var bargain = session.getPlayer() + .getPlayerProgress() + .getBargains() + .get(bargainId); + if (bargain == null) { + session.send(new PacketGetBargainDataRsp( + Retcode.RET_BARGAIN_NOT_ACTIVATED)); + return; + } + + session.send(new PacketGetBargainDataRsp(bargain)); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerItemGivingReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerItemGivingReq.java new file mode 100644 index 000000000..d80846dda --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerItemGivingReq.java @@ -0,0 +1,109 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.game.quest.enums.QuestContent; +import emu.grasscutter.net.packet.*; +import emu.grasscutter.net.proto.ItemGivingReqOuterClass.ItemGivingReq; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketItemGivingRsp; +import emu.grasscutter.server.packet.send.PacketItemGivingRsp.Mode; + +import java.util.*; + +@Opcodes(PacketOpcodes.ItemGivingReq) +public final class HandlerItemGivingReq extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + var req = ItemGivingReq.parseFrom(payload); + + var player = session.getPlayer(); + var inventory = player.getInventory(); + + var giveId = req.getGivingId(); + var items = req.getItemParamListList(); + + switch (req.getItemGivingType()) { + case QUEST -> { + var questManager = player.getQuestManager(); + var activeGivings = player.getPlayerProgress().getItemGivings(); + if (!activeGivings.containsKey(giveId)) return; + + // Check the items against the resources. + var data = GameData.getGivingDataMap().get(giveId); + if (data == null) throw new IllegalArgumentException("No giving data found for " + giveId + "."); + + switch (data.getGivingMethod()) { + case GIVING_METHOD_EXACT -> { + // Check if the player has all the items. + if (!inventory.hasAllItems(items)) { + player.sendPacket(new PacketItemGivingRsp()); + return; + } + + // Remove the items if the quest specifies. + if (data.isRemoveItem()) { + inventory.removeItems(items); + } + + // Send the response packet. + player.sendPacket(new PacketItemGivingRsp(giveId, Mode.EXACT_SUCCESS)); + // Remove the action from the active givings. + questManager.removeGivingItemAction(giveId); + // Queue the content action. + questManager.queueEvent(QuestContent.QUEST_CONTENT_FINISH_ITEM_GIVING, giveId, 0); + } + case GIVING_METHOD_VAGUE_GROUP -> { + var matchedGroups = new ArrayList(); + var givenItems = new HashMap(); + + // Resolve potential item IDs. + var groupData = GameData.getGivingGroupDataMap(); + data.getGivingGroupIds().stream() + .map(groupId -> groupData.get((int) groupId)) + .filter(Objects::nonNull) + .forEach(group -> { + var itemIds = group.getItemIds(); + + // Match item stacks to the group items. + items.forEach(param -> { + // Get the item instance. + var itemInstance = inventory.getFirstItem(param.getItemId()); + if (itemInstance == null) return; + + // Get the item ID. + var itemId = itemInstance.getItemId(); + if (!itemIds.contains(itemId)) return; + + // Add the item to the given items. + givenItems.put(itemId, param.getCount()); + matchedGroups.add(group.getId()); + }); + }); + + // Check if the player has any items. + if (givenItems.isEmpty() && matchedGroups.isEmpty()) { + player.sendPacket(new PacketItemGivingRsp()); + } else { + // Remove the items if the quest specifies. + if (data.isRemoveItem()) { + inventory.removeItems(items); + } + + // Send the response packet. + player.sendPacket(new PacketItemGivingRsp(matchedGroups.get(0), Mode.GROUP_SUCCESS)); + // Mark the giving action as completed. + questManager.markCompleted(giveId); + // Queue the content action. + questManager.queueEvent(QuestContent.QUEST_CONTENT_FINISH_ITEM_GIVING, giveId, 0); + } + } + } + } + case GADGET -> { + Grasscutter.getLogger().debug("Unimplemented gadget giving was executed for {}.", giveId); + player.sendPacket(new PacketItemGivingRsp()); + } + } + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketBargainOfferPriceRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketBargainOfferPriceRsp.java new file mode 100644 index 000000000..a9bdee9ba --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketBargainOfferPriceRsp.java @@ -0,0 +1,21 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.quest.BargainRecord; +import emu.grasscutter.net.packet.*; +import emu.grasscutter.net.proto.BargainOfferPriceRspOuterClass.BargainOfferPriceRsp; +import emu.grasscutter.net.proto.BargainResultTypeOuterClass.BargainResultType; +import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode; + +public final class PacketBargainOfferPriceRsp extends BasePacket { + public PacketBargainOfferPriceRsp(BargainResultType result, BargainRecord record) { + super(PacketOpcodes.BargainOfferPriceRsp); + + this.setData(BargainOfferPriceRsp.newBuilder() + .setRetcode(record.isFinished() ? + Retcode.RET_BARGAIN_FINISHED.getNumber() : + Retcode.RET_BARGAIN_NOT_ACTIVATED.getNumber()) + .setCurMood(record.getCurrentMood()) + .setBargainResult(result) + .setResultParam(0)); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketBargainStartNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketBargainStartNotify.java new file mode 100644 index 000000000..ec0a88d1a --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketBargainStartNotify.java @@ -0,0 +1,15 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.quest.BargainRecord; +import emu.grasscutter.net.packet.*; +import emu.grasscutter.net.proto.BargainStartNotifyOuterClass.BargainStartNotify; + +public final class PacketBargainStartNotify extends BasePacket { + public PacketBargainStartNotify(BargainRecord record) { + super(PacketOpcodes.BargainStartNotify); + + this.setData(BargainStartNotify.newBuilder() + .setBargainId(record.getBargainId()) + .setSnapshot(record.toSnapshot())); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketBargainTerminateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketBargainTerminateNotify.java new file mode 100644 index 000000000..1dff443c9 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketBargainTerminateNotify.java @@ -0,0 +1,13 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.*; +import emu.grasscutter.net.proto.BargainTerminateNotifyOuterClass.BargainTerminateNotify; + +public final class PacketBargainTerminateNotify extends BasePacket { + public PacketBargainTerminateNotify(int bargainId) { + super(PacketOpcodes.BargainTerminateNotify); + + this.setData(BargainTerminateNotify.newBuilder() + .setBargainId(bargainId)); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetAllActivatedBargainDataRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetAllActivatedBargainDataRsp.java new file mode 100644 index 000000000..1cb54b6a3 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetAllActivatedBargainDataRsp.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.quest.BargainRecord; +import emu.grasscutter.net.packet.*; +import emu.grasscutter.net.proto.GetAllActivatedBargainDataRspOuterClass.GetAllActivatedBargainDataRsp; +import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode; + +import java.util.Collection; + +public final class PacketGetAllActivatedBargainDataRsp extends BasePacket { + public PacketGetAllActivatedBargainDataRsp(Collection records) { + super(PacketOpcodes.GetAllActivatedBargainDataRsp); + + this.setData(GetAllActivatedBargainDataRsp.newBuilder() + .setRetcode(Retcode.RET_SUCC.getNumber()) + .addAllSnapshotList(records.stream() + .map(BargainRecord::toSnapshot) + .toList())); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetBargainDataRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetBargainDataRsp.java new file mode 100644 index 000000000..965ae51d4 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetBargainDataRsp.java @@ -0,0 +1,24 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.quest.BargainRecord; +import emu.grasscutter.net.packet.*; +import emu.grasscutter.net.proto.GetBargainDataRspOuterClass.GetBargainDataRsp; +import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode; + +public final class PacketGetBargainDataRsp extends BasePacket { + public PacketGetBargainDataRsp(Retcode retcode) { + super(PacketOpcodes.GetBargainDataRsp); + + this.setData(GetBargainDataRsp.newBuilder() + .setRetcode(retcode.getNumber())); + } + + public PacketGetBargainDataRsp(BargainRecord record) { + super(PacketOpcodes.GetBargainDataRsp); + + this.setData(GetBargainDataRsp.newBuilder() + .setRetcode(Retcode.RET_SUCC.getNumber()) + .setBargainId(record.getBargainId()) + .setSnapshot(record.toSnapshot())); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGivingRecordNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGivingRecordNotify.java new file mode 100644 index 000000000..ffbe29e3c --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGivingRecordNotify.java @@ -0,0 +1,17 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.*; +import emu.grasscutter.net.proto.GivingRecordNotifyOuterClass.GivingRecordNotify; +import emu.grasscutter.net.proto.GivingRecordOuterClass.GivingRecord; + +import java.util.Collection; + +public final class PacketGivingRecordNotify extends BasePacket { + public PacketGivingRecordNotify(Collection records) { + super(PacketOpcodes.GivingRecordNotify); + + this.setData(GivingRecordNotify.newBuilder() + .addAllGivingRecordList(records) + .build()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketItemGivingRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketItemGivingRsp.java new file mode 100644 index 000000000..250842a3f --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketItemGivingRsp.java @@ -0,0 +1,30 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.*; +import emu.grasscutter.net.proto.ItemGivingRspOuterClass.ItemGivingRsp; + +public final class PacketItemGivingRsp extends BasePacket { + public PacketItemGivingRsp() { + this(0, Mode.FAILURE); + } + + public PacketItemGivingRsp(int value, Mode mode) { + super(PacketOpcodes.ItemGivingRsp); + + var packet = ItemGivingRsp.newBuilder() + .setRetcode(mode == Mode.FAILURE ? 1 : 0); + if (mode == Mode.EXACT_SUCCESS) { + packet.setGivingId(value); + } else if (mode == Mode.GROUP_SUCCESS) { + packet.setGivingGroupId(value); + } + + this.setData(packet); + } + + public enum Mode { + GROUP_SUCCESS, + EXACT_SUCCESS, + FAILURE + } +}