fix(server): keep chest claims independent of quest difficulty
This commit is contained in:
@@ -8,20 +8,20 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func fieldRewardKey(pack, difficulty, id int) string {
|
||||
return fmt.Sprintf("%d:%d:%d", pack, difficulty, id)
|
||||
func fieldRewardKey(pack, id int) string {
|
||||
return fmt.Sprintf("%d:%d", pack, id)
|
||||
}
|
||||
|
||||
// FieldRewardOpened reads persisted entries on every call; request rollback
|
||||
// therefore does not leave an opened-object cache behind.
|
||||
func (s *Store) FieldRewardOpened(pack, difficulty, id int, period string) (bool, error) {
|
||||
func (s *Store) FieldRewardOpened(pack, id int, period string) (bool, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
store, err := s.fieldRewardEntries()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
raw, found, err := store.LoadEntry("progress", "field_rewards", fieldRewardKey(pack, difficulty, id))
|
||||
raw, found, err := store.LoadEntry("progress", "field_rewards", fieldRewardKey(pack, id))
|
||||
return found && string(raw) == period, err
|
||||
}
|
||||
func (s *Store) fieldRewardEntries() (stateio.EntryStore, error) {
|
||||
@@ -34,19 +34,19 @@ func (s *Store) fieldRewardEntries() (stateio.EntryStore, error) {
|
||||
}
|
||||
return store, nil
|
||||
}
|
||||
func (s *Store) MarkFieldRewardOpened(pack, difficulty, id int, period string) error {
|
||||
func (s *Store) MarkFieldRewardOpened(pack, id int, period string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if pack <= 0 || difficulty < 0 || difficulty > 4 || id <= 0 {
|
||||
if pack <= 0 || id <= 0 {
|
||||
return fmt.Errorf("progress: invalid field reward identity")
|
||||
}
|
||||
store, err := s.fieldRewardEntries()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return store.PutEntry("progress", "field_rewards", fieldRewardKey(pack, difficulty, id), []byte(period))
|
||||
return store.PutEntry("progress", "field_rewards", fieldRewardKey(pack, id), []byte(period))
|
||||
}
|
||||
func (s *Store) OpenedFieldRewards(pack, difficulty int) ([]int, error) {
|
||||
func (s *Store) OpenedFieldRewards(pack int) ([]int, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
store, err := s.fieldRewardEntries()
|
||||
@@ -57,7 +57,7 @@ func (s *Store) OpenedFieldRewards(pack, difficulty int) ([]int, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prefix := fmt.Sprintf("%d:%d:", pack, difficulty)
|
||||
prefix := fmt.Sprintf("%d:", pack)
|
||||
var ids []int
|
||||
for key, raw := range entries {
|
||||
if len(raw) == 0 {
|
||||
|
||||
@@ -44,7 +44,7 @@ func (s *Service) fieldObjectDesign(pack int) (gamedata.FieldObjectDesign, error
|
||||
return design, nil
|
||||
}
|
||||
func (s *Service) openedFieldObjects(pack int) ([]int, error) {
|
||||
ids, err := s.state.OpenedFieldRewards(pack, s.questDifficulty(pack))
|
||||
ids, err := s.state.OpenedFieldRewards(pack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -65,7 +65,7 @@ func (s *Service) openedFieldObjects(pack int) ([]int, error) {
|
||||
if e != nil {
|
||||
continue
|
||||
}
|
||||
opened, e := s.state.FieldRewardOpened(pack, s.questDifficulty(pack), id, period)
|
||||
opened, e := s.state.FieldRewardOpened(pack, id, period)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
@@ -134,12 +134,12 @@ func (s *Service) openFieldObject(pack, group, id int) ([]byte, error) {
|
||||
if obj.Type < 1 || obj.Type > 3 || obj.BuffID != 0 || obj.MonsterID != 0 || obj.QuestID != 0 || len(obj.Rewards) == 0 {
|
||||
return nil, fmt.Errorf("%w: unsupported field object reward graph/reset", ErrInvalidRequest)
|
||||
}
|
||||
difficulty := s.questDifficulty(pack)
|
||||
|
||||
period, err := s.fieldObjectPeriod(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opened, err := s.state.FieldRewardOpened(pack, difficulty, id, period)
|
||||
opened, err := s.state.FieldRewardOpened(pack, id, period)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -171,7 +171,7 @@ func (s *Service) openFieldObject(pack, group, id int) ([]byte, error) {
|
||||
return nil, fmt.Errorf("%w: unsupported field reward type %d", ErrInvalidRequest, r.Type)
|
||||
}
|
||||
}
|
||||
identity := fmt.Sprintf("field-reward:%d:%d:%d:%s", pack, difficulty, id, period)
|
||||
identity := fmt.Sprintf("field-reward:%d:%d:%s", pack, id, period)
|
||||
if _, err = s.wallet.GrantQuestOnce(identity, rewards); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -179,7 +179,7 @@ func (s *Service) openFieldObject(pack, group, id int) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = s.state.MarkFieldRewardOpened(pack, difficulty, id, period); err != nil {
|
||||
if err = s.state.MarkFieldRewardOpened(pack, id, period); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var bundle []byte
|
||||
|
||||
@@ -79,7 +79,7 @@ func TestFieldChestAtomicAwardRollback(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
opened, err := s.state.FieldRewardOpened(21, 0, 1001, "once")
|
||||
opened, err := s.state.FieldRewardOpened(21, 1001, "once")
|
||||
if err != nil || opened || s.wallet.Snapshot().FreeJewelry != 100 || len(s.inventory.All()) != 0 {
|
||||
t.Fatalf("partial state after rollback opened=%v wallet=%+v err=%v", opened, s.wallet.Snapshot(), err)
|
||||
}
|
||||
@@ -129,7 +129,7 @@ func TestFieldChestAtomicAwardRollback(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
opened, err = s.state.FieldRewardOpened(21, 0, 1002, "once")
|
||||
opened, err = s.state.FieldRewardOpened(21, 1002, "once")
|
||||
if err != nil || opened || len(s.inventory.All()) != 0 {
|
||||
t.Fatalf("inventory partially committed after rollback opened=%v items=%v err=%v", opened, s.inventory.All(), err)
|
||||
}
|
||||
@@ -170,6 +170,11 @@ func TestFieldChestDeltaAndPersistentRepeat(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Quest selection difficulty is independent of the pack's opened chests.
|
||||
// Switching it must neither hide the chest nor create a second grant.
|
||||
if err = s.state.SelectQuest(21, progress.QuestSelection{QuestID: 1, Difficulty: 2}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req = wire.AppendVarint(req, 1, 999)
|
||||
_, res, _, err = s.handleFieldObjectReward(req)
|
||||
if err != nil {
|
||||
|
||||
@@ -16,7 +16,7 @@ func TestCollectedFieldObjectsAppearInDetailAndSummary(t *testing.T) {
|
||||
1002: {ID: 1002, GroupID: 102, Type: 1, ResetType: 1},
|
||||
}}})
|
||||
for _, id := range []int{1001, 1002} {
|
||||
if err := s.state.MarkFieldRewardOpened(21, 0, id, "once"); err != nil {
|
||||
if err := s.state.MarkFieldRewardOpened(21, id, "once"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user