Compare commits

..

No commits in common. "c2a260858c371c5c5a5ebd5a567e1840c7477df7" and "2006a2100879fbb6f1b65da3064cabfa33b2eb74" have entirely different histories.

16 changed files with 76 additions and 4463 deletions

View File

@ -8,7 +8,6 @@ import emu.grasscutter.auth.DefaultAuthentication;
import emu.grasscutter.command.CommandMap;
import emu.grasscutter.command.DefaultPermissionHandler;
import emu.grasscutter.command.PermissionHandler;
import emu.grasscutter.game.dungeons.challenge.DungeonChallenge;
import emu.grasscutter.data.ResourceLoader;
import emu.grasscutter.database.DatabaseManager;
import emu.grasscutter.game.managers.energy.EnergyManager;
@ -118,7 +117,6 @@ public final class Grasscutter {
ResourceLoader.loadAll();
ScriptLoader.init();
EnergyManager.initialize();
DungeonChallenge.initialize();
// Initialize database.
DatabaseManager.initialize();

View File

@ -1,22 +0,0 @@
package emu.grasscutter.game.dungeons;
import java.util.List;
public class DungeonDrop {
private int dungeonId;
private List<DungeonDropEntry> drops;
public int getDungeonId() {
return dungeonId;
}
public void setDungeonId(int dungeonId) {
this.dungeonId = dungeonId;
}
public List<DungeonDropEntry> getDrops() {
return drops;
}
public void setDrops(List<DungeonDropEntry> drops) {
this.drops = drops;
}
}

View File

@ -1,46 +0,0 @@
package emu.grasscutter.game.dungeons;
import java.util.List;
public class DungeonDropEntry {
private List<Integer> counts;
private List<Integer> items;
private List<Integer> probabilities;
private List<Integer> itemProbabilities;
private boolean mpDouble;
public List<Integer> getCounts() {
return counts;
}
public void setCounts(List<Integer> counts) {
this.counts = counts;
}
public List<Integer> getItems() {
return items;
}
public void setItems(List<Integer> items) {
this.items = items;
}
public List<Integer> getProbabilities() {
return probabilities;
}
public void setProbabilities(List<Integer> probabilities) {
this.probabilities = probabilities;
}
public List<Integer> getItemProbabilities() {
return itemProbabilities;
}
public void setItemProbabilities(List<Integer> itemProbabilities) {
this.itemProbabilities = itemProbabilities;
}
public boolean isMpDouble() {
return mpDouble;
}
public void setMpDouble(boolean mpDouble) {
this.mpDouble = mpDouble;
}
}

View File

@ -1,37 +1,21 @@
package emu.grasscutter.game.dungeons.challenge;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.DungeonData;
import emu.grasscutter.game.dungeons.DungeonDrop;
import emu.grasscutter.game.dungeons.DungeonDropEntry;
import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.packet.send.PacketGadgetAutoPickDropInfoNotify;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import com.google.gson.reflect.TypeToken;
public class DungeonChallenge extends WorldChallenge {
@ -41,32 +25,14 @@ public class DungeonChallenge extends WorldChallenge {
private boolean stage;
private IntSet rewardedPlayers;
private final static Int2ObjectMap<List<DungeonDropEntry>> dungeonDropData = new Int2ObjectOpenHashMap<>();
public static void initialize() {
// Read the data we need for dungeon rewards drops.
try (Reader fileReader = new InputStreamReader(DataLoader.load("DungeonDrop.json"))) {
List<DungeonDrop> dungeonDropList = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, DungeonDrop.class).getType());
for (DungeonDrop entry : dungeonDropList) {
dungeonDropData.put(entry.getDungeonId(), entry.getDrops());
}
Grasscutter.getLogger().info("Loaded {} dungeon drop data entries.", dungeonDropData.size());
}
catch (Exception ex) {
Grasscutter.getLogger().error("Unable to load dungeon drop data.", ex);
}
}
public DungeonChallenge(Scene scene, SceneGroup group,
int challengeId, int challengeIndex,
List<Integer> paramList,
int timeLimit, int goal,
List<ChallengeTrigger> challengeTriggers) {
super(scene, group, challengeId, challengeIndex, paramList, timeLimit, goal, challengeTriggers);
this.setRewardedPlayers(new IntOpenHashSet());
}
public DungeonChallenge(Scene scene, SceneGroup group,
int challengeId, int challengeIndex,
List<Integer> paramList,
int timeLimit, int goal,
List<ChallengeTrigger> challengeTriggers) {
super(scene, group, challengeId, challengeIndex, paramList, timeLimit, goal, challengeTriggers);
this.setRewardedPlayers(new IntOpenHashSet());
}
public boolean isStage() {
return this.stage;
@ -84,129 +50,42 @@ public class DungeonChallenge extends WorldChallenge {
this.rewardedPlayers = rewardedPlayers;
}
@Override
public void done() {
super.done();
if (this.isSuccess()) {
// Settle
settle();
}
}
@Override
public void done() {
super.done();
if (this.isSuccess()) {
// Settle
this.settle();
}
}
private void settle() {
if(!stage){
getScene().getDungeonSettleObservers().forEach(o -> o.onDungeonSettle(getScene()));
getScene().getScriptManager().callEvent(EventType.EVENT_DUNGEON_SETTLE,
new ScriptArgs(this.isSuccess() ? 1 : 0));
}
}
private void settle() {
if (!this.stage) {
this.getScene().getDungeonSettleObservers().forEach(o -> o.onDungeonSettle(this.getScene()));
this.getScene().getScriptManager().callEvent(EventType.EVENT_DUNGEON_SETTLE,
new ScriptArgs(this.isSuccess() ? 1 : 0));
}
}
private List<GameItem> rollRewards(boolean useCondensed) {
List<GameItem> rewards = new ArrayList<>();
int dungeonId = this.getScene().getDungeonData().getId();
// If we have specific drop data for this dungeon, we use it.
if (dungeonDropData.containsKey(dungeonId)) {
List<DungeonDropEntry> dropEntries = dungeonDropData.get(dungeonId);
public void getStatueDrops(Player player) {
DungeonData dungeonData = this.getScene().getDungeonData();
if (!this.isSuccess() || dungeonData == null || dungeonData.getRewardPreview() == null || dungeonData.getRewardPreview().getPreviewItems().length == 0) {
return;
}
// Roll for each drop group.
for (var entry : dropEntries) {
// Determine the number of drops we get for this entry.
int start = entry.getCounts().get(0);
int end = entry.getCounts().get(entry.getCounts().size() - 1);
var candidateAmounts = IntStream.range(start, end + 1).boxed().collect(Collectors.toList());
// Already rewarded
if (this.getRewardedPlayers().contains(player.getUid())) {
return;
}
int amount = Utils.drawRandomListElement(candidateAmounts, entry.getProbabilities());
List<GameItem> rewards = new ArrayList<>();
for (ItemParamData param : this.getScene().getDungeonData().getRewardPreview().getPreviewItems()) {
rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1)));
}
if (useCondensed) {
amount += Utils.drawRandomListElement(candidateAmounts, entry.getProbabilities());
}
player.getInventory().addItems(rewards, ActionReason.DungeonStatueDrop);
player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(rewards));
// Double rewards in multiplay mode, if specified.
if (entry.isMpDouble() && this.getScene().getPlayerCount() > 1) {
amount *= 2;
}
// Roll items for this group.
// Here, we have to handle stacking, or the client will not display results correctly.
// For now, we use the following logic: If the possible drop item are a list of multiple items,
// we roll them separately. If not, we stack them. This should work out in practice, at least
// for the currently existing set of dungeons.
if (entry.getItems().size() == 1) {
rewards.add(new GameItem(entry.getItems().get(0), amount));
}
else {
for (int i = 0; i < amount; i++) {
// int itemIndex = ThreadLocalRandom.current().nextInt(0, entry.getItems().size());
// int itemId = entry.getItems().get(itemIndex);
int itemId = Utils.drawRandomListElement(entry.getItems(), entry.getItemProbabilities());
rewards.add(new GameItem(itemId, 1));
}
}
}
}
// Otherwise, we fall back to the preview data.
else {
Grasscutter.getLogger().info("No drop data found or dungeon {}, falling back to preview data ...", dungeonId);
for (ItemParamData param : getScene().getDungeonData().getRewardPreview().getPreviewItems()) {
rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1)));
}
}
return rewards;
}
public void getStatueDrops(Player player, GadgetInteractReq request) {
DungeonData dungeonData = getScene().getDungeonData();
int resinCost = dungeonData.getStatueCostCount() != 0 ? dungeonData.getStatueCostCount() : 20;
if (!isSuccess() || dungeonData == null || dungeonData.getRewardPreview() == null || dungeonData.getRewardPreview().getPreviewItems().length == 0) {
return;
}
// Already rewarded
if (getRewardedPlayers().contains(player.getUid())) {
return;
}
// Get rewards.
List<GameItem> rewards = new ArrayList<>();
if (request.getIsUseCondenseResin()) {
// Check if condensed resin is usable here.
// For this, we use the following logic for now:
// The normal resin cost of the dungeon has to be 20.
if (resinCost != 20) {
return;
}
// Make sure the player has condensed resin.
GameItem condensedResin = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(220007);
if (condensedResin == null || condensedResin.getCount() <= 0) {
return;
}
// Deduct.
player.getInventory().removeItem(condensedResin, 1);
// Roll rewards.
rewards.addAll(this.rollRewards(true));
}
else {
// If the player used regular resin, try to deduct.
// Stop if insufficient resin.
boolean success = player.getResinManager().useResin(resinCost);
if (!success) {
return;
}
// Roll rewards.
rewards.addAll(this.rollRewards(false));
}
// Add rewards to player and send notification.
player.getInventory().addItems(rewards, ActionReason.DungeonStatueDrop);
player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(rewards));
getRewardedPlayers().add(player.getUid());
}
this.getRewardedPlayers().add(player.getUid());
}
}

