mirror of
https://github.com/Walter-Sparrow/lunar-tear.git
synced 2026-07-02 05:43:41 +03:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 63df7d7055 | |||
| c961fde8ac | |||
| 72b2bd1ec5 |
@@ -82,7 +82,7 @@ func loadEnhanceRows() ([]enhanceRow, error) {
|
|||||||
}
|
}
|
||||||
rows = append(rows, enhanceRow{
|
rows = append(rows, enhanceRow{
|
||||||
effectType: EnhanceCampaignEffectType(c.EnhanceCampaignEffectType),
|
effectType: EnhanceCampaignEffectType(c.EnhanceCampaignEffectType),
|
||||||
effectValue: c.EnhanceCampaignEffectValue,
|
effectValue: c.EnhanceCampaignEffectValue / 10,
|
||||||
targets: grp,
|
targets: grp,
|
||||||
startMillis: c.StartDatetime,
|
startMillis: c.StartDatetime,
|
||||||
endMillis: c.EndDatetime,
|
endMillis: c.EndDatetime,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ func (b RateBonus) Apply(basePermil int32) int32 {
|
|||||||
if b.override > 0 {
|
if b.override > 0 {
|
||||||
base = b.override
|
base = b.override
|
||||||
}
|
}
|
||||||
return clampPermil(base + b.bonusPermil)
|
return clampPermil(int32(int64(base) + int64(b.bonusPermil)))
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExpBonus struct {
|
type ExpBonus struct {
|
||||||
@@ -18,7 +18,7 @@ type ExpBonus struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b ExpBonus) Apply(base int32) int32 {
|
func (b ExpBonus) Apply(base int32) int32 {
|
||||||
return base * (1000 + b.bonusPermil) / 1000
|
return int32(int64(base) * int64(1000+b.bonusPermil) / 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
type StaminaMul struct {
|
type StaminaMul struct {
|
||||||
@@ -29,7 +29,7 @@ func (m StaminaMul) Apply(base int32) int32 {
|
|||||||
if m.permil == 1000 {
|
if m.permil == 1000 {
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
return base * m.permil / 1000
|
return int32(int64(base) * int64(m.permil) / 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
type DropRateMul struct {
|
type DropRateMul struct {
|
||||||
@@ -37,7 +37,7 @@ type DropRateMul struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m DropRateMul) Apply(base int32) int32 {
|
func (m DropRateMul) Apply(base int32) int32 {
|
||||||
return (base*(1000+m.bonusPermil) + 999) / 1000
|
return int32((int64(base)*int64(1000+m.bonusPermil) + 999) / 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
type BonusDrop struct {
|
type BonusDrop struct {
|
||||||
|
|||||||
@@ -17,3 +17,16 @@ func LevelAndCap(exp int32, thresholds []int32) (level, capped int32) {
|
|||||||
}
|
}
|
||||||
return level, exp
|
return level, exp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ApplyExpWithMaxLevel runs LevelAndCap and then clamps the resulting
|
||||||
|
// level to the per-instance maxLevel (e.g. limit break + awaken for
|
||||||
|
// weapons, limit break + rebirth for costumes). A maxLevel <= 0 means
|
||||||
|
// "no per-instance cap" and the result is identical to LevelAndCap.
|
||||||
|
func ApplyExpWithMaxLevel(exp int32, thresholds []int32, maxLevel int32) (level, capped int32) {
|
||||||
|
level, capped = LevelAndCap(exp, thresholds)
|
||||||
|
if maxLevel > 0 && level > maxLevel && int(maxLevel) < len(thresholds) {
|
||||||
|
level = maxLevel
|
||||||
|
capped = thresholds[maxLevel]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,25 @@ type CharacterRebirthCatalog struct {
|
|||||||
MaterialsByGroupId map[int32][]EntityMCharacterRebirthMaterialGroup
|
MaterialsByGroupId map[int32][]EntityMCharacterRebirthMaterialGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *CharacterRebirthCatalog) CostumeLevelLimitUp(characterId, rebirthCount int32) int32 {
|
||||||
|
if c == nil || rebirthCount <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
stepGroupId, ok := c.StepGroupByCharacterId[characterId]
|
||||||
|
if !ok {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var total int32
|
||||||
|
for i := range rebirthCount {
|
||||||
|
step, ok := c.StepByGroupAndCount[StepKey{GroupId: stepGroupId, BeforeRebirthCount: i}]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
total += step.CostumeLevelLimitUp
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
func LoadCharacterRebirthCatalog() (*CharacterRebirthCatalog, error) {
|
func LoadCharacterRebirthCatalog() (*CharacterRebirthCatalog, error) {
|
||||||
rebirthRows, err := utils.ReadTable[EntityMCharacterRebirth]("m_character_rebirth")
|
rebirthRows, err := utils.ReadTable[EntityMCharacterRebirth]("m_character_rebirth")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -2,6 +2,16 @@ package model
|
|||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
|
type QuestType int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
QuestTypeUnknown QuestType = 0
|
||||||
|
QuestTypeMain QuestType = 1
|
||||||
|
QuestTypeEvent QuestType = 2
|
||||||
|
QuestTypeExtra QuestType = 3
|
||||||
|
QuestTypeBigHunt QuestType = 4
|
||||||
|
)
|
||||||
|
|
||||||
type QuestFlowType int32
|
type QuestFlowType int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -35,8 +35,9 @@ func (h *QuestHandler) HandleBigHuntQuestFinish(user *store.UserState, questId i
|
|||||||
}
|
}
|
||||||
|
|
||||||
target := h.targetForBigHunt(questId)
|
target := h.targetForBigHunt(questId)
|
||||||
outcome := h.evaluateFinishOutcome(user, questId, target, nowMillis)
|
var outcome FinishOutcome
|
||||||
if !isRetired {
|
if !isRetired && !isAnnihilated {
|
||||||
|
outcome = h.evaluateFinishOutcome(user, questId, target, nowMillis)
|
||||||
h.applyQuestVictory(user, questId, &outcome, nowMillis, false)
|
h.applyQuestVictory(user, questId, &outcome, nowMillis, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,8 +44,9 @@ func (h *QuestHandler) HandleEventQuestFinish(user *store.UserState, eventQuestC
|
|||||||
}
|
}
|
||||||
|
|
||||||
target := h.targetForEvent(eventQuestChapterId, questId)
|
target := h.targetForEvent(eventQuestChapterId, questId)
|
||||||
outcome := h.evaluateFinishOutcome(user, questId, target, nowMillis)
|
var outcome FinishOutcome
|
||||||
if !isRetired {
|
if !isRetired && !isAnnihilated {
|
||||||
|
outcome = h.evaluateFinishOutcome(user, questId, target, nowMillis)
|
||||||
h.applyQuestVictory(user, questId, &outcome, nowMillis, false)
|
h.applyQuestVictory(user, questId, &outcome, nowMillis, false)
|
||||||
h.recordSideStoryLimitContentStatus(user, questId, nowMillis)
|
h.recordSideStoryLimitContentStatus(user, questId, nowMillis)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,8 +42,9 @@ func (h *QuestHandler) HandleExtraQuestFinish(user *store.UserState, questId int
|
|||||||
}
|
}
|
||||||
|
|
||||||
target := h.targetForExtra(questId)
|
target := h.targetForExtra(questId)
|
||||||
outcome := h.evaluateFinishOutcome(user, questId, target, nowMillis)
|
var outcome FinishOutcome
|
||||||
if !isRetired {
|
if !isRetired && !isAnnihilated {
|
||||||
|
outcome = h.evaluateFinishOutcome(user, questId, target, nowMillis)
|
||||||
h.applyQuestVictory(user, questId, &outcome, nowMillis, false)
|
h.applyQuestVictory(user, questId, &outcome, nowMillis, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ type RewardGrant struct {
|
|||||||
PossessionType model.PossessionType
|
PossessionType model.PossessionType
|
||||||
PossessionId int32
|
PossessionId int32
|
||||||
Count int32
|
Count int32
|
||||||
|
IsAutoSale bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type FinishOutcome struct {
|
type FinishOutcome struct {
|
||||||
@@ -32,10 +33,11 @@ type QuestHandler struct {
|
|||||||
Granter *store.PossessionGranter
|
Granter *store.PossessionGranter
|
||||||
SideStoryChapterByEventQuestId map[int32]int32
|
SideStoryChapterByEventQuestId map[int32]int32
|
||||||
Campaigns *campaign.Catalog
|
Campaigns *campaign.Catalog
|
||||||
|
CharacterRebirth *masterdata.CharacterRebirthCatalog
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewQuestHandler(catalog *masterdata.QuestCatalog, config *masterdata.GameConfig, sideStory *masterdata.SideStoryCatalog, campaigns *campaign.Catalog) *QuestHandler {
|
func NewQuestHandler(catalog *masterdata.QuestCatalog, config *masterdata.GameConfig, sideStory *masterdata.SideStoryCatalog, campaigns *campaign.Catalog, characterRebirth *masterdata.CharacterRebirthCatalog) *QuestHandler {
|
||||||
granter := BuildGranter(catalog)
|
granter := BuildGranter(catalog, config)
|
||||||
var sideStoryChapters map[int32]int32
|
var sideStoryChapters map[int32]int32
|
||||||
if sideStory != nil {
|
if sideStory != nil {
|
||||||
sideStoryChapters = sideStory.ChapterByEventQuestId
|
sideStoryChapters = sideStory.ChapterByEventQuestId
|
||||||
@@ -46,10 +48,11 @@ func NewQuestHandler(catalog *masterdata.QuestCatalog, config *masterdata.GameCo
|
|||||||
Granter: granter,
|
Granter: granter,
|
||||||
SideStoryChapterByEventQuestId: sideStoryChapters,
|
SideStoryChapterByEventQuestId: sideStoryChapters,
|
||||||
Campaigns: campaigns,
|
Campaigns: campaigns,
|
||||||
|
CharacterRebirth: characterRebirth,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildGranter(catalog *masterdata.QuestCatalog) *store.PossessionGranter {
|
func BuildGranter(catalog *masterdata.QuestCatalog, config *masterdata.GameConfig) *store.PossessionGranter {
|
||||||
costumeById := make(map[int32]store.CostumeRef, len(catalog.CostumeById))
|
costumeById := make(map[int32]store.CostumeRef, len(catalog.CostumeById))
|
||||||
for id, cm := range catalog.CostumeById {
|
for id, cm := range catalog.CostumeById {
|
||||||
costumeById[id] = store.CostumeRef{CharacterId: cm.CharacterId}
|
costumeById[id] = store.CostumeRef{CharacterId: cm.CharacterId}
|
||||||
@@ -109,6 +112,15 @@ func BuildGranter(catalog *masterdata.QuestCatalog) *store.PossessionGranter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
partsSellPriceL1 := make(map[int32]int32, len(catalog.SellPriceByRarity))
|
||||||
|
for rarity, fn := range catalog.SellPriceByRarity {
|
||||||
|
partsSellPriceL1[int32(rarity)] = fn.Evaluate(1)
|
||||||
|
}
|
||||||
|
var goldItemId int32
|
||||||
|
if config != nil {
|
||||||
|
goldItemId = config.ConsumableItemIdForGold
|
||||||
|
}
|
||||||
|
|
||||||
return &store.PossessionGranter{
|
return &store.PossessionGranter{
|
||||||
CostumeById: costumeById,
|
CostumeById: costumeById,
|
||||||
WeaponById: weaponById,
|
WeaponById: weaponById,
|
||||||
@@ -120,5 +132,7 @@ func BuildGranter(catalog *masterdata.QuestCatalog) *store.PossessionGranter {
|
|||||||
PartsVariantsByGroupRarity: partsVariants,
|
PartsVariantsByGroupRarity: partsVariants,
|
||||||
PartsSubStatusPool: catalog.SubStatusPool,
|
PartsSubStatusPool: catalog.SubStatusPool,
|
||||||
PartsSubStatusDefs: partsSubDefs,
|
PartsSubStatusDefs: partsSubDefs,
|
||||||
|
PartsSellPriceL1ByRarity: partsSellPriceL1,
|
||||||
|
GoldConsumableItemId: goldItemId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -204,9 +204,8 @@ func (h *QuestHandler) applyQuestVictory(user *store.UserState, questId int32, o
|
|||||||
}
|
}
|
||||||
questState.IsRewardGranted = true
|
questState.IsRewardGranted = true
|
||||||
}
|
}
|
||||||
for _, drop := range outcome.DropRewards {
|
raritySet, rankSet := parseAutoSaleRules(user.AutoSaleSettings)
|
||||||
h.applyRewardPossession(user, drop.PossessionType, drop.PossessionId, drop.Count, nowMillis)
|
h.grantDropRewards(user, outcome.DropRewards, raritySet, rankSet, nowMillis)
|
||||||
}
|
|
||||||
for _, reward := range outcome.ReplayFlowFirstClearRewards {
|
for _, reward := range outcome.ReplayFlowFirstClearRewards {
|
||||||
h.applyRewardPossession(user, reward.PossessionType, reward.PossessionId, reward.Count, nowMillis)
|
h.applyRewardPossession(user, reward.PossessionType, reward.PossessionId, reward.Count, nowMillis)
|
||||||
}
|
}
|
||||||
@@ -260,11 +259,12 @@ func (h *QuestHandler) HandleQuestFinish(user *store.UserState, questId int32, i
|
|||||||
|
|
||||||
h.initQuestState(user, questId)
|
h.initQuestState(user, questId)
|
||||||
|
|
||||||
outcome := h.evaluateFinishOutcome(user, questId, h.targetForMain(questId), nowMillis)
|
|
||||||
wasReplay := model.IsReplayQuestFlowType(user.MainQuest.CurrentQuestFlowType)
|
wasReplay := model.IsReplayQuestFlowType(user.MainQuest.CurrentQuestFlowType)
|
||||||
wasMenuReplay := user.MainQuest.SavedContext.Active
|
wasMenuReplay := user.MainQuest.SavedContext.Active
|
||||||
|
|
||||||
if !isRetired {
|
var outcome FinishOutcome
|
||||||
|
if !isRetired && !isAnnihilated {
|
||||||
|
outcome = h.evaluateFinishOutcome(user, questId, h.targetForMain(questId), nowMillis)
|
||||||
h.applyQuestVictory(user, questId, &outcome, nowMillis, wasReplay)
|
h.applyQuestVictory(user, questId, &outcome, nowMillis, wasReplay)
|
||||||
|
|
||||||
// A replay-flow finish must NOT move the MainFlow scene pointer: the
|
// A replay-flow finish must NOT move the MainFlow scene pointer: the
|
||||||
@@ -334,12 +334,11 @@ func (h *QuestHandler) HandleQuestSkip(user *store.UserState, questId, skipCount
|
|||||||
if user.ConsumableItems[skipTicketId] < 0 {
|
if user.ConsumableItems[skipTicketId] < 0 {
|
||||||
user.ConsumableItems[skipTicketId] = 0
|
user.ConsumableItems[skipTicketId] = 0
|
||||||
}
|
}
|
||||||
|
raritySet, rankSet := parseAutoSaleRules(user.AutoSaleSettings)
|
||||||
var allDrops []RewardGrant
|
var allDrops []RewardGrant
|
||||||
for range skipCount {
|
for range skipCount {
|
||||||
drops := h.computeDropRewards(questDef, target, nowMillis)
|
drops := h.computeDropRewards(questDef, target, nowMillis)
|
||||||
for _, drop := range drops {
|
h.grantDropRewards(user, drops, raritySet, rankSet, nowMillis)
|
||||||
h.applyRewardPossession(user, drop.PossessionType, drop.PossessionId, drop.Count, nowMillis)
|
|
||||||
}
|
|
||||||
allDrops = append(allDrops, drops...)
|
allDrops = append(allDrops, drops...)
|
||||||
|
|
||||||
if questDef.Gold != 0 {
|
if questDef.Gold != 0 {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package questflow
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"lunar-tear/server/internal/campaign"
|
"lunar-tear/server/internal/campaign"
|
||||||
"lunar-tear/server/internal/gameutil"
|
"lunar-tear/server/internal/gameutil"
|
||||||
@@ -128,6 +130,54 @@ func (h *QuestHandler) evaluateFinishOutcome(user *store.UserState, questId int3
|
|||||||
return outcome
|
return outcome
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var autoSaleRarityTiers = map[int32]bool{10: true, 20: true, 30: true, 40: true, 50: true}
|
||||||
|
|
||||||
|
// Rarity tiers (10..50) and ranks (1..5) are disjoint, so the delimited values
|
||||||
|
// are classified by range — independent of the client's map key or delimiter.
|
||||||
|
func parseAutoSaleRules(settings map[int32]store.AutoSaleSettingState) (raritySet, rankSet map[int32]bool) {
|
||||||
|
raritySet = map[int32]bool{}
|
||||||
|
rankSet = map[int32]bool{}
|
||||||
|
for _, s := range settings {
|
||||||
|
for _, n := range extractInts(s.PossessionAutoSaleItemValue) {
|
||||||
|
switch {
|
||||||
|
case autoSaleRarityTiers[n]:
|
||||||
|
raritySet[n] = true
|
||||||
|
case n >= 1 && n <= 5:
|
||||||
|
rankSet[n] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return raritySet, rankSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractInts(s string) []int32 {
|
||||||
|
fields := strings.FieldsFunc(s, func(r rune) bool { return r < '0' || r > '9' })
|
||||||
|
out := make([]int32, 0, len(fields))
|
||||||
|
for _, f := range fields {
|
||||||
|
if v, err := strconv.Atoi(f); err == nil {
|
||||||
|
out = append(out, int32(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *QuestHandler) grantDropRewards(user *store.UserState, drops []RewardGrant, raritySet, rankSet map[int32]bool, nowMillis int64) {
|
||||||
|
for i := range drops {
|
||||||
|
d := drops[i]
|
||||||
|
if d.PossessionType == model.PossessionTypeParts || d.PossessionType == model.PossessionTypePartsEnhanced {
|
||||||
|
chosenId, sold := h.Granter.GrantOrSellPartsDrop(user, d.PossessionId, raritySet, rankSet, nowMillis)
|
||||||
|
if sold {
|
||||||
|
// Sold parts have no inventory row, so the popup needs the rolled
|
||||||
|
// variant id; kept parts read theirs from the parts table diff.
|
||||||
|
drops[i].PossessionId = chosenId
|
||||||
|
drops[i].IsAutoSale = true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
h.applyRewardPossession(user, d.PossessionType, d.PossessionId, d.Count, nowMillis)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (h *QuestHandler) computeDropRewards(questDef masterdata.EntityMQuest, target campaign.QuestTarget, nowMillis int64) []RewardGrant {
|
func (h *QuestHandler) computeDropRewards(questDef masterdata.EntityMQuest, target campaign.QuestTarget, nowMillis int64) []RewardGrant {
|
||||||
var drops []RewardGrant
|
var drops []RewardGrant
|
||||||
var dropRate campaign.DropRateMul
|
var dropRate campaign.DropRateMul
|
||||||
@@ -197,8 +247,10 @@ func (h *QuestHandler) applyExpRewards(user *store.UserState, questId int32, now
|
|||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
var maxLevel int32
|
||||||
if maxLevelFunc, hasMax := h.CostumeMaxLevelByRarity[cm.RarityType]; hasMax {
|
if maxLevelFunc, hasMax := h.CostumeMaxLevelByRarity[cm.RarityType]; hasMax {
|
||||||
maxLevel := maxLevelFunc.Evaluate(row.LimitBreakCount)
|
maxLevel = maxLevelFunc.Evaluate(row.LimitBreakCount) +
|
||||||
|
h.CharacterRebirth.CostumeLevelLimitUp(cm.CharacterId, user.CharacterRebirths[cm.CharacterId].RebirthCount)
|
||||||
if row.Level >= maxLevel {
|
if row.Level >= maxLevel {
|
||||||
log.Printf("[applyExpRewards] questId=%d costume=%d (key=%s): at max level %d, skipping", questId, row.CostumeId, key, row.Level)
|
log.Printf("[applyExpRewards] questId=%d costume=%d (key=%s): at max level %d, skipping", questId, row.CostumeId, key, row.Level)
|
||||||
continue
|
continue
|
||||||
@@ -206,14 +258,7 @@ func (h *QuestHandler) applyExpRewards(user *store.UserState, questId int32, now
|
|||||||
}
|
}
|
||||||
row.Exp += questDef.CostumeExp
|
row.Exp += questDef.CostumeExp
|
||||||
if thresholds, ok := h.CostumeExpByRarity[cm.RarityType]; ok {
|
if thresholds, ok := h.CostumeExpByRarity[cm.RarityType]; ok {
|
||||||
row.Level, row.Exp = gameutil.LevelAndCap(row.Exp, thresholds)
|
row.Level, row.Exp = gameutil.ApplyExpWithMaxLevel(row.Exp, thresholds, maxLevel)
|
||||||
if maxLevelFunc, hasMax := h.CostumeMaxLevelByRarity[cm.RarityType]; hasMax {
|
|
||||||
maxLevel := maxLevelFunc.Evaluate(row.LimitBreakCount)
|
|
||||||
if row.Level > maxLevel && int(maxLevel) < len(thresholds) {
|
|
||||||
row.Level = maxLevel
|
|
||||||
row.Exp = thresholds[maxLevel]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
user.Costumes[key] = row
|
user.Costumes[key] = row
|
||||||
log.Printf("[applyExpRewards] questId=%d costume=%d (key=%s): +%d exp -> total=%d level=%d", questId, row.CostumeId, key, questDef.CostumeExp, row.Exp, row.Level)
|
log.Printf("[applyExpRewards] questId=%d costume=%d (key=%s): +%d exp -> total=%d level=%d", questId, row.CostumeId, key, questDef.CostumeExp, row.Exp, row.Level)
|
||||||
|
|||||||
@@ -41,7 +41,12 @@ func buildCatalogs() (*Catalogs, error) {
|
|||||||
return nil, fmt.Errorf("load campaign catalog: %w", err)
|
return nil, fmt.Errorf("load campaign catalog: %w", err)
|
||||||
}
|
}
|
||||||
log.Printf("campaign catalog loaded: %d enhance, %d quest", campaignCatalog.EnhanceCount(), campaignCatalog.QuestCount())
|
log.Printf("campaign catalog loaded: %d enhance, %d quest", campaignCatalog.EnhanceCount(), campaignCatalog.QuestCount())
|
||||||
questHandler := questflow.NewQuestHandler(questCatalog, gameConfig, sideStoryCatalog, campaignCatalog)
|
characterRebirthCatalog, err := masterdata.LoadCharacterRebirthCatalog()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load character rebirth catalog: %w", err)
|
||||||
|
}
|
||||||
|
log.Printf("character rebirth catalog loaded: %d characters", len(characterRebirthCatalog.StepGroupByCharacterId))
|
||||||
|
questHandler := questflow.NewQuestHandler(questCatalog, gameConfig, sideStoryCatalog, campaignCatalog, characterRebirthCatalog)
|
||||||
userdata.SetQuestHandler(questHandler)
|
userdata.SetQuestHandler(questHandler)
|
||||||
|
|
||||||
gachaEntries, medalInfo, err := masterdata.LoadGachaCatalog()
|
gachaEntries, medalInfo, err := masterdata.LoadGachaCatalog()
|
||||||
@@ -133,12 +138,6 @@ func buildCatalogs() (*Catalogs, error) {
|
|||||||
}
|
}
|
||||||
log.Printf("character board catalog loaded: %d panels, %d boards", len(characterBoardCatalog.PanelById), len(characterBoardCatalog.BoardById))
|
log.Printf("character board catalog loaded: %d panels, %d boards", len(characterBoardCatalog.PanelById), len(characterBoardCatalog.BoardById))
|
||||||
|
|
||||||
characterRebirthCatalog, err := masterdata.LoadCharacterRebirthCatalog()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("load character rebirth catalog: %w", err)
|
|
||||||
}
|
|
||||||
log.Printf("character rebirth catalog loaded: %d characters", len(characterRebirthCatalog.StepGroupByCharacterId))
|
|
||||||
|
|
||||||
companionCatalog, err := masterdata.LoadCompanionCatalog()
|
companionCatalog, err := masterdata.LoadCompanionCatalog()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load companion catalog: %w", err)
|
return nil, fmt.Errorf("load companion catalog: %w", err)
|
||||||
|
|||||||
@@ -90,7 +90,12 @@ func (s *CostumeServiceServer) Enhance(ctx context.Context, req *pb.EnhanceReque
|
|||||||
costume.Exp += totalExp
|
costume.Exp += totalExp
|
||||||
|
|
||||||
if thresholds, ok := catalog.ExpByRarity[cm.RarityType]; ok {
|
if thresholds, ok := catalog.ExpByRarity[cm.RarityType]; ok {
|
||||||
costume.Level, costume.Exp = gameutil.LevelAndCap(costume.Exp, thresholds)
|
var maxLevel int32
|
||||||
|
if maxLevelFunc, hasMax := catalog.MaxLevelByRarity[cm.RarityType]; hasMax {
|
||||||
|
maxLevel = maxLevelFunc.Evaluate(costume.LimitBreakCount) +
|
||||||
|
cat.CharacterRebirth.CostumeLevelLimitUp(cm.CharacterId, user.CharacterRebirths[cm.CharacterId].RebirthCount)
|
||||||
|
}
|
||||||
|
costume.Level, costume.Exp = gameutil.ApplyExpWithMaxLevel(costume.Exp, thresholds, maxLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
costume.LatestVersion = nowMillis
|
costume.LatestVersion = nowMillis
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"lunar-tear/server/internal/model"
|
||||||
|
"lunar-tear/server/internal/questflow"
|
||||||
|
"lunar-tear/server/internal/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
func startAutoOrbit(user *store.UserState, questType model.QuestType, chapterId, questId, maxCount int32, nowMillis int64) {
|
||||||
|
if maxCount <= 0 {
|
||||||
|
if user.QuestAutoOrbit.MaxAutoOrbitCount > 0 {
|
||||||
|
log.Printf("[autoOrbit] clear (start without max): prev questType=%d chapter=%d quest=%d cleared=%d/%d",
|
||||||
|
user.QuestAutoOrbit.QuestType, user.QuestAutoOrbit.ChapterId, user.QuestAutoOrbit.QuestId,
|
||||||
|
user.QuestAutoOrbit.ClearedAutoOrbitCount, user.QuestAutoOrbit.MaxAutoOrbitCount)
|
||||||
|
}
|
||||||
|
user.QuestAutoOrbit = store.QuestAutoOrbitState{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s := user.QuestAutoOrbit
|
||||||
|
if s.MaxAutoOrbitCount > 0 &&
|
||||||
|
s.QuestType == int32(questType) && s.ChapterId == chapterId &&
|
||||||
|
s.QuestId == questId && s.MaxAutoOrbitCount == maxCount {
|
||||||
|
s.LatestVersion = nowMillis
|
||||||
|
user.QuestAutoOrbit = s
|
||||||
|
log.Printf("[autoOrbit] continue cleared=%d/%d", s.ClearedAutoOrbitCount, s.MaxAutoOrbitCount)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("[autoOrbit] start questType=%d chapter=%d quest=%d max=%d", questType, chapterId, questId, maxCount)
|
||||||
|
user.QuestAutoOrbit = store.QuestAutoOrbitState{
|
||||||
|
QuestType: int32(questType),
|
||||||
|
ChapterId: chapterId,
|
||||||
|
QuestId: questId,
|
||||||
|
MaxAutoOrbitCount: maxCount,
|
||||||
|
LatestVersion: nowMillis,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func finishAutoOrbit(user *store.UserState, isAutoOrbit, isRetired, isAnnihilated bool, questType model.QuestType, chapterId, questId int32, nowMillis int64, drops []questflow.RewardGrant) (endedDrops []store.AutoOrbitDropEntry, loopEnded bool) {
|
||||||
|
s := user.QuestAutoOrbit
|
||||||
|
if s.MaxAutoOrbitCount <= 0 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
if s.QuestType != int32(questType) || s.ChapterId != chapterId || s.QuestId != questId {
|
||||||
|
log.Printf("[autoOrbit] finish for other quest, ignored: tracked={qt=%d ch=%d q=%d} got={qt=%d ch=%d q=%d}",
|
||||||
|
s.QuestType, s.ChapterId, s.QuestId, int32(questType), chapterId, questId)
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
if !isRetired && !isAnnihilated {
|
||||||
|
added := 0
|
||||||
|
for _, d := range drops {
|
||||||
|
s.AccumulatedDrops = append(s.AccumulatedDrops, store.AutoOrbitDropEntry{
|
||||||
|
PossessionType: int32(d.PossessionType),
|
||||||
|
PossessionId: d.PossessionId,
|
||||||
|
Count: d.Count,
|
||||||
|
IsAutoSale: d.IsAutoSale,
|
||||||
|
})
|
||||||
|
added++
|
||||||
|
}
|
||||||
|
s.ClearedAutoOrbitCount++
|
||||||
|
log.Printf("[autoOrbit] iter cleared=%d/%d +%d drops (total=%d)",
|
||||||
|
s.ClearedAutoOrbitCount, s.MaxAutoOrbitCount, added, len(s.AccumulatedDrops))
|
||||||
|
}
|
||||||
|
s.LastClearDatetime = nowMillis
|
||||||
|
s.LatestVersion = nowMillis
|
||||||
|
if !isAutoOrbit || isRetired || isAnnihilated || s.ClearedAutoOrbitCount >= s.MaxAutoOrbitCount {
|
||||||
|
log.Printf("[autoOrbit] loop end: cleared=%d/%d total drops=%d (returned in response, accumulator kept)",
|
||||||
|
s.ClearedAutoOrbitCount, s.MaxAutoOrbitCount, len(s.AccumulatedDrops))
|
||||||
|
user.QuestAutoOrbit = store.QuestAutoOrbitState{AccumulatedDrops: s.AccumulatedDrops}
|
||||||
|
return s.AccumulatedDrops, true
|
||||||
|
}
|
||||||
|
user.QuestAutoOrbit = s
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func consumeAutoOrbitRewards(user *store.UserState) []store.AutoOrbitDropEntry {
|
||||||
|
drops := user.QuestAutoOrbit.AccumulatedDrops
|
||||||
|
log.Printf("[autoOrbit] consume on FinishAutoOrbit: returning %d drops (loop status max=%d cleared=%d)",
|
||||||
|
len(drops), user.QuestAutoOrbit.MaxAutoOrbitCount, user.QuestAutoOrbit.ClearedAutoOrbitCount)
|
||||||
|
user.QuestAutoOrbit = store.QuestAutoOrbitState{}
|
||||||
|
return drops
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
|
"lunar-tear/server/internal/model"
|
||||||
"lunar-tear/server/internal/questflow"
|
"lunar-tear/server/internal/questflow"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
|
|
||||||
@@ -13,13 +14,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (s *QuestServiceServer) StartEventQuest(ctx context.Context, req *pb.StartEventQuestRequest) (*pb.StartEventQuestResponse, error) {
|
func (s *QuestServiceServer) StartEventQuest(ctx context.Context, req *pb.StartEventQuestRequest) (*pb.StartEventQuestResponse, error) {
|
||||||
log.Printf("[QuestService] StartEventQuest: chapterId=%d questId=%d isBattleOnly=%v", req.EventQuestChapterId, req.QuestId, req.IsBattleOnly)
|
log.Printf("[QuestService] StartEventQuest: chapterId=%d questId=%d isBattleOnly=%v maxAutoOrbitCount=%d",
|
||||||
|
req.EventQuestChapterId, req.QuestId, req.IsBattleOnly, req.MaxAutoOrbitCount)
|
||||||
|
|
||||||
engine := s.holder.Get().QuestHandler
|
engine := s.holder.Get().QuestHandler
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
engine.HandleEventQuestStart(user, req.EventQuestChapterId, req.QuestId, req.IsBattleOnly, req.UserDeckNumber, nowMillis)
|
engine.HandleEventQuestStart(user, req.EventQuestChapterId, req.QuestId, req.IsBattleOnly, req.UserDeckNumber, nowMillis)
|
||||||
|
startAutoOrbit(user, model.QuestTypeEvent, req.EventQuestChapterId, req.QuestId, req.MaxAutoOrbitCount, nowMillis)
|
||||||
})
|
})
|
||||||
|
|
||||||
drops := engine.BattleDropRewards(req.QuestId)
|
drops := engine.BattleDropRewards(req.QuestId)
|
||||||
@@ -38,16 +41,25 @@ func (s *QuestServiceServer) StartEventQuest(ctx context.Context, req *pb.StartE
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *QuestServiceServer) FinishEventQuest(ctx context.Context, req *pb.FinishEventQuestRequest) (*pb.FinishEventQuestResponse, error) {
|
func (s *QuestServiceServer) FinishEventQuest(ctx context.Context, req *pb.FinishEventQuestRequest) (*pb.FinishEventQuestResponse, error) {
|
||||||
log.Printf("[QuestService] FinishEventQuest: chapterId=%d questId=%d isRetired=%v isAnnihilated=%v", req.EventQuestChapterId, req.QuestId, req.IsRetired, req.IsAnnihilated)
|
log.Printf("[QuestService] FinishEventQuest: chapterId=%d questId=%d isRetired=%v isAnnihilated=%v isAutoOrbit=%v",
|
||||||
|
req.EventQuestChapterId, req.QuestId, req.IsRetired, req.IsAnnihilated, req.IsAutoOrbit)
|
||||||
|
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
engine := s.holder.Get().QuestHandler
|
engine := s.holder.Get().QuestHandler
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
var outcome questflow.FinishOutcome
|
var outcome questflow.FinishOutcome
|
||||||
|
var endedDrops []store.AutoOrbitDropEntry
|
||||||
|
var loopEnded bool
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
outcome = engine.HandleEventQuestFinish(user, req.EventQuestChapterId, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis)
|
outcome = engine.HandleEventQuestFinish(user, req.EventQuestChapterId, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis)
|
||||||
|
endedDrops, loopEnded = finishAutoOrbit(user, req.IsAutoOrbit, req.IsRetired, req.IsAnnihilated, model.QuestTypeEvent, req.EventQuestChapterId, req.QuestId, nowMillis, outcome.DropRewards)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
autoOrbitReward := emptyAutoOrbitReward()
|
||||||
|
if loopEnded {
|
||||||
|
autoOrbitReward.DropReward = autoOrbitDropsToProto(endedDrops)
|
||||||
|
}
|
||||||
|
|
||||||
return &pb.FinishEventQuestResponse{
|
return &pb.FinishEventQuestResponse{
|
||||||
DropReward: toProtoRewards(outcome.DropRewards),
|
DropReward: toProtoRewards(outcome.DropRewards),
|
||||||
FirstClearReward: toProtoRewards(outcome.FirstClearRewards),
|
FirstClearReward: toProtoRewards(outcome.FirstClearRewards),
|
||||||
@@ -57,6 +69,7 @@ func (s *QuestServiceServer) FinishEventQuest(ctx context.Context, req *pb.Finis
|
|||||||
IsBigWin: outcome.IsBigWin,
|
IsBigWin: outcome.IsBigWin,
|
||||||
BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds,
|
BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds,
|
||||||
UserStatusCampaignReward: []*pb.QuestReward{},
|
UserStatusCampaignReward: []*pb.QuestReward{},
|
||||||
|
AutoOrbitReward: autoOrbitReward,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,8 @@ func (s *QuestServiceServer) UpdateMainQuestSceneProgress(ctx context.Context, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *QuestServiceServer) StartMainQuest(ctx context.Context, req *pb.StartMainQuestRequest) (*pb.StartMainQuestResponse, error) {
|
func (s *QuestServiceServer) StartMainQuest(ctx context.Context, req *pb.StartMainQuestRequest) (*pb.StartMainQuestResponse, error) {
|
||||||
log.Printf("[QuestService] StartMainQuest: %+v", req)
|
log.Printf("[QuestService] StartMainQuest: questId=%d isMainFlow=%v isReplayFlow=%v isBattleOnly=%v maxAutoOrbitCount=%d",
|
||||||
|
req.QuestId, req.IsMainFlow, req.IsReplayFlow, req.IsBattleOnly, req.MaxAutoOrbitCount)
|
||||||
|
|
||||||
engine := s.holder.Get().QuestHandler
|
engine := s.holder.Get().QuestHandler
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
@@ -76,6 +77,7 @@ func (s *QuestServiceServer) StartMainQuest(ctx context.Context, req *pb.StartMa
|
|||||||
} else {
|
} else {
|
||||||
engine.HandleQuestStart(user, req.QuestId, req.IsBattleOnly, req.IsMainFlow, req.UserDeckNumber, nowMillis)
|
engine.HandleQuestStart(user, req.QuestId, req.IsBattleOnly, req.IsMainFlow, req.UserDeckNumber, nowMillis)
|
||||||
}
|
}
|
||||||
|
startAutoOrbit(user, model.QuestTypeMain, 0, req.QuestId, req.MaxAutoOrbitCount, nowMillis)
|
||||||
})
|
})
|
||||||
|
|
||||||
drops := engine.BattleDropRewards(req.QuestId)
|
drops := engine.BattleDropRewards(req.QuestId)
|
||||||
@@ -93,6 +95,26 @@ func (s *QuestServiceServer) StartMainQuest(ctx context.Context, req *pb.StartMa
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func emptyAutoOrbitReward() *pb.QuestAutoOrbitResult {
|
||||||
|
return &pb.QuestAutoOrbitResult{
|
||||||
|
DropReward: []*pb.QuestReward{},
|
||||||
|
UserStatusCampaignReward: []*pb.QuestReward{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoOrbitDropsToProto(drops []store.AutoOrbitDropEntry) []*pb.QuestReward {
|
||||||
|
out := make([]*pb.QuestReward, len(drops))
|
||||||
|
for i, d := range drops {
|
||||||
|
out[i] = &pb.QuestReward{
|
||||||
|
PossessionType: d.PossessionType,
|
||||||
|
PossessionId: d.PossessionId,
|
||||||
|
Count: d.Count,
|
||||||
|
IsAutoSale: d.IsAutoSale,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func toProtoRewards(grants []questflow.RewardGrant) []*pb.QuestReward {
|
func toProtoRewards(grants []questflow.RewardGrant) []*pb.QuestReward {
|
||||||
if len(grants) == 0 {
|
if len(grants) == 0 {
|
||||||
return []*pb.QuestReward{}
|
return []*pb.QuestReward{}
|
||||||
@@ -103,23 +125,32 @@ func toProtoRewards(grants []questflow.RewardGrant) []*pb.QuestReward {
|
|||||||
PossessionType: int32(g.PossessionType),
|
PossessionType: int32(g.PossessionType),
|
||||||
PossessionId: g.PossessionId,
|
PossessionId: g.PossessionId,
|
||||||
Count: g.Count,
|
Count: g.Count,
|
||||||
|
IsAutoSale: g.IsAutoSale,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *QuestServiceServer) FinishMainQuest(ctx context.Context, req *pb.FinishMainQuestRequest) (*pb.FinishMainQuestResponse, error) {
|
func (s *QuestServiceServer) FinishMainQuest(ctx context.Context, req *pb.FinishMainQuestRequest) (*pb.FinishMainQuestResponse, error) {
|
||||||
log.Printf("[QuestService] FinishMainQuest: questId=%d isMainFlow=%v isRetired=%v isAnnihilated=%v storySkipType=%d",
|
log.Printf("[QuestService] FinishMainQuest: questId=%d isMainFlow=%v isRetired=%v isAnnihilated=%v isAutoOrbit=%v storySkipType=%d",
|
||||||
req.QuestId, req.IsMainFlow, req.IsRetired, req.IsAnnihilated, req.StorySkipType)
|
req.QuestId, req.IsMainFlow, req.IsRetired, req.IsAnnihilated, req.IsAutoOrbit, req.StorySkipType)
|
||||||
|
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
engine := s.holder.Get().QuestHandler
|
engine := s.holder.Get().QuestHandler
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
var outcome questflow.FinishOutcome
|
var outcome questflow.FinishOutcome
|
||||||
|
var endedDrops []store.AutoOrbitDropEntry
|
||||||
|
var loopEnded bool
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
outcome = engine.HandleQuestFinish(user, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis)
|
outcome = engine.HandleQuestFinish(user, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis)
|
||||||
|
endedDrops, loopEnded = finishAutoOrbit(user, req.IsAutoOrbit, req.IsRetired, req.IsAnnihilated, model.QuestTypeMain, 0, req.QuestId, nowMillis, outcome.DropRewards)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
autoOrbitReward := emptyAutoOrbitReward()
|
||||||
|
if loopEnded {
|
||||||
|
autoOrbitReward.DropReward = autoOrbitDropsToProto(endedDrops)
|
||||||
|
}
|
||||||
|
|
||||||
return &pb.FinishMainQuestResponse{
|
return &pb.FinishMainQuestResponse{
|
||||||
DropReward: toProtoRewards(outcome.DropRewards),
|
DropReward: toProtoRewards(outcome.DropRewards),
|
||||||
FirstClearReward: toProtoRewards(outcome.FirstClearRewards),
|
FirstClearReward: toProtoRewards(outcome.FirstClearRewards),
|
||||||
@@ -130,6 +161,7 @@ func (s *QuestServiceServer) FinishMainQuest(ctx context.Context, req *pb.Finish
|
|||||||
BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds,
|
BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds,
|
||||||
ReplayFlowFirstClearReward: toProtoRewards(outcome.ReplayFlowFirstClearRewards),
|
ReplayFlowFirstClearReward: toProtoRewards(outcome.ReplayFlowFirstClearRewards),
|
||||||
UserStatusCampaignReward: []*pb.QuestReward{},
|
UserStatusCampaignReward: []*pb.QuestReward{},
|
||||||
|
AutoOrbitReward: autoOrbitReward,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +194,26 @@ func (s *QuestServiceServer) RestartMainQuest(ctx context.Context, req *pb.Resta
|
|||||||
|
|
||||||
func (s *QuestServiceServer) FinishAutoOrbit(ctx context.Context, req *emptypb.Empty) (*pb.FinishAutoOrbitResponse, error) {
|
func (s *QuestServiceServer) FinishAutoOrbit(ctx context.Context, req *emptypb.Empty) (*pb.FinishAutoOrbitResponse, error) {
|
||||||
log.Printf("[QuestService] FinishAutoOrbit")
|
log.Printf("[QuestService] FinishAutoOrbit")
|
||||||
return &pb.FinishAutoOrbitResponse{}, nil
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
|
var drops []store.AutoOrbitDropEntry
|
||||||
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
|
drops = consumeAutoOrbitRewards(user)
|
||||||
|
})
|
||||||
|
pbDrops := make([]*pb.QuestReward, len(drops))
|
||||||
|
for i, d := range drops {
|
||||||
|
pbDrops[i] = &pb.QuestReward{
|
||||||
|
PossessionType: d.PossessionType,
|
||||||
|
PossessionId: d.PossessionId,
|
||||||
|
Count: d.Count,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &pb.FinishAutoOrbitResponse{
|
||||||
|
AutoOrbitResult: []*pb.QuestReward{},
|
||||||
|
AutoOrbitReward: &pb.QuestAutoOrbitResult{
|
||||||
|
DropReward: pbDrops,
|
||||||
|
UserStatusCampaignReward: []*pb.QuestReward{},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *QuestServiceServer) SkipQuest(ctx context.Context, req *pb.SkipQuestRequest) (*pb.SkipQuestResponse, error) {
|
func (s *QuestServiceServer) SkipQuest(ctx context.Context, req *pb.SkipQuestRequest) (*pb.SkipQuestResponse, error) {
|
||||||
|
|||||||
@@ -131,16 +131,11 @@ func (s *WeaponServiceServer) EnhanceByMaterial(ctx context.Context, req *pb.Enh
|
|||||||
weapon.Exp += totalExp
|
weapon.Exp += totalExp
|
||||||
levelingEnhanceId := catalog.LevelingEnhanceIdByWeaponId[weapon.WeaponId]
|
levelingEnhanceId := catalog.LevelingEnhanceIdByWeaponId[weapon.WeaponId]
|
||||||
if thresholds, ok := catalog.ExpByEnhanceId[levelingEnhanceId]; ok {
|
if thresholds, ok := catalog.ExpByEnhanceId[levelingEnhanceId]; ok {
|
||||||
weapon.Level, weapon.Exp = gameutil.LevelAndCap(weapon.Exp, thresholds)
|
var maxLevel int32
|
||||||
if maxFunc, ok := catalog.MaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
|
if maxFunc, ok := catalog.MaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
|
||||||
cap := awakenedLevelCap(catalog, user, weapon, req.UserWeaponUuid, maxFunc.Evaluate(weapon.LimitBreakCount))
|
maxLevel = awakenedLevelCap(catalog, user, weapon, req.UserWeaponUuid, maxFunc.Evaluate(weapon.LimitBreakCount))
|
||||||
if weapon.Level > cap {
|
|
||||||
weapon.Level = cap
|
|
||||||
if int(cap) >= 0 && int(cap) < len(thresholds) {
|
|
||||||
weapon.Exp = thresholds[cap]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
weapon.Level, weapon.Exp = gameutil.ApplyExpWithMaxLevel(weapon.Exp, thresholds, maxLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
note := user.WeaponNotes[weapon.WeaponId]
|
note := user.WeaponNotes[weapon.WeaponId]
|
||||||
@@ -759,16 +754,11 @@ func (s *WeaponServiceServer) EnhanceByWeapon(ctx context.Context, req *pb.Enhan
|
|||||||
weapon.Exp += totalExp
|
weapon.Exp += totalExp
|
||||||
levelingEnhanceId := catalog.LevelingEnhanceIdByWeaponId[weapon.WeaponId]
|
levelingEnhanceId := catalog.LevelingEnhanceIdByWeaponId[weapon.WeaponId]
|
||||||
if thresholds, ok := catalog.ExpByEnhanceId[levelingEnhanceId]; ok {
|
if thresholds, ok := catalog.ExpByEnhanceId[levelingEnhanceId]; ok {
|
||||||
weapon.Level, weapon.Exp = gameutil.LevelAndCap(weapon.Exp, thresholds)
|
var maxLevel int32
|
||||||
if maxFunc, ok := catalog.MaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
|
if maxFunc, ok := catalog.MaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
|
||||||
cap := awakenedLevelCap(catalog, user, weapon, req.UserWeaponUuid, maxFunc.Evaluate(weapon.LimitBreakCount))
|
maxLevel = awakenedLevelCap(catalog, user, weapon, req.UserWeaponUuid, maxFunc.Evaluate(weapon.LimitBreakCount))
|
||||||
if weapon.Level > cap {
|
|
||||||
weapon.Level = cap
|
|
||||||
if int(cap) >= 0 && int(cap) < len(thresholds) {
|
|
||||||
weapon.Exp = thresholds[cap]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
weapon.Level, weapon.Exp = gameutil.ApplyExpWithMaxLevel(weapon.Exp, thresholds, maxLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
note := user.WeaponNotes[weapon.WeaponId]
|
note := user.WeaponNotes[weapon.WeaponId]
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ func CloneUserState(u UserState) UserState {
|
|||||||
out.CostumeLotteryEffectPending = maps.Clone(u.CostumeLotteryEffectPending)
|
out.CostumeLotteryEffectPending = maps.Clone(u.CostumeLotteryEffectPending)
|
||||||
out.AutoSaleSettings = maps.Clone(u.AutoSaleSettings)
|
out.AutoSaleSettings = maps.Clone(u.AutoSaleSettings)
|
||||||
out.CharacterRebirths = maps.Clone(u.CharacterRebirths)
|
out.CharacterRebirths = maps.Clone(u.CharacterRebirths)
|
||||||
|
out.QuestAutoOrbit.AccumulatedDrops = append([]AutoOrbitDropEntry(nil), u.QuestAutoOrbit.AccumulatedDrops...)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -131,6 +131,9 @@ type PossessionGranter struct {
|
|||||||
PartsSubStatusPool map[int32][]int32
|
PartsSubStatusPool map[int32][]int32
|
||||||
PartsSubStatusDefs map[int32]PartsStatusSubDef
|
PartsSubStatusDefs map[int32]PartsStatusSubDef
|
||||||
|
|
||||||
|
PartsSellPriceL1ByRarity map[int32]int32
|
||||||
|
GoldConsumableItemId int32
|
||||||
|
|
||||||
LastChangedStoryWeaponIds []int32
|
LastChangedStoryWeaponIds []int32
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,19 +204,51 @@ func (g *PossessionGranter) GrantCompanion(user *UserState, companionId int32, n
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *PossessionGranter) GrantParts(user *UserState, requestedPartsId int32, nowMillis int64) {
|
func (g *PossessionGranter) GrantParts(user *UserState, requestedPartsId int32, nowMillis int64) {
|
||||||
ref, refOk := g.PartsById[requestedPartsId]
|
chosenPartsId, chosenRef, ok := g.rollPartsVariant(requestedPartsId)
|
||||||
if !refOk {
|
if !ok {
|
||||||
key := uuid.New().String()
|
g.grantBareParts(user, requestedPartsId, nowMillis)
|
||||||
user.Parts[key] = PartsState{
|
|
||||||
UserPartsUuid: key,
|
|
||||||
PartsId: requestedPartsId,
|
|
||||||
Level: 1,
|
|
||||||
AcquisitionDatetime: nowMillis,
|
|
||||||
}
|
|
||||||
log.Printf("[GrantParts] unknown partsId=%d, granted as-is with no variant roll", requestedPartsId)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
g.createParts(user, chosenPartsId, chosenRef, nowMillis)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The rolled variant sets both rarity and rank, so the auto-sale decision can
|
||||||
|
// only happen after the roll. Returns the rolled variant id and whether it sold.
|
||||||
|
func (g *PossessionGranter) GrantOrSellPartsDrop(user *UserState, requestedPartsId int32, raritySet, rankSet map[int32]bool, nowMillis int64) (int32, bool) {
|
||||||
|
chosenPartsId, chosenRef, ok := g.rollPartsVariant(requestedPartsId)
|
||||||
|
if !ok {
|
||||||
|
g.grantBareParts(user, requestedPartsId, nowMillis)
|
||||||
|
return requestedPartsId, false
|
||||||
|
}
|
||||||
|
rarity := chosenRef.RarityType
|
||||||
|
rank := chosenRef.PartsInitialLotteryId
|
||||||
|
if price, ok := g.PartsSellPriceL1ByRarity[rarity]; ok && raritySet[rarity] && rankSet[rank] {
|
||||||
|
user.ConsumableItems[g.GoldConsumableItemId] += price
|
||||||
|
log.Printf("[GrantParts] auto-sold chosen=%d rarity=%d rank=%d -> %d gold", chosenPartsId, rarity, rank, price)
|
||||||
|
return chosenPartsId, true
|
||||||
|
}
|
||||||
|
g.createParts(user, chosenPartsId, chosenRef, nowMillis)
|
||||||
|
return chosenPartsId, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *PossessionGranter) grantBareParts(user *UserState, partsId int32, nowMillis int64) {
|
||||||
|
key := uuid.New().String()
|
||||||
|
user.Parts[key] = PartsState{
|
||||||
|
UserPartsUuid: key,
|
||||||
|
PartsId: partsId,
|
||||||
|
Level: 1,
|
||||||
|
AcquisitionDatetime: nowMillis,
|
||||||
|
}
|
||||||
|
log.Printf("[GrantParts] unknown partsId=%d, granted as-is with no variant roll", partsId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// rollPartsVariant picks one of a parts group's 5 variants at random; the five
|
||||||
|
// carry distinct PartsInitialLotteryId 1..5, which is the part's rank.
|
||||||
|
func (g *PossessionGranter) rollPartsVariant(requestedPartsId int32) (int32, PartsRef, bool) {
|
||||||
|
ref, refOk := g.PartsById[requestedPartsId]
|
||||||
|
if !refOk {
|
||||||
|
return requestedPartsId, PartsRef{}, false
|
||||||
|
}
|
||||||
chosenPartsId := requestedPartsId
|
chosenPartsId := requestedPartsId
|
||||||
chosenRef := ref
|
chosenRef := ref
|
||||||
if variants := g.PartsVariantsByGroupRarity[ref.PartsGroupId][ref.RarityType]; len(variants) == 5 {
|
if variants := g.PartsVariantsByGroupRarity[ref.PartsGroupId][ref.RarityType]; len(variants) == 5 {
|
||||||
@@ -222,7 +257,10 @@ func (g *PossessionGranter) GrantParts(user *UserState, requestedPartsId int32,
|
|||||||
} else {
|
} else {
|
||||||
log.Printf("[GrantParts] no 5-variant set for group=%d rarity=%d (have %d), granting requested=%d", ref.PartsGroupId, ref.RarityType, len(variants), requestedPartsId)
|
log.Printf("[GrantParts] no 5-variant set for group=%d rarity=%d (have %d), granting requested=%d", ref.PartsGroupId, ref.RarityType, len(variants), requestedPartsId)
|
||||||
}
|
}
|
||||||
|
return chosenPartsId, chosenRef, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *PossessionGranter) createParts(user *UserState, chosenPartsId int32, chosenRef PartsRef, nowMillis int64) {
|
||||||
mainStatId := g.DefaultPartsStatusMainByLotteryGroup[chosenRef.PartsStatusMainLotteryGroupId]
|
mainStatId := g.DefaultPartsStatusMainByLotteryGroup[chosenRef.PartsStatusMainLotteryGroupId]
|
||||||
if _, exists := user.PartsGroupNotes[chosenRef.PartsGroupId]; !exists {
|
if _, exists := user.PartsGroupNotes[chosenRef.PartsGroupId]; !exists {
|
||||||
user.PartsGroupNotes[chosenRef.PartsGroupId] = PartsGroupNoteState{
|
user.PartsGroupNotes[chosenRef.PartsGroupId] = PartsGroupNoteState{
|
||||||
@@ -266,7 +304,7 @@ func (g *PossessionGranter) GrantParts(user *UserState, requestedPartsId int32,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[GrantParts] requested=%d chosen=%d variant=%d group=%d rarity=%d preUnlockedSubs=%d", requestedPartsId, chosenPartsId, initialCount, chosenRef.PartsGroupId, chosenRef.RarityType, initialCount-1)
|
log.Printf("[GrantParts] chosen=%d group=%d rarity=%d preUnlockedSubs=%d", chosenPartsId, chosenRef.PartsGroupId, chosenRef.RarityType, initialCount-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *PossessionGranter) GrantWeapon(user *UserState, weaponId int32, nowMillis int64) {
|
func (g *PossessionGranter) GrantWeapon(user *UserState, weaponId int32, nowMillis int64) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package sqlite
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
@@ -210,6 +211,17 @@ func load1to1(db *sql.DB, uid int64, u *store.UserState) {
|
|||||||
Scan(&u.GuerrillaFreeOpen.StartDatetime, &u.GuerrillaFreeOpen.OpenMinutes,
|
Scan(&u.GuerrillaFreeOpen.StartDatetime, &u.GuerrillaFreeOpen.OpenMinutes,
|
||||||
&u.GuerrillaFreeOpen.DailyOpenedCount, &u.GuerrillaFreeOpen.LatestVersion)
|
&u.GuerrillaFreeOpen.DailyOpenedCount, &u.GuerrillaFreeOpen.LatestVersion)
|
||||||
|
|
||||||
|
var accumulatedDropsJSON string
|
||||||
|
_ = db.QueryRow(`SELECT quest_type, chapter_id, quest_id, max_auto_orbit_count, cleared_auto_orbit_count, last_clear_datetime, latest_version, accumulated_drops_json
|
||||||
|
FROM user_quest_auto_orbit WHERE user_id=?`, uid).
|
||||||
|
Scan(&u.QuestAutoOrbit.QuestType, &u.QuestAutoOrbit.ChapterId, &u.QuestAutoOrbit.QuestId,
|
||||||
|
&u.QuestAutoOrbit.MaxAutoOrbitCount, &u.QuestAutoOrbit.ClearedAutoOrbitCount,
|
||||||
|
&u.QuestAutoOrbit.LastClearDatetime, &u.QuestAutoOrbit.LatestVersion,
|
||||||
|
&accumulatedDropsJSON)
|
||||||
|
if accumulatedDropsJSON != "" && accumulatedDropsJSON != "[]" {
|
||||||
|
_ = json.Unmarshal([]byte(accumulatedDropsJSON), &u.QuestAutoOrbit.AccumulatedDrops)
|
||||||
|
}
|
||||||
|
|
||||||
var isTicket int
|
var isTicket int
|
||||||
_ = db.QueryRow(`SELECT is_use_explore_ticket, playing_explore_id, latest_play_datetime, latest_version
|
_ = db.QueryRow(`SELECT is_use_explore_ticket, playing_explore_id, latest_play_datetime, latest_version
|
||||||
FROM user_explore WHERE user_id=?`, uid).
|
FROM user_explore WHERE user_id=?`, uid).
|
||||||
|
|||||||
@@ -2,12 +2,24 @@ package sqlite
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func marshalAutoOrbitDrops(drops []store.AutoOrbitDropEntry) string {
|
||||||
|
if len(drops) == 0 {
|
||||||
|
return "[]"
|
||||||
|
}
|
||||||
|
b, err := json.Marshal(drops)
|
||||||
|
if err != nil {
|
||||||
|
return "[]"
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
func boolToInt(b bool) int {
|
func boolToInt(b bool) int {
|
||||||
if b {
|
if b {
|
||||||
return 1
|
return 1
|
||||||
@@ -109,6 +121,13 @@ func writeUserState(tx *sql.Tx, uid int64, u *store.UserState) error {
|
|||||||
uid, u.GuerrillaFreeOpen.StartDatetime, u.GuerrillaFreeOpen.OpenMinutes, u.GuerrillaFreeOpen.DailyOpenedCount, u.GuerrillaFreeOpen.LatestVersion); err != nil {
|
uid, u.GuerrillaFreeOpen.StartDatetime, u.GuerrillaFreeOpen.OpenMinutes, u.GuerrillaFreeOpen.DailyOpenedCount, u.GuerrillaFreeOpen.LatestVersion); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := exec(`INSERT INTO user_quest_auto_orbit (user_id, quest_type, chapter_id, quest_id, max_auto_orbit_count, cleared_auto_orbit_count, last_clear_datetime, latest_version, accumulated_drops_json) VALUES (?,?,?,?,?,?,?,?,?)`,
|
||||||
|
uid, u.QuestAutoOrbit.QuestType, u.QuestAutoOrbit.ChapterId, u.QuestAutoOrbit.QuestId,
|
||||||
|
u.QuestAutoOrbit.MaxAutoOrbitCount, u.QuestAutoOrbit.ClearedAutoOrbitCount,
|
||||||
|
u.QuestAutoOrbit.LastClearDatetime, u.QuestAutoOrbit.LatestVersion,
|
||||||
|
marshalAutoOrbitDrops(u.QuestAutoOrbit.AccumulatedDrops)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := exec(`INSERT INTO user_explore (user_id, is_use_explore_ticket, playing_explore_id, latest_play_datetime, latest_version) VALUES (?,?,?,?,?)`,
|
if err := exec(`INSERT INTO user_explore (user_id, is_use_explore_ticket, playing_explore_id, latest_play_datetime, latest_version) VALUES (?,?,?,?,?)`,
|
||||||
uid, boolToInt(u.Explore.IsUseExploreTicket), u.Explore.PlayingExploreId, u.Explore.LatestPlayDatetime, u.Explore.LatestVersion); err != nil {
|
uid, boolToInt(u.Explore.IsUseExploreTicket), u.Explore.PlayingExploreId, u.Explore.LatestPlayDatetime, u.Explore.LatestVersion); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -674,6 +693,15 @@ func diffAndSave(tx *sql.Tx, uid int64, before, after *store.UserState) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !before.QuestAutoOrbit.Equal(after.QuestAutoOrbit) {
|
||||||
|
if err := exec(`UPDATE user_quest_auto_orbit SET quest_type=?, chapter_id=?, quest_id=?, max_auto_orbit_count=?, cleared_auto_orbit_count=?, last_clear_datetime=?, latest_version=?, accumulated_drops_json=? WHERE user_id=?`,
|
||||||
|
after.QuestAutoOrbit.QuestType, after.QuestAutoOrbit.ChapterId, after.QuestAutoOrbit.QuestId,
|
||||||
|
after.QuestAutoOrbit.MaxAutoOrbitCount, after.QuestAutoOrbit.ClearedAutoOrbitCount,
|
||||||
|
after.QuestAutoOrbit.LastClearDatetime, after.QuestAutoOrbit.LatestVersion,
|
||||||
|
marshalAutoOrbitDrops(after.QuestAutoOrbit.AccumulatedDrops), uid); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
if before.Explore != after.Explore {
|
if before.Explore != after.Explore {
|
||||||
if err := exec(`UPDATE user_explore SET is_use_explore_ticket=?, playing_explore_id=?, latest_play_datetime=?, latest_version=? WHERE user_id=?`,
|
if err := exec(`UPDATE user_explore SET is_use_explore_ticket=?, playing_explore_id=?, latest_play_datetime=?, latest_version=? WHERE user_id=?`,
|
||||||
boolToInt(after.Explore.IsUseExploreTicket), after.Explore.PlayingExploreId, after.Explore.LatestPlayDatetime, after.Explore.LatestVersion, uid); err != nil {
|
boolToInt(after.Explore.IsUseExploreTicket), after.Explore.PlayingExploreId, after.Explore.LatestPlayDatetime, after.Explore.LatestVersion, uid); err != nil {
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ func (s *SQLiteStore) ImportUser(u *store.UserState) error {
|
|||||||
"user_viewed_movies",
|
"user_viewed_movies",
|
||||||
"user_navi_cutin_played",
|
"user_navi_cutin_played",
|
||||||
"user_auto_sale_settings",
|
"user_auto_sale_settings",
|
||||||
|
"user_quest_auto_orbit",
|
||||||
"user_explore_scores",
|
"user_explore_scores",
|
||||||
"user_tutorials",
|
"user_tutorials",
|
||||||
"user_premium_items",
|
"user_premium_items",
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ type UserState struct {
|
|||||||
CostumeLotteryEffectPending map[string]CostumeLotteryEffectPendingState // key: userCostumeUuid
|
CostumeLotteryEffectPending map[string]CostumeLotteryEffectPendingState // key: userCostumeUuid
|
||||||
AutoSaleSettings map[int32]AutoSaleSettingState
|
AutoSaleSettings map[int32]AutoSaleSettingState
|
||||||
CharacterRebirths map[int32]CharacterRebirthState
|
CharacterRebirths map[int32]CharacterRebirthState
|
||||||
|
QuestAutoOrbit QuestAutoOrbitState
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UserState) EnsureMaps() {
|
func (u *UserState) EnsureMaps() {
|
||||||
@@ -331,6 +332,45 @@ type GuerrillaFreeOpenState struct {
|
|||||||
LatestVersion int64
|
LatestVersion int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AutoOrbitDropEntry struct {
|
||||||
|
PossessionType int32
|
||||||
|
PossessionId int32
|
||||||
|
Count int32
|
||||||
|
IsAutoSale bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type QuestAutoOrbitState struct {
|
||||||
|
QuestType int32
|
||||||
|
ChapterId int32
|
||||||
|
QuestId int32
|
||||||
|
MaxAutoOrbitCount int32
|
||||||
|
ClearedAutoOrbitCount int32
|
||||||
|
LastClearDatetime int64
|
||||||
|
LatestVersion int64
|
||||||
|
AccumulatedDrops []AutoOrbitDropEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s QuestAutoOrbitState) Equal(other QuestAutoOrbitState) bool {
|
||||||
|
if s.QuestType != other.QuestType ||
|
||||||
|
s.ChapterId != other.ChapterId ||
|
||||||
|
s.QuestId != other.QuestId ||
|
||||||
|
s.MaxAutoOrbitCount != other.MaxAutoOrbitCount ||
|
||||||
|
s.ClearedAutoOrbitCount != other.ClearedAutoOrbitCount ||
|
||||||
|
s.LastClearDatetime != other.LastClearDatetime ||
|
||||||
|
s.LatestVersion != other.LatestVersion {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(s.AccumulatedDrops) != len(other.AccumulatedDrops) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range s.AccumulatedDrops {
|
||||||
|
if s.AccumulatedDrops[i] != other.AccumulatedDrops[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
type PortalCageStatusState struct {
|
type PortalCageStatusState struct {
|
||||||
IsCurrentProgress bool
|
IsCurrentProgress bool
|
||||||
DropItemStartDatetime int64
|
DropItemStartDatetime int64
|
||||||
|
|||||||
@@ -268,6 +268,9 @@ func ChangedTables(before, after *store.UserState) []string {
|
|||||||
if !mapsEqualStruct(before.TowerAccumulationRewards, after.TowerAccumulationRewards) {
|
if !mapsEqualStruct(before.TowerAccumulationRewards, after.TowerAccumulationRewards) {
|
||||||
add("IUserEventQuestTowerAccumulationReward")
|
add("IUserEventQuestTowerAccumulationReward")
|
||||||
}
|
}
|
||||||
|
if !before.QuestAutoOrbit.Equal(after.QuestAutoOrbit) {
|
||||||
|
add("IUserQuestAutoOrbit")
|
||||||
|
}
|
||||||
if !mapsEqualStruct(before.LabyrinthStages, after.LabyrinthStages) {
|
if !mapsEqualStruct(before.LabyrinthStages, after.LabyrinthStages) {
|
||||||
add("IUserEventQuestLabyrinthStage")
|
add("IUserEventQuestLabyrinthStage")
|
||||||
}
|
}
|
||||||
@@ -476,6 +479,8 @@ func keyFieldsForTable(table string) []string {
|
|||||||
return []string{"userId", "bigHuntWeeklyVersion"}
|
return []string{"userId", "bigHuntWeeklyVersion"}
|
||||||
case "IUserDeckTypeNote":
|
case "IUserDeckTypeNote":
|
||||||
return []string{"userId", "deckType"}
|
return []string{"userId", "deckType"}
|
||||||
|
case "IUserQuestAutoOrbit":
|
||||||
|
return []string{"userId"}
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -268,10 +268,26 @@ func init() {
|
|||||||
s, _ := utils.EncodeJSONMaps(records...)
|
s, _ := utils.EncodeJSONMaps(records...)
|
||||||
return s
|
return s
|
||||||
})
|
})
|
||||||
|
register("IUserQuestAutoOrbit", func(user store.UserState) string {
|
||||||
|
s := user.QuestAutoOrbit
|
||||||
|
if s.MaxAutoOrbitCount <= 0 {
|
||||||
|
return "[]"
|
||||||
|
}
|
||||||
|
out, _ := utils.EncodeJSONMaps(map[string]any{
|
||||||
|
"userId": user.UserId,
|
||||||
|
"questType": s.QuestType,
|
||||||
|
"chapterId": s.ChapterId,
|
||||||
|
"questId": s.QuestId,
|
||||||
|
"maxAutoOrbitCount": s.MaxAutoOrbitCount,
|
||||||
|
"clearedAutoOrbitCount": s.ClearedAutoOrbitCount,
|
||||||
|
"lastClearDatetime": s.LastClearDatetime,
|
||||||
|
"latestVersion": s.LatestVersion,
|
||||||
|
})
|
||||||
|
return out
|
||||||
|
})
|
||||||
registerStatic(
|
registerStatic(
|
||||||
"IUserEventQuestDailyGroupCompleteReward",
|
"IUserEventQuestDailyGroupCompleteReward",
|
||||||
"IUserQuestReplayFlowRewardGroup",
|
"IUserQuestReplayFlowRewardGroup",
|
||||||
"IUserQuestAutoOrbit",
|
|
||||||
"IUserQuestSceneChoice",
|
"IUserQuestSceneChoice",
|
||||||
"IUserQuestSceneChoiceHistory",
|
"IUserQuestSceneChoiceHistory",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
-- +goose Up
|
||||||
|
CREATE TABLE user_quest_auto_orbit (
|
||||||
|
user_id INTEGER NOT NULL PRIMARY KEY REFERENCES users(user_id),
|
||||||
|
quest_type INTEGER NOT NULL DEFAULT 0,
|
||||||
|
chapter_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
quest_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
max_auto_orbit_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
cleared_auto_orbit_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
last_clear_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
accumulated_drops_json TEXT NOT NULL DEFAULT '[]'
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO user_quest_auto_orbit (user_id) SELECT user_id FROM users;
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
DROP TABLE IF EXISTS user_quest_auto_orbit;
|
||||||
Reference in New Issue
Block a user