diff --git a/data/gacha_records.html b/data/gacha_records.html
new file mode 100644
index 000000000..21270a046
--- /dev/null
+++ b/data/gacha_records.html
@@ -0,0 +1,176 @@
+
+
+
+
+
+
+
+
+
Gacha Records
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/emu/grasscutter/database/DatabaseHelper.java b/src/main/java/emu/grasscutter/database/DatabaseHelper.java
index b2dae0446..2a247b46e 100644
--- a/src/main/java/emu/grasscutter/database/DatabaseHelper.java
+++ b/src/main/java/emu/grasscutter/database/DatabaseHelper.java
@@ -3,11 +3,14 @@ package emu.grasscutter.database;
import java.util.List;
import com.mongodb.client.result.DeleteResult;
+import dev.morphia.query.FindOptions;
+import dev.morphia.query.Sort;
import dev.morphia.query.experimental.filters.Filters;
import emu.grasscutter.GameConstants;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.friends.Friendship;
+import emu.grasscutter.game.gacha.GachaRecord;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
@@ -78,6 +81,11 @@ public final class DatabaseHelper {
return DatabaseManager.getDatastore().find(Account.class).filter(Filters.eq("token", token)).first();
}
+ public static Account getAccountBySessionKey(String sessionKey) {
+ if(sessionKey == null) return null;
+ return DatabaseManager.getDatastore().find(Account.class).filter(Filters.eq("sessionKey", sessionKey)).first();
+ }
+
public static Account getAccountById(String uid) {
return DatabaseManager.getDatastore().find(Account.class).filter(Filters.eq("_id", uid)).first();
}
@@ -181,5 +189,36 @@ public final class DatabaseHelper {
)).first();
}
+ public static List getGachaRecords(int ownerId, int page, int gachaType){
+ return getGachaRecords(ownerId, page, gachaType, 10);
+ }
+
+ public static List getGachaRecords(int ownerId, int page, int gachaType, int pageSize){
+ return DatabaseManager.getDatastore().find(GachaRecord.class).filter(
+ Filters.eq("ownerId", ownerId),
+ Filters.eq("gachaType", gachaType)
+ ).iterator(new FindOptions()
+ .sort(Sort.descending("transactionDate"))
+ .skip(pageSize * page)
+ .limit(pageSize)
+ ).toList();
+ }
+
+ public static long getGachaRecordsMaxPage(int ownerId, int page, int gachaType){
+ return getGachaRecordsMaxPage(ownerId, page, gachaType, 10);
+ }
+
+ public static long getGachaRecordsMaxPage(int ownerId, int page, int gachaType, int pageSize){
+ long count = DatabaseManager.getDatastore().find(GachaRecord.class).filter(
+ Filters.eq("ownerId", ownerId),
+ Filters.eq("gachaType", gachaType)
+ ).count();
+ return count / 10 + (count % 10 > 0 ? 1 : 0 );
+ }
+
+ public static void saveGachaRecord(GachaRecord gachaRecord){
+ DatabaseManager.getDatastore().save(gachaRecord);
+ }
+
public static char AWJVN = 'e';
}
diff --git a/src/main/java/emu/grasscutter/database/DatabaseManager.java b/src/main/java/emu/grasscutter/database/DatabaseManager.java
index c6a5f329a..2376451db 100644
--- a/src/main/java/emu/grasscutter/database/DatabaseManager.java
+++ b/src/main/java/emu/grasscutter/database/DatabaseManager.java
@@ -16,6 +16,7 @@ import emu.grasscutter.Grasscutter.ServerRunMode;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.friends.Friendship;
+import emu.grasscutter.game.gacha.GachaRecord;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
@@ -28,7 +29,7 @@ public final class DatabaseManager {
private static Datastore dispatchDatastore;
private static final Class>[] mappedClasses = new Class>[] {
- DatabaseCounter.class, Account.class, Player.class, Avatar.class, GameItem.class, Friendship.class
+ DatabaseCounter.class, Account.class, Player.class, Avatar.class, GameItem.class, Friendship.class, GachaRecord.class
};
public static Datastore getDatastore() {
diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java
index 9b54c924f..2317af38e 100644
--- a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java
+++ b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java
@@ -91,9 +91,21 @@ public class GachaBanner {
return eventChance;
}
+ @Deprecated
public GachaInfo toProto() {
- String record = "http://" + (Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty() ? Grasscutter.getConfig().getDispatchOptions().Ip : Grasscutter.getConfig().getDispatchOptions().PublicIp) + "/gacha";
-
+ return toProto("");
+ }
+ public GachaInfo toProto(String sessionKey) {
+ String record = "https://"
+ + (Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty() ?
+ Grasscutter.getConfig().getDispatchOptions().Ip :
+ Grasscutter.getConfig().getDispatchOptions().PublicIp)
+ + ":"
+ + Integer.toString(Grasscutter.getConfig().getDispatchOptions().PublicPort == 0 ?
+ Grasscutter.getConfig().getDispatchOptions().Port :
+ Grasscutter.getConfig().getDispatchOptions().PublicPort)
+ + "/gacha?s=" + sessionKey + "&gachaType=" + gachaType;
+ // Grasscutter.getLogger().info("record = " + record);
GachaInfo.Builder info = GachaInfo.newBuilder()
.setGachaType(this.getGachaType())
.setScheduleId(this.getScheduleId())
diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java
index 5cd484e9a..cd1b5ea94 100644
--- a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java
+++ b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java
@@ -14,6 +14,7 @@ import com.sun.nio.file.SensitivityWatchEventModifier;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.def.ItemData;
+import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.gacha.GachaBanner.BannerType;
import emu.grasscutter.game.inventory.GameItem;
@@ -196,6 +197,10 @@ public class GachaManager {
if (itemData == null) {
continue;
}
+
+ // Write gacha record
+ GachaRecord gachaRecord = new GachaRecord(itemId, player.getUid(), gachaType);
+ DatabaseHelper.saveGachaRecord(gachaRecord);
// Create gacha item
GachaItem.Builder gachaItem = GachaItem.newBuilder();
@@ -321,6 +326,7 @@ public class GachaManager {
}
}
+ @Deprecated
private synchronized GetGachaInfoRsp createProto() {
GetGachaInfoRsp.Builder proto = GetGachaInfoRsp.newBuilder().setGachaRandom(12345);
@@ -330,12 +336,26 @@ public class GachaManager {
return proto.build();
}
+
+ private synchronized GetGachaInfoRsp createProto(String sessionKey) {
+ GetGachaInfoRsp.Builder proto = GetGachaInfoRsp.newBuilder().setGachaRandom(12345);
+
+ for (GachaBanner banner : getGachaBanners().values()) {
+ proto.addGachaInfoList(banner.toProto(sessionKey));
+ }
+
+ return proto.build();
+ }
+ @Deprecated
public GetGachaInfoRsp toProto() {
if (this.cachedProto == null) {
this.cachedProto = createProto();
}
-
return this.cachedProto;
}
+
+ public GetGachaInfoRsp toProto(String sessionKey) {
+ return createProto(sessionKey);
+ }
}
diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaRecord.java b/src/main/java/emu/grasscutter/game/gacha/GachaRecord.java
new file mode 100644
index 000000000..ffaf983b6
--- /dev/null
+++ b/src/main/java/emu/grasscutter/game/gacha/GachaRecord.java
@@ -0,0 +1,75 @@
+package emu.grasscutter.game.gacha;
+
+import java.util.Date;
+
+import org.bson.types.ObjectId;
+
+import dev.morphia.annotations.*;
+
+@Entity(value = "gachas", useDiscriminator = false)
+public class GachaRecord {
+ @Id private ObjectId id;
+
+ @Indexed private int ownerId;
+
+ private Date transactionDate;
+ private int itemID;
+ @Indexed private int gachaType;
+
+ public GachaRecord() {}
+
+ public GachaRecord(int itemId ,int ownerId, int gachaType){
+ this.transactionDate = new Date();
+ this.itemID = itemId;
+ this.ownerId = ownerId;
+ this.gachaType = gachaType;
+ }
+
+ public int getOwnerId() {
+ return ownerId;
+ }
+
+ public void setOwnerId(int ownerId) {
+ this.ownerId = ownerId;
+ }
+
+ public int getGachaType() {
+ return gachaType;
+ }
+
+ public void setGachaType(int type) {
+ this.gachaType = type;
+ }
+
+ public Date getTransactionDate() {
+ return transactionDate;
+ }
+
+ public void setTransactionDate(Date transactionDate) {
+ this.transactionDate = transactionDate;
+ }
+
+ public int getItemID() {
+ return itemID;
+ }
+
+ public void setItemID(int itemID) {
+ this.itemID = itemID;
+ }
+
+ public ObjectId getId(){
+ return id;
+ }
+
+ public void setId(ObjectId id) {
+ this.id = id;
+ }
+
+ public String toString() {
+ return toJsonString();
+ }
+ public String toJsonString() {
+ return "{\"time\": " + this.transactionDate.getTime() + ",\"item\":" + this.itemID + "}";
+ }
+
+}
diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java
index b7e1e34ee..7b5418e15 100644
--- a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java
+++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java
@@ -18,6 +18,7 @@ import emu.grasscutter.server.dispatch.json.*;
import emu.grasscutter.server.dispatch.json.ComboTokenReqJson.LoginTokenData;
import emu.grasscutter.server.event.dispatch.QueryAllRegionsEvent;
import emu.grasscutter.server.event.dispatch.QueryCurrentRegionEvent;
+import emu.grasscutter.server.http.gacha.GachaRecordHandler;
import emu.grasscutter.utils.FileUtils;
import express.Express;
import org.eclipse.jetty.server.Connector;
@@ -485,7 +486,7 @@ public final class DispatchServer {
// webstatic-sea.hoyoverse.com
httpServer.get("/admin/mi18n/plat_oversea/m202003048/m202003048-version.json", new DispatchHttpJsonHandler("{\"version\":51}"));
- httpServer.get("/gacha", (req, res) -> res.send("Gacha"));
+ httpServer.get("/gacha", new GachaRecordHandler());
httpServer.listen(Grasscutter.getConfig().getDispatchOptions().Port);
Grasscutter.getLogger().info("[Dispatch] Dispatch server started on port " + httpServer.raw().port());
diff --git a/src/main/java/emu/grasscutter/server/http/gacha/GachaRecordHandler.java b/src/main/java/emu/grasscutter/server/http/gacha/GachaRecordHandler.java
new file mode 100644
index 000000000..0798a150f
--- /dev/null
+++ b/src/main/java/emu/grasscutter/server/http/gacha/GachaRecordHandler.java
@@ -0,0 +1,52 @@
+package emu.grasscutter.server.http.gacha;
+
+import java.io.File;
+import java.io.IOException;
+
+import emu.grasscutter.Grasscutter;
+import emu.grasscutter.database.DatabaseHelper;
+import emu.grasscutter.game.Account;
+import emu.grasscutter.utils.FileUtils;
+import express.http.HttpContextHandler;
+import express.http.Request;
+import express.http.Response;
+
+public final class GachaRecordHandler implements HttpContextHandler {
+ String render_template;
+ public GachaRecordHandler() {
+ File template = new File(Grasscutter.getConfig().DATA_FOLDER + "gacha_records.html");
+ if (template.exists()) {
+ // Load from cache
+ render_template = new String(FileUtils.read(template));
+ } else {
+ render_template = "{{REPLACE_RECORD}}";
+ }
+ }
+
+ @Override
+ public void handle(Request req, Response res) throws IOException {
+ // Grasscutter.getLogger().info( req.query().toString() );
+ String sessionKey = req.query("s");
+ int page = 0;
+ int gachaType = 0;
+ if (req.query("p") != null) {
+ page = Integer.valueOf(req.query("p"));
+ }
+
+ if (req.query("gachaType") != null) {
+ gachaType = Integer.valueOf(req.query("gachaType"));
+ }
+
+ Account account = DatabaseHelper.getAccountBySessionKey(sessionKey);
+ if (account != null) {
+ String records = DatabaseHelper.getGachaRecords(account.getPlayerUid(), page, gachaType).toString();
+ // Grasscutter.getLogger().info(records);
+ String response = render_template.replace("{{REPLACE_RECORD}}", records)
+ .replace("{{REPLACE_MAXPAGE}}", String.valueOf(DatabaseHelper.getGachaRecordsMaxPage(account.getPlayerUid(), page, gachaType)));
+
+ res.send(response);
+ } else {
+ res.send("404");
+ }
+ }
+}
diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetGachaInfoReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetGachaInfoReq.java
index 6c4c703a8..76d267b99 100644
--- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetGachaInfoReq.java
+++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetGachaInfoReq.java
@@ -11,7 +11,10 @@ public class HandlerGetGachaInfoReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
- session.send(new PacketGetGachaInfoRsp(session.getServer().getGachaManager()));
+ session.send(new PacketGetGachaInfoRsp(session.getServer().getGachaManager(),
+ // TODO: use other Nonce/key insteadof session key to ensure the overall security for the player
+ session.getPlayer().getAccount().getSessionKey())
+ );
}
}
diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetGachaInfoRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetGachaInfoRsp.java
index 89af334a5..84d857681 100644
--- a/src/main/java/emu/grasscutter/server/packet/send/PacketGetGachaInfoRsp.java
+++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetGachaInfoRsp.java
@@ -6,9 +6,17 @@ import emu.grasscutter.net.packet.PacketOpcodes;
public class PacketGetGachaInfoRsp extends BasePacket {
+ @Deprecated
public PacketGetGachaInfoRsp(GachaManager manager) {
super(PacketOpcodes.GetGachaInfoRsp);
this.setData(manager.toProto());
}
+
+ public PacketGetGachaInfoRsp(GachaManager manager, String sessionKey) {
+ super(PacketOpcodes.GetGachaInfoRsp);
+
+ this.setData(manager.toProto(sessionKey));
+ }
+
}