Rebuild SCUM plugin-owned data flow

This commit is contained in:
npc0-hue
2026-08-18 07:01:17 +08:00
parent 302f1f64b7
commit 98bf944f4c
39 changed files with 1832 additions and 223 deletions
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"math"
"sort"
"strings"
"unicode/utf8"
@@ -416,7 +417,7 @@ func rewardGrant(payload map[string]any) (RewardGrant, error) {
playerID, playerOK := payload["playerId"].(string)
rawItems, itemsOK := payload["items"].([]any)
rawOperations, operationsOK := payload["operations"].([]any)
if !grantOK || !playerOK || !itemsOK || !operationsOK || (len(rawItems) == 0 && len(rawOperations) == 0) || len(rawItems) > 8 {
if !grantOK || !playerOK || !itemsOK || !operationsOK || (len(rawItems) == 0 && len(rawOperations) == 0) {
return RewardGrant{}, fmt.Errorf("reward payload is invalid")
}
items := make([]RewardItem, 0, len(rawItems))
@@ -427,7 +428,7 @@ func rewardGrant(payload map[string]any) (RewardGrant, error) {
}
code, codeOK := item["catalogCode"].(string)
quantity, quantityOK := integerPayloadValue(item["quantity"])
if !codeOK || !supportedCatalogCode(code) || !quantityOK || quantity < 1 || quantity > 100 {
if !codeOK || !supportedCatalogCode(code) || !quantityOK || quantity < 1 {
return RewardGrant{}, fmt.Errorf("reward payload is invalid")
}
items = append(items, RewardItem{CatalogCode: code, Quantity: quantity})
@@ -456,7 +457,7 @@ func eventStartRequest(payload map[string]any) (EventStartRequest, error) {
for index, key := range []string{"npc", "item", "zombie", "animal"} {
if value, exists := payload[key]; exists {
count, ok := integerPayloadValue(value)
if !ok || count < 0 || count > 10000 {
if !ok || count < 0 {
return EventStartRequest{}, fmt.Errorf("event start payload is invalid")
}
counts[index] = count
@@ -466,7 +467,7 @@ func eventStartRequest(payload map[string]any) (EventStartRequest, error) {
if value, exists := payload["maxParticipants"]; exists {
var ok bool
participants, ok = integerPayloadValue(value)
if !ok || participants < 1 || participants > 1000 {
if !ok || participants < 1 {
return EventStartRequest{}, fmt.Errorf("event start payload is invalid")
}
}
@@ -478,7 +479,7 @@ func eventStartRequest(payload map[string]any) (EventStartRequest, error) {
return EventStartRequest{}, fmt.Errorf("event start payload is invalid")
}
}
if !eventIDOK || !eventTypeOK || !classOK || !titleOK || !durationOK || !percentOK || !placardOK || !producesOK || !supportedEventType(eventType) || eventClass < 1 || eventClass > 2 || (eventClass == 1) != (eventType == "range") || strings.TrimSpace(eventID) == "" || strings.TrimSpace(title) == "" || len(placard) > 500 || percent < 0 || percent > 100 || duration < 30 || duration > 86400 {
if !eventIDOK || !eventTypeOK || !classOK || !titleOK || !durationOK || !percentOK || !placardOK || !producesOK || !supportedEventType(eventType) || eventClass < 1 || eventClass > 2 || (eventClass == 1) != (eventType == "range") || strings.TrimSpace(eventID) == "" || strings.TrimSpace(title) == "" || len(placard) > 500 || percent < 0 || percent > 100 || duration < 1 {
return EventStartRequest{}, fmt.Errorf("event start payload is invalid")
}
return EventStartRequest{EventID: eventID, EventType: eventType, Class: eventClass, Title: title, Placard: placard, Percent: percent, NPC: counts[0], Item: counts[1], Zombie: counts[2], Animal: counts[3], Produces: produces, DurationSeconds: duration, MaxParticipants: participants, Announce: announce}, nil
@@ -486,7 +487,7 @@ func eventStartRequest(payload map[string]any) (EventStartRequest, error) {
func eventProduceRequests(value any) ([]EventProduceRequest, bool) {
raw, ok := value.([]any)
if !ok || len(raw) > 100 {
if !ok {
return nil, false
}
result := make([]EventProduceRequest, 0, len(raw))
@@ -502,7 +503,7 @@ func eventProduceRequests(value any) ([]EventProduceRequest, bool) {
x, xOK := numberPayloadValue(produce["x"])
y, yOK := numberPayloadValue(produce["y"])
z, zOK := numberPayloadValue(produce["z"])
if !idOK || strings.TrimSpace(tradeGoodsID) == "" || len(tradeGoodsID) > 128 || !percentOK || percent < 0 || percent > 100 || !quantityOK || quantity < 1 || quantity > 10000 || !radiusOK || radius < 0 || radius > 2000000 || !xOK || !yOK || !zOK || x < -2000000 || x > 2000000 || y < -2000000 || y > 2000000 || z < -2000000 || z > 2000000 {
if !idOK || strings.TrimSpace(tradeGoodsID) == "" || len(tradeGoodsID) > 128 || !percentOK || percent < 0 || percent > 100 || !quantityOK || quantity < 1 || !radiusOK || radius < 0 || !xOK || !yOK || !zOK {
return nil, false
}
result = append(result, EventProduceRequest{TradeGoodsID: tradeGoodsID, Percent: percent, Value: quantity, Radius: radius, X: x, Y: y, Z: z})
@@ -529,20 +530,22 @@ func integerPayloadValue(value any) (int, bool) {
}
func numberPayloadValue(value any) (float64, bool) {
var result float64
switch number := value.(type) {
case float64:
return number, true
result = number
case float32:
return float64(number), true
result = float64(number)
case int:
return float64(number), true
result = float64(number)
case int32:
return float64(number), true
result = float64(number)
case int64:
return float64(number), true
result = float64(number)
default:
return 0, false
}
return result, !math.IsNaN(result) && !math.IsInf(result, 0)
}
func supportedCatalogCode(value string) bool {
@@ -104,6 +104,13 @@ func TestRewardDeliveryAcceptsRealSCUMCatalogCodesAndReturnsDeclaredResult(t *te
if len(port.grants) != 1 || port.grants[0].Items[0] != (RewardItem{CatalogCode: "BPC_Improvised_Backpack.01", Quantity: 2}) || len(port.grants[0].Operations) != 1 || port.grants[0].Operations[0] != "#SpawnItem BPC_Improvised_Backpack.01 2" {
t.Fatalf("reward items or operations did not reach the typed reward port: %+v", port.grants)
}
manyItems := make([]any, 9)
for index := range manyItems {
manyItems[index] = map[string]any{"catalogCode": "BPC_Apple", "quantity": float64(101 + index)}
}
if _, err := adapter.DeliverReward(context.Background(), map[string]any{"grantId": "grant-many", "playerId": "76561198000000001", "items": manyItems, "operations": []any{}}); err != nil || len(port.grants) != 2 || len(port.grants[1].Items) != 9 || port.grants[1].Items[0].Quantity != 101 {
t.Fatalf("valid reward count or quantity was rejected: grants=%+v err=%v", port.grants, err)
}
before := len(port.grants)
if _, err := adapter.DeliverReward(context.Background(), map[string]any{
"grantId": "grant-2", "playerId": "76561198000000001",
@@ -114,6 +121,17 @@ func TestRewardDeliveryAcceptsRealSCUMCatalogCodesAndReturnsDeclaredResult(t *te
}
}
func TestEventStartAcceptsPositiveCountsAndDurationWithoutInventedUpperLimits(t *testing.T) {
produces := make([]any, 101)
for index := range produces {
produces[index] = map[string]any{"tradeGoodsId": "cargo-drop", "percent": float64(80), "value": float64(10001 + index), "r": float64(2000001 + index), "x": float64(3000000 + index), "y": float64(-3000000 - index), "z": float64(index)}
}
request, err := eventStartRequest(map[string]any{"eventId": "event-large", "eventType": "range", "class": float64(1), "title": "Large Event", "placard": "", "percent": float64(100), "npc": float64(10001), "item": float64(10002), "zombie": float64(10003), "animal": float64(10004), "produces": produces, "durationSeconds": float64(86401), "maxParticipants": float64(1001)})
if err != nil || request.DurationSeconds != 86401 || request.MaxParticipants != 1001 || request.NPC != 10001 || len(request.Produces) != 101 || request.Produces[0].Value != 10001 || request.Produces[0].Radius != 2000001 {
t.Fatalf("valid event values above old limits were rejected: request=%+v err=%v", request, err)
}
}
func TestRewardDeliverySupportsOperationsWithoutItemsAndRejectsEmptyGrant(t *testing.T) {
port := &rewardPortFixture{receipt: DeliveryReceipt{Outcome: "delivered"}}
adapter := RuntimeAdapter{BoundServerID: "server-1", Rewards: port}