docs & feat: add license & attribution notice (Apache-2.0 / GPL-3.0) and core system enhancements

This commit is contained in:
CAtlas0784 committed 2026-11-01 20:14:36 +07:00
1 parent 507836d367
commit ce6aabfc55
47 files changed
+5674 -724

No files matched your search

+140 -33
View File
@@ -699,8 +699,11 @@ func apiTeleport(w http.ResponseWriter, r *http.Request) {
data.Position.RotY = *req.RotY
}
data.SavePersistent()
writeJSON(w, map[string]any{"message": "teleported successfully", "status": 200})
if err := data.SavePersistentChecked(); err != nil {
writeJSON(w, map[string]any{"error": "failed to save location: " + err.Error(), "status": 500})
return
}
writeJSON(w, map[string]any{"message": "location saved", "status": 200})
}
// apiGachabaseFetch proxies requests to hsr.gachabase.net to bypass browser CORS restrictions.
@@ -828,7 +831,8 @@ func apiCommand(w http.ResponseWriter, r *http.Request) {
switch rootCmd {
case "/heal":
output = "[OK] Team HP, SP, and Energy fully restored."
output = "Healing through this API is not implemented."
success = false
case "/max":
for _, av := range data.Avatars {
@@ -866,53 +870,156 @@ func apiCommand(w http.ResponseWriter, r *http.Request) {
}
case "/avatar":
if len(parts) > 1 {
aid, _ := strconv.ParseUint(parts[1], 10, 32)
if av, exists := data.Avatars[uint32(aid)]; exists {
if len(parts) > 2 {
lvl, _ := strconv.ParseUint(parts[2], 10, 32)
if lvl > 0 && lvl <= 80 {
av.Level = uint32(lvl)
}
}
if len(parts) > 3 {
rank, _ := strconv.ParseUint(parts[3], 10, 32)
av.Data.Rank = uint32(rank)
}
output = fmt.Sprintf("[OK] Avatar %d updated (Lv.%d, Rank %d).", aid, av.Level, av.Data.Rank)
} else {
output = fmt.Sprintf("[Error] Avatar ID %d not found in freesr-data.", aid)
success = false
if len(parts) < 3 || len(parts) > 4 {
output, success = "Usage: /avatar <avatar_id> <level 1-80> [rank 0-6]", false
break
}
aid, aidErr := strconv.ParseUint(parts[1], 10, 32)
level, levelErr := strconv.ParseUint(parts[2], 10, 32)
if aidErr != nil || aid == 0 || levelErr != nil || level < 1 || level > 80 {
output, success = "Invalid avatar ID or level (1-80 required).", false
break
}
levelValue := uint32(level)
var rankValue *uint32
if len(parts) == 4 {
parsed, err := strconv.ParseUint(parts[3], 10, 32)
if err != nil || parsed > 6 {
output, success = "Invalid rank (0-6 required).", false
break
}
rank := uint32(parsed)
rankValue = &rank
}
if err := common.SaveAvatarProgress(uint32(aid), &levelValue, rankValue); err != nil {
output, success = fmt.Sprintf("Cannot update avatar: %v", err), false
} else {
output = "Usage: /avatar <avatar_id> [level] [rank]"
success = false
output = fmt.Sprintf("Avatar %d saved. Reconnect the game to load the change.", aid)
}
case "/give", "/item":
output = "Giving items through this command is not implemented. Use the inventory editor."
success = false
case "/unlockall", "/ua":
sub := ""
if len(parts) > 1 {
itemID := parts[1]
count := "1"
sub = strings.ToLower(parts[1])
}
switch sub {
case "warp":
arg := "all"
if len(parts) > 2 {
count = parts[2]
arg = strings.ToLower(parts[2])
}
output = fmt.Sprintf("[OK] Item %s x%s added to inventory.", itemID, count)
} else {
output = "Usage: /give <item_id> [count]"
mode := 1
switch arg {
case "all", "on", "1", "true":
mode = 1
case "off", "0", "none", "disable", "false":
mode = 0
case "avatar", "character", "char", "2":
mode = 2
case "lightcone", "lc", "weapon", "3":
mode = 3
default:
output = "Usage: /unlockall warp [all|avatar|lightcone|off]"
success = false
}
if success {
data.WarpUnlockMode = mode
visible, visibilityErr := common.LoadVisibleGachaBanners(mode)
if visibilityErr != nil {
output = visibilityErr.Error()
success = false
break
}
count := len(visible)
desc := "all warps"
if mode == 0 {
desc = "default active warps"
} else if mode == 2 {
desc = "character warps"
} else if mode == 3 {
desc = "lightcone warps"
}
output = fmt.Sprintf("[OK] Set %s (%d banners visible). Reopen Warp to refresh.", desc, count)
}
case "all":
data.WarpUnlockMode = 1
visible, visibilityErr := common.LoadVisibleGachaBanners(1)
if visibilityErr != nil {
output = visibilityErr.Error()
success = false
break
}
count := len(visible)
output = fmt.Sprintf("[OK] Selected all %d verified warps. Reopen Warp to refresh.", count)
case "skin":
output = "Skin unlocking is not implemented by this command."
success = false
default:
output = "Usage: /unlockall [warp|all|skin]\n /unlockall warp [all|avatar|lightcone|off]"
success = false
}
case "/reload":
sub := ""
if len(parts) > 1 {
sub = strings.ToLower(parts[1])
}
switch sub {
case "banner", "banners":
if err := common.ReloadBanners(); err != nil {
output = fmt.Sprintf("[Error] Failed to reload banners: %v", err)
success = false
} else {
visible, visibilityErr := common.LoadVisibleGachaBanners(data.WarpUnlockMode)
if visibilityErr != nil {
output = visibilityErr.Error()
success = false
break
}
count := len(visible)
output = fmt.Sprintf("[OK] Banner configurations reloaded successfully (%d visible in current mode).", count)
}
default:
output = "Usage: /reload [banner]"
success = false
}
case "/help":
output = "/faketime [status|off|Unix seconds|RFC3339 with timezone] - Set game calendar time\n"
output += "Available GM Commands:\n /heal - Fully restore team HP, SP, and Energy\n /max - Set all avatars to Max Lv.80 E6\n /teleport <plane_id> [floor_id] - Teleport to scene\n /avatar <id> [level] [rank] - Modify avatar\n /give <item_id> [count] - Add items to inventory\n /clear lineup - Clear active lineup slots"
output += "Available GM Commands:\n /max - Set all avatars to Max Lv.80 E6\n /teleport <plane_id> [floor_id] - Set scene\n /avatar <id> <level> [rank] - Modify avatar\n /clear lineup - Clear active lineup slots\n /unlockall warp [all|avatar|lightcone|off] - Toggle unlocked gacha banners\n /reload banner - Reload banner configurations"
default:
output = fmt.Sprintf("[OK] Custom command '%s' dispatched to server.", cmd)
output = fmt.Sprintf("Unknown command: %s", cmd)
success = false
}
raw, _ := json.MarshalIndent(data, "", " ")
_ = os.WriteFile("freesr-data.json", raw, 0o644)
data.SavePersistent()
// Banner commands only patch their own persistent setting. Rewriting the
// complete imported roster here can overwrite concurrent inventory updates.
if rootCmd == "/unlockall" || rootCmd == "/ua" || rootCmd == "/reload" {
if success && rootCmd != "/reload" {
mode, _ := json.Marshal(data.WarpUnlockMode)
if err := common.PatchPersistentFields(map[string]json.RawMessage{"warp_unlock_mode": mode}); err != nil {
success = false
output = err.Error()
}
}
} else if success && rootCmd != "/help" && rootCmd != "/avatar" {
raw, err := json.MarshalIndent(data, "", " ")
if err == nil {
err = os.WriteFile("freesr-data.json", raw, 0o644)
}
if err == nil {
err = data.SavePersistentChecked()
}
if err != nil {
success = false
output = err.Error()
}
}
Log(fmt.Sprintf("[GM Command] %s -> %s", cmd, output))