From 2035a5138d702cd9c910b2ca213226b5e0804085 Mon Sep 17 00:00:00 2001 From: tomas Date: Thu, 27 Feb 2025 09:12:15 +0800 Subject: [PATCH] add sugarrush plugin --- .../slotspkg/internal/generic/key/theme.go | 11 +- gamesrv/slotspkg/slots/plugin/init.go | 5 + .../slotspkg/slots/plugin/sugarrush/base.go | 419 ++++++++++++++++++ .../slots/plugin/sugarrush/choose_wheel.go | 31 ++ .../slotspkg/slots/plugin/sugarrush/common.go | 12 + .../slotspkg/slots/plugin/sugarrush/descx.go | 165 +++++++ .../slots/plugin/sugarrush/free_spin.go | 89 ++++ .../slotspkg/slots/plugin/sugarrush/init.go | 9 + 8 files changed, 731 insertions(+), 10 deletions(-) create mode 100644 gamesrv/slotspkg/slots/plugin/sugarrush/base.go create mode 100644 gamesrv/slotspkg/slots/plugin/sugarrush/choose_wheel.go create mode 100644 gamesrv/slotspkg/slots/plugin/sugarrush/common.go create mode 100644 gamesrv/slotspkg/slots/plugin/sugarrush/descx.go create mode 100644 gamesrv/slotspkg/slots/plugin/sugarrush/free_spin.go create mode 100644 gamesrv/slotspkg/slots/plugin/sugarrush/init.go diff --git a/gamesrv/slotspkg/internal/generic/key/theme.go b/gamesrv/slotspkg/internal/generic/key/theme.go index 53c9f09..9e50841 100644 --- a/gamesrv/slotspkg/internal/generic/key/theme.go +++ b/gamesrv/slotspkg/internal/generic/key/theme.go @@ -8,6 +8,7 @@ const ( FortuneMouse = "FortuneMouse" CashMania = "CashMania" GatesOfOlympus = "GatesOfOlympus" + SugarRush = "SugarRush" Test = "Test" ) const ( @@ -34,16 +35,6 @@ var GameMap = map[uint]string{ GameId_GatesOfOlympus: GatesOfOlympus, GameId_Test: Test, } -var GameMapTheme = map[string]uint{ - FortuneTiger: GameId_Tiger, - FortuneDragon: GameId_Dragon, - FortuneRabbit: GameId_Rabbit, - FortuneOx: GameId_OX, - FortuneMouse: GameId_Mouse, - CashMania: GameId_Cash_Mania, - GatesOfOlympus: GameId_GatesOfOlympus, - Test: GameId_Test, -} var GameKeyMap = map[int64]uint{ 0: GameId_Min, 308: GameId_Tiger, diff --git a/gamesrv/slotspkg/slots/plugin/init.go b/gamesrv/slotspkg/slots/plugin/init.go index 2f5dc28..e98ffdb 100644 --- a/gamesrv/slotspkg/slots/plugin/init.go +++ b/gamesrv/slotspkg/slots/plugin/init.go @@ -9,6 +9,7 @@ import ( "mongo.games.com/game/gamesrv/slotspkg/slots/plugin/fortunerabbit" "mongo.games.com/game/gamesrv/slotspkg/slots/plugin/fortunetiger" "mongo.games.com/game/gamesrv/slotspkg/slots/plugin/gatesofolympus" + "mongo.games.com/game/gamesrv/slotspkg/slots/plugin/sugarrush" "mongo.games.com/game/gamesrv/slotspkg/slots/plugin/test" "mongo.games.com/game/gamesrv/slotspkg/slots/reg" ) @@ -22,6 +23,7 @@ func Init() { reg.Register(fortunemouse.Plugins...) reg.Register(cashmania.Plugins...) reg.Register(gatesofolympus.Plugins...) + reg.Register(sugarrush.Plugins...) reg.Register(test.Plugins...) if global.Mock { @@ -31,6 +33,7 @@ func Init() { reg.Register(fortunedragon.SimulatorPlugins...) reg.Register(cashmania.SimulatorPlugins...) reg.Register(gatesofolympus.SimulatorPlugins...) + reg.Register(sugarrush.SimulatorPlugins...) reg.Register(test.SimulatorPlugins...) } } @@ -44,6 +47,7 @@ func Close() { reg.Deregister(fortunemouse.Plugins...) reg.Deregister(cashmania.Plugins...) reg.Deregister(gatesofolympus.Plugins...) + reg.Deregister(sugarrush.Plugins...) reg.Deregister(test.Plugins...) if global.Mock { reg.Deregister(fortuneox.SimulatorPlugins...) @@ -52,6 +56,7 @@ func Close() { reg.Deregister(fortunedragon.SimulatorPlugins...) reg.Deregister(cashmania.SimulatorPlugins...) reg.Deregister(gatesofolympus.SimulatorPlugins...) + reg.Deregister(sugarrush.SimulatorPlugins...) reg.Deregister(test.SimulatorPlugins...) } } diff --git a/gamesrv/slotspkg/slots/plugin/sugarrush/base.go b/gamesrv/slotspkg/slots/plugin/sugarrush/base.go new file mode 100644 index 0000000..c8cbdc4 --- /dev/null +++ b/gamesrv/slotspkg/slots/plugin/sugarrush/base.go @@ -0,0 +1,419 @@ +package sugarrush + +import ( + "github.com/tomas-qstarrs/boost/mathx" + "github.com/tomas-qstarrs/boost/randx" + "mongo.games.com/game/gamesrv/slotspkg/internal/generic/key" + "mongo.games.com/game/gamesrv/slotspkg/internal/module/shared" + "mongo.games.com/game/gamesrv/slotspkg/slots/intf" + "mongo.games.com/game/gamesrv/slotspkg/slots/plugin/generic" +) + +// PluginBaseSpin is derived from generic.PluginBase +type PluginBaseSpin struct { + generic.PluginBase +} + +// Theme implements generic.PluginBase.Theme +func (p *PluginBaseSpin) Theme() string { + return key.SugarRush +} + +type CustomEliminate struct { + LinkPositions []*shared.LinkPositions + AppendSymbols [][]int64 + FormattedSymbols [][]int64 + LinePays []float64 + LineMultis []int64 + WinCoins []int64 + MaxWin bool +} + +type CustomPay struct { + Pay int64 +} + +type CustomMulti struct { + MultiTable [][]int64 +} + +type Col struct { + Values []int64 +} + +type Formation struct { + Cols []Col +} + +// Customs implements generic.PluginBase.Customs +func (p *PluginBaseSpin) Customs() []interface{} { + return []interface{}{ + &CustomPay{}, + &CustomEliminate{}, + &MultiTable{}, + &CustomMulti{}, + } +} + +// AfterBaseSpin is called after base spin +func (p *PluginBaseSpin) AfterBaseSpin(m intf.Master) { + +} + +func (f *Formation) AddFreeSpin(m intf.Master) { + if m.Cursor().GetType() != key.BaseSpin { + return + } + scatterCount := Descx(m).RandScatterCount() + // get current count + currentCount := int64(0) + for _, col := range f.Cols { + for _, v := range col.Values { + if v == SymbolScatter { + currentCount++ + } + } + } + + // get col not has scatter + colNotHasScatter := make([]int64, 0) + for i, col := range f.Cols { + hasScatter := false + for _, v := range col.Values { + if v == SymbolScatter { + hasScatter = true + break + } + } + if !hasScatter { + colNotHasScatter = append(colNotHasScatter, int64(i)) + } + } + + needCount := scatterCount - currentCount + if needCount <= 0 { + needCount = 0 + } + + // rand new scatter for col not has scatter + randx.RandShuffle(m.Randx(), colNotHasScatter) + for i := int64(0); i < needCount; i++ { + col := colNotHasScatter[i] + f.Cols[col].Values[randx.RandRangeInt63n(m.Randx(), 0, 6)] = SymbolScatter + } +} + +func (f *Formation) fillCol(m intf.Master, col int64, eliminate bool, isFree bool) []int64 { + appendValues := make([]int64, 0) + values := f.Cols[col].Values + left := ReelHeight - int64(len(values)) + scatterCount := int64(0) + for _, v := range values { + if v == SymbolScatter { + scatterCount++ + break + } + } + + topItemID := int64(0) + isTop := false + if len(values) > 0 && eliminate { + topItemID = values[0] + isTop = true + } + + for left > 0 { + itemID := Descx(m).RandItemID(topItemID, isFree, isTop) + if isTop { + isTop = false + } + topItemID = itemID + + itemCount := Descx(m).RandItemCount(itemID, left, isFree) + if SymbolScatter == itemID { + itemCount = mathx.Min(itemCount, Descx(m).GetOtherConfig().MaxScatterPerCol-scatterCount) + } + + if SymbolScatter == itemID { + scatterCount += itemCount + } + + for i := int64(0); i < itemCount; i++ { + // append at head + values = append([]int64{itemID}, values...) + appendValues = append([]int64{itemID}, appendValues...) + } + + left -= itemCount + } + f.Cols[col].Values = values + return appendValues +} + +func (f *Formation) fillCols(m intf.Master, eliminate bool, isFree bool) [][]int64 { + newSymbols := make([][]int64, ReelWidth) + for j := int64(0); j < ReelWidth; j++ { + newSymbols[j] = f.fillCol(m, j, eliminate, isFree) + } + return newSymbols +} + +func (f *Formation) GetValues() [][]int64 { + values := make([][]int64, ReelHeight) + for j := int64(0); j < ReelWidth; j++ { + for i := int64(0); i < ReelHeight; i++ { + values[j] = append(values[j], f.Cols[j].Values[i]) + } + } + return values +} + +func createFormation() *Formation { + formation := &Formation{} + for i := 0; i < 7; i++ { + formation.Cols = make([]Col, 7) + } + return formation +} + +func (p *PluginBaseSpin) RandFormation(m intf.Master) [][]int64 { + formation := &Formation{ + Cols: make([]Col, 7), + } + for i := 0; i < 7; i++ { + formation.Cols[i] = Col{ + Values: make([]int64, 0), + } + formation.fillCol(m, int64(i), false, m.Cursor().GetType() == key.FreeSpin) + } + + // 购买freespin + if m.Exists(key.MachineRoundType) && m.Value(key.MachineRoundType).(int64) == RoundTypeBuyFreeSpin { + formation.AddFreeSpin(m) + } + + return formation.GetValues() +} + +func DumpReel(symbols [][]int64) { + // str := "" + // for row := int64(0); row < 7; row++ { + // for col := int64(0); col < 7; col++ { + // str += fmt.Sprintf("%d ", symbols[col][row]) + // } + // str += "\n" + // } + // log.Infof("reel: \n%s\n", str) +} + +func IsPositionLinked(linkPositions []*shared.LinkPositions, row int64, col int64) bool { + for _, positions := range linkPositions { + for _, position := range positions.Positions { + if position == col*7+row { + return true + } + } + } + return false +} + +type MultiTable struct { + Multi [][]int64 + Count [][]int64 +} + +func (t *MultiTable) Clear() { + for i := 0; i < 7; i++ { + for j := 0; j < 7; j++ { + t.Multi[i][j] = 0 + t.Count[i][j] = 0 + } + } +} + +func (t *MultiTable) Update(m intf.Master, linkPositions []*shared.LinkPositions) { + for _, positions := range linkPositions { + for _, position := range positions.Positions { + t.Count[position/7][position%7]++ + } + } + // update multi + for i := 0; i < 7; i++ { + for j := 0; j < 7; j++ { + t.Multi[i][j] = Descx(m).GetMulti(t.Count[i][j]) + } + } +} + +func calcMulti(linkPositions *shared.LinkPositions, multiTable *MultiTable) int64 { + totalMulti := int64(0) + + for _, p := range linkPositions.Positions { + if multiTable.Multi[p/7][p%7] > 1 { + totalMulti += multiTable.Multi[p/7][p%7] + } + } + + if totalMulti < 1 { + totalMulti = 1 + } + + return totalMulti +} + +func (p *PluginBaseSpin) Eliminate(m intf.Master, customPay *CustomPay, multiTable *MultiTable) bool { + cursorFormation := m.CursorFormation() + formattedSymbols := cursorFormation.GetReelFormattedSymbols() + DumpReel(formattedSymbols) + // 清空基础赢钱 + cursorFormation.SetWin(0) + + // 根据赔付计算multi type + linkPositions, _, linePays := m.TryLinkMatrixSymbols(1, formattedSymbols) + DumpReel(multiTable.Multi) + lineMultis := make([]int64, len(linePays)) + + symbols := cursorFormation.GetReelFormattedSymbols() + success := mathx.Sum(linePays) > 0 + + // erase + formation := createFormation() + for j := int64(0); j < 7; j++ { + for i := int64(0); i < 7; i++ { + if !IsPositionLinked(linkPositions, i, j) { + formation.Cols[j].Values = append(formation.Cols[j].Values, symbols[j][i]) + } + } + } + + appendFormattedSymbols := formation.fillCols(m, true, m.Cursor().GetType() == key.FreeSpin) + + lineNum := len(linePays) + winCoins := make([]int64, lineNum) + totalPay := int64(0) + + for lineIdx, pay := range linePays { + multi := calcMulti(linkPositions[lineIdx], multiTable) + lineMultis[lineIdx] = multi + winCoins[lineIdx] = int64(float64(m.Cursor().GetSingleBet()) * pay * float64(multi)) + totalPay += winCoins[lineIdx] + } + + isMaxWin := false + + maxValue := Descx(m).GetOtherConfig().FreespinMaxWin * m.Cursor().GetBet() + currentWin := m.TotalWin() + customPay.Pay + + // log.Infof("Eliminate totalPay: %d, currentWin: %d, maxValue: %d", totalPay, currentWin, maxValue) + + if m.Cursor().GetType() == key.FreeSpin { + if currentWin+totalPay >= maxValue { + success = false + isMaxWin = true + totalPay = maxValue - currentWin + m.SetProgressLeft(0) + } + } + + // 添加后续feature,这里是消除 + m.AddCursorFeature(&CustomEliminate{ + LinkPositions: linkPositions, + AppendSymbols: appendFormattedSymbols, + FormattedSymbols: formattedSymbols, + LinePays: linePays, + LineMultis: lineMultis, + WinCoins: winCoins, + MaxWin: isMaxWin, + }).SetLifetime(1) + + DumpReel(formation.GetValues()) + cursorFormation.SetFormattedSymbols(formation.GetValues()) + + // 累加pay + customPay.Pay += totalPay + multiTable.Update(m, linkPositions) + + // add new + return success +} + +func (p *PluginBaseSpin) BeforeSpin(m intf.Master) { + m.AddCursorFeature(&CustomPay{}).SetLifetime(1) +} + +func (p *PluginBaseSpin) Spin(m intf.Master, isFree bool) { + // 生成轴 + symbols := p.RandFormation(m) + DumpReel(symbols) + formation := m.CursorFormation() + formation.SetFormattedSymbols(symbols) + customPay := m.CursorCustom(&CustomPay{}).(*CustomPay) + + table := getMultiTable(m) + + // 存储 Formation元素 + formation.SetFormattedDisplaySymbols(symbols) + + // 消除 + n := 0 + for p.Eliminate(m, customPay, table) { + n++ + if n > 1000 { + break + } + } + + if customPay.Pay > 0 { + m.CursorFeature(&CustomPay{}).SetWin(customPay.Pay) + } + + m.AddCursorFeature(&CustomMulti{ + MultiTable: table.Multi, + }).SetLifetime(1) + + formattedSymbols := formation.GetReelFormattedSymbols() + formation.SetFormattedFinalSymbols(formattedSymbols) +} + +func (p *PluginBaseSpin) CheckFreeLimit(m intf.Master) { + +} + +// AfterSpin implements generic.PluginBase.AfterSpin +func (p *PluginBaseSpin) AfterSpin(m intf.Master) { + p.Spin(m, m.Cursor().GetType() == key.FreeSpin) + + switch m.Cursor().GetType() { + case key.BaseSpin: + p.AfterBaseSpin(m) + case key.FreeSpin: + p.AfterFreeSpin(m) + p.CheckFreeLimit(m) + } +} + +// AfterFreeSpin is called after free spin +func (p *PluginBaseSpin) AfterFreeSpin(m intf.Master) { + +} + +func (p *PluginBaseSpin) OnLeaveNode(m intf.Master) { + if m.Next().GetType() == key.FreeSpin || m.Next().GetType() == key.BaseSpin { + getMultiTable(m).Clear() + } +} + +func getMultiTable(m intf.Master) *MultiTable { + if len(m.RootFeatures(&MultiTable{})) == 0 { + table := &MultiTable{ + Multi: make([][]int64, 7), + Count: make([][]int64, 7), + } + for i := 0; i < 7; i++ { + table.Multi[i] = make([]int64, 7) + table.Count[i] = make([]int64, 7) + } + m.AddRootFeature(table) + } + return m.RootCustom(&MultiTable{}).(*MultiTable) +} diff --git a/gamesrv/slotspkg/slots/plugin/sugarrush/choose_wheel.go b/gamesrv/slotspkg/slots/plugin/sugarrush/choose_wheel.go new file mode 100644 index 0000000..0296a64 --- /dev/null +++ b/gamesrv/slotspkg/slots/plugin/sugarrush/choose_wheel.go @@ -0,0 +1,31 @@ +package sugarrush + +import ( + "mongo.games.com/game/gamesrv/slotspkg/internal/generic/key" + "mongo.games.com/game/gamesrv/slotspkg/slots/intf" + "mongo.games.com/game/gamesrv/slotspkg/slots/plugin/generic" +) + +type PluginChooseWheel struct { + generic.PluginBase +} + +func (p *PluginChooseWheel) Theme() string { + return key.SugarRush +} + +func (p *PluginChooseWheel) OnStepBegin(m intf.Master) { + isFreeSpin := m.Next().GetType() == key.FreeSpin + typ := m.Choice() + + if !isFreeSpin { + if typ == RoundTypeBuyFreeSpin { + m.SetRatio(key.MachineRatioMoreCoinSameBet, 100) + } + } + + // 设置日志中的RoundType + if m.Next().GetType() == key.BaseSpin { + m.Set(key.MachineRoundType, typ) + } +} diff --git a/gamesrv/slotspkg/slots/plugin/sugarrush/common.go b/gamesrv/slotspkg/slots/plugin/sugarrush/common.go new file mode 100644 index 0000000..bd709bd --- /dev/null +++ b/gamesrv/slotspkg/slots/plugin/sugarrush/common.go @@ -0,0 +1,12 @@ +package sugarrush + +const ( + ReelWidth = int64(7) + ReelHeight = int64(7) + SymbolScatter = int64(1) +) +const ( + RoundTypeBaseSpin = iota + RoundTypeMoreScatter // 25% more cost + RoundTypeBuyFreeSpin // 10000% more cost +) diff --git a/gamesrv/slotspkg/slots/plugin/sugarrush/descx.go b/gamesrv/slotspkg/slots/plugin/sugarrush/descx.go new file mode 100644 index 0000000..06b7282 --- /dev/null +++ b/gamesrv/slotspkg/slots/plugin/sugarrush/descx.go @@ -0,0 +1,165 @@ +package sugarrush + +import ( + "github.com/tomas-qstarrs/boost/randx" + "mongo.games.com/game/gamesrv/slotspkg/internal/exported/excel2go/structs" + "mongo.games.com/game/gamesrv/slotspkg/internal/generic/errors" + "mongo.games.com/game/gamesrv/slotspkg/slots/desc" + "mongo.games.com/game/gamesrv/slotspkg/slots/intf" +) + +type descx struct { + *randx.Randx + *desc.NodeDesc +} + +func Descx(m intf.Master) *descx { + return &descx{ + Randx: m.Randx(), + NodeDesc: m.Desc(), + } +} + +func (n descx) GetOtherConfig() *structs.SugarRushOthers { + v := n.DefaultSheet("Others") + + values, ok := v.([]*structs.SugarRushOthers) + if !ok { + panic(errors.ConfigTypeError.Error()) + } + return values[0] +} + +func (n descx) GetMulti(count int64) int64 { + v := n.DefaultSheet("Multiplier") + values, ok := v.([]*structs.SugarRushMultiplier) + if !ok { + panic(errors.ConfigTypeError.Error()) + } + for _, v := range values { + if v.MatchTimes == count { + return v.Multiple + } + } + // return last one + return values[len(values)-1].Multiple +} + +func (n descx) RandScatterCount() int64 { + v := n.Sheet("BuyFreeScatterShow", "Weight") + + values, ok := v.([]*structs.SugarRushBuyFreeScatterShowWeight) + if !ok { + panic(errors.ConfigTypeError.Error()) + } + + weights := make([]float64, 0) + for _, v := range values { + weights = append(weights, v.Weight) + } + index := randx.RandWeight(n.Randx, weights) + + return values[index].ScatterCount +} + +func (n descx) RandItemID(topItemID int64, isFree bool, isTop bool) int64 { + if isFree { + v := n.Sheet("FreeSymbolShow", "Weight") + + values, ok := v.([]*structs.SugarRushFreeSymbolShowWeight) + if !ok { + panic(errors.ConfigTypeError.Error()) + } + + typeWeights := make([]float64, 0) + + for _, v := range values { + if v.ItemID == topItemID { + if isTop { + typeWeights = append(typeWeights, v.Weight*float64(n.GetOtherConfig().FallWeightMultiplier)) + } else { + typeWeights = append(typeWeights, 0) + } + } else { + typeWeights = append(typeWeights, v.Weight) + } + } + + value := randx.RandWeight(n.Randx, typeWeights) + return values[value].ItemID + } + + v := n.Sheet("BaseSymbolShow", "Weight") + + values, ok := v.([]*structs.SugarRushBaseSymbolShowWeight) + if !ok { + panic(errors.ConfigTypeError.Error()) + } + + typeWeights := make([]float64, 0) + + for _, v := range values { + if v.ItemID == topItemID { + if isTop { + typeWeights = append(typeWeights, v.Weight*float64(n.GetOtherConfig().FallWeightMultiplier)) + } else { + typeWeights = append(typeWeights, 0) + } + } else { + typeWeights = append(typeWeights, v.Weight) + } + } + + value := randx.RandWeight(n.Randx, typeWeights) + return values[value].ItemID +} + +func (n descx) RandItemCount(itemID int64, left int64, isFree bool) int64 { + if isFree { + v := n.Sheet("FreeSymbolShowNumber", "Weight") + + values, ok := v.([]*structs.SugarRushFreeSymbolShowNumberWeight) + if !ok { + panic(errors.ConfigTypeError.Error()) + } + + for _, v := range values { + if v.ItemID == itemID { + typeWeights := make([]float64, 0) + typeWeights = append(typeWeights, v.ShowNumberWeight...) + + value := randx.RandWeight(n.Randx, typeWeights) + count := int64(value) + 1 + if count > left { + return left + } + return count + } + } + + return 0 + } + + v := n.Sheet("BaseSymbolShowNumber", "Weight") + + values, ok := v.([]*structs.SugarRushBaseSymbolShowNumberWeight) + if !ok { + panic(errors.ConfigTypeError.Error()) + } + + for _, v := range values { + if v.ItemID == itemID { + typeWeights := make([]float64, 0) + typeWeights = append(typeWeights, v.ShowNumberWeight...) + + value := randx.RandWeight(n.Randx, typeWeights) + count := int64(value) + 1 + if count > left { + return left + } + return count + } + } + + return 0 +} diff --git a/gamesrv/slotspkg/slots/plugin/sugarrush/free_spin.go b/gamesrv/slotspkg/slots/plugin/sugarrush/free_spin.go new file mode 100644 index 0000000..2b2d853 --- /dev/null +++ b/gamesrv/slotspkg/slots/plugin/sugarrush/free_spin.go @@ -0,0 +1,89 @@ +package sugarrush + +import ( + "github.com/tomas-qstarrs/boost/mathx" + "mongo.games.com/game/gamesrv/slotspkg/internal/generic/key" + "mongo.games.com/game/gamesrv/slotspkg/slots/intf" + "mongo.games.com/game/gamesrv/slotspkg/slots/plugin/generic" +) + +// PluginFreeSpin is derived from generic.PluginBase +type PluginFreeSpin struct { + generic.PluginScatter +} + +// Theme implements generic.PluginBase.Theme +func (p *PluginFreeSpin) Theme() string { + return key.SugarRush +} + +// Customs implements generic.PluginBase.Customs +func (p *PluginFreeSpin) Customs() []interface{} { + return []interface{}{} +} + +// AfterSpin implements generic.PluginBase.AfterSpin +func (p *PluginFreeSpin) AfterSpin(m intf.Master) { + switch m.Cursor().GetType() { + case key.BaseSpin: + p.AfterBaseSpin(m) + case key.FreeSpin: + p.AfterFreeSpin(m) + } +} + +// AfterBaseSpin is called after base spin +func (p *PluginFreeSpin) AfterBaseSpin(m intf.Master) { + addTimes, win := p.GetScatterInfo(m, false) + if addTimes > 0 { + m.AddNodeOnCursor(key.FreeSpin, addTimes) + } + if win > 0 { + m.AddCursorFeature(&generic.CustomScatterWin{}).SetWin(win) + } +} + +// AfterFreeSpin is called after free spin +func (p *PluginFreeSpin) AfterFreeSpin(m intf.Master) { + addTimes, win := p.GetScatterInfo(m, true) + if addTimes > 0 { + m.AddProgress(addTimes) + m.AddCursorFeature(&generic.CustomExtraFreeSpin{ExtraTimes: addTimes}).SetLifetime(1) + } + if win > 0 { + m.AddCursorFeature(&generic.CustomScatterWin{}).SetWin(win) + } +} + +// GetScatterInfo gets add free spin times & pay rate +func (p *PluginFreeSpin) GetScatterInfo(m intf.Master, inFreeSpin bool) (int64, int64) { + var scatterCount int64 + symbols := m.CursorFormation().GetFinalSymbols() + scatterSymbols := p.Scatters(m) + for _, scatterSymbol := range scatterSymbols { + scatterCount += int64(mathx.Count(scatterSymbol, symbols)) + } + + if scatterCount == 0 { + return 0, 0 + } + + freeSpinCount := generic.Descx(m).FreeSpin(inFreeSpin, scatterCount) + + payRate := generic.Descx(m).ScatterPayRate(inFreeSpin, scatterCount) + + win := m.Bet() * payRate + + return freeSpinCount, win +} + +// OnStepEnd is called on finalizing a step +func (p *PluginFreeSpin) OnStepEnd(m intf.Master) { + nextType := m.Next().GetType() + + if m.Cursor().GetType() == key.FreeSpin && nextType == key.BaseSpin { + formation := m.NodeFormation(m.Next().GetID()) + initSymbols := m.CursorFormation().GetFinalSymbols() + formation.SetInitSymbols(initSymbols) + } +} diff --git a/gamesrv/slotspkg/slots/plugin/sugarrush/init.go b/gamesrv/slotspkg/slots/plugin/sugarrush/init.go new file mode 100644 index 0000000..0974b6c --- /dev/null +++ b/gamesrv/slotspkg/slots/plugin/sugarrush/init.go @@ -0,0 +1,9 @@ +package sugarrush + +var Plugins = []interface{}{ + &PluginBaseSpin{}, + &PluginFreeSpin{}, + &PluginChooseWheel{}, +} + +var SimulatorPlugins = []interface{}{}