Utils for gacha history record subsystem

* Auto generate mapping files with command `java -jar grasscutter.jar -gachamap`

* Static file provider
  * For gacha record webpage
  * All static files should be stored at `GRASSCUTTER_RESOURCE/gcstatic/`
  * Can benefit other subsystem in future when webpages involved
This commit is contained in:
mingjun97 2022-05-01 23:17:18 -07:00 committed by Melledy
parent 2661cc5ef3
commit d912b59d93
7 changed files with 148 additions and 4 deletions

View File

@ -102,6 +102,8 @@ You can find the output jar in the root of the project folder.
You might want to use this command (`java -jar grasscutter.jar -handbook`) in a cmd that is in the grasscutter folder. It will create a handbook file (GM Handbook.txt) where you can find the item IDs for stuff you want
You may want to use this command (`java -jar grasscutter.jar -gachamap`) to generate a mapping file for the gacha record subsystem. The file will be generated to `GRASSCUTTER_RESOURCE/gcstatic` folder. Otherwise you may only see number IDs in the gacha record page.
There is a dummy user named "Server" in every player's friends list that you can message to use commands. Commands also work in other chat rooms, such as private/team chats. to run commands ingame, you need to add prefix `/` or `!` such as `/pos`
| Commands | Usage | Permission node | Availability | description | Alias |

View File

@ -102,6 +102,8 @@ chmod +x gradlew
你可能需要在终端中运行 `java -jar grasscutter.jar -handbook` 它将会创建一个 `GM Handbook.txt` 以方便您查阅物品ID等
你可能需要在终端中运行 `java -jar grasscutter.jar -gachamap` 来使得祈愿历史记录系统正常显示物品信息。 这个命令生成一个配置文件到如下文件夹:`GRASSCUTTER_RESOURCE/gcstatic`。 不执行此命令您的祈愿历史记录中将只会显示数字ID而非物品名称。目前仅支持自动生成英文记录信息
在每个玩家的朋友列表中都有一个名为“Server”的虚拟用户你可以通过发送消息来使用命令。命令也适用于其他聊天室例如私人/团队聊天。
要在游戏中使用命令,需要添加 `/``!` 前缀,如 `/pos`

View File

