mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-05-11 22:45:54 +08:00
Compare commits
15 Commits
662c9d4ec2
...
992742d746
Author | SHA1 | Date | |
---|---|---|---|
|
992742d746 | ||
|
6149e326ba | ||
|
51637ab01e | ||
|
840bea2b0f | ||
|
e30516d698 | ||
|
36e7e028f7 | ||
|
e193781f0c | ||
|
5a58a0b765 | ||
|
8a3c25ff8e | ||
|
8840f22bba | ||
|
aae799f9a7 | ||
|
4ffda39b12 | ||
|
d7a4209138 | ||
|
47bcfe96f6 | ||
|
0c0719bd2a |
14
proto/ForgeGetQueueDataRsp.proto
Normal file
14
proto/ForgeGetQueueDataRsp.proto
Normal file
@ -0,0 +1,14 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option java_package = "emu.grasscutter.net.proto";
|
||||
|
||||
import "ForgeQueueData.proto";
|
||||
|
||||
// CmdId: 628
|
||||
// EnetChannelId: 0
|
||||
// EnetIsReliable: true
|
||||
message ForgeGetQueueDataRsp {
|
||||
int32 retcode = 15;
|
||||
uint32 max_queue_num = 13;
|
||||
map<uint32, ForgeQueueData> forge_queue_map = 11;
|
||||
}
|
14
proto/ForgeQueueDataNotify.proto
Normal file
14
proto/ForgeQueueDataNotify.proto
Normal file
@ -0,0 +1,14 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option java_package = "emu.grasscutter.net.proto";
|
||||
|
||||
import "ForgeQueueData.proto";
|
||||
|
||||
// CmdId: 633
|
||||
// EnetChannelId: 0
|
||||
// EnetIsReliable: true
|
||||
// IsAllowClient: true
|
||||
message ForgeQueueDataNotify {
|
||||
map<uint32, ForgeQueueData> forge_queue_map = 14;
|
||||
repeated uint32 removed_forge_queue_list = 8;
|
||||
}
|
14
proto/ForgeQueueManipulateReq.proto
Normal file
14
proto/ForgeQueueManipulateReq.proto
Normal file
@ -0,0 +1,14 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option java_package = "emu.grasscutter.net.proto";
|
||||
|
||||
import "ForgeQueueManipulateType.proto";
|
||||
|
||||
// CmdId: 659
|
||||
// EnetChannelId: 0
|
||||
// EnetIsReliable: true
|
||||
// IsAllowClient: true
|
||||
message ForgeQueueManipulateReq {
|
||||
uint32 forge_queue_id = 11;
|
||||
ForgeQueueManipulateType manipulate_type = 7;
|
||||
}
|
17
proto/ForgeQueueManipulateRsp.proto
Normal file
17
proto/ForgeQueueManipulateRsp.proto
Normal file
@ -0,0 +1,17 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option java_package = "emu.grasscutter.net.proto";
|
||||
|
||||
import "ForgeQueueManipulateType.proto";
|
||||
import "ItemParam.proto";
|
||||
|
||||
// CmdId: 684
|
||||
// EnetChannelId: 0
|
||||
// EnetIsReliable: true
|
||||
message ForgeQueueManipulateRsp {
|
||||
int32 retcode = 13;
|
||||
ForgeQueueManipulateType manipulate_type = 8;
|
||||
repeated ItemParam output_item_list = 6;
|
||||
repeated ItemParam return_item_list = 10;
|
||||
repeated ItemParam extra_output_item_list = 3;
|
||||
}
|
8
proto/ForgeQueueManipulateType.proto
Normal file
8
proto/ForgeQueueManipulateType.proto
Normal file
@ -0,0 +1,8 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option java_package = "emu.grasscutter.net.proto";
|
||||
|
||||
enum ForgeQueueManipulateType {
|
||||
FORGE_QUEUE_MANIPULATE_TYPE_RECEIVE_OUTPUT = 0;
|
||||
FORGE_QUEUE_MANIPULATE_TYPE_STOP_FORGE = 1;
|
||||
}
|
13
proto/ForgeStartReq.proto
Normal file
13
proto/ForgeStartReq.proto
Normal file
@ -0,0 +1,13 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option java_package = "emu.grasscutter.net.proto";
|
||||
|
||||
// CmdId: 676
|
||||
// EnetChannelId: 0
|
||||
// EnetIsReliable: true
|
||||
// IsAllowClient: true
|
||||
message ForgeStartReq {
|
||||
uint32 forge_id = 9;
|
||||
uint32 forge_count = 11;
|
||||
uint32 avatar_id = 13;
|
||||
}
|
10
proto/ForgeStartRsp.proto
Normal file
10
proto/ForgeStartRsp.proto
Normal file
@ -0,0 +1,10 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option java_package = "emu.grasscutter.net.proto";
|
||||
|
||||
// CmdId: 672
|
||||
// EnetChannelId: 0
|
||||
// EnetIsReliable: true
|
||||
message ForgeStartRsp {
|
||||
int32 retcode = 11;
|
||||
}
|
83
proxy.py
83
proxy.py
@ -20,7 +20,12 @@
|
||||
#
|
||||
##
|
||||
|
||||
from mitmproxy import http
|
||||
import collections
|
||||
import random
|
||||
from mitmproxy import http, connection, ctx, tls
|
||||
from abc import ABC, abstractmethod
|
||||
from enum import Enum
|
||||
from mitmproxy.utils import human
|
||||
from proxy_config import USE_SSL
|
||||
from proxy_config import REMOTE_HOST
|
||||
from proxy_config import REMOTE_PORT
|
||||
@ -71,6 +76,80 @@ class MlgmXyysd_Genshin_Impact_Proxy:
|
||||
flow.request.host = REMOTE_HOST
|
||||
flow.request.port = REMOTE_PORT
|
||||
|
||||
class InterceptionResult(Enum):
|
||||
SUCCESS = 1
|
||||
FAILURE = 2
|
||||
SKIPPED = 3
|
||||
|
||||
|
||||
class TlsStrategy(ABC):
|
||||
def __init__(self):
|
||||
self.history = collections.defaultdict(lambda: collections.deque(maxlen=200))
|
||||
|
||||
@abstractmethod
|
||||
def should_intercept(self, server_address: connection.Address) -> bool:
|
||||
raise NotImplementedError()
|
||||
|
||||
def record_success(self, server_address):
|
||||
self.history[server_address].append(InterceptionResult.SUCCESS)
|
||||
|
||||
def record_failure(self, server_address):
|
||||
self.history[server_address].append(InterceptionResult.FAILURE)
|
||||
|
||||
def record_skipped(self, server_address):
|
||||
self.history[server_address].append(InterceptionResult.SKIPPED)
|
||||
|
||||
|
||||
class ConservativeStrategy(TlsStrategy):
|
||||
def should_intercept(self, server_address: connection.Address) -> bool:
|
||||
return InterceptionResult.FAILURE not in self.history[server_address]
|
||||
|
||||
|
||||
class ProbabilisticStrategy(TlsStrategy):
|
||||
def __init__(self, p: float):
|
||||
self.p = p
|
||||
super().__init__()
|
||||
|
||||
def should_intercept(self, server_address: connection.Address) -> bool:
|
||||
return random.uniform(0, 1) < self.p
|
||||
|
||||
|
||||
class MaybeTls:
|
||||
strategy: TlsStrategy
|
||||
|
||||
def load(self, l):
|
||||
l.add_option(
|
||||
"tls_strategy", int, 0,
|
||||
"TLS passthrough strategy. If set to 0, connections will be passed through after the first unsuccessful "
|
||||
"handshake. If set to 0 < p <= 100, connections with be passed through with probability p.",
|
||||
)
|
||||
|
||||
def configure(self, updated):
|
||||
if "tls_strategy" not in updated:
|
||||
return
|
||||
if ctx.options.tls_strategy > 0:
|
||||
self.strategy = ProbabilisticStrategy(ctx.options.tls_strategy / 100)
|
||||
else:
|
||||
self.strategy = ConservativeStrategy()
|
||||
|
||||
def tls_clienthello(self, data: tls.ClientHelloData):
|
||||
server_address = data.context.server.peername
|
||||
if not self.strategy.should_intercept(server_address):
|
||||
ctx.log(f"TLS passthrough: {human.format_address(server_address)}.")
|
||||
data.ignore_connection = True
|
||||
self.strategy.record_skipped(server_address)
|
||||
|
||||
def tls_established_client(self, data: tls.TlsData):
|
||||
server_address = data.context.server.peername
|
||||
ctx.log(f"TLS handshake successful: {human.format_address(server_address)}")
|
||||
self.strategy.record_success(server_address)
|
||||
|
||||
def tls_failed_client(self, data: tls.TlsData):
|
||||
server_address = data.context.server.peername
|
||||
ctx.log(f"TLS handshake failed: {human.format_address(server_address)}")
|
||||
self.strategy.record_failure(server_address)
|
||||
|
||||
addons = [
|
||||
MlgmXyysd_Genshin_Impact_Proxy()
|
||||
MlgmXyysd_Genshin_Impact_Proxy(),
|
||||
MaybeTls()
|
||||
]
|
||||
|
@ -87,6 +87,7 @@ public class GameData {
|
||||
private static final Int2ObjectMap<TowerFloorData> towerFloorDataMap = new Int2ObjectOpenHashMap<>();
|
||||
private static final Int2ObjectMap<TowerLevelData> towerLevelDataMap = new Int2ObjectOpenHashMap<>();
|
||||
private static final Int2ObjectMap<TowerScheduleData> towerScheduleDataMap = new Int2ObjectOpenHashMap<>();
|
||||
private static final Int2ObjectMap<ForgeData> forgeDataMap = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
// Cache
|
||||
private static Map<Integer, List<Integer>> fetters = new HashMap<>();
|
||||
@ -367,4 +368,8 @@ public class GameData {
|
||||
public static Int2ObjectMap<QuestData> getQuestDataMap() {
|
||||
return questDataMap;
|
||||
}
|
||||
|
||||
public static Int2ObjectMap<ForgeData> getForgeDataMap() {
|
||||
return forgeDataMap;
|
||||
}
|
||||
}
|
||||
|
68
src/main/java/emu/grasscutter/data/excels/ForgeData.java
Normal file
68
src/main/java/emu/grasscutter/data/excels/ForgeData.java
Normal file
@ -0,0 +1,68 @@
|
||||
package emu.grasscutter.data.excels;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import emu.grasscutter.data.GameResource;
|
||||
import emu.grasscutter.data.ResourceType;
|
||||
import emu.grasscutter.data.ResourceType.LoadPriority;
|
||||
import emu.grasscutter.data.common.ItemParamData;
|
||||
import emu.grasscutter.data.common.OpenCondData;
|
||||
|
||||
@ResourceType(name = {"ForgeExcelConfigData.json"}, loadPriority = LoadPriority.HIGHEST)
|
||||
public class ForgeData extends GameResource {
|
||||
private int id;
|
||||
private int playerLevel;
|
||||
private int forgeType;
|
||||
private int resultItemId;
|
||||
private int resultItemCount;
|
||||
private int forgeTime;
|
||||
private int queueNum;
|
||||
private int scoinCost;
|
||||
private int priority;
|
||||
private List<ItemParamData> materialItems;
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public int getPlayerLevel() {
|
||||
return playerLevel;
|
||||
}
|
||||
|
||||
public int getForgeType() {
|
||||
return forgeType;
|
||||
}
|
||||
|
||||
public int getResultItemId() {
|
||||
return resultItemId;
|
||||
}
|
||||
|
||||
public int getResultItemCount() {
|
||||
return resultItemCount;
|
||||
}
|
||||
|
||||
public int getForgeTime() {
|
||||
return forgeTime;
|
||||
}
|
||||
|
||||
public int getQueueNum() {
|
||||
return queueNum;
|
||||
}
|
||||
|
||||
public int getScoinCost() {
|
||||
return scoinCost;
|
||||
}
|
||||
|
||||
public int getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
public List<ItemParamData> getMaterialItems() {
|
||||
return materialItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package emu.grasscutter.game.managers.ForgingManager;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
import emu.grasscutter.utils.Utils;
|
||||
|
||||
@Entity
|
||||
public class ActiveForgeData {
|
||||
private int forgeId;
|
||||
private int avatarId;
|
||||
private int count;
|
||||
|
||||
private int startTime;
|
||||
private int forgeTime;
|
||||
|
||||
private int lastUnfinishedCount;
|
||||
private boolean changed;
|
||||
|
||||
public int getFinishedCount(int currentTime) {
|
||||
int timeDelta = currentTime - this.startTime;
|
||||
int finishedCount = (int)Math.floor(timeDelta / this.forgeTime);
|
||||
|
||||
return Math.min(finishedCount, this.count);
|
||||
}
|
||||
|
||||
public int getUnfinishedCount(int currentTime) {
|
||||
return this.count - this.getFinishedCount(currentTime);
|
||||
}
|
||||
|
||||
public int getNextFinishTimestamp(int currentTime) {
|
||||
return
|
||||
(currentTime >= this.getTotalFinishTimestamp())
|
||||
? this.getTotalFinishTimestamp()
|
||||
: (this.getFinishedCount(currentTime) * this.forgeTime + this.forgeTime + this.startTime);
|
||||
}
|
||||
|
||||
public int getTotalFinishTimestamp() {
|
||||
return this.startTime + this.forgeTime * this.count;
|
||||
}
|
||||
|
||||
public int getForgeId() {
|
||||
return this.forgeId;
|
||||
}
|
||||
public void setForgeId(int value) {
|
||||
this.forgeId = value;
|
||||
}
|
||||
|
||||
public int getAvatarId() {
|
||||
return this.avatarId;
|
||||
}
|
||||
public void setAvatarId(int value) {
|
||||
this.avatarId = value;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
public void setCount(int value) {
|
||||
this.count = value;
|
||||
}
|
||||
|
||||
public int getStartTime() {
|
||||
return this.startTime;
|
||||
}
|
||||
public void setStartTime(int value) {
|
||||
this.startTime = value;
|
||||
}
|
||||
|
||||
public int getForgeTime() {
|
||||
return this.forgeTime;
|
||||
}
|
||||
public void setForgeTime(int value) {
|
||||
this.forgeTime = value;
|
||||
}
|
||||
|
||||
public boolean isChanged() {
|
||||
return this.changed;
|
||||
}
|
||||
public void setChanged(boolean value) {
|
||||
this.changed = value;
|
||||
}
|
||||
|
||||
public boolean updateChanged(int currentTime) {
|
||||
int currentUnfinished = this.getUnfinishedCount(currentTime);
|
||||
|
||||
if (currentUnfinished != this.lastUnfinishedCount) {
|
||||
this.changed = true;
|
||||
this.lastUnfinishedCount = currentUnfinished;
|
||||
}
|
||||
|
||||
return this.changed;
|
||||
}
|
||||
}
|
@ -0,0 +1,331 @@
|
||||
package emu.grasscutter.game.managers.ForgingManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.mongodb.QueryBuilder;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.common.ItemParamData;
|
||||
import emu.grasscutter.data.excels.ForgeData;
|
||||
import emu.grasscutter.data.excels.ItemData;
|
||||
import emu.grasscutter.game.inventory.GameItem;
|
||||
import emu.grasscutter.game.inventory.ItemType;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.net.proto.ForgeStartReqOuterClass;
|
||||
import emu.grasscutter.net.proto.ForgeQueueDataOuterClass.ForgeQueueData;
|
||||
import emu.grasscutter.net.proto.ForgeQueueManipulateReqOuterClass.ForgeQueueManipulateReq;
|
||||
import emu.grasscutter.net.proto.ForgeQueueManipulateTypeOuterClass.ForgeQueueManipulateType;
|
||||
import emu.grasscutter.net.proto.ForgeStartReqOuterClass.ForgeStartReq;
|
||||
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
|
||||
import emu.grasscutter.server.packet.send.PacketForgeDataNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketForgeFormulaDataNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketForgeGetQueueDataRsp;
|
||||
import emu.grasscutter.server.packet.send.PacketForgeQueueDataNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketForgeQueueManipulateRsp;
|
||||
import emu.grasscutter.server.packet.send.PacketForgeStartRsp;
|
||||
import emu.grasscutter.utils.Utils;
|
||||
|
||||
public class ForgingManager {
|
||||
private final Player player;
|
||||
|
||||
public ForgingManager(Player player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
/**********
|
||||
Blueprint unlocking.
|
||||
**********/
|
||||
public synchronized boolean unlockForgingBlueprint(GameItem blueprintItem) {
|
||||
// Make sure this is actually a forging blueprint.
|
||||
if (!blueprintItem.getItemData().getItemUse().get(0).getUseOp().equals("ITEM_USE_UNLOCK_FORGE")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determine the forging item we should unlock.
|
||||
int forgeId = Integer.parseInt(blueprintItem.getItemData().getItemUse().get(0).getUseParam().get(0));
|
||||
|
||||
// Remove the blueprint from the player's inventory.
|
||||
// We need to do this here, before sending ForgeFormulaDataNotify, or the the forging UI won't correctly
|
||||
// update when unlocking the blueprint.
|
||||
player.getInventory().removeItem(blueprintItem, 1);
|
||||
|
||||
// Tell the client that this blueprint is now unlocked and add the unlocked item to the player.
|
||||
this.player.getUnlockedForgingBlueprints().add(forgeId);
|
||||
this.player.sendPacket(new PacketForgeFormulaDataNotify(forgeId));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**********
|
||||
Communicate forging information to the client.
|
||||
**********/
|
||||
private synchronized int determineNumberOfQueues() {
|
||||
int adventureRank = player.getLevel();
|
||||
return
|
||||
(adventureRank >= 15) ? 4
|
||||
: (adventureRank >= 10) ? 3
|
||||
: (adventureRank >= 5) ? 2
|
||||
: 1;
|
||||
}
|
||||
|
||||
private synchronized Map<Integer, ForgeQueueData> determineCurrentForgeQueueData() {
|
||||
Map<Integer, ForgeQueueData> res = new HashMap<>();
|
||||
int currentTime = Utils.getCurrentSeconds();
|
||||
|
||||
// Create queue information for all active forges.
|
||||
for (int i = 0; i < this.player.getActiveForges().size(); i++) {
|
||||
ActiveForgeData activeForge = this.player.getActiveForges().get(i);
|
||||
|
||||
ForgeQueueData data = ForgeQueueData.newBuilder()
|
||||
.setQueueId(i + 1)
|
||||
.setForgeId(activeForge.getForgeId())
|
||||
.setFinishCount(activeForge.getFinishedCount(currentTime))
|
||||
.setUnfinishCount(activeForge.getUnfinishedCount(currentTime))
|
||||
.setTotalFinishTimestamp(activeForge.getTotalFinishTimestamp())
|
||||
.setNextFinishTimestamp(activeForge.getNextFinishTimestamp(currentTime))
|
||||
.setAvatarId(activeForge.getAvatarId())
|
||||
.build();
|
||||
|
||||
res.put(i + 1, data);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public synchronized void sendForgeDataNotify() {
|
||||
// Determine the number of queues and unlocked items.
|
||||
int numQueues = this.determineNumberOfQueues();
|
||||
var unlockedItems = this.player.getUnlockedForgingBlueprints();
|
||||
var queueData = this.determineCurrentForgeQueueData();
|
||||
|
||||
// Send notification.
|
||||
this.player.sendPacket(new PacketForgeDataNotify(unlockedItems, numQueues, queueData));
|
||||
}
|
||||
|
||||
public synchronized void handleForgeGetQueueDataReq() {
|
||||
// Determine the number of queues.
|
||||
int numQueues = this.determineNumberOfQueues();
|
||||
var queueData = this.determineCurrentForgeQueueData();
|
||||
|
||||
// Reply.
|
||||
this.player.sendPacket(new PacketForgeGetQueueDataRsp(Retcode.RET_SUCC, numQueues, queueData));
|
||||
}
|
||||
|
||||
/**********
|
||||
Initiate forging process.
|
||||
**********/
|
||||
private synchronized void sendForgeQueueDataNotify() {
|
||||
var queueData = this.determineCurrentForgeQueueData();
|
||||
this.player.sendPacket(new PacketForgeQueueDataNotify(queueData, List.of()));
|
||||
}
|
||||
private synchronized void sendForgeQueueDataNotify(boolean hasRemoved) {
|
||||
var queueData = this.determineCurrentForgeQueueData();
|
||||
|
||||
if (hasRemoved) {
|
||||
this.player.sendPacket(new PacketForgeQueueDataNotify(Map.of(), List.of(1, 2, 3, 4)));
|
||||
}
|
||||
|
||||
this.player.sendPacket(new PacketForgeQueueDataNotify(queueData, List.of()));
|
||||
}
|
||||
|
||||
public synchronized void handleForgeStartReq(ForgeStartReq req) {
|
||||
// Refuse if all queues are already full.
|
||||
if (this.player.getActiveForges().size() >= this.determineNumberOfQueues()) {
|
||||
this.player.sendPacket(new PacketForgeStartRsp(Retcode.RET_FORGE_QUEUE_FULL));
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the required forging information for the target item.
|
||||
if (!GameData.getForgeDataMap().containsKey(req.getForgeId())) {
|
||||
this.player.sendPacket(new PacketForgeStartRsp(Retcode.RET_FAIL)); //ToDo: Probably the wrong return code.
|
||||
return;
|
||||
}
|
||||
|
||||
ForgeData forgeData = GameData.getForgeDataMap().get(req.getForgeId());
|
||||
|
||||
// Check if we have enough of each material.
|
||||
for (var material : forgeData.getMaterialItems()) {
|
||||
if (material.getItemId() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int currentCount = this.player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(material.getItemId()).getCount();
|
||||
|
||||
if (currentCount < material.getCount() * req.getForgeCount()) {
|
||||
this.player.sendPacket(new PacketForgeStartRsp(Retcode.RET_FORGE_POINT_NOT_ENOUGH)); //ToDo: Probably the wrong return code.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we have enough Mora.
|
||||
if (this.player.getMora() < forgeData.getScoinCost() * req.getForgeCount()) {
|
||||
this.player.sendPacket(new PacketForgeStartRsp(Retcode.RET_FORGE_POINT_NOT_ENOUGH)); //ToDo: Probably the wrong return code.
|
||||
return;
|
||||
}
|
||||
|
||||
// Consume material and Mora.
|
||||
for (var material : forgeData.getMaterialItems()) {
|
||||
if (material.getItemId() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
GameItem item = this.player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(material.getItemId());
|
||||
this.player.getInventory().removeItem(item, material.getCount() * req.getForgeCount());
|
||||
}
|
||||
|
||||
this.player.setMora(this.player.getMora() - forgeData.getScoinCost() * req.getForgeCount());
|
||||
|
||||
// Create and add active forge.
|
||||
ActiveForgeData activeForge = new ActiveForgeData();
|
||||
activeForge.setForgeId(req.getForgeId());
|
||||
activeForge.setAvatarId(req.getAvatarId());
|
||||
activeForge.setCount(req.getForgeCount());
|
||||
activeForge.setStartTime(Utils.getCurrentSeconds());
|
||||
activeForge.setForgeTime(forgeData.getForgeTime());
|
||||
|
||||
this.player.getActiveForges().add(activeForge);
|
||||
|
||||
// Done.
|
||||
this.sendForgeQueueDataNotify();
|
||||
this.player.sendPacket(new PacketForgeStartRsp(Retcode.RET_SUCC));
|
||||
}
|
||||
|
||||
/**********
|
||||
Forge queue manipulation (obtaining results and cancelling forges).
|
||||
**********/
|
||||
private synchronized void obtainItems(int queueId) {
|
||||
// Determin how many items are finished.
|
||||
int currentTime = Utils.getCurrentSeconds();
|
||||
ActiveForgeData forge = this.player.getActiveForges().get(queueId - 1);
|
||||
|
||||
int finished = forge.getFinishedCount(currentTime);
|
||||
int unfinished = forge.getUnfinishedCount(currentTime);
|
||||
|
||||
// Sanity check: Are any items finished?
|
||||
if (finished <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Give finished items to the player.
|
||||
ForgeData data = GameData.getForgeDataMap().get(forge.getForgeId());
|
||||
ItemData resultItemData = GameData.getItemDataMap().get(data.getResultItemId());
|
||||
|
||||
GameItem addItem = new GameItem(resultItemData, data.getResultItemCount() * finished);
|
||||
this.player.getInventory().addItem(addItem);
|
||||
|
||||
// Replace active forge with a new one for the unfinished items, if there are any.
|
||||
if (unfinished > 0) {
|
||||
ActiveForgeData remainingForge = new ActiveForgeData();
|
||||
|
||||
remainingForge.setForgeId(forge.getForgeId());
|
||||
remainingForge.setAvatarId(forge.getAvatarId());
|
||||
remainingForge.setCount(unfinished);
|
||||
remainingForge.setForgeTime(forge.getForgeTime());
|
||||
remainingForge.setStartTime(forge.getStartTime() + finished * forge.getForgeTime());
|
||||
|
||||
this.player.getActiveForges().set(queueId - 1, remainingForge);
|
||||
this.sendForgeQueueDataNotify();
|
||||
}
|
||||
// Otherwise, completely remove it.
|
||||
else {
|
||||
this.player.getActiveForges().remove(queueId - 1);
|
||||
// this.sendForgeQueueDataNotify(queueId);
|
||||
this.sendForgeQueueDataNotify(true);
|
||||
}
|
||||
|
||||
// Send response.
|
||||
this.player.sendPacket(new PacketForgeQueueManipulateRsp(Retcode.RET_SUCC, ForgeQueueManipulateType.FORGE_QUEUE_MANIPULATE_TYPE_RECEIVE_OUTPUT, List.of(addItem), List.of(), List.of()));
|
||||
}
|
||||
|
||||
private synchronized void cancelForge(int queueId) {
|
||||
// Make sure there are no unfinished items.
|
||||
int currentTime = Utils.getCurrentSeconds();
|
||||
ActiveForgeData forge = this.player.getActiveForges().get(queueId - 1);
|
||||
|
||||
if (forge.getFinishedCount(currentTime) > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Return material items to the player.
|
||||
ForgeData data = GameData.getForgeDataMap().get(forge.getForgeId());
|
||||
|
||||
var returnItems = new ArrayList<GameItem>();
|
||||
for (var material : data.getMaterialItems()) {
|
||||
if (material.getItemId() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ItemData resultItemData = GameData.getItemDataMap().get(material.getItemId());
|
||||
GameItem returnItem = new GameItem(resultItemData, material.getItemCount() * forge.getCount());
|
||||
|
||||
this.player.getInventory().addItem(returnItem);
|
||||
returnItems.add(returnItem);
|
||||
}
|
||||
|
||||
// Return Mora to the player.
|
||||
this.player.setMora(this.player.getMora() + data.getScoinCost() * forge.getCount());
|
||||
|
||||
ItemData moraItem = GameData.getItemDataMap().get(202);
|
||||
GameItem returnMora = new GameItem(moraItem, data.getScoinCost() * forge.getCount());
|
||||
returnItems.add(returnMora);
|
||||
|
||||
// Remove the forge queue.
|
||||
this.player.getActiveForges().remove(queueId - 1);
|
||||
this.sendForgeQueueDataNotify(true);
|
||||
|
||||
// Send response.
|
||||
this.player.sendPacket(new PacketForgeQueueManipulateRsp(Retcode.RET_SUCC, ForgeQueueManipulateType.FORGE_QUEUE_MANIPULATE_TYPE_STOP_FORGE, List.of(), returnItems, List.of()));
|
||||
}
|
||||
|
||||
public synchronized void handleForgeQueueManipulateReq(ForgeQueueManipulateReq req) {
|
||||
// Get info from the request.
|
||||
int queueId = req.getForgeQueueId();
|
||||
var manipulateType = req.getManipulateType();
|
||||
|
||||
// Handle according to the manipulation type.
|
||||
switch (manipulateType) {
|
||||
case FORGE_QUEUE_MANIPULATE_TYPE_RECEIVE_OUTPUT:
|
||||
this.obtainItems(queueId);
|
||||
break;
|
||||
case FORGE_QUEUE_MANIPULATE_TYPE_STOP_FORGE:
|
||||
this.cancelForge(queueId);
|
||||
break;
|
||||
default:
|
||||
break; //Should never happen.
|
||||
}
|
||||
}
|
||||
|
||||
/**********
|
||||
Periodic forging updates.
|
||||
**********/
|
||||
public synchronized void sendPlayerForgingUpdate() {
|
||||
int currentTime = Utils.getCurrentSeconds();
|
||||
|
||||
// Determine if sending an update is necessary.
|
||||
// We only send an update if there are forges in the forge queue
|
||||
// that have changed since the last notification.
|
||||
if (this.player.getActiveForges().size() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean hasChanges = this.player.getActiveForges().stream()
|
||||
.filter(forge -> forge.updateChanged(currentTime))
|
||||
.findAny()
|
||||
.isPresent();
|
||||
|
||||
if (!hasChanges) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Send notification.
|
||||
this.sendForgeQueueDataNotify();
|
||||
|
||||
// Reset changed flags.
|
||||
this.player.getActiveForges().stream()
|
||||
.forEach(forge -> forge.setChanged(false));
|
||||
}
|
||||
}
|
@ -822,6 +822,7 @@ public class InventoryManager {
|
||||
}
|
||||
|
||||
int used = 0;
|
||||
boolean useSuccess = false;
|
||||
|
||||
// Use
|
||||
switch (useItem.getItemData().getMaterialType()) {
|
||||
@ -852,15 +853,8 @@ public class InventoryManager {
|
||||
|
||||
// Handle forging blueprints.
|
||||
if (useItem.getItemData().getItemUse().get(0).getUseOp().equals("ITEM_USE_UNLOCK_FORGE")) {
|
||||
// Determine the forging item we should unlock.
|
||||
int forgeId = Integer.parseInt(useItem.getItemData().getItemUse().get(0).getUseParam().get(0));
|
||||
|
||||
// Tell the client that this blueprint is now unlocked and add the unlocked item to the player.
|
||||
player.sendPacket(new PacketForgeFormulaDataNotify(forgeId));
|
||||
player.getUnlockedForgingBlueprints().add(forgeId);
|
||||
|
||||
// Use up the blueprint item.
|
||||
used = 1;
|
||||
// Unlock.
|
||||
useSuccess = player.getForgingManager().unlockForgingBlueprint(useItem);
|
||||
}
|
||||
break;
|
||||
case MATERIAL_CHEST:
|
||||
@ -927,10 +921,15 @@ public class InventoryManager {
|
||||
used = 1;
|
||||
}
|
||||
|
||||
// If we used at least one item, or one of the methods called here reports using the item successfully,
|
||||
// we return the item to make UseItemRsp a success.
|
||||
if (used > 0) {
|
||||
player.getInventory().removeItem(useItem, used);
|
||||
return useItem;
|
||||
}
|
||||
if (useSuccess) {
|
||||
return useItem;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -30,6 +30,8 @@ import emu.grasscutter.game.managers.InsectCaptureManager;
|
||||
import emu.grasscutter.game.managers.StaminaManager.StaminaManager;
|
||||
import emu.grasscutter.game.managers.SotSManager;
|
||||
import emu.grasscutter.game.managers.EnergyManager.EnergyManager;
|
||||
import emu.grasscutter.game.managers.ForgingManager.ActiveForgeData;
|
||||
import emu.grasscutter.game.managers.ForgingManager.ForgingManager;
|
||||
import emu.grasscutter.game.props.ActionReason;
|
||||
import emu.grasscutter.game.props.EntityType;
|
||||
import emu.grasscutter.game.props.PlayerProperty;
|
||||
@ -91,6 +93,7 @@ public class Player {
|
||||
private Set<Integer> flyCloakList;
|
||||
private Set<Integer> costumeList;
|
||||
private Set<Integer> unlockedForgingBlueprints;
|
||||
private List<ActiveForgeData> activeForges;
|
||||
|
||||
private Integer widgetId;
|
||||
|
||||
@ -153,6 +156,7 @@ public class Player {
|
||||
@Transient private MapMarksManager mapMarksManager;
|
||||
@Transient private StaminaManager staminaManager;
|
||||
@Transient private EnergyManager energyManager;
|
||||
@Transient private ForgingManager forgingManager;
|
||||
@Transient private DeforestationManager deforestationManager;
|
||||
|
||||
private long springLastUsed;
|
||||
@ -186,6 +190,7 @@ public class Player {
|
||||
this.flyCloakList = new HashSet<>();
|
||||
this.costumeList = new HashSet<>();
|
||||
this.unlockedForgingBlueprints = new HashSet<>();
|
||||
this.activeForges = new ArrayList<>();
|
||||
|
||||
this.setSceneId(3);
|
||||
this.setRegionId(1);
|
||||
@ -209,6 +214,7 @@ public class Player {
|
||||
this.staminaManager = new StaminaManager(this);
|
||||
this.sotsManager = new SotSManager(this);
|
||||
this.energyManager = new EnergyManager(this);
|
||||
this.forgingManager = new ForgingManager(this);
|
||||
}
|
||||
|
||||
// On player creation
|
||||
@ -240,6 +246,7 @@ public class Player {
|
||||
this.sotsManager = new SotSManager(this);
|
||||
this.energyManager = new EnergyManager(this);
|
||||
this.deforestationManager = new DeforestationManager(this);
|
||||
this.forgingManager = new ForgingManager(this);
|
||||
}
|
||||
|
||||
public int getUid() {
|
||||
@ -519,7 +526,11 @@ public class Player {
|
||||
}
|
||||
|
||||
public Set<Integer> getUnlockedForgingBlueprints() {
|
||||
return unlockedForgingBlueprints;
|
||||
return this.unlockedForgingBlueprints;
|
||||
}
|
||||
|
||||
public List<ActiveForgeData> getActiveForges() {
|
||||
return this.activeForges;
|
||||
}
|
||||
|
||||
public MpSettingType getMpSetting() {
|
||||
@ -1120,6 +1131,10 @@ public class Player {
|
||||
return this.energyManager;
|
||||
}
|
||||
|
||||
public ForgingManager getForgingManager() {
|
||||
return this.forgingManager;
|
||||
}
|
||||
|
||||
public AbilityManager getAbilityManager() {
|
||||
return abilityManager;
|
||||
}
|
||||
@ -1179,6 +1194,9 @@ public class Player {
|
||||
this.save();
|
||||
this.sendPacket(new PacketAvatarExpeditionDataNotify(this));
|
||||
}
|
||||
|
||||
// Send updated forge queue data, if necessary.
|
||||
this.getForgingManager().sendPlayerForgingUpdate();
|
||||
}
|
||||
|
||||
|
||||
@ -1265,7 +1283,7 @@ public class Player {
|
||||
session.send(new PacketWidgetGadgetAllDataNotify());
|
||||
session.send(new PacketPlayerHomeCompInfoNotify(this));
|
||||
session.send(new PacketHomeComfortInfoNotify(this));
|
||||
session.send(new PacketForgeDataNotify(this));
|
||||
this.forgingManager.sendForgeDataNotify();
|
||||
|
||||
getTodayMoonCard(); // The timer works at 0:0, some users log in after that, use this method to check if they have received a reward today or not. If not, send the reward.
|
||||
|
||||
|
@ -0,0 +1,14 @@
|
||||
package emu.grasscutter.server.packet.recv;
|
||||
|
||||
import emu.grasscutter.net.packet.Opcodes;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.net.packet.PacketHandler;
|
||||
import emu.grasscutter.server.game.GameSession;
|
||||
|
||||
@Opcodes(PacketOpcodes.ForgeGetQueueDataReq)
|
||||
public class HandlerForgeGetQueueDataReq extends PacketHandler {
|
||||
@Override
|
||||
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
|
||||
session.getPlayer().getForgingManager().handleForgeGetQueueDataReq();
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package emu.grasscutter.server.packet.recv;
|
||||
|
||||
import emu.grasscutter.net.packet.Opcodes;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.net.proto.ForgeQueueManipulateReqOuterClass.ForgeQueueManipulateReq;
|
||||
import emu.grasscutter.net.packet.PacketHandler;
|
||||
import emu.grasscutter.server.game.GameSession;
|
||||
|
||||
@Opcodes(PacketOpcodes.ForgeQueueManipulateReq)
|
||||
public class HandlerForgeQueueManipulateReq extends PacketHandler {
|
||||
@Override
|
||||
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
|
||||
ForgeQueueManipulateReq req = ForgeQueueManipulateReq.parseFrom(payload);
|
||||
session.getPlayer().getForgingManager().handleForgeQueueManipulateReq(req);
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package emu.grasscutter.server.packet.recv;
|
||||
|
||||
import java.lang.invoke.StringConcatFactory;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.net.packet.Opcodes;
|
||||
import emu.grasscutter.net.packet.PacketHandler;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.net.proto.ForgeStartReqOuterClass;
|
||||
import emu.grasscutter.net.proto.DeleteFriendReqOuterClass;
|
||||
import emu.grasscutter.server.game.GameSession;
|
||||
import emu.grasscutter.server.packet.send.PacketDelMailRsp;
|
||||
|
||||
@Opcodes(PacketOpcodes.ForgeStartReq)
|
||||
public class HandlerForgeStartReq extends PacketHandler {
|
||||
|
||||
@Override
|
||||
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
|
||||
ForgeStartReqOuterClass.ForgeStartReq req = ForgeStartReqOuterClass.ForgeStartReq.parseFrom(payload);
|
||||
session.getPlayer().getForgingManager().handleForgeStartReq(req);
|
||||
}
|
||||
|
||||
}
|
@ -1,30 +1,27 @@
|
||||
package emu.grasscutter.server.packet.send;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.net.packet.BasePacket;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.net.proto.ForgeDataNotifyOuterClass.ForgeDataNotify;
|
||||
import emu.grasscutter.net.proto.ForgeQueueDataOuterClass.ForgeQueueData;
|
||||
|
||||
public class PacketForgeDataNotify extends BasePacket {
|
||||
|
||||
public PacketForgeDataNotify(Player player) {
|
||||
public PacketForgeDataNotify(Iterable<Integer> unlockedItem, int numQueues, Map<Integer, ForgeQueueData> queueData) {
|
||||
super(PacketOpcodes.ForgeDataNotify);
|
||||
|
||||
int adventureRank = player.getLevel();
|
||||
int numQueues =
|
||||
(adventureRank >= 15) ? 4
|
||||
: (adventureRank >= 10) ? 3
|
||||
: (adventureRank >= 5) ? 2
|
||||
: 1;
|
||||
ForgeDataNotify.Builder builder = ForgeDataNotify.newBuilder()
|
||||
.addAllForgeIdList(unlockedItem)
|
||||
.setMaxQueueNum(numQueues);
|
||||
|
||||
ForgeDataNotify proto = ForgeDataNotify.newBuilder()
|
||||
.addAllForgeIdList(player.getUnlockedForgingBlueprints())
|
||||
.setMaxQueueNum(numQueues)
|
||||
.build();
|
||||
for (int queueId : queueData.keySet()) {
|
||||
var data = queueData.get(queueId);
|
||||
builder.putForgeQueueMap(queueId, data);
|
||||
}
|
||||
|
||||
// ToDo: Add the information for the actual forging queues
|
||||
// and ongoing forges.
|
||||
|
||||
this.setData(proto);
|
||||
this.setData(builder.build());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
package emu.grasscutter.server.packet.send;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import emu.grasscutter.net.packet.BasePacket;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.net.proto.ForgeGetQueueDataRspOuterClass.ForgeGetQueueDataRsp;
|
||||
import emu.grasscutter.net.proto.ForgeQueueDataOuterClass.ForgeQueueData;
|
||||
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
|
||||
|
||||
public class PacketForgeGetQueueDataRsp extends BasePacket {
|
||||
|
||||
public PacketForgeGetQueueDataRsp(Retcode retcode, int numQueues, Map<Integer, ForgeQueueData> queueData) {
|
||||
super(PacketOpcodes.ForgeGetQueueDataRsp);
|
||||
|
||||
ForgeGetQueueDataRsp.Builder builder = ForgeGetQueueDataRsp.newBuilder()
|
||||
.setRetcode(retcode.getNumber())
|
||||
.setMaxQueueNum(numQueues);
|
||||
|
||||
for (int queueId : queueData.keySet()) {
|
||||
var data = queueData.get(queueId);
|
||||
builder.putForgeQueueMap(queueId, data);
|
||||
}
|
||||
|
||||
this.setData(builder.build());
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package emu.grasscutter.server.packet.send;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import emu.grasscutter.net.packet.BasePacket;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.net.proto.ForgeQueueDataNotifyOuterClass.ForgeQueueDataNotify;
|
||||
import emu.grasscutter.net.proto.ForgeQueueDataOuterClass.ForgeQueueData;
|
||||
|
||||
public class PacketForgeQueueDataNotify extends BasePacket {
|
||||
|
||||
public PacketForgeQueueDataNotify(Map<Integer, ForgeQueueData> queueData, List<Integer> removedQueues) {
|
||||
super(PacketOpcodes.ForgeQueueDataNotify);
|
||||
|
||||
ForgeQueueDataNotify.Builder builder = ForgeQueueDataNotify.newBuilder()
|
||||
.addAllRemovedForgeQueueList(removedQueues);
|
||||
|
||||
for (int queueId : queueData.keySet()) {
|
||||
var data = queueData.get(queueId);
|
||||
builder.putForgeQueueMap(queueId, data);
|
||||
}
|
||||
|
||||
this.setData(builder.build());
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package emu.grasscutter.server.packet.send;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import emu.grasscutter.game.inventory.GameItem;
|
||||
import emu.grasscutter.net.packet.BasePacket;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.net.proto.ForgeQueueManipulateRspOuterClass.ForgeQueueManipulateRsp;
|
||||
import emu.grasscutter.net.proto.ForgeQueueManipulateTypeOuterClass.ForgeQueueManipulateType;
|
||||
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
|
||||
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
|
||||
|
||||
public class PacketForgeQueueManipulateRsp extends BasePacket {
|
||||
|
||||
public PacketForgeQueueManipulateRsp(Retcode retcode, ForgeQueueManipulateType type, List<GameItem> output, List<GameItem> refund, List<GameItem> extra) {
|
||||
super(PacketOpcodes.ForgeQueueManipulateRsp);
|
||||
|
||||
ForgeQueueManipulateRsp.Builder builder = ForgeQueueManipulateRsp.newBuilder()
|
||||
.setRetcode(retcode.getNumber())
|
||||
.setManipulateType(type);
|
||||
|
||||
for (GameItem item : output) {
|
||||
ItemParam toAdd = ItemParam.newBuilder()
|
||||
.setItemId(item.getItemId())
|
||||
.setCount(item.getCount())
|
||||
.build();
|
||||
|
||||
builder.addOutputItemList(toAdd);
|
||||
}
|
||||
|
||||
for (GameItem item : refund) {
|
||||
ItemParam toAdd = ItemParam.newBuilder()
|
||||
.setItemId(item.getItemId())
|
||||
.setCount(item.getCount())
|
||||
.build();
|
||||
|
||||
builder.addReturnItemList(toAdd);
|
||||
}
|
||||
|
||||
// ToDo: Add extra items when once we have handling for it.
|
||||
|
||||
this.setData(builder.build());
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package emu.grasscutter.server.packet.send;
|
||||
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.net.packet.BasePacket;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.net.proto.ForgeStartRspOuterClass.ForgeStartRsp;
|
||||
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class PacketForgeStartRsp extends BasePacket {
|
||||
|
||||
public PacketForgeStartRsp(Retcode retcode) {
|
||||
super(PacketOpcodes.ForgeStartRsp);
|
||||
|
||||
ForgeStartRsp proto = ForgeStartRsp.newBuilder()
|
||||
.setRetcode(retcode.getNumber())
|
||||
.build();
|
||||
|
||||
this.setData(proto);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user