mirror of
https://github.com/Walter-Sparrow/lunar-tear.git
synced 2026-07-02 05:43:41 +03:00
Initial commit
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
package gacha
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math/rand"
|
||||
|
||||
"lunar-tear/server/internal/masterdata"
|
||||
"lunar-tear/server/internal/model"
|
||||
)
|
||||
|
||||
type RateTier struct {
|
||||
Weight int
|
||||
PossessionType int32
|
||||
RarityType model.RarityType
|
||||
}
|
||||
|
||||
type DrawnItem struct {
|
||||
PossessionType int32
|
||||
PossessionId int32
|
||||
RarityType model.RarityType
|
||||
CharacterId int32
|
||||
}
|
||||
|
||||
var premiumRates = []RateTier{
|
||||
{200, int32(model.PossessionTypeCostume), model.RaritySSRare},
|
||||
{300, int32(model.PossessionTypeWeapon), model.RaritySSRare},
|
||||
{500, int32(model.PossessionTypeCostume), model.RaritySRare},
|
||||
{1000, int32(model.PossessionTypeWeapon), model.RaritySRare},
|
||||
{8000, int32(model.PossessionTypeWeapon), model.RarityRare},
|
||||
}
|
||||
|
||||
func DrawPremium(bp *masterdata.BannerPool, count int, fixedRarityMin int32, fixedCount int, rateMultiplier float64) []DrawnItem {
|
||||
result := make([]DrawnItem, 0, count)
|
||||
rates := adjustRates(premiumRates, rateMultiplier)
|
||||
totalWeight := 0
|
||||
for _, r := range rates {
|
||||
totalWeight += r.Weight
|
||||
}
|
||||
|
||||
for i := range count {
|
||||
isGuaranteeSlot := fixedCount > 0 && i >= count-fixedCount
|
||||
item := rollOne(bp, rates, totalWeight)
|
||||
|
||||
if isGuaranteeSlot && item.RarityType < fixedRarityMin {
|
||||
item = rollAtMinRarity(bp, rates, fixedRarityMin)
|
||||
}
|
||||
result = append(result, item)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func DrawBox(items []BoxItem, count int) []DrawnItem {
|
||||
var available []int
|
||||
for i, item := range items {
|
||||
remaining := item.MaxCount - item.DrewCount
|
||||
for range remaining {
|
||||
available = append(available, i)
|
||||
}
|
||||
}
|
||||
|
||||
result := make([]DrawnItem, 0, count)
|
||||
for i := 0; i < count && len(available) > 0; i++ {
|
||||
pick := rand.Intn(len(available))
|
||||
idx := available[pick]
|
||||
item := items[idx]
|
||||
result = append(result, DrawnItem{
|
||||
PossessionType: item.PossessionType,
|
||||
PossessionId: item.PossessionId,
|
||||
RarityType: item.RarityType,
|
||||
})
|
||||
items[idx].DrewCount++
|
||||
available = append(available[:pick], available[pick+1:]...)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func DrawReward(materials []masterdata.GachaPoolItem, count int) []DrawnItem {
|
||||
if len(materials) == 0 {
|
||||
return nil
|
||||
}
|
||||
result := make([]DrawnItem, 0, count)
|
||||
for range count {
|
||||
m := materials[rand.Intn(len(materials))]
|
||||
result = append(result, DrawnItem{
|
||||
PossessionType: m.PossessionType,
|
||||
PossessionId: m.PossessionId,
|
||||
RarityType: m.RarityType,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type BoxItem struct {
|
||||
PossessionType int32
|
||||
PossessionId int32
|
||||
RarityType model.RarityType
|
||||
Count int32
|
||||
MaxCount int32
|
||||
DrewCount int32
|
||||
IsTarget bool
|
||||
}
|
||||
|
||||
func adjustRates(base []RateTier, multiplier float64) []RateTier {
|
||||
if multiplier == 1.0 || multiplier == 0 {
|
||||
return base
|
||||
}
|
||||
adjusted := make([]RateTier, len(base))
|
||||
copy(adjusted, base)
|
||||
|
||||
var fourStarExtra int
|
||||
var nonFourStar int
|
||||
for i, r := range adjusted {
|
||||
if r.RarityType >= model.RaritySSRare {
|
||||
extra := int(float64(r.Weight) * (multiplier - 1.0))
|
||||
adjusted[i].Weight += extra
|
||||
fourStarExtra += extra
|
||||
} else {
|
||||
nonFourStar += r.Weight
|
||||
}
|
||||
}
|
||||
if nonFourStar > 0 && fourStarExtra > 0 {
|
||||
for i, r := range adjusted {
|
||||
if r.RarityType < model.RaritySSRare {
|
||||
reduction := fourStarExtra * r.Weight / nonFourStar
|
||||
adjusted[i].Weight -= reduction
|
||||
if adjusted[i].Weight < 1 {
|
||||
adjusted[i].Weight = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return adjusted
|
||||
}
|
||||
|
||||
func rollOne(bp *masterdata.BannerPool, rates []RateTier, totalWeight int) DrawnItem {
|
||||
roll := rand.Intn(totalWeight)
|
||||
cumulative := 0
|
||||
var tier RateTier
|
||||
for _, r := range rates {
|
||||
cumulative += r.Weight
|
||||
if roll < cumulative {
|
||||
tier = r
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if item, ok := tryFeaturedRateUp(bp, tier); ok {
|
||||
return item
|
||||
}
|
||||
return pickFromPool(bp, tier.PossessionType, tier.RarityType)
|
||||
}
|
||||
|
||||
func tryFeaturedRateUp(bp *masterdata.BannerPool, tier RateTier) (DrawnItem, bool) {
|
||||
var matches []masterdata.GachaPoolItem
|
||||
for _, f := range bp.Featured {
|
||||
if f.PossessionType == tier.PossessionType && f.RarityType == tier.RarityType {
|
||||
matches = append(matches, f)
|
||||
}
|
||||
}
|
||||
if len(matches) == 0 {
|
||||
return DrawnItem{}, false
|
||||
}
|
||||
if rand.Intn(model.FeaturedRateUpDenom) >= model.FeaturedRateUpPercent {
|
||||
return DrawnItem{}, false
|
||||
}
|
||||
f := matches[rand.Intn(len(matches))]
|
||||
return DrawnItem{
|
||||
PossessionType: f.PossessionType,
|
||||
PossessionId: f.PossessionId,
|
||||
RarityType: f.RarityType,
|
||||
CharacterId: f.CharacterId,
|
||||
}, true
|
||||
}
|
||||
|
||||
func rollAtMinRarity(bp *masterdata.BannerPool, rates []RateTier, minRarity model.RarityType) DrawnItem {
|
||||
var filtered []RateTier
|
||||
filteredTotal := 0
|
||||
for _, r := range rates {
|
||||
if r.RarityType >= minRarity {
|
||||
filtered = append(filtered, r)
|
||||
filteredTotal += r.Weight
|
||||
}
|
||||
}
|
||||
if filteredTotal == 0 {
|
||||
return pickFromPool(bp, int32(model.PossessionTypeWeapon), minRarity)
|
||||
}
|
||||
return rollOne(bp, filtered, filteredTotal)
|
||||
}
|
||||
|
||||
func pickFromPool(bp *masterdata.BannerPool, possessionType int32, rarityType model.RarityType) DrawnItem {
|
||||
if possessionType == int32(model.PossessionTypeCostume) {
|
||||
items := bp.CostumesByRarity[rarityType]
|
||||
if len(items) == 0 {
|
||||
items = bp.CostumesByRarity[model.RaritySSRare]
|
||||
}
|
||||
if len(items) == 0 {
|
||||
log.Printf("[pickFromPool] empty costume pool for rarity=%d, returning phantom item", rarityType)
|
||||
return DrawnItem{PossessionType: int32(model.PossessionTypeWeapon), RarityType: rarityType}
|
||||
}
|
||||
pick := items[rand.Intn(len(items))]
|
||||
return DrawnItem{
|
||||
PossessionType: pick.PossessionType,
|
||||
PossessionId: pick.PossessionId,
|
||||
RarityType: pick.RarityType,
|
||||
CharacterId: pick.CharacterId,
|
||||
}
|
||||
}
|
||||
|
||||
items := bp.WeaponsByRarity[rarityType]
|
||||
if len(items) == 0 {
|
||||
items = bp.WeaponsByRarity[model.RarityRare]
|
||||
}
|
||||
if len(items) == 0 {
|
||||
log.Printf("[pickFromPool] empty weapon pool for rarity=%d, returning phantom item", rarityType)
|
||||
return DrawnItem{PossessionType: int32(model.PossessionTypeWeapon), RarityType: rarityType}
|
||||
}
|
||||
pick := items[rand.Intn(len(items))]
|
||||
return DrawnItem{
|
||||
PossessionType: pick.PossessionType,
|
||||
PossessionId: pick.PossessionId,
|
||||
RarityType: pick.RarityType,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user