Restore event and mini hubs, current shop and hunt calendars, and regular seasons; preserve all published windows. Implement story/quiz routes, native shop purchases, avatar sets, currencies, rank-change items and permanent buffs with rollback and reward coverage tests. Refresh client buff stats and update Cash Shop to 0.1.1.
114 lines
3.8 KiB
Go
114 lines
3.8 KiB
Go
package calendar
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"bd2server/internal/server/events"
|
|
"bd2server/internal/server/gamedata"
|
|
"bd2server/internal/server/wire"
|
|
)
|
|
|
|
// validateMiniHubBindings rejects invalid published routes at startup instead
|
|
// of letting a client open an incompatible prefab or use another activity UID.
|
|
func (s *Set) validateMiniHubBindings(design *gamedata.EventPlayCatalog) error {
|
|
uid := make(map[uint64]events.Schedule)
|
|
for _, v := range s.Events {
|
|
if v.UID != 0 {
|
|
uid[v.UID] = v
|
|
}
|
|
}
|
|
value := func(row []byte, field int) uint64 { v, _, _ := wire.Varint(row, field); return v }
|
|
for _, hub := range s.EventHubs {
|
|
table, err := design.Row("PackEventHubTable", 14, hub.HubID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if value(table, 13) != 1 {
|
|
continue
|
|
}
|
|
if hub.UID == 0 {
|
|
return fmt.Errorf("calendar: mini hub %d requires a nonzero UID", hub.HubID)
|
|
}
|
|
start, err := timestamp(hub.Start)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
playEnd, err := timestamp(hub.PlayEnd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
end, err := timestamp(hub.End)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, binding := range hub.Settings {
|
|
var slot []byte
|
|
for _, row := range design.Rows("PackEventListTable", 6, hub.HubID) {
|
|
if value(row, 11) != binding.Slot {
|
|
continue
|
|
}
|
|
if slot != nil {
|
|
return fmt.Errorf("calendar: mini hub %d slot %d is ambiguous in GameData", hub.HubID, binding.Slot)
|
|
}
|
|
slot = row
|
|
}
|
|
if slot == nil {
|
|
return fmt.Errorf("calendar: mini hub %d slot %d missing GameData", hub.HubID, binding.Slot)
|
|
}
|
|
contentType, contentID := value(slot, 9), value(slot, 7)
|
|
if binding.ProgressType != contentType {
|
|
return fmt.Errorf("calendar: mini hub %d slot %d content type %d, want %d", hub.HubID, binding.Slot, binding.ProgressType, contentType)
|
|
}
|
|
eventType, supported := gamedata.MiniHubEventType(contentType)
|
|
// Mini stories and NPC quizzes have their own slot UID namespace,
|
|
// distinct from Define_EventType (13 there means bingo). Their group
|
|
// and availability derive from this static slot and the hub window.
|
|
if contentType == 13 || contentType == 14 {
|
|
if len(binding.EventUIDs) != 1 || binding.EventUIDs[0] == 0 {
|
|
return fmt.Errorf("calendar: mini hub %d slot %d requires one local content UID", hub.HubID, binding.Slot)
|
|
}
|
|
if _, collision := uid[binding.EventUIDs[0]]; collision {
|
|
return fmt.Errorf("calendar: mini hub %d slot %d content UID collides with global event", hub.HubID, binding.Slot)
|
|
}
|
|
if value(slot, 4) != 0 {
|
|
return fmt.Errorf("calendar: mini hub %d story/quiz slot has invalid end type", hub.HubID)
|
|
}
|
|
continue
|
|
}
|
|
if !supported {
|
|
return fmt.Errorf("calendar: mini hub %d slot %d content type %d has no supported scheduled route", hub.HubID, binding.Slot, contentType)
|
|
}
|
|
endType := value(slot, 4)
|
|
if endType > 1 {
|
|
return fmt.Errorf("calendar: mini hub %d slot %d unsupported end type %d", hub.HubID, binding.Slot, endType)
|
|
}
|
|
matches := 0
|
|
for _, id := range binding.EventUIDs {
|
|
child, ok := uid[id]
|
|
if !ok || child.Type != eventType || child.ID != contentID || child.SubID != 0 {
|
|
return fmt.Errorf("calendar: mini hub %d slot %d references incompatible event UID %d", hub.HubID, binding.Slot, id)
|
|
}
|
|
childStart, childEnd := child.Start, child.End
|
|
if childStart < int64(start) {
|
|
childStart = int64(start)
|
|
}
|
|
if childEnd > int64(end) {
|
|
childEnd = int64(end)
|
|
}
|
|
// Project policy: EndDateType=0 closes the slot at PlayEnd;
|
|
// EndDateType=1 allows it through the final hub End window.
|
|
if endType == 0 && childEnd > int64(playEnd) {
|
|
childEnd = int64(playEnd)
|
|
}
|
|
if childStart < childEnd {
|
|
matches++
|
|
}
|
|
}
|
|
if matches != 1 {
|
|
return fmt.Errorf("calendar: mini hub %d slot %d has %d schedules in its window, want one", hub.HubID, binding.Slot, matches)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|