Attempt to implement bargaining (untested)

This commit is contained in:
KingRainbow44 2023-08-13 12:28:56 -04:00
parent 40bbfd90e1
commit 597574ddda
No known key found for this signature in database
GPG Key ID: FC2CB64B00D257BE
20 changed files with 514 additions and 7 deletions

View File

@ -140,6 +140,10 @@ public final class GameData {
private static final Int2ObjectMap<AvatarTalentData> avatarTalentDataMap =
new Int2ObjectOpenHashMap<>();
@Getter
private static final Int2ObjectMap<BargainData> bargainDataMap
= new Int2ObjectOpenHashMap<>();
@Getter
private static final Int2ObjectMap<BattlePassMissionData> battlePassMissionDataMap =
new Int2ObjectOpenHashMap<>();

View File

@ -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<Integer> 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<Integer> expectedValue;
private int space;
private List<Integer> 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<Integer> 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<Integer> singleFailTalkId;
private boolean deleteItem;
private int itemId;
}

View File

@ -2,7 +2,7 @@ package emu.grasscutter.game.player;
import dev.morphia.annotations.*;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.quest.ItemGiveRecord;
import emu.grasscutter.game.quest.*;
import emu.grasscutter.game.quest.enums.QuestContent;
import it.unimi.dsi.fastutil.ints.*;
import lombok.*;
@ -29,12 +29,14 @@ public class PlayerProgress {
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

@ -170,7 +170,54 @@ public final class QuestManager extends BasePlayerManager {
}
/**
* Attempts to remove the giving action.
* 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.

View File

@ -0,0 +1,20 @@
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) {
var bargain = quest.getOwner()
.getPlayerProgress()
.getBargains()
.get(condition.getParam()[0]);
if (bargain == null) return false;
return bargain.getResult() == BargainResultType.BARGAIN_COMPLETE_FAIL;
}
}

View File

@ -0,0 +1,20 @@
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) {
var bargain = quest.getOwner()
.getPlayerProgress()
.getBargains()
.get(condition.getParam()[0]);
if (bargain == null) return false;
return bargain.getResult() == BargainResultType.BARGAIN_SINGLE_FAIL;
}
}

View File

@ -0,0 +1,20 @@
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_SUCC)
public final class ContentBargainSuccess extends BaseContent {
@Override
public boolean execute(GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) {
var bargain = quest.getOwner()
.getPlayerProgress()
.getBargains()
.get(condition.getParam()[0]);
if (bargain == null) return false;
return bargain.getResult() == BargainResultType.BARGAIN_COMPLETE_SUCC;
}
}

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<BargainRecord> records) {
super(PacketOpcodes.GetAllActivatedBargainDataRsp);
this.setData(GetAllActivatedBargainDataRsp.newBuilder()
.setRetcode(Retcode.RET_SUCC.getNumber())
.addAllSnapshotList(records.stream()
.map(BargainRecord::toSnapshot)
.toList()));
}
}

View File

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