mirror of
https://github.com/Walter-Sparrow/lunar-tear.git
synced 2026-07-02 05:43:41 +03:00
Add campaign bonuses; fix parts variant/sub-stat grants and menu-pick quest resume state
Build and Push Docker images to Docker Hub / build-and-push (push) Has been cancelled
Build and Push Docker images to Docker Hub / build-and-push (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
package campaign
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"lunar-tear/server/internal/masterdata"
|
||||
"lunar-tear/server/internal/utils"
|
||||
)
|
||||
|
||||
type Catalog struct {
|
||||
enhance []enhanceRow
|
||||
quest []questRow
|
||||
}
|
||||
|
||||
type enhanceRow struct {
|
||||
effectType EnhanceCampaignEffectType
|
||||
effectValue int32
|
||||
targets []enhanceMatch
|
||||
startMillis int64
|
||||
endMillis int64
|
||||
userStatus TargetUserStatusType
|
||||
}
|
||||
|
||||
type enhanceMatch struct {
|
||||
t EnhanceCampaignTargetType
|
||||
v int32
|
||||
}
|
||||
|
||||
type questRow struct {
|
||||
effectType QuestCampaignEffectType
|
||||
effectValue int32
|
||||
bonusItems []BonusDrop
|
||||
targets []questMatch
|
||||
startMillis int64
|
||||
endMillis int64
|
||||
userStatus TargetUserStatusType
|
||||
}
|
||||
|
||||
type questMatch struct {
|
||||
t QuestCampaignTargetType
|
||||
v int32
|
||||
}
|
||||
|
||||
func Load() (*Catalog, error) {
|
||||
enhance, err := loadEnhanceRows()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load enhance campaigns: %w", err)
|
||||
}
|
||||
quest, err := loadQuestRows()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load quest campaigns: %w", err)
|
||||
}
|
||||
return &Catalog{enhance: enhance, quest: quest}, nil
|
||||
}
|
||||
|
||||
func (c *Catalog) EnhanceCount() int { return len(c.enhance) }
|
||||
func (c *Catalog) QuestCount() int { return len(c.quest) }
|
||||
|
||||
func loadEnhanceRows() ([]enhanceRow, error) {
|
||||
campaigns, err := utils.ReadTable[masterdata.EntityMEnhanceCampaign]("m_enhance_campaign")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
targets, err := utils.ReadTable[masterdata.EntityMEnhanceCampaignTargetGroup]("m_enhance_campaign_target_group")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
byGroup := make(map[int32][]enhanceMatch, len(targets))
|
||||
for _, t := range targets {
|
||||
byGroup[t.EnhanceCampaignTargetGroupId] = append(byGroup[t.EnhanceCampaignTargetGroupId], enhanceMatch{
|
||||
t: EnhanceCampaignTargetType(t.EnhanceCampaignTargetType),
|
||||
v: t.EnhanceCampaignTargetValue,
|
||||
})
|
||||
}
|
||||
|
||||
rows := make([]enhanceRow, 0, len(campaigns))
|
||||
for _, c := range campaigns {
|
||||
grp := byGroup[c.EnhanceCampaignTargetGroupId]
|
||||
if len(grp) == 0 {
|
||||
continue
|
||||
}
|
||||
rows = append(rows, enhanceRow{
|
||||
effectType: EnhanceCampaignEffectType(c.EnhanceCampaignEffectType),
|
||||
effectValue: c.EnhanceCampaignEffectValue,
|
||||
targets: grp,
|
||||
startMillis: c.StartDatetime,
|
||||
endMillis: c.EndDatetime,
|
||||
userStatus: TargetUserStatusType(c.TargetUserStatusType),
|
||||
})
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func loadQuestRows() ([]questRow, error) {
|
||||
campaigns, err := utils.ReadTable[masterdata.EntityMQuestCampaign]("m_quest_campaign")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
targets, err := utils.ReadTable[masterdata.EntityMQuestCampaignTargetGroup]("m_quest_campaign_target_group")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
effects, err := utils.ReadTable[masterdata.EntityMQuestCampaignEffectGroup]("m_quest_campaign_effect_group")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
itemGroups, err := utils.ReadTable[masterdata.EntityMQuestCampaignTargetItemGroup]("m_quest_campaign_target_item_group")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
targetsByGroup := make(map[int32][]questMatch, len(targets))
|
||||
for _, t := range targets {
|
||||
targetsByGroup[t.QuestCampaignTargetGroupId] = append(targetsByGroup[t.QuestCampaignTargetGroupId], questMatch{
|
||||
t: QuestCampaignTargetType(t.QuestCampaignTargetType),
|
||||
v: t.QuestCampaignTargetValue,
|
||||
})
|
||||
}
|
||||
|
||||
bonusByGroup := make(map[int32][]BonusDrop, len(itemGroups))
|
||||
for _, ig := range itemGroups {
|
||||
bonusByGroup[ig.QuestCampaignTargetItemGroupId] = append(bonusByGroup[ig.QuestCampaignTargetItemGroupId], BonusDrop{
|
||||
PossessionType: ig.PossessionType,
|
||||
PossessionId: ig.PossessionId,
|
||||
Count: ig.Count,
|
||||
})
|
||||
}
|
||||
|
||||
effectByGroup := make(map[int32]masterdata.EntityMQuestCampaignEffectGroup, len(effects))
|
||||
for _, e := range effects {
|
||||
effectByGroup[e.QuestCampaignEffectGroupId] = e
|
||||
}
|
||||
|
||||
rows := make([]questRow, 0, len(campaigns))
|
||||
for _, c := range campaigns {
|
||||
grp := targetsByGroup[c.QuestCampaignTargetGroupId]
|
||||
if len(grp) == 0 {
|
||||
continue
|
||||
}
|
||||
eff, ok := effectByGroup[c.QuestCampaignEffectGroupId]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
rows = append(rows, questRow{
|
||||
effectType: QuestCampaignEffectType(eff.QuestCampaignEffectType),
|
||||
effectValue: eff.QuestCampaignEffectValue,
|
||||
bonusItems: bonusByGroup[eff.QuestCampaignTargetItemGroupId],
|
||||
targets: grp,
|
||||
startMillis: c.StartDatetime,
|
||||
endMillis: c.EndDatetime,
|
||||
userStatus: TargetUserStatusType(c.TargetUserStatusType),
|
||||
})
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func (r enhanceRow) isActive(f Filter) bool {
|
||||
if f.NowMillis < r.startMillis || f.NowMillis > r.endMillis {
|
||||
return false
|
||||
}
|
||||
return r.userStatus == TargetUserStatusAll || r.userStatus == f.UserStatus
|
||||
}
|
||||
|
||||
func (r questRow) isActive(f Filter) bool {
|
||||
if f.NowMillis < r.startMillis || f.NowMillis > r.endMillis {
|
||||
return false
|
||||
}
|
||||
return r.userStatus == TargetUserStatusAll || r.userStatus == f.UserStatus
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package campaign
|
||||
|
||||
func (c *Catalog) PartsRateBonus(t PartsTarget, f Filter) RateBonus {
|
||||
var out RateBonus
|
||||
for _, r := range c.enhance {
|
||||
if !r.isActive(f) {
|
||||
continue
|
||||
}
|
||||
if !matchesParts(r.targets, t) {
|
||||
continue
|
||||
}
|
||||
out = applyEnhanceEffect(out, r)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (c *Catalog) CostumeExpBonus(t CostumeTarget, f Filter) ExpBonus {
|
||||
var sum int32
|
||||
for _, r := range c.enhance {
|
||||
if !r.isActive(f) || r.effectType != EnhanceEffectAdditionalPerm {
|
||||
continue
|
||||
}
|
||||
if matchesCostume(r.targets, t) {
|
||||
sum += r.effectValue
|
||||
}
|
||||
}
|
||||
return ExpBonus{bonusPermil: sum}
|
||||
}
|
||||
|
||||
func (c *Catalog) WeaponExpBonus(t WeaponTarget, f Filter) ExpBonus {
|
||||
var sum int32
|
||||
for _, r := range c.enhance {
|
||||
if !r.isActive(f) || r.effectType != EnhanceEffectAdditionalPerm {
|
||||
continue
|
||||
}
|
||||
if matchesWeapon(r.targets, t) {
|
||||
sum += r.effectValue
|
||||
}
|
||||
}
|
||||
return ExpBonus{bonusPermil: sum}
|
||||
}
|
||||
|
||||
func applyEnhanceEffect(b RateBonus, r enhanceRow) RateBonus {
|
||||
switch r.effectType {
|
||||
case EnhanceEffectProbability:
|
||||
b.override = r.effectValue
|
||||
case EnhanceEffectAdditionalPerm:
|
||||
b.bonusPermil += r.effectValue
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func matchesParts(targets []enhanceMatch, t PartsTarget) bool {
|
||||
for _, m := range targets {
|
||||
switch m.t {
|
||||
case EnhanceTargetPartsAll:
|
||||
return true
|
||||
case EnhanceTargetPartsSeriesId:
|
||||
if m.v == t.PartsGroupId {
|
||||
return true
|
||||
}
|
||||
case EnhanceTargetPartsId:
|
||||
if m.v == t.PartsId {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func matchesCostume(targets []enhanceMatch, t CostumeTarget) bool {
|
||||
for _, m := range targets {
|
||||
switch m.t {
|
||||
case EnhanceTargetCostumeAll:
|
||||
return true
|
||||
case EnhanceTargetCostumeCharacterId:
|
||||
if m.v == t.CharacterId {
|
||||
return true
|
||||
}
|
||||
case EnhanceTargetCostumeSkillfulWeapon:
|
||||
if m.v == t.SkillfulWeaponType {
|
||||
return true
|
||||
}
|
||||
case EnhanceTargetCostumeId:
|
||||
if m.v == t.CostumeId {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func matchesWeapon(targets []enhanceMatch, t WeaponTarget) bool {
|
||||
for _, m := range targets {
|
||||
switch m.t {
|
||||
case EnhanceTargetWeaponAll:
|
||||
return true
|
||||
case EnhanceTargetWeaponTypeId:
|
||||
if m.v == t.WeaponType {
|
||||
return true
|
||||
}
|
||||
case EnhanceTargetWeaponAttributeTypeId:
|
||||
if m.v == t.AttributeType {
|
||||
return true
|
||||
}
|
||||
case EnhanceTargetWeaponId:
|
||||
if m.v == t.WeaponId {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package campaign
|
||||
|
||||
type RateBonus struct {
|
||||
override int32
|
||||
bonusPermil int32
|
||||
}
|
||||
|
||||
func (b RateBonus) Apply(basePermil int32) int32 {
|
||||
base := basePermil
|
||||
if b.override > 0 {
|
||||
base = b.override
|
||||
}
|
||||
return clampPermil(base + b.bonusPermil)
|
||||
}
|
||||
|
||||
type ExpBonus struct {
|
||||
bonusPermil int32
|
||||
}
|
||||
|
||||
func (b ExpBonus) Apply(base int32) int32 {
|
||||
return base * (1000 + b.bonusPermil) / 1000
|
||||
}
|
||||
|
||||
type StaminaMul struct {
|
||||
permil int32
|
||||
}
|
||||
|
||||
func (m StaminaMul) Apply(base int32) int32 {
|
||||
if m.permil == 1000 {
|
||||
return base
|
||||
}
|
||||
return base * m.permil / 1000
|
||||
}
|
||||
|
||||
type DropRateMul struct {
|
||||
bonusPermil int32
|
||||
}
|
||||
|
||||
func (m DropRateMul) Apply(base int32) int32 {
|
||||
return (base*(1000+m.bonusPermil) + 999) / 1000
|
||||
}
|
||||
|
||||
type BonusDrop struct {
|
||||
PossessionType int32
|
||||
PossessionId int32
|
||||
Count int32
|
||||
}
|
||||
|
||||
func clampPermil(v int32) int32 {
|
||||
if v < 0 {
|
||||
return 0
|
||||
}
|
||||
if v > 1000 {
|
||||
return 1000
|
||||
}
|
||||
return v
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package campaign
|
||||
|
||||
func (c *Catalog) QuestStamina(t QuestTarget, f Filter) StaminaMul {
|
||||
return questPermilMin(c.quest, QuestEffectStaminaConsume, t, f)
|
||||
}
|
||||
|
||||
func (c *Catalog) QuestDropRate(t QuestTarget, f Filter) DropRateMul {
|
||||
var best int32
|
||||
for _, r := range c.quest {
|
||||
if !r.isActive(f) || r.effectType != QuestEffectDropRate {
|
||||
continue
|
||||
}
|
||||
if !matchesQuest(r.targets, t) {
|
||||
continue
|
||||
}
|
||||
if r.effectValue > best {
|
||||
best = r.effectValue
|
||||
}
|
||||
}
|
||||
return DropRateMul{bonusPermil: best}
|
||||
}
|
||||
|
||||
func (c *Catalog) QuestBonusDrops(t QuestTarget, f Filter) []BonusDrop {
|
||||
var out []BonusDrop
|
||||
for _, r := range c.quest {
|
||||
if !r.isActive(f) || r.effectType != QuestEffectDropItemAdd {
|
||||
continue
|
||||
}
|
||||
if !matchesQuest(r.targets, t) {
|
||||
continue
|
||||
}
|
||||
out = append(out, r.bonusItems...)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func questPermilMin(rows []questRow, want QuestCampaignEffectType, t QuestTarget, f Filter) StaminaMul {
|
||||
min := int32(1000)
|
||||
for _, r := range rows {
|
||||
if !r.isActive(f) || r.effectType != want {
|
||||
continue
|
||||
}
|
||||
if !matchesQuest(r.targets, t) {
|
||||
continue
|
||||
}
|
||||
if r.effectValue < min {
|
||||
min = r.effectValue
|
||||
}
|
||||
}
|
||||
return StaminaMul{permil: min}
|
||||
}
|
||||
|
||||
func matchesQuest(targets []questMatch, t QuestTarget) bool {
|
||||
for _, m := range targets {
|
||||
switch m.t {
|
||||
case QuestTargetWholeQuest:
|
||||
return true
|
||||
case QuestTargetQuestType:
|
||||
if int32(t.QuestType) == m.v {
|
||||
return true
|
||||
}
|
||||
case QuestTargetEventQuestType:
|
||||
if t.QuestType == QuestTypeEventQuest && t.EventQuestType == m.v {
|
||||
return true
|
||||
}
|
||||
case QuestTargetMainQuestChapterId:
|
||||
if t.QuestType == QuestTypeMainQuest && t.ChapterId == m.v {
|
||||
return true
|
||||
}
|
||||
case QuestTargetMainQuestQuestId:
|
||||
if t.QuestType == QuestTypeMainQuest && t.QuestId == m.v {
|
||||
return true
|
||||
}
|
||||
case QuestTargetSubQuestChapterId:
|
||||
if t.QuestType == QuestTypeEventQuest && t.ChapterId == m.v {
|
||||
return true
|
||||
}
|
||||
case QuestTargetSubQuestQuestId:
|
||||
if t.QuestType == QuestTypeEventQuest && t.QuestId == m.v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package campaign
|
||||
|
||||
import "lunar-tear/server/internal/model"
|
||||
|
||||
type EnhanceCampaignEffectType int32
|
||||
|
||||
const (
|
||||
EnhanceEffectUnknown EnhanceCampaignEffectType = 0
|
||||
EnhanceEffectProbability EnhanceCampaignEffectType = 1
|
||||
EnhanceEffectAdditionalPerm EnhanceCampaignEffectType = 2
|
||||
)
|
||||
|
||||
type EnhanceCampaignTargetType int32
|
||||
|
||||
const (
|
||||
EnhanceTargetUnknown EnhanceCampaignTargetType = 0
|
||||
EnhanceTargetCostumeAll EnhanceCampaignTargetType = 1
|
||||
EnhanceTargetWeaponAll EnhanceCampaignTargetType = 2
|
||||
EnhanceTargetPartsAll EnhanceCampaignTargetType = 3
|
||||
EnhanceTargetCostumeCharacterId EnhanceCampaignTargetType = 11
|
||||
EnhanceTargetCostumeSkillfulWeapon EnhanceCampaignTargetType = 12
|
||||
EnhanceTargetCostumeId EnhanceCampaignTargetType = 13
|
||||
EnhanceTargetWeaponTypeId EnhanceCampaignTargetType = 21
|
||||
EnhanceTargetWeaponAttributeTypeId EnhanceCampaignTargetType = 22
|
||||
EnhanceTargetWeaponId EnhanceCampaignTargetType = 23
|
||||
EnhanceTargetPartsSeriesId EnhanceCampaignTargetType = 31
|
||||
EnhanceTargetPartsId EnhanceCampaignTargetType = 32
|
||||
)
|
||||
|
||||
type QuestCampaignEffectType int32
|
||||
|
||||
const (
|
||||
QuestEffectUnknown QuestCampaignEffectType = 0
|
||||
QuestEffectDropRate QuestCampaignEffectType = 1
|
||||
QuestEffectDropCount QuestCampaignEffectType = 2
|
||||
QuestEffectStaminaConsume QuestCampaignEffectType = 3
|
||||
QuestEffectClearRewardGold QuestCampaignEffectType = 4
|
||||
QuestEffectDropItemAdd QuestCampaignEffectType = 5
|
||||
)
|
||||
|
||||
type QuestCampaignTargetType int32
|
||||
|
||||
const (
|
||||
QuestTargetUnknown QuestCampaignTargetType = 0
|
||||
QuestTargetWholeQuest QuestCampaignTargetType = 1
|
||||
QuestTargetQuestType QuestCampaignTargetType = 2
|
||||
QuestTargetEventQuestType QuestCampaignTargetType = 3
|
||||
QuestTargetMainQuestChapterId QuestCampaignTargetType = 4
|
||||
QuestTargetMainQuestQuestId QuestCampaignTargetType = 5
|
||||
QuestTargetSubQuestChapterId QuestCampaignTargetType = 6
|
||||
QuestTargetSubQuestQuestId QuestCampaignTargetType = 7
|
||||
)
|
||||
|
||||
type QuestType int32
|
||||
|
||||
const (
|
||||
QuestTypeUnknown QuestType = 0
|
||||
QuestTypeMainQuest QuestType = 1
|
||||
QuestTypeEventQuest QuestType = 2
|
||||
QuestTypeExtraQuest QuestType = 3
|
||||
QuestTypeBigHunt QuestType = 4
|
||||
)
|
||||
|
||||
type TargetUserStatusType int32
|
||||
|
||||
const (
|
||||
TargetUserStatusUnknown TargetUserStatusType = 0
|
||||
TargetUserStatusAll TargetUserStatusType = 1
|
||||
TargetUserStatusComeback TargetUserStatusType = 2
|
||||
TargetUserStatusBeginner TargetUserStatusType = 3
|
||||
)
|
||||
|
||||
type Filter struct {
|
||||
NowMillis int64
|
||||
UserStatus TargetUserStatusType
|
||||
}
|
||||
|
||||
type PartsTarget struct {
|
||||
PartsId int32
|
||||
PartsGroupId int32
|
||||
Rarity model.RarityType
|
||||
}
|
||||
|
||||
type CostumeTarget struct {
|
||||
CostumeId int32
|
||||
CharacterId int32
|
||||
SkillfulWeaponType int32
|
||||
}
|
||||
|
||||
type WeaponTarget struct {
|
||||
WeaponId int32
|
||||
WeaponType int32
|
||||
AttributeType int32
|
||||
}
|
||||
|
||||
type QuestTarget struct {
|
||||
QuestId int32
|
||||
QuestType QuestType
|
||||
EventQuestType int32
|
||||
ChapterId int32
|
||||
}
|
||||
Reference in New Issue
Block a user