mirror of
https://github.com/Melledy/Grasscutter.git
synced 2024-11-22 06:31:26 +00:00
Bring back dungeon drops.
This commit is contained in:
parent
b01a29c13f
commit
63b6b805cc
@ -8,6 +8,7 @@ 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.game.managers.energy.EnergyManager;
|
||||
import emu.grasscutter.game.managers.stamina.StaminaManager;
|
||||
import emu.grasscutter.plugin.PluginManager;
|
||||
@ -113,6 +114,7 @@ public final class Grasscutter {
|
||||
ResourceLoader.loadAll();
|
||||
ScriptLoader.init();
|
||||
EnergyManager.initialize();
|
||||
DungeonChallenge.initialize();
|
||||
|
||||
// Initialize database.
|
||||
DatabaseManager.initialize();
|
||||
|
22
src/main/java/emu/grasscutter/game/dungeons/DungeonDrop.java
Normal file
22
src/main/java/emu/grasscutter/game/dungeons/DungeonDrop.java
Normal file
@ -0,0 +1,22 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,21 +1,37 @@
|
||||
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 {
|
||||
|
||||
@ -25,6 +41,24 @@ 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,
|
||||
@ -67,8 +101,64 @@ public class DungeonChallenge extends WorldChallenge {
|
||||
}
|
||||
}
|
||||
|
||||
public void getStatueDrops(Player player) {
|
||||
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);
|
||||
|
||||
// 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());
|
||||
|
||||
int amount = Utils.drawRandomListElement(candidateAmounts, entry.getProbabilities());
|
||||
|
||||
if (useCondensed) {
|
||||
amount += Utils.drawRandomListElement(candidateAmounts, entry.getProbabilities());
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
@ -78,11 +168,42 @@ public class DungeonChallenge extends WorldChallenge {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get rewards.
|
||||
List<GameItem> rewards = new ArrayList<>();
|
||||
for (ItemParamData param : getScene().getDungeonData().getRewardPreview().getPreviewItems()) {
|
||||
rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1)));
|
||||
|
||||
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));
|
||||
|
||||
|
@ -4,7 +4,7 @@ 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.InterOpTypeOuterClass;
|
||||
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;
|
||||
@ -18,7 +18,7 @@ public class GadgetChest extends GadgetContent {
|
||||
super(gadget);
|
||||
}
|
||||
|
||||
public boolean onInteract(Player player, InterOpType opType) {
|
||||
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){
|
||||
@ -26,7 +26,7 @@ public class GadgetChest extends GadgetContent {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(opType == InterOpType.INTER_OP_TYPE_START && handler.isTwoStep()){
|
||||
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));
|
||||
return false;
|
||||
}else{
|
||||
|
@ -3,6 +3,7 @@ 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 {
|
||||
@ -16,7 +17,7 @@ public abstract class GadgetContent {
|
||||
return gadget;
|
||||
}
|
||||
|
||||
public abstract boolean onInteract(Player player, InterOpTypeOuterClass.InterOpType opType);
|
||||
public abstract boolean onInteract(Player player, GadgetInteractReq req);
|
||||
|
||||
public abstract void onBuildProto(SceneGadgetInfo.Builder gadgetInfo);
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import emu.grasscutter.game.inventory.GameItem;
|
||||
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 {
|
||||
@ -26,7 +26,7 @@ public class GadgetGatherPoint extends GadgetContent {
|
||||
return getGatherData().getItemId();
|
||||
}
|
||||
|
||||
public boolean onInteract(Player player, InterOpTypeOuterClass.InterOpType opType) {
|
||||
public boolean onInteract(Player player, GadgetInteractReq req) {
|
||||
GameItem item = new GameItem(gatherData.getItemId(), 1);
|
||||
|
||||
player.getInventory().addItem(item, ActionReason.Gather);
|
||||
|
@ -3,7 +3,7 @@ package emu.grasscutter.game.entity.gadget;
|
||||
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;
|
||||
@ -14,9 +14,9 @@ public class GadgetRewardStatue extends GadgetContent {
|
||||
super(gadget);
|
||||
}
|
||||
|
||||
public boolean onInteract(Player player, InterOpTypeOuterClass.InterOpType opType) {
|
||||
public boolean onInteract(Player player, GadgetInteractReq req) {
|
||||
if (player.getScene().getChallenge() != null && player.getScene().getChallenge() instanceof DungeonChallenge dungeonChallenge) {
|
||||
dungeonChallenge.getStatueDrops(player);
|
||||
dungeonChallenge.getStatueDrops(player, req);
|
||||
}
|
||||
|
||||
player.sendPacket(new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_TYPE_OPEN_STATUE));
|
||||
|
@ -4,7 +4,7 @@ import java.util.Arrays;
|
||||
|
||||
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;
|
||||
@ -35,7 +35,7 @@ public class GadgetWorktop extends GadgetContent {
|
||||
this.worktopOptions.remove(option);
|
||||
}
|
||||
|
||||
public boolean onInteract(Player player, InterOpTypeOuterClass.InterOpType opType) {
|
||||
public boolean onInteract(Player player, GadgetInteractReq req) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -990,7 +990,7 @@ public class Player {
|
||||
}
|
||||
|
||||
|
||||
public void interactWith(int gadgetEntityId, InterOpTypeOuterClass.InterOpType opType) {
|
||||
public void interactWith(int gadgetEntityId, GadgetInteractReq req) {
|
||||
GameEntity entity = getScene().getEntityById(gadgetEntityId);
|
||||
if (entity == null) {
|
||||
return;
|
||||
@ -1023,7 +1023,7 @@ public class Player {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean shouldDelete = gadget.getContent().onInteract(this, opType);
|
||||
boolean shouldDelete = gadget.getContent().onInteract(this, req);
|
||||
|
||||
if (shouldDelete) {
|
||||
entity.getScene().removeEntity(entity);
|
||||
|
@ -13,7 +13,7 @@ public class HandlerGadgetInteractReq extends PacketHandler {
|
||||
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
|
||||
GadgetInteractReq req = GadgetInteractReq.parseFrom(payload);
|
||||
|
||||
session.getPlayer().interactWith(req.getGadgetEntityId(), req.getOpType());
|
||||
session.getPlayer().interactWith(req.getGadgetEntityId(), req);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import java.nio.file.StandardCopyOption;
|
||||
import java.time.*;
|
||||
import java.time.temporal.TemporalAdjusters;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.data.DataLoader;
|
||||
@ -377,4 +378,35 @@ 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);
|
||||
}
|
||||
}
|
||||
|
4142
src/main/resources/defaults/data/DungeonDrop.json
Normal file
4142
src/main/resources/defaults/data/DungeonDrop.json
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user