Skip to content

Commit 8b51727

Browse files
committed
test: cover registered profile drop reward
1 parent 8ac9afa commit 8b51727

2 files changed

Lines changed: 89 additions & 0 deletions

File tree

internal/minimal/shared_world_test.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16001,6 +16001,94 @@ func TestNewGameSessionFactoryAppliesRegisteredProfileDefaultPracticeMobDeathRew
1600116001
}
1600216002
}
1600316003

16004+
func TestNewGameSessionFactoryAppliesRegisteredProfileDefaultPracticeMobDropReward(t *testing.T) {
16005+
const profile = "rewarded_drop_profile_runtime"
16006+
if !worldruntime.RegisterStaticActorCombatProfile(profile, worldruntime.StaticActorCombatProfileDefaults{
16007+
MaxHP: 4,
16008+
DamagePerNormalAttack: 2,
16009+
RespawnDelay: worldruntime.PracticeMobBootstrapRespawnDelay,
16010+
DeathReward: worldruntime.StaticActorDeathReward{DropVnums: []uint32{27003}},
16011+
}) {
16012+
t.Fatalf("expected registered drop reward profile %q to be accepted", profile)
16013+
}
16014+
t.Cleanup(func() { worldruntime.UnregisterStaticActorCombatProfileForTest(profile) })
16015+
16016+
store := loginticket.NewFileStore(t.TempDir())
16017+
actor := worldruntime.StaticEntity{
16018+
Entity: worldruntime.Entity{ID: 0x0105022E, Kind: worldruntime.EntityKindStaticActor, VID: 0x0105022E, Name: "RegisteredDropRewardProfileMob"},
16019+
Position: worldruntime.NewPosition(bootstrapMapIndex, 1200, 2200),
16020+
RaceNum: 20350,
16021+
CombatProfile: profile,
16022+
CombatKind: profile,
16023+
SpawnGroupRef: "practice.registered_drop_reward_profile_mob",
16024+
}
16025+
killer := peerVisibilityCharacter("DropRewardKiller", 0x0103012E, 0x0204012E, 1100, 2100, 0, 101, 201)
16026+
issuePeerTicket(t, store, "registered-drop-reward-killer", 0x2e2e2e2e, killer)
16027+
16028+
accounts := accountstore.NewFileStore(t.TempDir())
16029+
if err := accounts.Save(accountstore.Account{Login: "registered-drop-reward-killer", Empire: killer.Empire, Characters: []loginticket.Character{killer}}); err != nil {
16030+
t.Fatalf("seed registered drop reward killer account: %v", err)
16031+
}
16032+
currentTime := time.Unix(1_700_000_729, 0)
16033+
runtime, err := newGameRuntimeWithAccountStore(config.Service{LegacyAddr: ":13000", PublicAddr: "127.0.0.1"}, store, accounts)
16034+
if err != nil {
16035+
t.Fatalf("new game runtime: %v", err)
16036+
}
16037+
runtime.now = func() time.Time { return currentTime }
16038+
if _, ok := runtime.sharedWorld.registerStaticActor(actor.Entity.ID, actor.Entity.Name, actor.Position.MapIndex, actor.Position.X, actor.Position.Y, actor.RaceNum, "", "", actor.CombatKind, actor.SpawnGroupRef, worldruntime.StaticActorDeathReward{}); !ok {
16039+
t.Fatal("expected registered-profile drop reward mob registration to succeed")
16040+
}
16041+
16042+
flow, _ := enterGameWithLoginTicket(t, runtime.SessionFactory(), "registered-drop-reward-killer", 0x2e2e2e2e)
16043+
defer closeSessionFlow(t, flow)
16044+
targetVID := uint32(actor.Entity.ID)
16045+
if selectOut, err := flow.HandleClientFrame(decodeSingleFrame(t, combatproto.EncodeClientTarget(combatproto.ClientTargetPacket{TargetVID: targetVID}))); err != nil || len(selectOut) != 1 {
16046+
t.Fatalf("expected target selection before registered-profile drop reward kill to succeed with one frame, got frames=%d err=%v", len(selectOut), err)
16047+
}
16048+
16049+
var killOut [][]byte
16050+
for hit := 1; hit <= 2; hit++ {
16051+
if hit > 1 {
16052+
currentTime = currentTime.Add(bootstrapNormalAttackCadenceWindow)
16053+
}
16054+
killOut, err = flow.HandleClientFrame(decodeSingleFrame(t, combatproto.EncodeClientAttack(combatproto.ClientAttackPacket{AttackType: combatproto.ClientAttackTypeNormal, TargetVID: targetVID})))
16055+
if err != nil {
16056+
t.Fatalf("unexpected attack error on registered-profile drop reward hit %d: %v", hit, err)
16057+
}
16058+
}
16059+
if len(killOut) != 4 {
16060+
t.Fatalf("expected killing hit to return dead, clear target, ground-add, and ownership frames, got %d", len(killOut))
16061+
}
16062+
if _, err := worldproto.DecodeDead(decodeSingleFrame(t, killOut[0])); err != nil {
16063+
t.Fatalf("decode registered-profile drop reward killing hit dead frame: %v", err)
16064+
}
16065+
clearTarget, err := combatproto.DecodeServerTarget(decodeSingleFrame(t, killOut[1]))
16066+
if err != nil {
16067+
t.Fatalf("decode registered-profile drop reward killing hit clear target frame: %v", err)
16068+
}
16069+
if clearTarget.TargetVID != 0 || clearTarget.HPPercent != 0 {
16070+
t.Fatalf("expected registered-profile drop reward killing hit to clear target, got %+v", clearTarget)
16071+
}
16072+
ground, err := itemproto.DecodeGroundAdd(decodeSingleFrame(t, killOut[2]))
16073+
if err != nil {
16074+
t.Fatalf("decode registered-profile drop reward ground add: %v", err)
16075+
}
16076+
expectedVID := bootstrapRewardGroundItemVID(killer, 27003, 0)
16077+
if ground.VID != expectedVID || ground.Vnum != 27003 || ground.X != killer.X || ground.Y != killer.Y {
16078+
t.Fatalf("unexpected registered-profile drop reward ground add: %+v want vid=%d", ground, expectedVID)
16079+
}
16080+
ownership, err := itemproto.DecodeOwnership(decodeSingleFrame(t, killOut[3]))
16081+
if err != nil {
16082+
t.Fatalf("decode registered-profile drop reward ownership: %v", err)
16083+
}
16084+
if ownership != (itemproto.OwnershipPacket{VID: expectedVID, OwnerName: killer.Name}) {
16085+
t.Fatalf("unexpected registered-profile drop reward ownership: %+v", ownership)
16086+
}
16087+
if !runtime.sharedWorld.GroundItemExists(expectedVID) {
16088+
t.Fatalf("expected registered-profile drop reward ground item %d to be registered", expectedVID)
16089+
}
16090+
}
16091+
1600416092
func TestNewGameSessionFactoryPrefersExplicitDeathRewardOverRegisteredProfileDefault(t *testing.T) {
1600516093
const profile = "explicit_reward_profile_runtime"
1600616094
if !worldruntime.RegisterStaticActorCombatProfile(profile, worldruntime.StaticActorCombatProfileDefaults{

spec/protocol/non-player-reward-bootstrap.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Both profile-level default helpers are covered directly so a later combat-profil
4444

4545
Registered bootstrap combat profiles may carry a validated death-reward default for authored spawn-group use, and lookup returns cloned descriptor slices so callers cannot mutate the registry by editing returned defaults.
4646
Runtime coverage freezes that a spawn-backed actor using such a registered profile can apply the profile's default EXP/gold/drop descriptor on the accepted killing hit.
47+
That includes a drop-only profile default case where the killing hit emits the normal death/clear frames followed by one `ITEM_GROUND_ADD` / `ITEM_OWNERSHIP` pair and registers the deterministic reward ground item.
4748
That runtime coverage now also includes formula-only registered profiles whose `damage_per_normal_attack` is canonicalized from `attack_value - defense_value`, so the profile can both drive deterministic HP mutation and apply its reward descriptor on the same death edge.
4849
If the authored spawn-group snapshot also carries an explicit reward descriptor, that authored descriptor wins over the profile-level default for the current actor life; profile defaults are only the fallback for spawn-backed actors whose live/authored snapshot has no explicit reward descriptor.
4950
That profile-level default still does **not** make standalone runtime static actors reward-bearing: without a non-empty `spawn_group_ref` or explicit authored live snapshot descriptor, the shared-world death attempt returns a rewardless descriptor.

0 commit comments

Comments
 (0)