View File

@ -4,7 +4,6 @@ import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.BossChestInfoOuterClass.BossChestInfo;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.InterOpTypeOuterClass.InterOpType;
import emu.grasscutter.net.proto.InteractTypeOuterClass;
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
@ -13,24 +12,25 @@ import emu.grasscutter.scripts.constants.ScriptGadgetState;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
public class GadgetChest extends GadgetContent {
public GadgetChest(EntityGadget gadget) {
super(gadget);
}
public boolean onInteract(Player player, GadgetInteractReq req) {
var chestInteractHandlerMap = getGadget().getScene().getWorld().getServer().getWorldDataManager().getChestInteractHandlerMap();
var handler = chestInteractHandlerMap.get(getGadget().getGadgetData().getJsonName());
if(handler == null){
Grasscutter.getLogger().warn("Could not found the handler of this type of Chests {}", getGadget().getGadgetData().getJsonName());
public boolean onInteract(Player player, InterOpType opType) {
var chestInteractHandlerMap = this.getGadget().getScene().getWorld().getServer().getWorldDataManager().getChestInteractHandlerMap();
var handler = chestInteractHandlerMap.get(this.getGadget().getGadgetData().getJsonName());
if (handler == null) {
Grasscutter.getLogger().warn("Could not found the handler of this type of Chests {}", this.getGadget().getGadgetData().getJsonName());
return false;
}
if(req.getOpType() == InterOpType.INTER_OP_TYPE_START && handler.isTwoStep()){
player.sendPacket(new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_TYPE_OPEN_CHEST, InterOpType.INTER_OP_TYPE_START));
if (opType == InterOpType.INTER_OP_TYPE_START && handler.isTwoStep()) {
player.sendPacket(new PacketGadgetInteractRsp(this.getGadget(), InteractType.INTERACT_TYPE_OPEN_CHEST, InterOpType.INTER_OP_TYPE_START));
return false;
}else{
} else {
var success = handler.onInteract(this, player);
if (!success){
if (!success) {
return false;
}
@ -58,5 +58,6 @@ public class GadgetChest extends GadgetContent {
.addAllRemainUidList(players)
.build());
}
}
}

View File

@ -3,7 +3,6 @@ package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.InterOpTypeOuterClass;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
public abstract class GadgetContent {
@ -14,10 +13,10 @@ public abstract class GadgetContent {
}
public EntityGadget getGadget() {
return gadget;
return this.gadget;
}
public abstract boolean onInteract(Player player, GadgetInteractReq req);
public abstract boolean onInteract(Player player, InterOpTypeOuterClass.InterOpType opType);
public abstract void onBuildProto(SceneGadgetInfo.Builder gadgetInfo);
}

View File

@ -8,13 +8,11 @@ import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.GatherGadgetInfoOuterClass.GatherGadgetInfo;
import emu.grasscutter.net.proto.InterOpTypeOuterClass;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
public class GadgetGatherPoint extends GadgetContent {
private final GatherData gatherData;
public GadgetGatherPoint(EntityGadget gadget) {
super(gadget);
this.gatherData = GameData.getGatherDataMap().get(gadget.getPointType());
@ -28,8 +26,8 @@ public class GadgetGatherPoint extends GadgetContent {
return this.getGatherData().getItemId();
}
public boolean onInteract(Player player, GadgetInteractReq req) {
GameItem item = new GameItem(gatherData.getItemId(), 1);
public boolean onInteract(Player player, InterOpTypeOuterClass.InterOpType opType) {
GameItem item = new GameItem(this.gatherData.getItemId(), 1);
player.getInventory().addItem(item, ActionReason.Gather);

View File

@ -4,22 +4,22 @@ import emu.grasscutter.game.dungeons.challenge.DungeonChallenge;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.InterOpTypeOuterClass;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
public class GadgetRewardStatue extends GadgetContent {
public GadgetRewardStatue(EntityGadget gadget) {
super(gadget);
}
public boolean onInteract(Player player, GadgetInteractReq req) {
public boolean onInteract(Player player, InterOpTypeOuterClass.InterOpType opType) {
if (player.getScene().getChallenge() != null && player.getScene().getChallenge() instanceof DungeonChallenge dungeonChallenge) {
dungeonChallenge.getStatueDrops(player, req);
dungeonChallenge.getStatueDrops(player);
}
player.sendPacket(new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_TYPE_OPEN_STATUE));
player.sendPacket(new PacketGadgetInteractRsp(this.getGadget(), InteractType.INTERACT_TYPE_OPEN_STATUE));
return false;
}

View File

@ -3,7 +3,6 @@ package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.InterOpTypeOuterClass;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.net.proto.WorktopInfoOuterClass.WorktopInfo;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
@ -14,9 +13,6 @@ import java.util.Arrays;
public class GadgetWorktop extends GadgetContent {
private IntSet worktopOptions;
public boolean onInteract(Player player, GadgetInteractReq req) {
return false;
}
public GadgetWorktop(EntityGadget gadget) {
super(gadget);
}
@ -39,6 +35,10 @@ public class GadgetWorktop extends GadgetContent {
this.worktopOptions.remove(option);
}
public boolean onInteract(Player player, InterOpTypeOuterClass.InterOpType opType) {
return false;
}
public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {
if (this.worktopOptions == null) {
return;

View File

@ -45,7 +45,6 @@ import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry;
import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
import emu.grasscutter.net.proto.MpSettingTypeOuterClass.MpSettingType;
@ -1027,7 +1026,7 @@ public class Player {
}
public void interactWith(int gadgetEntityId, GadgetInteractReq req) {
public void interactWith(int gadgetEntityId, InterOpTypeOuterClass.InterOpType opType) {
GameEntity entity = this.getScene().getEntityById(gadgetEntityId);
if (entity == null) {
return;
@ -1060,7 +1059,7 @@ public class Player {
return;
}
boolean shouldDelete = gadget.getContent().onInteract(this, req);
boolean shouldDelete = gadget.getContent().onInteract(this, opType);
if (shouldDelete) {
entity.getScene().removeEntity(entity);

View File

@ -366,17 +366,6 @@ public class SceneScriptManager {
}
public EntityGadget createGadget(int groupId, int blockId, SceneGadget g) {
if(g.isOneoff){
var hasEntity = getScene().getEntities().values().stream()
.filter(e -> e instanceof EntityGadget)
.filter(e -> e.getGroupId() == g.group.id)
.filter(e -> e.getConfigId() == g.config_id)
.findFirst();
if(hasEntity.isPresent()){
return null;
}
}
EntityGadget entity = new EntityGadget(this.getScene(), g.gadget_id, g.pos);
if (entity.getGadgetData() == null) {

View File

@ -11,9 +11,4 @@ public class SceneGadget extends SceneObject {
public int point_type;
public SceneBossChest boss_chest;
public int interact_id;
public boolean isOneoff;
public void setIsOneoff(boolean isOneoff){
this.isOneoff = isOneoff;
}
}

View File

@ -53,8 +53,6 @@ public class LuaSerializer implements Serializer {
object = (T) (Float) keyValue.tofloat(); // terrible...
} else if (keyValue.isstring()) {
object = (T) keyValue.tojstring();
} else if (keyValue.isboolean()) {
object = (T) (Boolean) keyValue.toboolean();
} else {
object = (T) keyValue;
}
@ -117,8 +115,6 @@ public class LuaSerializer implements Serializer {
methodAccess.invoke(object, fieldMeta.index, keyValue.toint());
} else if (fieldMeta.getType().equals(String.class)) {
methodAccess.invoke(object, fieldMeta.index, keyValue.tojstring());
} else if (fieldMeta.getType().equals(boolean.class)) {
methodAccess.invoke(object, fieldMeta.index, keyValue.toboolean());
} else {
methodAccess.invoke(object, fieldMeta.index, keyValue.tojstring());
}

View File

@ -8,10 +8,12 @@ import emu.grasscutter.server.game.GameSession;
@Opcodes(PacketOpcodes.GadgetInteractReq)
public class HandlerGadgetInteractReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
GadgetInteractReq req = GadgetInteractReq.parseFrom(payload);
session.getPlayer().interactWith(req.getGadgetEntityId(), req);
session.getPlayer().interactWith(req.getGadgetEntityId(), req.getOpType());
}
}

View File

@ -5,7 +5,6 @@ import emu.grasscutter.data.DataLoader;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.util.internal.ThreadLocalRandom;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import org.slf4j.Logger;
@ -21,7 +20,6 @@ import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.temporal.TemporalAdjusters;
import java.util.Base64;
import java.util.List;
import java.util.Locale;
import java.util.Random;
@ -404,35 +402,4 @@ public final class Utils {
return null;
}
}
/***
* Draws a random element from the given list, following the given probability distribution, if given.
* @param list The list from which to draw the element.
* @param probabilities The probability distribution. This is given as a list of probabilities of the same length it `list`.
* @return A randomly drawn element from the given list.
*/
public static <T> T drawRandomListElement(List<T> list, List<Integer> probabilities) {
// If we don't have a probability distribution, or the size of the distribution does not match
// the size of the list, we assume uniform distribution.
if (probabilities == null || probabilities.size() <= 1 || probabilities.size() != list.size()) {
int index = ThreadLocalRandom.current().nextInt(0, list.size());
return list.get(index);
}
// Otherwise, we roll with the given distribution.
int totalProbabilityMass = probabilities.stream().reduce(Integer::sum).get();
int roll = ThreadLocalRandom.current().nextInt(1, totalProbabilityMass + 1);
int currentTotalChance = 0;
for (int i = 0; i < list.size(); i++) {
currentTotalChance += probabilities.get(i);
if (roll <= currentTotalChance) {
return list.get(i);
}
}
// Should never happen.
return list.get(0);
}
}

File diff suppressed because it is too large Load Diff