Implement secretbox handlers and align gacha behavior
- Add secretbox API schemas, handlers, and routes for all/showDetail/pon/multi - Load secretbox server data through the shared serverdata loader - Fix rarity mapping and pool rules for SR/SSR/UR special boxes - Implement thanks festival pool behavior, including UR guarantee and SSR/UR-only rates - Correct animation-related fields such as is_hit and lowest_rarity, and add rare UR promotion animation Signed-off-by: Sean Du <do4suki@gmail.com>
This commit is contained in:
@@ -24,6 +24,7 @@ import (
|
||||
"honoka-chan/internal/handler/api/profile"
|
||||
"honoka-chan/internal/handler/api/reward"
|
||||
"honoka-chan/internal/handler/api/scenario"
|
||||
"honoka-chan/internal/handler/api/secretbox"
|
||||
"honoka-chan/internal/handler/api/stamp"
|
||||
"honoka-chan/internal/handler/api/subscenario"
|
||||
"honoka-chan/internal/handler/api/unit"
|
||||
@@ -96,6 +97,8 @@ func api(ctx *gin.Context) {
|
||||
result, err = reward.RewardApi(v.Action)
|
||||
case "scenario":
|
||||
result, err = scenario.ScenarioApi(ctx, v.Action)
|
||||
case "secretbox":
|
||||
result, err = secretbox.SecretBoxApi(v.Action)
|
||||
case "stamp":
|
||||
result, err = stamp.StampApi(v.Action)
|
||||
case "subscenario":
|
||||
|
||||
@@ -11,48 +11,44 @@ func bannerList() (res any, err error) {
|
||||
TimeLimit: "2037-12-31 23:59:59",
|
||||
BannerList: []bannerapischema.BannerList{
|
||||
{
|
||||
BannerType: 1,
|
||||
TargetID: 1743,
|
||||
AssetPath: "assets/image/secretbox/icon/s_ba_1718_1.png",
|
||||
FixedFlag: false,
|
||||
BackSide: false,
|
||||
BannerID: 101151,
|
||||
StartDate: "2013-04-15 00:00:00",
|
||||
EndDate: "2037-12-31 23:59:59",
|
||||
AddUnitStartDate: "2022-01-01 00:00:00",
|
||||
BannerType: 1,
|
||||
TargetID: 1718,
|
||||
AssetPath: "assets/image/secretbox/icon/s_ba_1718_1.png",
|
||||
FixedFlag: false,
|
||||
BackSide: false,
|
||||
BannerID: 101151,
|
||||
StartDate: "2013-04-15 00:00:00",
|
||||
EndDate: "2037-12-31 23:59:59",
|
||||
},
|
||||
{
|
||||
BannerType: 1,
|
||||
TargetID: 1741,
|
||||
AssetPath: "assets/image/secretbox/icon/s_ba_1719_1.png",
|
||||
FixedFlag: false,
|
||||
BackSide: false,
|
||||
BannerID: 101150,
|
||||
StartDate: "2013-04-15 00:00:00",
|
||||
EndDate: "2037-12-31 23:59:59",
|
||||
AddUnitStartDate: "2022-01-01 00:00:00",
|
||||
BannerType: 1,
|
||||
TargetID: 1719,
|
||||
AssetPath: "assets/image/secretbox/icon/s_ba_1719_1.png",
|
||||
FixedFlag: false,
|
||||
BackSide: false,
|
||||
BannerID: 101150,
|
||||
StartDate: "2013-04-15 00:00:00",
|
||||
EndDate: "2037-12-31 23:59:59",
|
||||
},
|
||||
{
|
||||
BannerType: 1,
|
||||
TargetID: 1740,
|
||||
AssetPath: "assets/image/secretbox/icon/s_ba_1720_1.png",
|
||||
FixedFlag: false,
|
||||
BackSide: false,
|
||||
BannerID: 101149,
|
||||
StartDate: "2013-04-15 00:00:00",
|
||||
EndDate: "2037-12-31 23:59:59",
|
||||
AddUnitStartDate: "2022-01-01 00:00:00",
|
||||
BannerType: 1,
|
||||
TargetID: 1720,
|
||||
AssetPath: "assets/image/secretbox/icon/s_ba_1720_1.png",
|
||||
FixedFlag: false,
|
||||
BackSide: false,
|
||||
BannerID: 101149,
|
||||
StartDate: "2013-04-15 00:00:00",
|
||||
EndDate: "2037-12-31 23:59:59",
|
||||
},
|
||||
{
|
||||
BannerType: 1,
|
||||
TargetID: 1739,
|
||||
AssetPath: "assets/image/secretbox/icon/s_ba_1721_1.png",
|
||||
FixedFlag: false,
|
||||
BackSide: false,
|
||||
BannerID: 101144,
|
||||
StartDate: "2013-04-15 00:00:00",
|
||||
EndDate: "2037-12-31 23:59:59",
|
||||
AddUnitStartDate: "2022-01-01 00:00:00",
|
||||
BannerType: 1,
|
||||
TargetID: 1721,
|
||||
AssetPath: "assets/image/secretbox/icon/s_ba_1721_1.png",
|
||||
FixedFlag: false,
|
||||
BackSide: false,
|
||||
BannerID: 101144,
|
||||
StartDate: "2013-04-15 00:00:00",
|
||||
EndDate: "2037-12-31 23:59:59",
|
||||
},
|
||||
{
|
||||
BannerType: 2,
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package secretbox
|
||||
|
||||
import (
|
||||
secretboxapischema "honoka-chan/internal/schema/api/secretbox"
|
||||
honokautils "honoka-chan/internal/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
func all() (res any, err error) {
|
||||
secretBoxData, err := honokautils.LoadServerData[secretboxapischema.AllData]("secretbox_data.json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secretBoxData.UseCache = 0
|
||||
secretBoxData.IsUnitMax = false
|
||||
secretBoxData.GaugeInfo.MaxGaugePoint = 100
|
||||
secretBoxData.GaugeInfo.GaugePoint = 0
|
||||
|
||||
for i := range secretBoxData.ItemList {
|
||||
if secretBoxData.ItemList[i].Amount < 999 {
|
||||
secretBoxData.ItemList[i].Amount = 999
|
||||
}
|
||||
}
|
||||
|
||||
for i := range secretBoxData.MemberCategoryList {
|
||||
for j := range secretBoxData.MemberCategoryList[i].PageList {
|
||||
for k := range secretBoxData.MemberCategoryList[i].PageList[j].ButtonList {
|
||||
for m := range secretBoxData.MemberCategoryList[i].PageList[j].ButtonList[k].CostList {
|
||||
secretBoxData.MemberCategoryList[i].PageList[j].ButtonList[k].CostList[m].Payable = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res = secretboxapischema.AllResp{
|
||||
Result: secretBoxData,
|
||||
Status: 200,
|
||||
CommandNum: false,
|
||||
TimeStamp: time.Now().Unix(),
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package secretbox
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func SecretBoxApi(action string) (res any, err error) {
|
||||
switch action {
|
||||
case "all":
|
||||
res, err = all()
|
||||
default:
|
||||
err = fmt.Errorf("unimplemented action: secretbox: %s", action)
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package banner
|
||||
|
||||
import "reflect"
|
||||
|
||||
func extractResultField(data any) any {
|
||||
if data == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
value := reflect.ValueOf(data)
|
||||
if value.Kind() == reflect.Pointer {
|
||||
if value.IsNil() {
|
||||
return nil
|
||||
}
|
||||
value = value.Elem()
|
||||
}
|
||||
if value.Kind() != reflect.Struct {
|
||||
return data
|
||||
}
|
||||
|
||||
field := value.FieldByName("Result")
|
||||
if !field.IsValid() || !field.CanInterface() {
|
||||
return data
|
||||
}
|
||||
return field.Interface()
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package banner
|
||||
|
||||
import (
|
||||
apibanner "honoka-chan/internal/handler/api/banner"
|
||||
"honoka-chan/internal/middleware"
|
||||
"honoka-chan/internal/router"
|
||||
"honoka-chan/internal/session"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type bannerResp struct {
|
||||
ResponseData any `json:"response_data"`
|
||||
ReleaseInfo []any `json:"release_info"`
|
||||
StatusCode int `json:"status_code"`
|
||||
}
|
||||
|
||||
func list(ctx *gin.Context) {
|
||||
ss := session.Get(ctx)
|
||||
defer ss.Finalize()
|
||||
|
||||
data, err := apibanner.BannerApi("bannerList")
|
||||
if ss.CheckErr(err) {
|
||||
return
|
||||
}
|
||||
|
||||
resp, ok := data.(interface {
|
||||
GetResult() any
|
||||
})
|
||||
_ = resp
|
||||
_ = ok
|
||||
|
||||
ss.Respond(bannerResp{
|
||||
ResponseData: extractBannerResult(data),
|
||||
ReleaseInfo: []any{},
|
||||
StatusCode: 200,
|
||||
})
|
||||
}
|
||||
|
||||
func extractBannerResult(data any) any {
|
||||
if v, ok := data.(map[string]any); ok {
|
||||
if result, ok := v["result"]; ok {
|
||||
return result
|
||||
}
|
||||
}
|
||||
return extractResultField(data)
|
||||
}
|
||||
|
||||
func init() {
|
||||
router.AddHandler("main.php", "POST", "/banner/bannerList", middleware.Common, list)
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
_ "honoka-chan/internal/handler/api"
|
||||
_ "honoka-chan/internal/handler/award"
|
||||
_ "honoka-chan/internal/handler/background"
|
||||
_ "honoka-chan/internal/handler/banner"
|
||||
_ "honoka-chan/internal/handler/download"
|
||||
_ "honoka-chan/internal/handler/event"
|
||||
_ "honoka-chan/internal/handler/eventscenario"
|
||||
@@ -25,6 +26,7 @@ import (
|
||||
_ "honoka-chan/internal/handler/profile"
|
||||
_ "honoka-chan/internal/handler/reward"
|
||||
_ "honoka-chan/internal/handler/scenario"
|
||||
_ "honoka-chan/internal/handler/secretbox"
|
||||
_ "honoka-chan/internal/handler/subscenario"
|
||||
_ "honoka-chan/internal/handler/tos"
|
||||
_ "honoka-chan/internal/handler/unit"
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package secretbox
|
||||
|
||||
import (
|
||||
"honoka-chan/internal/middleware"
|
||||
"honoka-chan/internal/router"
|
||||
secretboxschema "honoka-chan/internal/schema/secretbox"
|
||||
"honoka-chan/internal/session"
|
||||
honokautils "honoka-chan/internal/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func multi(ctx *gin.Context) {
|
||||
ss := session.Get(ctx)
|
||||
defer ss.Finalize()
|
||||
|
||||
multiReq := secretboxschema.MultiReq{}
|
||||
err := honokautils.ParseRequestData(ctx, &multiReq)
|
||||
if ss.CheckErr(err) {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := drawData(ctx, multiReq.SecretBoxID, multiReq.ID)
|
||||
if ss.CheckErr(err) {
|
||||
return
|
||||
}
|
||||
|
||||
ss.Respond(secretboxschema.MultiResp{
|
||||
ResponseData: data,
|
||||
ReleaseInfo: []any{},
|
||||
StatusCode: 200,
|
||||
})
|
||||
}
|
||||
|
||||
func init() {
|
||||
router.AddHandler("main.php", "POST", "/secretbox/multi", middleware.Common, multi)
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package secretbox
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"honoka-chan/internal/middleware"
|
||||
unitmodel "honoka-chan/internal/model/unit"
|
||||
"honoka-chan/internal/router"
|
||||
secretboxapischema "honoka-chan/internal/schema/api/secretbox"
|
||||
secretboxschema "honoka-chan/internal/schema/secretbox"
|
||||
"honoka-chan/internal/session"
|
||||
honokautils "honoka-chan/internal/utils"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func pon(ctx *gin.Context) {
|
||||
ss := session.Get(ctx)
|
||||
defer ss.Finalize()
|
||||
|
||||
ponReq := secretboxschema.PonReq{}
|
||||
err := honokautils.ParseRequestData(ctx, &ponReq)
|
||||
if ss.CheckErr(err) {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := drawData(ctx, ponReq.SecretBoxID, ponReq.ID)
|
||||
if ss.CheckErr(err) {
|
||||
return
|
||||
}
|
||||
|
||||
ss.Respond(secretboxschema.PonResp{
|
||||
ResponseData: data,
|
||||
ReleaseInfo: []any{},
|
||||
StatusCode: 200,
|
||||
})
|
||||
}
|
||||
|
||||
func init() {
|
||||
router.AddHandler("main.php", "POST", "/secretbox/pon", middleware.Common, pon)
|
||||
}
|
||||
|
||||
func drawData(ctx *gin.Context, secretBoxID, costID int) (secretboxschema.PonData, error) {
|
||||
if secretBoxID <= 0 || costID <= 0 {
|
||||
return secretboxschema.PonData{}, fmt.Errorf("invalid secretbox draw params: secret_box_id=%d id=%d", secretBoxID, costID)
|
||||
}
|
||||
|
||||
ss := session.Get(ctx)
|
||||
config, err := loadSecretBoxAllData()
|
||||
if err != nil {
|
||||
return secretboxschema.PonData{}, err
|
||||
}
|
||||
|
||||
memberCategory, page, err := findSecretBoxPage(config, secretBoxID)
|
||||
if err != nil {
|
||||
return secretboxschema.PonData{}, err
|
||||
}
|
||||
|
||||
button, cost, err := findSecretBoxCost(page, costID)
|
||||
if err != nil {
|
||||
return secretboxschema.PonData{}, err
|
||||
}
|
||||
|
||||
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
drawnUnits, lowestRarity, err := buildDrawUnits(ss, rng, memberCategory, page, button, cost)
|
||||
if err != nil {
|
||||
return secretboxschema.PonData{}, err
|
||||
}
|
||||
|
||||
for i := range page.ButtonList {
|
||||
for j := range page.ButtonList[i].CostList {
|
||||
page.ButtonList[i].CostList[j].Payable = true
|
||||
}
|
||||
}
|
||||
page.SecretBoxInfo.PonCount += cost.UnitCount
|
||||
|
||||
itemList := config.Result.ItemList
|
||||
for i := range itemList {
|
||||
if itemList[i].Amount < 999 {
|
||||
itemList[i].Amount = 999
|
||||
}
|
||||
}
|
||||
|
||||
return secretboxschema.PonData{
|
||||
IsUnitMax: false,
|
||||
ItemList: itemList,
|
||||
GaugeInfo: secretboxapischema.GaugeInfo{
|
||||
MaxGaugePoint: 100,
|
||||
GaugePoint: 0,
|
||||
},
|
||||
ButtonList: page.ButtonList,
|
||||
SecretBoxInfo: page.SecretBoxInfo,
|
||||
SecretBoxItems: secretboxschema.SecretBoxItems{Unit: drawnUnits, Item: []secretboxschema.SecretBoxRewardItem{}},
|
||||
BeforeUserInfo: ss.GetUserInfo(),
|
||||
AfterUserInfo: ss.GetUserInfo(),
|
||||
FreeMuseGachaFlag: false,
|
||||
FreeAqoursGachaFlag: false,
|
||||
LowestRarity: lowestRarity,
|
||||
PromotionPerformanceRate: 25,
|
||||
SecretBoxParcelType: 2,
|
||||
LimitBonusInfo: []any{},
|
||||
LimitBonusRewards: []any{},
|
||||
UnitSupportList: []any{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func buildDrawUnits(ss *session.Session, rng *rand.Rand, memberCategory int, page secretboxapischema.SecretBoxPage, button secretboxapischema.SecretBoxButton, cost secretboxapischema.SecretBoxCost) ([]secretboxschema.SecretBoxUnitItem, int, error) {
|
||||
drawCount := cost.UnitCount
|
||||
if drawCount <= 0 {
|
||||
drawCount = 1
|
||||
}
|
||||
|
||||
rarities := make([]int, 0, drawCount)
|
||||
for i := 0; i < drawCount; i++ {
|
||||
rarities = append(rarities, pickRarity(rng, page, button, i == drawCount-1 && drawCount > 1))
|
||||
}
|
||||
|
||||
if drawCount > 1 {
|
||||
guaranteedRarity := raritySR
|
||||
if hasGuaranteedUR(button, cost) {
|
||||
guaranteedRarity = rarityUR
|
||||
}
|
||||
|
||||
guaranteed := false
|
||||
for _, rarity := range rarities {
|
||||
if satisfiesGuaranteedRarity(rarity, guaranteedRarity) {
|
||||
guaranteed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !guaranteed {
|
||||
if guaranteedRarity == rarityUR {
|
||||
rarities[drawCount-1] = rarityUR
|
||||
} else {
|
||||
rarities[drawCount-1] = pickGuaranteedRarity(rng, page, button)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result := make([]secretboxschema.SecretBoxUnitItem, 0, drawCount)
|
||||
lowestRarity := 0
|
||||
for _, rarity := range rarities {
|
||||
unitID, err := randomUnitID(ss, rng, memberCategory, page, button, rarity)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
unitData, err := getUserUnitByUnitID(ss, unitID)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
item := buildUnitItem(unitData)
|
||||
item.IsHit = shouldShowDirectURHit(rng, page, button, rarity)
|
||||
result = append(result, item)
|
||||
if lowestRarity == 0 || isLowerQualityRarity(rarity, lowestRarity) {
|
||||
lowestRarity = rarity
|
||||
}
|
||||
}
|
||||
|
||||
return result, lowestRarity, nil
|
||||
}
|
||||
|
||||
func buildUnitItem(unitData unitmodel.UnitDataMap) secretboxschema.SecretBoxUnitItem {
|
||||
isSupportMember := unitData.MaxLove == 0 && unitData.UnitSkillLevel == 0 && unitData.UnitRemovableSkillCapacity == 0
|
||||
insertDate := time.Unix(unitData.InsertDate, 0).Format("2006-01-02 15:04:05")
|
||||
|
||||
return secretboxschema.SecretBoxUnitItem{
|
||||
UnitOwningUserID: unitData.UnitOwningUserID,
|
||||
UnitOwningIDs: []int{unitData.UnitOwningUserID},
|
||||
UnitID: unitData.UnitID,
|
||||
Exp: unitData.Exp,
|
||||
NextExp: 0,
|
||||
Level: unitData.Level,
|
||||
MaxLevel: unitData.MaxLevel,
|
||||
LevelLimitID: unitData.LevelLimitID,
|
||||
Rank: unitData.Rank,
|
||||
MaxRank: unitData.MaxRank,
|
||||
Love: unitData.Love,
|
||||
MaxLove: unitData.MaxLove,
|
||||
UnitSkillLevel: unitData.UnitSkillLevel,
|
||||
SkillLevel: unitData.UnitSkillLevel,
|
||||
UnitSkillExp: unitData.UnitSkillExp,
|
||||
MaxHp: unitData.MaxHp,
|
||||
FavoriteFlag: unitData.FavoriteFlag,
|
||||
DisplayRank: unitData.DisplayRank,
|
||||
UnitRemovableSkillCapacity: unitData.UnitRemovableSkillCapacity,
|
||||
MaxRemovableSkillCapacity: unitData.UnitRemovableSkillCapacity,
|
||||
Attribute: unitData.Attribute,
|
||||
Smile: unitData.Smile,
|
||||
Cute: unitData.Cute,
|
||||
Cool: unitData.Cool,
|
||||
IsRankMax: unitData.IsRankMax,
|
||||
IsLoveMax: unitData.IsLoveMax,
|
||||
IsLevelMax: unitData.IsLevelMax,
|
||||
IsSigned: unitData.IsSigned,
|
||||
IsSkillLevelMax: unitData.IsSkillLevelMax,
|
||||
IsRemovableSkillCapacityMax: unitData.IsRemovableSkillCapacityMax,
|
||||
IsSupportMember: isSupportMember,
|
||||
NewUnitFlag: false,
|
||||
UnitRarityID: unitData.Rarity,
|
||||
IsHit: unitData.Rarity == rarityUR,
|
||||
RemovableSkillIDs: []int{},
|
||||
InsertDate: insertDate,
|
||||
TotalSmile: unitData.Smile,
|
||||
TotalCute: unitData.Cute,
|
||||
TotalCool: unitData.Cool,
|
||||
TotalHp: unitData.MaxHp,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
package secretbox
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
secretboxapischema "honoka-chan/internal/schema/api/secretbox"
|
||||
secretboxschema "honoka-chan/internal/schema/secretbox"
|
||||
"honoka-chan/internal/session"
|
||||
"math/rand"
|
||||
"slices"
|
||||
)
|
||||
|
||||
const (
|
||||
rarityN = 1
|
||||
rarityR = 2
|
||||
raritySR = 3
|
||||
rarityUR = 4
|
||||
raritySSR = 5
|
||||
|
||||
urPromotionAnimationRate = 10
|
||||
)
|
||||
|
||||
type mainQuery interface {
|
||||
findUnitIDs(memberCategory int, supportOnly bool, rarity int) ([]int, error)
|
||||
}
|
||||
|
||||
type sessionMainQuery struct {
|
||||
ss *session.Session
|
||||
}
|
||||
|
||||
type sessionlessMainQuery struct{}
|
||||
|
||||
func (q sessionMainQuery) findUnitIDs(memberCategory int, supportOnly bool, rarity int) ([]int, error) {
|
||||
return queryUnitIDs(q.ss, memberCategory, supportOnly, rarity)
|
||||
}
|
||||
|
||||
func (sessionlessMainQuery) findUnitIDs(memberCategory int, supportOnly bool, rarity int) ([]int, error) {
|
||||
return queryUnitIDs(nil, memberCategory, supportOnly, rarity)
|
||||
}
|
||||
|
||||
type poolRule struct {
|
||||
memberCategory int
|
||||
supportOnly bool
|
||||
allowedRarities []int
|
||||
}
|
||||
|
||||
func buildUnitLineUp(memberCategory int, page secretboxapischema.SecretBoxPage, button secretboxapischema.SecretBoxButton) []secretboxschema.UnitLineUp {
|
||||
rarities := []int{rarityUR, raritySSR, raritySR, rarityR, rarityN}
|
||||
result := make([]secretboxschema.UnitLineUp, 0, len(rarities))
|
||||
for _, rarity := range rarities {
|
||||
unitIDs, err := findUnitIDsByRule(sessionlessMainQuery{}, memberCategory, page, button, rarity)
|
||||
if err != nil || len(unitIDs) == 0 {
|
||||
continue
|
||||
}
|
||||
result = append(result, secretboxschema.UnitLineUp{
|
||||
Rarity: rarity,
|
||||
UnitIDs: unitIDs,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func findUnitIDsByRule(q mainQuery, memberCategory int, page secretboxapischema.SecretBoxPage, button secretboxapischema.SecretBoxButton, rarity int) ([]int, error) {
|
||||
rule := buildPoolRule(memberCategory, page, button)
|
||||
if len(rule.allowedRarities) > 0 && !slices.Contains(rule.allowedRarities, rarity) {
|
||||
return []int{}, nil
|
||||
}
|
||||
return q.findUnitIDs(rule.memberCategory, rule.supportOnly, rarity)
|
||||
}
|
||||
|
||||
func randomUnitID(ss *session.Session, rng *rand.Rand, memberCategory int, page secretboxapischema.SecretBoxPage, button secretboxapischema.SecretBoxButton, rarity int) (int, error) {
|
||||
unitIDs, err := findUnitIDsByRule(sessionMainQuery{ss: ss}, memberCategory, page, button, rarity)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(unitIDs) == 0 {
|
||||
return 0, fmt.Errorf("no units available for secretbox=%d rarity=%d", page.SecretBoxInfo.SecretBoxID, rarity)
|
||||
}
|
||||
return unitIDs[rng.Intn(len(unitIDs))], nil
|
||||
}
|
||||
|
||||
func buildPoolRule(memberCategory int, page secretboxapischema.SecretBoxPage, button secretboxapischema.SecretBoxButton) poolRule {
|
||||
rule := poolRule{
|
||||
memberCategory: memberCategory,
|
||||
allowedRarities: []int{rarityR, raritySR, raritySSR, rarityUR},
|
||||
}
|
||||
|
||||
if isThanksFestivalBox(page) {
|
||||
rule.allowedRarities = []int{raritySSR, rarityUR}
|
||||
}
|
||||
|
||||
switch page.SecretBoxInfo.SecretBoxID {
|
||||
case 1, 61:
|
||||
rule.memberCategory = 0
|
||||
rule.allowedRarities = []int{rarityN}
|
||||
return rule
|
||||
case 23:
|
||||
rule.memberCategory = 1
|
||||
rule.supportOnly = true
|
||||
rule.allowedRarities = []int{rarityN, rarityR, raritySR, rarityUR, raritySSR}
|
||||
return rule
|
||||
}
|
||||
|
||||
switch button.SecretBoxButtonType {
|
||||
case 6:
|
||||
rule.memberCategory = 0
|
||||
rule.allowedRarities = []int{rarityN}
|
||||
case 9:
|
||||
rule.allowedRarities = []int{raritySR, rarityUR}
|
||||
case 10:
|
||||
rule.allowedRarities = []int{raritySSR, rarityUR}
|
||||
case 11:
|
||||
rule.allowedRarities = []int{rarityUR}
|
||||
case 14, 15:
|
||||
rule.allowedRarities = []int{raritySR, raritySSR, rarityUR}
|
||||
}
|
||||
|
||||
return rule
|
||||
}
|
||||
|
||||
func isThanksFestivalBox(page secretboxapischema.SecretBoxPage) bool {
|
||||
switch page.SecretBoxInfo.SecretBoxID {
|
||||
case 1718, 1719, 1720, 1721:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func pickRarity(rng *rand.Rand, page secretboxapischema.SecretBoxPage, button secretboxapischema.SecretBoxButton, guaranteed bool) int {
|
||||
rule := buildPoolRule(0, page, button)
|
||||
if len(rule.allowedRarities) == 1 {
|
||||
return rule.allowedRarities[0]
|
||||
}
|
||||
|
||||
weights := map[int]int{
|
||||
rarityR: 70,
|
||||
raritySR: 20,
|
||||
raritySSR: 7,
|
||||
rarityUR: 3,
|
||||
}
|
||||
|
||||
if isThanksFestivalBox(page) {
|
||||
weights = map[int]int{
|
||||
raritySSR: 70,
|
||||
rarityUR: 30,
|
||||
}
|
||||
}
|
||||
|
||||
switch button.SecretBoxButtonType {
|
||||
case 9:
|
||||
weights = map[int]int{raritySR: 80, rarityUR: 20}
|
||||
case 10:
|
||||
weights = map[int]int{raritySSR: 80, rarityUR: 20}
|
||||
case 11:
|
||||
return rarityUR
|
||||
case 14, 15:
|
||||
weights = map[int]int{raritySR: 80, raritySSR: 15, rarityUR: 5}
|
||||
}
|
||||
|
||||
if guaranteed {
|
||||
return pickGuaranteedRarity(rng, page, button)
|
||||
}
|
||||
return weightedPick(rng, rule.allowedRarities, weights)
|
||||
}
|
||||
|
||||
func pickGuaranteedRarity(rng *rand.Rand, page secretboxapischema.SecretBoxPage, button secretboxapischema.SecretBoxButton) int {
|
||||
rule := buildPoolRule(0, page, button)
|
||||
weights := map[int]int{raritySR: 20, raritySSR: 7, rarityUR: 3}
|
||||
return weightedPick(rng, rule.allowedRarities, weights)
|
||||
}
|
||||
|
||||
func hasGuaranteedUR(button secretboxapischema.SecretBoxButton, cost secretboxapischema.SecretBoxCost) bool {
|
||||
if cost.UnitCount <= 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
switch button.SecretBoxButtonType {
|
||||
case 2, 16:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func satisfiesGuaranteedRarity(rarity int, guaranteedRarity int) bool {
|
||||
switch guaranteedRarity {
|
||||
case rarityUR:
|
||||
return rarity == rarityUR
|
||||
case raritySR:
|
||||
return rarity == raritySR || rarity == raritySSR || rarity == rarityUR
|
||||
default:
|
||||
return rarity == guaranteedRarity
|
||||
}
|
||||
}
|
||||
|
||||
func rarityQualityRank(rarity int) int {
|
||||
switch rarity {
|
||||
case rarityN:
|
||||
return 1
|
||||
case rarityR:
|
||||
return 2
|
||||
case raritySR:
|
||||
return 3
|
||||
case raritySSR:
|
||||
return 4
|
||||
case rarityUR:
|
||||
return 5
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func isLowerQualityRarity(left int, right int) bool {
|
||||
return rarityQualityRank(left) < rarityQualityRank(right)
|
||||
}
|
||||
|
||||
func shouldShowDirectURHit(rng *rand.Rand, page secretboxapischema.SecretBoxPage, button secretboxapischema.SecretBoxButton, rarity int) bool {
|
||||
if rarity != rarityUR {
|
||||
return false
|
||||
}
|
||||
|
||||
rule := buildPoolRule(0, page, button)
|
||||
for _, allowedRarity := range rule.allowedRarities {
|
||||
if allowedRarity != rarityUR {
|
||||
return rng.Intn(100) >= urPromotionAnimationRate
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func weightedPick(rng *rand.Rand, allowed []int, weights map[int]int) int {
|
||||
filtered := make([]int, 0, len(allowed))
|
||||
total := 0
|
||||
for _, rarity := range allowed {
|
||||
weight := weights[rarity]
|
||||
if weight <= 0 {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, rarity)
|
||||
total += weight
|
||||
}
|
||||
if len(filtered) == 0 {
|
||||
return allowed[len(allowed)-1]
|
||||
}
|
||||
|
||||
n := rng.Intn(total) + 1
|
||||
acc := 0
|
||||
for _, rarity := range filtered {
|
||||
acc += weights[rarity]
|
||||
if n <= acc {
|
||||
return rarity
|
||||
}
|
||||
}
|
||||
return filtered[len(filtered)-1]
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package secretbox
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
unitmodel "honoka-chan/internal/model/unit"
|
||||
secretboxapischema "honoka-chan/internal/schema/api/secretbox"
|
||||
"honoka-chan/internal/session"
|
||||
honokautils "honoka-chan/internal/utils"
|
||||
"honoka-chan/pkg/db"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
const secretBoxDataFile = "secretbox_data.json"
|
||||
|
||||
func loadSecretBoxAllData() (secretboxapischema.AllResp, error) {
|
||||
data, err := honokautils.LoadServerData[secretboxapischema.AllData](secretBoxDataFile)
|
||||
if err != nil {
|
||||
return secretboxapischema.AllResp{}, err
|
||||
}
|
||||
|
||||
return secretboxapischema.AllResp{
|
||||
Result: data,
|
||||
Status: 200,
|
||||
CommandNum: false,
|
||||
TimeStamp: time.Now().Unix(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func findSecretBoxPage(config secretboxapischema.AllResp, secretBoxID int) (int, secretboxapischema.SecretBoxPage, error) {
|
||||
for _, category := range config.Result.MemberCategoryList {
|
||||
for _, page := range category.PageList {
|
||||
if page.SecretBoxInfo.SecretBoxID == secretBoxID {
|
||||
return category.MemberCategory, page, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, secretboxapischema.SecretBoxPage{}, fmt.Errorf("secretbox not found: %d", secretBoxID)
|
||||
}
|
||||
|
||||
func findSecretBoxCost(page secretboxapischema.SecretBoxPage, costID int) (secretboxapischema.SecretBoxButton, secretboxapischema.SecretBoxCost, error) {
|
||||
for _, button := range page.ButtonList {
|
||||
for _, cost := range button.CostList {
|
||||
if cost.ID == costID {
|
||||
return button, cost, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return secretboxapischema.SecretBoxButton{}, secretboxapischema.SecretBoxCost{}, fmt.Errorf("secretbox cost not found: %d", costID)
|
||||
}
|
||||
|
||||
func queryUnitIDs(ss *session.Session, memberCategory int, supportOnly bool, rarity int) ([]int, error) {
|
||||
var (
|
||||
rows []struct {
|
||||
UnitID int `xorm:"unit_id"`
|
||||
}
|
||||
err error
|
||||
)
|
||||
|
||||
sql := `
|
||||
SELECT u.unit_id
|
||||
FROM unit_m u
|
||||
LEFT JOIN unit_type_m ut ON ut.unit_type_id = u.unit_type_id
|
||||
WHERE u.rarity = ?`
|
||||
args := []any{rarity}
|
||||
|
||||
if supportOnly {
|
||||
sql += ` AND u.disable_rank_up != 0`
|
||||
} else if memberCategory >= 0 {
|
||||
sql += ` AND ut.member_category = ?`
|
||||
args = append(args, memberCategory)
|
||||
}
|
||||
|
||||
if ss != nil {
|
||||
err = ss.MainEng.SQL(sql, args...).Find(&rows)
|
||||
} else {
|
||||
err = db.MainEng.SQL(sql, args...).Find(&rows)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]int, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
result = append(result, row.UnitID)
|
||||
}
|
||||
sort.Ints(result)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func getUserUnitByUnitID(ss *session.Session, unitID int) (unitmodel.UnitDataMap, error) {
|
||||
unitData := unitmodel.UnitDataMap{}
|
||||
has, err := ss.GetBasicUnitInfo().
|
||||
Where("a.user_id = ?", ss.UserID).
|
||||
Where("a.unit_id = ?", unitID).
|
||||
Get(&unitData)
|
||||
if err != nil {
|
||||
return unitmodel.UnitDataMap{}, err
|
||||
}
|
||||
if !has {
|
||||
return unitmodel.UnitDataMap{}, fmt.Errorf("user %d does not own unit %d", ss.UserID, unitID)
|
||||
}
|
||||
return unitData, nil
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package secretbox
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"honoka-chan/internal/middleware"
|
||||
"honoka-chan/internal/router"
|
||||
secretboxschema "honoka-chan/internal/schema/secretbox"
|
||||
"honoka-chan/internal/session"
|
||||
honokautils "honoka-chan/internal/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func showDetail(ctx *gin.Context) {
|
||||
ss := session.Get(ctx)
|
||||
defer ss.Finalize()
|
||||
|
||||
showDetailReq := secretboxschema.ShowDetailReq{}
|
||||
err := honokautils.ParseRequestData(ctx, &showDetailReq)
|
||||
if ss.CheckErr(err) {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := showDetailData(showDetailReq.SecretBoxID)
|
||||
if ss.CheckErr(err) {
|
||||
return
|
||||
}
|
||||
|
||||
ss.Respond(secretboxschema.ShowDetailResp{
|
||||
ResponseData: data,
|
||||
ReleaseInfo: []any{},
|
||||
StatusCode: 200,
|
||||
})
|
||||
}
|
||||
|
||||
func init() {
|
||||
router.AddHandler("main.php", "POST", "/secretbox/showDetail", middleware.Common, showDetail)
|
||||
}
|
||||
|
||||
func showDetailData(secretBoxID int) (secretboxschema.ShowDetailData, error) {
|
||||
if secretBoxID <= 0 {
|
||||
return secretboxschema.ShowDetailData{}, fmt.Errorf("invalid secret_box_id: %d", secretBoxID)
|
||||
}
|
||||
|
||||
config, err := loadSecretBoxAllData()
|
||||
if err != nil {
|
||||
return secretboxschema.ShowDetailData{}, err
|
||||
}
|
||||
|
||||
memberCategory, page, err := findSecretBoxPage(config, secretBoxID)
|
||||
if err != nil {
|
||||
return secretboxschema.ShowDetailData{}, err
|
||||
}
|
||||
|
||||
result := secretboxschema.ShowDetailData{
|
||||
URL: fmt.Sprintf("/webview.php/secretbox/detail?id=%d&no_title=1", secretBoxID),
|
||||
}
|
||||
|
||||
switch page.SecretBoxInfo.SecretBoxType {
|
||||
case 5:
|
||||
buttons := make([]secretboxschema.ButtonTypeUnitLineUp, 0, len(page.ButtonList))
|
||||
for _, button := range page.ButtonList {
|
||||
buttons = append(buttons, secretboxschema.ButtonTypeUnitLineUp{
|
||||
SecretBoxButtonType: button.SecretBoxButtonType,
|
||||
SecretBoxName: button.SecretBoxName,
|
||||
UnitLineUp: buildUnitLineUp(memberCategory, page, button),
|
||||
})
|
||||
}
|
||||
result.ButtonTypeUnitLineUp = buttons
|
||||
default:
|
||||
if len(page.ButtonList) == 0 {
|
||||
return secretboxschema.ShowDetailData{}, fmt.Errorf("secretbox %d has no buttons", secretBoxID)
|
||||
}
|
||||
result.UnitLineUp = buildUnitLineUp(memberCategory, page, page.ButtonList[0])
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
package apischema
|
||||
|
||||
type ApiReq struct {
|
||||
Module string `json:"module"`
|
||||
UserID int `json:"user_id"`
|
||||
Action string `json:"action"`
|
||||
Timestamp int64 `json:"timeStamp"`
|
||||
Module string `json:"module"`
|
||||
UserID int `json:"user_id"`
|
||||
Action string `json:"action"`
|
||||
ID int `json:"id"`
|
||||
SecretBoxID int `json:"secret_box_id"`
|
||||
Timestamp int64 `json:"timeStamp"`
|
||||
}
|
||||
|
||||
type ApiResp struct {
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
package bannerapischema
|
||||
|
||||
type BannerList struct {
|
||||
BannerType int `json:"banner_type"`
|
||||
TargetID int `json:"target_id"`
|
||||
AssetPath string `json:"asset_path"`
|
||||
FixedFlag bool `json:"fixed_flag"`
|
||||
BackSide bool `json:"back_side"`
|
||||
BannerID int `json:"banner_id"`
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
AddUnitStartDate string `json:"add_unit_start_date,omitempty"`
|
||||
WebviewURL string `json:"webview_url,omitempty"`
|
||||
BannerType int `json:"banner_type"`
|
||||
TargetID int `json:"target_id"`
|
||||
AssetPath string `json:"asset_path"`
|
||||
FixedFlag bool `json:"fixed_flag"`
|
||||
BackSide bool `json:"back_side"`
|
||||
BannerID int `json:"banner_id"`
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
WebviewURL string `json:"webview_url,omitempty"`
|
||||
}
|
||||
|
||||
type ListData struct {
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package secretboxapischema
|
||||
|
||||
type ItemAmount struct {
|
||||
ItemID int `json:"item_id"`
|
||||
Amount int `json:"amount"`
|
||||
}
|
||||
|
||||
type GaugeInfo struct {
|
||||
MaxGaugePoint int `json:"max_gauge_point"`
|
||||
GaugePoint int `json:"gauge_point"`
|
||||
}
|
||||
|
||||
type SecretBoxAnimationAssets struct {
|
||||
Type int `json:"type"`
|
||||
BackgroundAsset string `json:"background_asset"`
|
||||
AdditionalAsset1 string `json:"additional_asset_1"`
|
||||
AdditionalAsset2 string `json:"additional_asset_2"`
|
||||
AdditionalAsset3 string `json:"additional_asset_3"`
|
||||
}
|
||||
|
||||
type SecretBoxCost struct {
|
||||
ID int `json:"id"`
|
||||
Type int `json:"type"`
|
||||
Amount int `json:"amount"`
|
||||
ItemID *int `json:"item_id"`
|
||||
UnitCount int `json:"unit_count"`
|
||||
Payable bool `json:"payable"`
|
||||
}
|
||||
|
||||
type SecretBoxButton struct {
|
||||
SecretBoxButtonType int `json:"secret_box_button_type"`
|
||||
CostList []SecretBoxCost `json:"cost_list"`
|
||||
SecretBoxName string `json:"secret_box_name"`
|
||||
LimitCount int `json:"limit_count,omitempty"`
|
||||
LimitUnderMessage string `json:"limit_under_message,omitempty"`
|
||||
LimitUnderMessageSuffix string `json:"limit_under_message_suffix,omitempty"`
|
||||
BalloonAsset string `json:"balloon_asset,omitempty"`
|
||||
}
|
||||
|
||||
type SecretBoxInfo struct {
|
||||
SecretBoxID int `json:"secret_box_id"`
|
||||
SecretBoxType int `json:"secret_box_type"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
ShowEndDate string `json:"show_end_date"`
|
||||
AddGauge int `json:"add_gauge"`
|
||||
AlwaysDisplayFlag int `json:"always_display_flag"`
|
||||
PonCount int `json:"pon_count"`
|
||||
PonUpperLimit int `json:"pon_upper_limit"`
|
||||
}
|
||||
|
||||
type SecretBoxPage struct {
|
||||
MenuAsset string `json:"menu_asset"`
|
||||
PageOrder int `json:"page_order"`
|
||||
AnimationAssets SecretBoxAnimationAssets `json:"animation_assets"`
|
||||
ButtonList []SecretBoxButton `json:"button_list"`
|
||||
SecretBoxInfo SecretBoxInfo `json:"secret_box_info"`
|
||||
}
|
||||
|
||||
type MemberCategoryPageList struct {
|
||||
MemberCategory int `json:"member_category"`
|
||||
PageList []SecretBoxPage `json:"page_list"`
|
||||
}
|
||||
|
||||
type AllData struct {
|
||||
UseCache int `json:"use_cache"`
|
||||
IsUnitMax bool `json:"is_unit_max"`
|
||||
ItemList []ItemAmount `json:"item_list"`
|
||||
GaugeInfo GaugeInfo `json:"gauge_info"`
|
||||
MemberCategoryList []MemberCategoryPageList `json:"member_category_list"`
|
||||
}
|
||||
|
||||
type AllResp struct {
|
||||
Result AllData `json:"result"`
|
||||
Status int `json:"status"`
|
||||
CommandNum bool `json:"commandNum"`
|
||||
TimeStamp int64 `json:"timeStamp"`
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package secretboxschema
|
||||
|
||||
type MultiReq struct {
|
||||
Module string `json:"module"`
|
||||
Action string `json:"action"`
|
||||
Mgd int `json:"mgd"`
|
||||
ID int `json:"id"`
|
||||
SecretBoxID int `json:"secret_box_id"`
|
||||
Count int `json:"count"`
|
||||
TimeStamp int64 `json:"timeStamp"`
|
||||
CommandNum string `json:"commandNum"`
|
||||
UnitTypeIds []any `json:"unit_type_ids"`
|
||||
}
|
||||
|
||||
type MultiResp struct {
|
||||
ResponseData PonData `json:"response_data"`
|
||||
ReleaseInfo []any `json:"release_info"`
|
||||
StatusCode int `json:"status_code"`
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package secretboxschema
|
||||
|
||||
import secretboxapischema "honoka-chan/internal/schema/api/secretbox"
|
||||
|
||||
type PonReq struct {
|
||||
Module string `json:"module"`
|
||||
Mgd int `json:"mgd"`
|
||||
Action string `json:"action"`
|
||||
TimeStamp int64 `json:"timeStamp"`
|
||||
ID int `json:"id"`
|
||||
CommandNum string `json:"commandNum"`
|
||||
SecretBoxID int `json:"secret_box_id"`
|
||||
UnitTypeIds []any `json:"unit_type_ids"`
|
||||
}
|
||||
|
||||
type SecretBoxUnitItem struct {
|
||||
UnitOwningUserID int `json:"unit_owning_user_id"`
|
||||
UnitOwningIDs []int `json:"unit_owning_ids"`
|
||||
UnitID int `json:"unit_id"`
|
||||
Exp int `json:"exp"`
|
||||
NextExp int `json:"next_exp"`
|
||||
Level int `json:"level"`
|
||||
MaxLevel int `json:"max_level"`
|
||||
LevelLimitID int `json:"level_limit_id"`
|
||||
Rank int `json:"rank"`
|
||||
MaxRank int `json:"max_rank"`
|
||||
Love int `json:"love"`
|
||||
MaxLove int `json:"max_love"`
|
||||
UnitSkillLevel int `json:"unit_skill_level"`
|
||||
SkillLevel int `json:"skill_level"`
|
||||
UnitSkillExp int `json:"unit_skill_exp"`
|
||||
MaxHp int `json:"max_hp"`
|
||||
FavoriteFlag bool `json:"favorite_flag"`
|
||||
DisplayRank int `json:"display_rank"`
|
||||
UnitRemovableSkillCapacity int `json:"unit_removable_skill_capacity"`
|
||||
MaxRemovableSkillCapacity int `json:"max_removable_skill_capacity"`
|
||||
Attribute int `json:"attribute"`
|
||||
Smile int `json:"smile"`
|
||||
Cute int `json:"cute"`
|
||||
Cool int `json:"cool"`
|
||||
IsRankMax bool `json:"is_rank_max"`
|
||||
IsLoveMax bool `json:"is_love_max"`
|
||||
IsLevelMax bool `json:"is_level_max"`
|
||||
IsSigned bool `json:"is_signed"`
|
||||
IsSkillLevelMax bool `json:"is_skill_level_max"`
|
||||
IsRemovableSkillCapacityMax bool `json:"is_removable_skill_capacity_max"`
|
||||
IsSupportMember bool `json:"is_support_member"`
|
||||
NewUnitFlag bool `json:"new_unit_flag"`
|
||||
UnitRarityID int `json:"unit_rarity_id"`
|
||||
IsHit bool `json:"is_hit"`
|
||||
RemovableSkillIDs []int `json:"removable_skill_ids"`
|
||||
InsertDate string `json:"insert_date"`
|
||||
TotalSmile int `json:"total_smile"`
|
||||
TotalCute int `json:"total_cute"`
|
||||
TotalCool int `json:"total_cool"`
|
||||
TotalHp int `json:"total_hp"`
|
||||
}
|
||||
|
||||
type SecretBoxRewardItem struct {
|
||||
ItemID int `json:"item_id"`
|
||||
AddType int `json:"add_type"`
|
||||
Amount int `json:"amount"`
|
||||
ItemCategoryID int `json:"item_category_id"`
|
||||
RewardBoxFlag bool `json:"reward_box_flag"`
|
||||
InsertDate string `json:"insert_date"`
|
||||
}
|
||||
|
||||
type SecretBoxItems struct {
|
||||
Unit []SecretBoxUnitItem `json:"unit"`
|
||||
Item []SecretBoxRewardItem `json:"item"`
|
||||
}
|
||||
|
||||
type PonData struct {
|
||||
IsUnitMax bool `json:"is_unit_max"`
|
||||
ItemList []secretboxapischema.ItemAmount `json:"item_list"`
|
||||
GaugeInfo secretboxapischema.GaugeInfo `json:"gauge_info"`
|
||||
ButtonList []secretboxapischema.SecretBoxButton `json:"button_list"`
|
||||
SecretBoxInfo secretboxapischema.SecretBoxInfo `json:"secret_box_info"`
|
||||
SecretBoxItems SecretBoxItems `json:"secret_box_items"`
|
||||
BeforeUserInfo any `json:"before_user_info"`
|
||||
AfterUserInfo any `json:"after_user_info"`
|
||||
FreeMuseGachaFlag bool `json:"free_muse_gacha_flag"`
|
||||
FreeAqoursGachaFlag bool `json:"free_aqours_gacha_flag"`
|
||||
LowestRarity int `json:"lowest_rarity"`
|
||||
PromotionPerformanceRate int `json:"promotion_performance_rate"`
|
||||
SecretBoxParcelType int `json:"secret_box_parcel_type"`
|
||||
LimitBonusInfo []any `json:"limit_bonus_info"`
|
||||
LimitBonusRewards []any `json:"limit_bonus_rewards"`
|
||||
UnitSupportList []any `json:"unit_support_list"`
|
||||
}
|
||||
|
||||
type PonResp struct {
|
||||
ResponseData PonData `json:"response_data"`
|
||||
ReleaseInfo []any `json:"release_info"`
|
||||
StatusCode int `json:"status_code"`
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package secretboxschema
|
||||
|
||||
type ShowDetailReq struct {
|
||||
Module string `json:"module"`
|
||||
Action string `json:"action"`
|
||||
TimeStamp int64 `json:"timeStamp"`
|
||||
Mgd int `json:"mgd"`
|
||||
SecretBoxID int `json:"secret_box_id"`
|
||||
CommandNum string `json:"commandNum"`
|
||||
}
|
||||
|
||||
type UnitLineUp struct {
|
||||
Rarity int `json:"rarity"`
|
||||
UnitIDs []int `json:"unit_ids"`
|
||||
}
|
||||
|
||||
type ButtonTypeUnitLineUp struct {
|
||||
SecretBoxButtonType int `json:"secret_box_button_type"`
|
||||
SecretBoxName string `json:"secret_box_name"`
|
||||
UnitLineUp []UnitLineUp `json:"unit_line_up"`
|
||||
}
|
||||
|
||||
type ShowDetailData struct {
|
||||
ButtonTypeUnitLineUp []ButtonTypeUnitLineUp `json:"button_type_unit_line_up,omitempty"`
|
||||
UnitLineUp []UnitLineUp `json:"unit_line_up,omitempty"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type ShowDetailResp struct {
|
||||
ResponseData ShowDetailData `json:"response_data"`
|
||||
ReleaseInfo []any `json:"release_info"`
|
||||
StatusCode int `json:"status_code"`
|
||||
}
|
||||
Reference in New Issue
Block a user