Merge branch 'talking' into dev-4.0

This commit is contained in:
KingRainbow44
2023-08-15 20:46:38 -04:00
38 changed files with 1213 additions and 164 deletions

View File

@@ -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<AbilityModifierAction.Type, AbilityActionHandler> actionHandlers =
new HashMap<>();
private static final HashMap<AbilityMixinData.Type, AbilityMixinHandler> 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;
}

View File

@@ -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(

View File

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

View File

@@ -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<BagTab> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, BagTab> 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);
}
}

View File

@@ -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<GameItem> {
private final Long2ObjectMap<GameItem> store;
private final Int2ObjectMap<InventoryTab> inventoryTypes;
@@ -60,6 +61,19 @@ public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
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<GameItem> {
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<ItemParam> 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<GameItem> {
}
}
/**
* 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<ItemParam> 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<GameItem> {
* @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) {

View File

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

View File

@@ -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<Integer, ItemEntry> itemHistory;
@Setter @Transient private Player player;
private Map<Integer, ItemEntry> 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<String, Integer> questProgressCountMap;
private Map<Integer, ItemGiveRecord> itemGivings;
private Map<Integer, BargainRecord> bargains;
public PlayerProgress() {
this.questProgressCountMap = new ConcurrentHashMap<>();
this.completedDungeons = new IntArrayList();
this.itemHistory = new Int2ObjectOpenHashMap<>();
this.itemGivings = new Int2ObjectOpenHashMap<>();
this.bargains = new Int2ObjectOpenHashMap<>();
}
/**

View File

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

View File

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

View File

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

View File

@@ -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<GameMainQuest> mainQuests;
@Transient @Getter private final List<Integer> loggedQuests;
@Getter private final List<Integer> 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<Integer> 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<Integer> 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<GivingRecord> 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() {

View File

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

View File

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

View File

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

View File

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

View File

@@ -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),

View File

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

View File

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

View File

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

View File

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

View File

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