@ -1,5 +1,7 @@
<html>
<head>
<!--Not sure the page is provided in UTF-8 acutally, just put meta here-->
<meta charset="utf-8" />
<script>
// Debug entry
// record = [
@ -35,7 +37,13 @@
302: "Event Weapon",
}
};
mappings['default'] = mappings['en-us'];
</script>
<!-- This file could be generated automatically using `java -jar grasscutter.jar -gachamap` -->
<!-- You can also modify the file manually to customize it -->
<!-- Otherwise you may onle see number IDs in the gacha record -->
<script type="text/javascript" src="/gcstatic/mappings.js"></script>
<script>
mappings['default'] = mappings['en-us']; // make en-us as default/fallback option
</script>
<!-- TODO: Refine the CSS -->
<style>
@ -43,7 +51,7 @@
text-decoration: none !important;
}
.content {
width: 400px;
width: 600px;
margin: auto;
display: flex;
flex-direction: column;
@ -119,7 +127,7 @@
" "+String(date.getHours()).padStart(2, "0")+
":"+String(date.getMinutes()).padStart(2, "0")+
":"+String(date.getSeconds()).padStart(2, "0")+
"."+date.getMilliseconds();
"."+String(date.getMilliseconds()).padStart(3, "0");
} else if (lang == "zh-cn") { // YYYY/MM/DD hh:mm:ss.SSS
return date.getFullYear()+
"/" + String(date.getMonth()+1).padStart(2, "0") +
@ -127,7 +135,7 @@
" "+String(date.getHours()).padStart(2, "0")+
":"+String(date.getMinutes()).padStart(2, "0")+
":"+String(date.getSeconds()).padStart(2, "0")+
"."+date.getMilliseconds();
"."+String(date.getMilliseconds()).padStart(3, "0");
}
}
(function (){

View File

@ -60,6 +60,9 @@ public final class Grasscutter {
case "-handbook" -> {
Tools.createGmHandbook(); return;
}
case "-gachamap" -> {
Tools.createGachaMapping(); return;
}
}
}

View File

@ -21,6 +21,7 @@ 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.server.http.gcstatic.StaticFileHandler;
import emu.grasscutter.utils.FileUtils;
import express.Express;
import org.eclipse.jetty.server.Connector;
@ -445,8 +446,12 @@ public final class DispatchServer {
// webstatic-sea.hoyoverse.com
httpServer.get("/admin/mi18n/plat_oversea/m202003048/m202003048-version.json", new DispatchHttpJsonHandler("{\"version\":51}"));
// gacha record
httpServer.get("/gacha", new GachaRecordHandler());
// static file provider
httpServer.get("/gcstatic/*", new StaticFileHandler());
httpServer.listen(Grasscutter.getConfig().getDispatchOptions().Port);
Grasscutter.getLogger().info("[Dispatch] Dispatch server started on port " + httpServer.raw().port());
}

View File

@ -0,0 +1,31 @@
package emu.grasscutter.server.http.gcstatic;
import java.io.File;
import java.io.IOException;
import emu.grasscutter.Grasscutter;
import express.http.HttpContextHandler;
import express.http.Request;
import express.http.Response;
public final class StaticFileHandler implements HttpContextHandler {
String static_folder;
public StaticFileHandler() {
static_folder = Grasscutter.getConfig().RESOURCE_FOLDER + "/gcstatic";
}
@Override
public void handle(Request req, Response res) throws IOException {
// Grasscutter.getLogger().info( req.path());
String reqFilename = req.path().replace("/gcstatic", ""); // remove the leading path
reqFilename = reqFilename.replace("/../", "/./"); // security guard to prevent arbitrary read
File resFile = new File(static_folder + reqFilename);
if (resFile.exists()) {
res.sendFile(resFile.toPath());
} else {
res.status(404);
res.send("404");
}
}
}

View File

@ -1,5 +1,6 @@
package emu.grasscutter.tools;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
@ -93,4 +94,96 @@ public final class Tools {
Grasscutter.getLogger().info("GM Handbook generated!");
}
@SuppressWarnings("deprecation")
public static void createGachaMapping() throws Exception {
ResourceLoader.loadResources();
Map<Long, String> map;
try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + "TextMap/TextMapEN.json")), StandardCharsets.UTF_8)) {
map = Grasscutter.getGsonFactory().fromJson(fileReader, new TypeToken<Map<Long, String>>() {}.getType());
}
List<Integer> list;
String fileName = Grasscutter.getConfig().RESOURCE_FOLDER + "/gcstatic";
File folder = new File(fileName);
if (!folder.exists()) { folder.mkdirs(); } // create folder if it doesn't exist
fileName = fileName + "/mappings.js";
try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(fileName), StandardCharsets.UTF_8), false)) {
list = new ArrayList<>(GameData.getAvatarDataMap().keySet());
Collections.sort(list);
writer.println("mappings = {\"en-us\": {");
// Avatars
boolean first = true;
for (Integer id : list) {
AvatarData data = GameData.getAvatarDataMap().get(id);
int avatarID = data.getId();
if (avatarID >= 11000000) { // skip test avatar
continue;
}
if (first) { // skip adding comma for the first element
first = false;
} else {
writer.print(",");
}
String color;
switch (data.getQualityType()){
case "QUALITY_PURPLE":
color = "purple";
break;
case "QUALITY_ORANGE":
color = "yellow";
break;
case "QUALITY_BLUE":
default:
color = "blue";
}
writer.println(
"\"" + (avatarID % 1000 + 1000) + "\" : [\""
+ map.get(data.getNameTextMapHash()) + "(Avatar)\", \""
+ color + "\"]");
}
writer.println();
list = new ArrayList<>(GameData.getItemDataMap().keySet());
Collections.sort(list);
// Weapons
for (Integer id : list) {
ItemData data = GameData.getItemDataMap().get(id);
if (data.getId() <= 11101 || data.getId() >= 20000) {
continue; //skip non weapon items
}
String color;
switch (data.getRankLevel()){
case 3:
color = "blue";
break;
case 4:
color = "purple";
break;
case 5:
color = "yellow";
break;
default:
continue; // skip unnecessary entries
}
writer.println(",\"" + data.getId() +
"\" : [\"" + map.get(data.getNameTextMapHash()).replaceAll("\"", "")
+ "(Weapon)\",\""+ color + "\"]");
}
writer.println(",\"200\": \"Standard\", \"301\": \"Avatar Event\", \"302\": \"Weapon event\"");
writer.println("}\n}");
}
Grasscutter.getLogger().info("Mappings generated!");
}
}