186 lines
5.3 KiB
Go
186 lines
5.3 KiB
Go
package gameserver
|
|
|
|
import (
|
|
"fmt"
|
|
"gac-hkrpg-go/internal/common"
|
|
goproto "gac-hkrpg-go/protocol/proto"
|
|
)
|
|
|
|
func (s *Session) techniqueLineup() *goproto.LineupInfo {
|
|
var lineup *goproto.LineupInfo
|
|
if run := s.ChallengeRun; run != nil {
|
|
lineup = run.activeLineup(0)
|
|
} else if p := s.JSON(); p != nil {
|
|
lineup = presetLineup(p, p.ActiveTeamPreset)
|
|
} else {
|
|
return nil
|
|
}
|
|
rules, err := common.LoadTechniqueRules()
|
|
if err != nil {
|
|
return lineup
|
|
}
|
|
if s.techniqueMP == nil {
|
|
v := rules.InitialMP
|
|
if s.ChallengeRun != nil {
|
|
v = lineup.MaxMp
|
|
}
|
|
s.techniqueMP = &v
|
|
SceneDebugLog("[TECHNIQUE] initialized challenge=%t mp=%d/%d", s.ChallengeRun != nil, v, lineup.MaxMp)
|
|
}
|
|
if *s.techniqueMP > lineup.MaxMp {
|
|
*s.techniqueMP = lineup.MaxMp
|
|
}
|
|
lineup.Mp = *s.techniqueMP
|
|
if int(s.leaderSlot) < len(lineup.AvatarList) {
|
|
lineup.LeaderSlot = s.leaderSlot
|
|
}
|
|
return lineup
|
|
}
|
|
|
|
// PearlSR services/battle.zig uses this protocol entity namespace for techniques.
|
|
const techniqueAvatarEntityOffset uint32 = 100000
|
|
|
|
func (s *Session) techniqueAvatar(entity uint32) uint32 {
|
|
lineup := s.techniqueLineup()
|
|
if lineup == nil {
|
|
return 0
|
|
}
|
|
if entity == 0 {
|
|
entity = lineup.LeaderSlot + 1
|
|
}
|
|
for _, a := range lineup.AvatarList {
|
|
if a.Slot+1 == entity || (entity >= techniqueAvatarEntityOffset && a.Id == entity-techniqueAvatarEntityOffset) {
|
|
return a.Id
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
func (s *Session) resetTechniqueRoom() {
|
|
s.techniqueMP = nil
|
|
s.leaderSlot = 0
|
|
s.paidTechniqueCasts = nil
|
|
}
|
|
func (s *Session) syncTechnique(reason goproto.SyncLineupReason) error {
|
|
return s.Send(&goproto.SyncLineupNotify{Lineup: s.techniqueLineup(), ReasonList: []goproto.SyncLineupReason{reason}})
|
|
}
|
|
func techniqueMaterials(p *common.FreesrData) ([]*goproto.Material, error) {
|
|
if err := p.EnsureTechniqueFoods(); err != nil {
|
|
return nil, err
|
|
}
|
|
var out []*goproto.Material
|
|
for _, id := range sortedU32Keys2(p.TechniqueFoodStock) {
|
|
out = append(out, &goproto.Material{Tid: id, Num: p.TechniqueFoodStock[id]})
|
|
}
|
|
return out, nil
|
|
}
|
|
func (s *Session) consumeTechniqueFood(id, count uint32) error {
|
|
p := s.JSON()
|
|
if p == nil {
|
|
return fmt.Errorf("no player")
|
|
}
|
|
if _, active := CURRENT_BATTLE.Load(s.Token); active {
|
|
return fmt.Errorf("cannot eat during battle")
|
|
}
|
|
rules, err := common.LoadTechniqueRules()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
food, ok := rules.Foods[id]
|
|
if !ok || count == 0 || count > food.MaxUse {
|
|
return fmt.Errorf("unsupported food or count")
|
|
}
|
|
if err := p.EnsureTechniqueFoods(); err != nil {
|
|
return err
|
|
}
|
|
if p.TechniqueFoodStock[id] < count {
|
|
return fmt.Errorf("food %d is exhausted", id)
|
|
}
|
|
lineup := s.techniqueLineup()
|
|
if lineup == nil || lineup.Mp >= lineup.MaxMp {
|
|
return fmt.Errorf("technique points are full")
|
|
}
|
|
oldStock, oldMP := p.TechniqueFoodStock[id], *s.techniqueMP
|
|
p.TechniqueFoodStock[id] -= count
|
|
gain := uint64(food.RecoverMP)*uint64(count) + uint64(oldMP)
|
|
if gain > uint64(lineup.MaxMp) {
|
|
gain = uint64(lineup.MaxMp)
|
|
}
|
|
*s.techniqueMP = uint32(gain)
|
|
if err := p.SavePersistentChecked(); err != nil {
|
|
p.TechniqueFoodStock[id] = oldStock
|
|
*s.techniqueMP = oldMP
|
|
return err
|
|
}
|
|
if err := s.Send(&goproto.PlayerSyncScNotify{MaterialList: []*goproto.Material{{Tid: id, Num: p.TechniqueFoodStock[id]}}}); err != nil {
|
|
return err
|
|
}
|
|
SceneDebugLog("[TECHNIQUE] food=%d count=%d mp=%d/%d remaining=%d", id, count, *s.techniqueMP, lineup.MaxMp, p.TechniqueFoodStock[id])
|
|
return s.syncTechnique(goproto.SyncLineupReason_SyncLineupReason_SyncReasonMpAdd)
|
|
}
|
|
func (s *Session) payTechnique(castID, skillIndex uint32, actor ...uint32) (uint32, error) {
|
|
if s.paidTechniqueCasts[castID] {
|
|
return 0, nil
|
|
}
|
|
rules, err := common.LoadTechniqueRules()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
entity := uint32(0)
|
|
if len(actor) > 0 {
|
|
entity = actor[0]
|
|
}
|
|
avatar := s.techniqueAvatar(entity)
|
|
skills := rules.Avatars[avatar]
|
|
if int(skillIndex) >= len(skills) {
|
|
return uint32(goproto.Retcode_RET_MAZE_NO_ABILITY), nil
|
|
}
|
|
cost := skills[skillIndex].MPCost
|
|
lineup := s.techniqueLineup()
|
|
if lineup == nil {
|
|
return uint32(goproto.Retcode_RET_LINEUP_IS_EMPTY), nil
|
|
}
|
|
p := s.JSON()
|
|
if err := p.EnsureTechniqueFoods(); err != nil {
|
|
return 0, err
|
|
}
|
|
if cost > lineup.MaxMp {
|
|
return uint32(goproto.Retcode_RET_MAZE_MP_NOT_ENOUGH), nil
|
|
}
|
|
for *s.techniqueMP < cost && p.AutoTechniqueFood != nil && *p.AutoTechniqueFood {
|
|
var selected uint32
|
|
for _, id := range sortedU32Keys2(rules.Foods) {
|
|
if p.TechniqueFoodStock[id] > 0 {
|
|
selected = id
|
|
break
|
|
}
|
|
}
|
|
if selected == 0 {
|
|
break
|
|
}
|
|
if err := s.consumeTechniqueFood(selected, 1); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
if *s.techniqueMP < cost {
|
|
return uint32(goproto.Retcode_RET_MAZE_MP_NOT_ENOUGH), nil
|
|
}
|
|
*s.techniqueMP -= cost
|
|
if s.paidTechniqueCasts == nil {
|
|
s.paidTechniqueCasts = map[uint32]bool{}
|
|
}
|
|
s.paidTechniqueCasts[castID] = true
|
|
SceneDebugLog("[TECHNIQUE] cast=%d avatar=%d skill=%d cost=%d mp=%d/%d", castID, avatar, skillIndex, cost, *s.techniqueMP, lineup.MaxMp)
|
|
return 0, nil
|
|
}
|
|
func init() {
|
|
handle("UseItem", func(s *Session, req *goproto.UseItemCsReq, rsp *goproto.UseItemScRsp) error {
|
|
rsp.UseItemId = req.UseItemId
|
|
rsp.UseItemCount = req.UseItemCount
|
|
if err := s.consumeTechniqueFood(req.UseItemId, req.UseItemCount); err != nil {
|
|
SceneDebugLog("[TECHNIQUE] use item rejected: %v", err)
|
|
rsp.Retcode = uint32(goproto.Retcode_RET_ITEM_USE_TARGET_TYPE_INVALID)
|
|
}
|
|
return nil
|
|
})
|
|
}
|