Update GaCy-GO
This commit is contained in:
1 parent
da8a32f60d
commit
0cbe460e49
345 files changed
+98202
-77559
No files matched your search
+10
-45
@@ -49,6 +49,10 @@ func registerAPIRoutes(mux *http.ServeMux) {
|
||||
mux.HandleFunc("/api/tool-session", corsWrapper(apiToolSession))
|
||||
mux.HandleFunc("/api/status", corsWrapper(apiStatus))
|
||||
mux.HandleFunc("/api/technique", corsWrapper(apiTechnique))
|
||||
mux.HandleFunc("/api/aha-help", corsWrapper(apiAhaHelp))
|
||||
mux.HandleFunc("/api/camera", corsWrapper(apiCamera))
|
||||
mux.HandleFunc("/api/monster-settings/catalog", corsWrapper(apiMonsterSettingsCatalog))
|
||||
mux.HandleFunc("/api/monster-settings/aa-round", corsWrapper(apiAARoundSelection))
|
||||
mux.HandleFunc("/api/freesr", corsWrapper(apiFreesrData))
|
||||
mux.HandleFunc("/api/inventory/materials", corsWrapper(apiInventoryMaterials))
|
||||
mux.HandleFunc("/api/freesr/avatars", corsWrapper(apiFreesrAvatars))
|
||||
@@ -318,45 +322,16 @@ func apiInventoryMaterials(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
seen[update.ItemID] = struct{}{}
|
||||
}
|
||||
materials, err := readInventory()
|
||||
if err != nil {
|
||||
writeInventoryError(w, http.StatusInternalServerError, "failed to read inventory: "+err.Error())
|
||||
return
|
||||
}
|
||||
updates := make(map[uint32]*uint32, len(req.Updates))
|
||||
for _, update := range req.Updates {
|
||||
key := strconv.FormatUint(uint64(update.ItemID), 10)
|
||||
if update.Reset {
|
||||
delete(materials, key)
|
||||
updates[update.ItemID] = nil
|
||||
} else {
|
||||
materials[key] = *update.Count
|
||||
updates[update.ItemID] = update.Count
|
||||
}
|
||||
}
|
||||
raw, err := os.ReadFile("freesr-data.json")
|
||||
materials, err := common.PatchMaterialOverrides(updates)
|
||||
if err != nil {
|
||||
writeInventoryError(w, http.StatusInternalServerError, "failed to read freesr-data.json: "+err.Error())
|
||||
return
|
||||
}
|
||||
var data map[string]json.RawMessage
|
||||
if err := json.Unmarshal(raw, &data); err != nil {
|
||||
writeInventoryError(w, http.StatusInternalServerError, "freesr-data.json is invalid: "+err.Error())
|
||||
return
|
||||
}
|
||||
if data == nil {
|
||||
writeInventoryError(w, http.StatusInternalServerError, "freesr-data.json must contain a JSON object")
|
||||
return
|
||||
}
|
||||
materialJSON, err := json.Marshal(materials)
|
||||
if err != nil {
|
||||
writeInventoryError(w, http.StatusInternalServerError, "failed to encode inventory: "+err.Error())
|
||||
return
|
||||
}
|
||||
data["materials"] = materialJSON
|
||||
output, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
writeInventoryError(w, http.StatusInternalServerError, "failed to encode freesr-data.json: "+err.Error())
|
||||
return
|
||||
}
|
||||
if err := os.WriteFile("freesr-data.json", output, 0o644); err != nil {
|
||||
writeInventoryError(w, http.StatusInternalServerError, "failed to save inventory: "+err.Error())
|
||||
return
|
||||
}
|
||||
@@ -482,23 +457,13 @@ func apiPersistentData(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
var parsed map[string]any
|
||||
var parsed map[string]json.RawMessage
|
||||
if err := json.Unmarshal(body, &parsed); err != nil {
|
||||
writeJSON(w, map[string]any{"error": "invalid json: " + err.Error(), "status": 400})
|
||||
return
|
||||
}
|
||||
|
||||
// Merge into existing persistent file so partial updates (e.g. lineups or switches) preserve other settings
|
||||
existing := make(map[string]any)
|
||||
if raw, err := os.ReadFile("persistent"); err == nil {
|
||||
_ = json.Unmarshal(raw, &existing)
|
||||
}
|
||||
for k, v := range parsed {
|
||||
existing[k] = v
|
||||
}
|
||||
|
||||
out, _ := json.MarshalIndent(existing, "", " ")
|
||||
if err := os.WriteFile("persistent", out, 0o644); err != nil {
|
||||
if err := common.PatchPersistentFields(parsed); err != nil {
|
||||
writeJSON(w, map[string]any{"error": "failed to write persistent: " + err.Error(), "status": 500})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package sdkserver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"gac-hkrpg-go/internal/common"
|
||||
)
|
||||
|
||||
func apiAARoundSelection(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
selection, err := common.ReadAARoundSelection()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, map[string]any{"status": 200, "selection": selection})
|
||||
case http.MethodPost:
|
||||
var request struct {
|
||||
Action string `json:"action"`
|
||||
PeakGroupID uint32 `json:"peak_group_id"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
http.Error(w, "invalid JSON", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if request.Action == "clear" {
|
||||
if err := common.ClearAARoundSelection(); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, map[string]any{"status": 200, "selection": nil})
|
||||
return
|
||||
}
|
||||
selection, err := common.SetAARoundSelection(request.PeakGroupID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
writeJSON(w, map[string]any{"status": 200, "selection": selection})
|
||||
default:
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package sdkserver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"gac-hkrpg-go/internal/common"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func apiAhaHelp(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet && r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
if r.Method == http.MethodPost {
|
||||
var body map[string]json.RawMessage
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
http.Error(w, "invalid JSON", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
raw, ok := body["level"]
|
||||
var level *uint32
|
||||
if !ok || json.Unmarshal(raw, &level) != nil {
|
||||
http.Error(w, "level must be null or an unsigned integer", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err := common.SaveAhaHelpLevel(level); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
levels, err := common.LoadAhaHelpLevels()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
selected, err := common.ReadAhaHelpLevel()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
data, err := common.LoadFreesrData()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, map[string]any{"status": 200, "level": selected, "levels": levels, "legacy_enabled": data.BattleConfig.EnableInvasionSupport})
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package sdkserver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"gac-hkrpg-go/internal/common"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func apiCamera(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
case http.MethodPost:
|
||||
var settings common.FaceCamSettings
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(&settings); err != nil {
|
||||
http.Error(w, "invalid camera settings", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err := common.SaveFaceCamSettings(settings); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
default:
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
settings, err := common.ReadFaceCamSettings()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, settings)
|
||||
}
|
||||
@@ -111,6 +111,12 @@ func apiClientUnstuckResult(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
clientUnstuckState.status.Error = result.Error
|
||||
clientUnstuckState.status.CompletedAt = time.Now().UnixMilli()
|
||||
// Queueing and acknowledgement share the lock, so removing this owned
|
||||
// request cannot delete a newer recovery. A leftover script still checks
|
||||
// the live nonce before touching UI, including after an SDK restart.
|
||||
if err := os.Remove(clientUnstuckRequestFile); err != nil && !os.IsNotExist(err) {
|
||||
Log("[UNSTUCK] cannot remove completed request: " + err.Error())
|
||||
}
|
||||
Log("[UNSTUCK] UI recovery " + clientUnstuckState.status.State + " request=" + result.RequestID)
|
||||
writeJSON(w, clientUnstuckState.status)
|
||||
}
|
||||
@@ -120,27 +126,63 @@ func apiClientUnstuckResult(w http.ResponseWriter, r *http.Request) {
|
||||
// response, erase a run, or reset the Client's loading/phase flags.
|
||||
const clientUnstuckLua = `do
|
||||
local id = %q
|
||||
if _G.__GAC_UNSTUCK_HTTP and _G.__GAC_UNSTUCK_HTTP.isDone then
|
||||
pcall(function() _G.__GAC_UNSTUCK_HTTP:Dispose() end); _G.__GAC_UNSTUCK_HTTP=nil
|
||||
end
|
||||
if _G.__GAC_UNSTUCK_LAST ~= id then
|
||||
_G.__GAC_UNSTUCK_LAST=id
|
||||
local ok,err=pcall(function()
|
||||
local ui=CS.RPG.Client.GlobalVars.s_UIManager
|
||||
if not ui then error('Client UIManager is unavailable') end
|
||||
ui:ClearWaitNetPacketFullScreenBlock()
|
||||
end)
|
||||
CS.UnityEngine.Debug.Log('[GAC_UNSTUCK] request='..id..' success='..tostring(ok)..' '..tostring(err or ''))
|
||||
pcall(function()
|
||||
local url=_G.__GAC_SRTOOL_LIVE_URL
|
||||
if type(url)~='string' or url=='' then return end
|
||||
local body='{"request_id":"'..id..'","success":'..tostring(ok)..',"error":'..CS.Newtonsoft.Json.JsonConvert.SerializeObject(tostring(err or ''))..'}'
|
||||
local req=CS.UnityEngine.Networking.UnityWebRequest(url..'/api/client/unstuck/result','POST')
|
||||
req.uploadHandler=CS.UnityEngine.Networking.UploadHandlerRaw(CS.System.Text.Encoding.UTF8:GetBytes(body))
|
||||
local function tick()
|
||||
local url=_G.__GAC_SRTOOL_LIVE_URL
|
||||
if type(url)~='string' or url=='' then return end
|
||||
local s=_G.__GAC_UNSTUCK_WORK
|
||||
if not s or s.id~=id then
|
||||
if s and s.request then pcall(function()s.request:Dispose()end) end
|
||||
s={id=id,phase='check',next=0};_G.__GAC_UNSTUCK_WORK=s
|
||||
end
|
||||
if s.phase=='done' or s.phase=='stale' then return end
|
||||
local now=CS.UnityEngine.Time.unscaledTime
|
||||
if s.request then
|
||||
if not s.request.isDone then return end
|
||||
local request=s.request;s.request=nil
|
||||
local ok,err=pcall(function()
|
||||
local code=request.responseCode
|
||||
if code<200 or code>=300 then error('recovery HTTP '..tostring(code)) end
|
||||
if s.phase=='check' then
|
||||
local status=CS.Newtonsoft.Json.Linq.JObject.Parse(request.downloadHandler.text)
|
||||
local request_id=status:get_Item('request_id')
|
||||
local phase=status:get_Item('state')
|
||||
if not request_id or tostring(request_id)~=id or not phase or tostring(phase)~='queued' then
|
||||
s.phase='stale';return
|
||||
end
|
||||
-- Only the current queued SDK request authorizes a UI recovery. Old
|
||||
-- generated files are inert after reconnect or SDK process restart.
|
||||
local success,message=pcall(function()
|
||||
local ui=CS.RPG.Client.GlobalVars.s_UIManager
|
||||
if not ui then error('Client UIManager is unavailable') end
|
||||
ui:ClearWaitNetPacketFullScreenBlock()
|
||||
end)
|
||||
s.body='{"request_id":"'..id..'","success":'..tostring(success)..',"error":'..CS.Newtonsoft.Json.JsonConvert.SerializeObject(tostring(message or ''))..'}'
|
||||
s.phase='ack'
|
||||
CS.UnityEngine.Debug.Log('[GAC_UNSTUCK] request='..id..' success='..tostring(success)..' '..tostring(message or ''))
|
||||
else
|
||||
s.phase='done';_G.__GAC_UNSTUCK_LAST=id
|
||||
end
|
||||
end)
|
||||
pcall(function()request:Dispose()end)
|
||||
if not ok then
|
||||
s.next=now+5
|
||||
if s.error~=tostring(err) then CS.UnityEngine.Debug.Log('[GAC_UNSTUCK] '..tostring(err));s.error=tostring(err) end
|
||||
return
|
||||
end
|
||||
end
|
||||
if s.phase=='done' or s.phase=='stale' or now<s.next then return end
|
||||
local req
|
||||
if s.phase=='check' then
|
||||
req=CS.UnityEngine.Networking.UnityWebRequest.Get(url..'/api/client/unstuck')
|
||||
else
|
||||
req=CS.UnityEngine.Networking.UnityWebRequest(url..'/api/client/unstuck/result','POST')
|
||||
req.uploadHandler=CS.UnityEngine.Networking.UploadHandlerRaw(CS.System.Text.Encoding.UTF8:GetBytes(s.body))
|
||||
req.downloadHandler=CS.UnityEngine.Networking.DownloadHandlerBuffer()
|
||||
req:SetRequestHeader('Content-Type','application/json');req.timeout=10
|
||||
req:SendWebRequest();_G.__GAC_UNSTUCK_HTTP=req
|
||||
end)
|
||||
req:SetRequestHeader('Content-Type','application/json')
|
||||
end
|
||||
req.timeout=10;s.request=req;req:SendWebRequest()
|
||||
end
|
||||
local ok,err=pcall(tick)
|
||||
if not ok then CS.UnityEngine.Debug.Log('[GAC_UNSTUCK] '..tostring(err)) end
|
||||
end
|
||||
`
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
pb "gac-hkrpg-go/protocol/proto"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@@ -13,7 +14,7 @@ import (
|
||||
func apiCurrencyWarsCommand(w http.ResponseWriter, command string) {
|
||||
fail := func(err error) { writeJSON(w, map[string]any{"success": false, "output": err.Error()}) }
|
||||
parts := strings.Fields(command)
|
||||
usage := "Usage: /cw status | /cw gold <total> | /cw hp <total>. Changes apply in CW Preparation; adventure inventory is unchanged."
|
||||
usage := "Usage: /cw status | /cw list role|equip|orb|consumable|forge | /cw gold <total> | /cw hp <total> | /cw exp <gain> | /cw equip|orb|consumable|forge <itemID> [count] | /cw role <roleID> <star> [count]. Changes apply in CW Preparation."
|
||||
if len(parts) < 2 {
|
||||
fail(fmt.Errorf("%s", usage))
|
||||
return
|
||||
@@ -23,6 +24,79 @@ func apiCurrencyWarsCommand(w http.ResponseWriter, command string) {
|
||||
fail(err)
|
||||
return
|
||||
}
|
||||
if strings.EqualFold(parts[1], "help") {
|
||||
writeJSON(w, map[string]any{"success": true, "output": usage})
|
||||
return
|
||||
}
|
||||
if strings.EqualFold(parts[1], "list") {
|
||||
if len(parts) != 3 {
|
||||
fail(fmt.Errorf("%s", usage))
|
||||
return
|
||||
}
|
||||
var lines []string
|
||||
switch strings.ToLower(parts[2]) {
|
||||
case "role":
|
||||
for _, v := range catalog.CapturedVariants {
|
||||
lines = append(lines, fmt.Sprintf("role=%d star=%d avatar=%d rarity=%d", v.Config.ID, v.Star, v.Config.AvatarID, v.Config.Rarity))
|
||||
}
|
||||
case "equip":
|
||||
rows, e := common.LoadCurrencyWarsEquipment(catalog)
|
||||
if e != nil {
|
||||
fail(e)
|
||||
return
|
||||
}
|
||||
ids := make([]uint32, 0, len(rows))
|
||||
for id := range rows {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
|
||||
for _, id := range ids {
|
||||
v := rows[id]
|
||||
lines = append(lines, fmt.Sprintf("equip=%d type=%d category=%d ability=%s", id, v.EquipType, v.EquipCategory, v.AbilityName))
|
||||
}
|
||||
case "orb", "consumable", "forge":
|
||||
rows, e := common.LoadCurrencyWarsRewards(catalog)
|
||||
if e != nil {
|
||||
fail(e)
|
||||
return
|
||||
}
|
||||
ids := []uint32{}
|
||||
kind := strings.ToLower(parts[2])
|
||||
switch kind {
|
||||
case "orb":
|
||||
for id := range rows.Orbs {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
case "consumable":
|
||||
for id := range rows.Consumables {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
case "forge":
|
||||
for id := range rows.Forges {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
|
||||
for _, id := range ids {
|
||||
switch kind {
|
||||
case "orb":
|
||||
v := rows.Orbs[id]
|
||||
lines = append(lines, fmt.Sprintf("orb=%d type=%d bonus=%d", id, v.Type, v.Bonus))
|
||||
case "consumable":
|
||||
v := rows.Consumables[id]
|
||||
lines = append(lines, fmt.Sprintf("consumable=%d rule=%d stack=%t", id, v.ConsumableRule, v.IfStack))
|
||||
case "forge":
|
||||
v := rows.Forges[id]
|
||||
lines = append(lines, fmt.Sprintf("forge=%d function=%d choices=%d parameters=%v", id, v.FuncType, v.EquipNum, v.ParamList))
|
||||
}
|
||||
}
|
||||
default:
|
||||
fail(fmt.Errorf("available catalogs: role, equip, orb, consumable, forge"))
|
||||
return
|
||||
}
|
||||
writeJSON(w, map[string]any{"success": true, "output": strings.Join(lines, "\n")})
|
||||
return
|
||||
}
|
||||
cfg, err := common.LoadRuntimeConfig()
|
||||
if err != nil {
|
||||
fail(err)
|
||||
@@ -62,7 +136,7 @@ func apiCurrencyWarsCommand(w http.ResponseWriter, command string) {
|
||||
writeJSON(w, map[string]any{"success": true, "output": fmt.Sprintf("CW run=%d revision=%d gold=%d hp=%d/%d level=%d", run.UniqueId, snapshot.Revision, player.Gold, player.GridFightLineupHp, player.PPKKHHNEOHB, player.EHNJDIDABHD)})
|
||||
return
|
||||
}
|
||||
if len(parts) != 3 || (field != "gold" && field != "hp") {
|
||||
if len(parts) < 3 || len(parts) > 5 || (field != "gold" && field != "hp" && field != "exp" && field != "equip" && field != "role" && field != "orb" && field != "consumable" && field != "forge") {
|
||||
fail(fmt.Errorf("%s", usage))
|
||||
return
|
||||
}
|
||||
@@ -75,10 +149,85 @@ func apiCurrencyWarsCommand(w http.ResponseWriter, command string) {
|
||||
fail(fmt.Errorf("HP must be 1..%d", player.PPKKHHNEOHB))
|
||||
return
|
||||
}
|
||||
id, err := common.QueueCurrencyWarsCommand(cfg.PlayerUID, run.UniqueId, field, uint32(value))
|
||||
quantity, star := uint32(1), uint32(0)
|
||||
parse := func(text string) (uint32, error) {
|
||||
v, e := strconv.ParseUint(text, 10, 32)
|
||||
if e != nil || v == 0 {
|
||||
return 0, fmt.Errorf("star/count must be a positive uint32")
|
||||
}
|
||||
return uint32(v), nil
|
||||
}
|
||||
switch field {
|
||||
case "role":
|
||||
if len(parts) < 4 {
|
||||
fail(fmt.Errorf("%s", usage))
|
||||
return
|
||||
}
|
||||
star, err = parse(parts[3])
|
||||
if err == nil && len(parts) == 5 {
|
||||
quantity, err = parse(parts[4])
|
||||
}
|
||||
if err == nil {
|
||||
_, err = catalog.CapturedRoleVariant(uint32(value), star)
|
||||
}
|
||||
case "orb", "consumable", "forge":
|
||||
if len(parts) > 4 {
|
||||
fail(fmt.Errorf("%s", usage))
|
||||
return
|
||||
}
|
||||
if len(parts) == 4 {
|
||||
quantity, err = parse(parts[3])
|
||||
}
|
||||
if err == nil {
|
||||
var rows *common.CurrencyWarsRewards
|
||||
rows, err = common.LoadCurrencyWarsRewards(catalog)
|
||||
if err == nil {
|
||||
if field == "forge" {
|
||||
if _, ok := rows.Forges[uint32(value)]; !ok {
|
||||
err = fmt.Errorf("unknown forge %d", value)
|
||||
}
|
||||
} else if field == "orb" {
|
||||
if _, ok := rows.Orbs[uint32(value)]; !ok {
|
||||
err = fmt.Errorf("unknown Orb %d", value)
|
||||
}
|
||||
} else {
|
||||
if _, ok := rows.Consumables[uint32(value)]; !ok {
|
||||
err = fmt.Errorf("unknown consumable %d", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case "equip":
|
||||
if len(parts) > 4 {
|
||||
fail(fmt.Errorf("%s", usage))
|
||||
return
|
||||
}
|
||||
if len(parts) == 4 {
|
||||
quantity, err = parse(parts[3])
|
||||
}
|
||||
if err == nil {
|
||||
var rows map[uint32]common.CurrencyWarsEquipment
|
||||
rows, err = common.LoadCurrencyWarsEquipment(catalog)
|
||||
if err == nil {
|
||||
if _, ok := rows[uint32(value)]; !ok {
|
||||
err = fmt.Errorf("equipment %d not captured; use /cw list equip", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
if len(parts) != 3 {
|
||||
fail(fmt.Errorf("%s", usage))
|
||||
return
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
fail(err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, map[string]any{"success": true, "state": "queued", "request_id": id, "output": fmt.Sprintf("Queued CW %s=%d for run %d. Applies at the next heartbeat in Preparation.", field, value, run.UniqueId)})
|
||||
id, err := common.QueueCurrencyWarsGrant(cfg.PlayerUID, run.UniqueId, field, uint32(value), quantity, star)
|
||||
if err != nil {
|
||||
fail(err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, map[string]any{"success": true, "state": "queued", "request_id": id, "output": fmt.Sprintf("Queued CW %s=%d star=%d count=%d for run %d. Applies at the next heartbeat in Preparation; check CW_GM log for the applied result.", field, value, star, quantity, run.UniqueId)})
|
||||
}
|
||||
@@ -19,6 +19,10 @@ func TestInventoryMaterialsUpdateIsCatalogBoundAndPreservesSave(t *testing.T) {
|
||||
if err := os.WriteFile(filepath.Join("resources", "InventoryItemConfig.json"), []byte(`{"item_ids":[101,102,301]}`), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
eidolonConfig := `{"avatars":{"1001":{"ranks":[{"cost":[{"ItemID":11001,"ItemNum":1}]}]}},"ex_materials":{}}`
|
||||
if err := os.WriteFile(filepath.Join("resources", "EidolonConfig.json"), []byte(eidolonConfig), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
original := `{"avatars":{"1001":{"level":80}},"battle_config":{},"materials":{"101":4}}`
|
||||
if err := os.WriteFile("freesr-data.json", []byte(original), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package sdkserver
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"gac-hkrpg-go/internal/common"
|
||||
)
|
||||
|
||||
func apiMonsterSettingsCatalog(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
catalog, err := common.LoadMonsterSettingsCatalog()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
writeJSON(w, map[string]any{"error": err.Error(), "status": 500})
|
||||
return
|
||||
}
|
||||
cfg, err := common.LoadRuntimeConfig()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
writeJSON(w, map[string]any{"error": err.Error(), "status": 500})
|
||||
return
|
||||
}
|
||||
response := map[string]any{
|
||||
"status": 200,
|
||||
"modes": catalog.Modes,
|
||||
"monster_ids": catalog.MonsterIDs,
|
||||
"monsters": catalog.Monsters,
|
||||
"levels": catalog.Levels,
|
||||
"sources": catalog.Sources,
|
||||
"client_version": cfg.ClientVersion,
|
||||
}
|
||||
if metadata, metadataErr := common.LoadNanokaMonsterMetadata(cfg.ClientVersion); metadataErr != nil {
|
||||
response["metadata_error"] = metadataErr.Error()
|
||||
response["sources"] = append(catalog.Sources, "Nanoka metadata unavailable")
|
||||
} else {
|
||||
catalog.ApplyNanokaMonsterMetadata(metadata)
|
||||
response["monsters"] = catalog.Monsters
|
||||
response["modes"] = catalog.Modes
|
||||
response["sources"] = catalog.Sources
|
||||
response["metadata_version"] = metadata.Version
|
||||
response["metadata_source"] = "Nanoka"
|
||||
}
|
||||
writeJSON(w, response)
|
||||
}
|
||||
Reference in new issue
Block a user