Skip to content

Implement the Kill Collector mechanic - #178

Open
Chris443C wants to merge 10 commits into
Shmerrick:masterfrom
Chris443C:feature/kill-collector
Open

Implement the Kill Collector mechanic#178
Chris443C wants to merge 10 commits into
Shmerrick:masterfrom
Chris443C:feature/kill-collector

Conversation

@Chris443C

Copy link
Copy Markdown

Kill Collectors were cosmetic only: a CreatureTitle, a CreatureState and some FigLeafData, with no kill counting, no threshold, no XP grant and no persistence. This implements the mechanic end to end.

Behaviour

Kills accrue whether or not the player has ever met the collector, which is what makes the reward retroactive. Talking to a collector pays out min(accumulated, cap) - claimed, opens an NPC dialogue window, and shows the orange turn-in marker while anything is owed.

The claim is advanced before XP is granted, so a partial failure cannot pay the same kills twice, and accrual stops writing to the database once a collector is exhausted.

Schema

Table Database
kill_collector_definitions World 132 collectors: kill cap, XP per kill, optional completion ToK unlock
kill_collector_targets World 312 creature entries that credit a collector
characters_kill_collector Characters per-character accumulated / claimed / rewarded

Migrations are split by target database and each states which in its header.

Targets are creature entries, not Bestiary subtypes

22 of the 132 collectors want creatures spanning more than one CreatureSubType, and even within a subtype only some variants qualify, so the Bestiary counter cannot express them. TokInterface.AddKill is unchanged and remains a separate concern.

KillCollectorService builds a creature -> collectors reverse index at load, so the kill path costs one dictionary lookup and never scans the 132 definitions. Non-target creatures return a shared empty list and allocate nothing.

Where the data came from

Collector, quest, target label, kill cap and XP come from Return of Reckoning's public GraphQL API, which resolves all 132 NPCs with Title = 32. It returns a human-readable target label and no creature ids, so entries were resolved against creature_protos scoped to each collector's own zones:

  • 106 resolved by zone-scoped exact name match
  • 22 hand-mapped where the label names a camp or faction rather than a mob
  • 4 have no matching creature in this world data and are seeded inert, with no targets, listed at the end of the migration

Deliberate decisions

  • Caps are data. Seeded values are current RoR's 20-60 by chapter; retail is documented as 60 for every collector. Changing that is a row edit.
  • Not a quest. RoR implements these as repeatable quests. Retail did not, and the difference is behavioural: quest objectives only count kills made after acceptance, which loses the retroactive credit the client's own tip text describes. Reasoning is recorded in docs/kill-collector.md.
  • No influence message. Retail printed a spurious "you have received an influence reward" line on maxing a collector. No influence was granted; it was cosmetic. Not reproduced.
  • RoR's modern rewards are not imported. Current RoR gives dyes and consumables on a standardised schedule; that is RoR balancing, not retail behaviour. CompletionTokEntry is seeded 0.

Not included

Map pips (MAPPIPS_KILL_COLLECTOR_*) remain unreferenced.

Testing

Built clean against master (zero errors or warnings) and verified in game: retroactive credit, partial claiming, cap arithmetic, the turn-in icon on spawn, and the dialogue window. The repository has no test project, so docs/kill-collector.md carries a manual checklist instead.

One implementation note worth knowing for anyone touching quest icons: the head icon has to be set in two places, in two different enums. Creature.SendMeTo holds an inline copy of GetQuestStatusFor's if/else chain and never calls the method, so patching only QuestsInterface compiles, reads correctly, and does nothing.

Also included

  • docs/gm-teleport-reference.md - teleports to every collector and its target mobs, plus every battlefield objective. Written for testing this, useful beyond it.
  • docs/known-issues/open-items.md - unrelated problems found along the way, kept rather than discarded. The notable one: 294 of 1352 gameobject quest objectives reference entries absent from gameobject_protos, making 223 quests uncompletable - the same gap that aborts PQ spawn loops.

Happy to drop either doc if they do not belong in a feature PR.

🤖 Generated with Claude Code

Chris443C and others added 10 commits August 6, 2026 17:02
Kill Collectors were cosmetic only: a CreatureTitle, a CreatureState and some
FigLeafData, with no kill counting, no threshold, no XP grant and no persistence.

Adds definitions, per-collector target creatures and per-character progress, all
data-driven:

  kill_collector_definitions    132 collectors: kill cap, XP per kill, optional
                                completion ToK unlock
  kill_collector_targets        312 creature entries that credit a collector
  characters_kill_collector     per-character accumulated / claimed / rewarded

Kills accrue whether or not the player has met the collector, which is what makes
the reward retroactive; talking to the collector pays out
min(accumulated, cap) - claimed. The claim is advanced before XP is granted so a
partial failure cannot pay the same kills twice, and accrual stops writing to the
database once a collector is exhausted.

Targets are exact creature entries rather than a Bestiary subtype. 22 collectors
want creatures spanning more than one CreatureSubType, and even within a subtype
only some variants qualify, so the Bestiary counter cannot express them.
TokInterface.AddKill is unchanged and remains a separate concern.

KillCollectorService builds a creature -> collectors reverse index at load, so the
kill path costs one dictionary lookup and never scans the 132 definitions
(AGENTS.md rule 2). Non-target creatures return a shared empty list and allocate
nothing.

Data source: Return of Reckoning's public GraphQL API, which serves its live
collector quests and resolves all 132 ProjectWAR collector NPCs. It returns a
human-readable target label and no creature ids, so entries were resolved against
creature_protos scoped to each collector's own zones - 106 by exact name match,
22 hand-mapped where the label names a camp or faction. Four collectors have no
matching creature in ProjectWAR's world data and are seeded inert, with no
targets, and listed at the end of the migration.

Retail's spurious "influence reward" message on maxing a collector is deliberately
not reproduced; no influence was ever granted by it. RoR's modern dye and
consumable rewards are not imported. See docs/kill-collector.md for the full set
of decisions and the manual verification checklist.

Map pips (MAPPIPS_KILL_COLLECTOR_*) are still not wired: pending/complete is
per-player, but CreatureService builds States/FigLeafData once per creature
prototype and shares it across all players, so this needs a per-player state
dispatch rather than a proto edit.

Also adds docs/known-issues/open-items.md, a running list of unrelated problems
found along the way, and docs/local-dev-setup.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tives

Two lists for testing: every Kill Collector with a teleport to the NPC and a
second teleport to where its target creatures actually spawn, and every
Battlefield Objective with its id and coordinates.

The target-mob coordinate is the point of the collector list: progress is
retroactive, so testing means killing at the mobs first and talking to the
collector afterwards.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
G1: BO capture renown printing to the Combat tab is almost certainly by design,
not a bug. The server sends F_PLAYER_RENOWN with RewardType.ObjectiveCapture and
never picks a chat tab; the client composes and routes the line, and its
CHATLOGFILTERS_RENOWN (1008) sits in the same block as COMBAT_DEFAULT (1000), so
all renown gain goes to Combat by design. Notes both ways it could be changed.

G2: possible duplicate capture rewards, unverified - the same objective printed
its award twice in one capture. Needs a controlled capture with RewardLogger
before calling it a bug.

G3: 223 quests are uncompletable. 294 of 1352 gameobject quest objectives (22%)
reference entries absent from gameobject_protos, so the objective can never be
credited. Found via the client's "Invalid GameObject - QuestID 30001" tracker
message and confirmed against the database.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Collectors paid out correctly but looked identical whether or not anything was
owed, so there was no way to tell in game which ones to visit.

Kill Collectors are not quests, but they want the same per-player head icon, so
they reuse the quest state channel: GetQuestStatusFor reports QuestCompleted for a
collector with unclaimed progress, checked ahead of the ordinary quest states so it
wins on an NPC that has both. HasQuestsFor gets the same check, otherwise
UpdateQuestGiverAround skips collectors and the icon never refreshes.

Refresh points are deliberately narrow. On a kill it only fires on the 0 -> 1
unclaimed transition, so the surrounding-object scan runs once per collector rather
than once per kill. On claim the state is pushed directly for that creature,
because with nothing left unclaimed the collector no longer passes HasQuestsFor and
UpdateQuestGiverAround would not reach it.

Corrects K1 in the open-items list, which claimed this was blocked by
CreatureService building States/FigLeafData per prototype. A per-player channel
existed all along. Interaction dialogue and the map pips are still missing, and K1
now records what each would actually take.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous commit only touched QuestsInterface, which drives the live
UpdateQuestState packet. It never fired, because the head icon a player sees when
a creature spawns for them comes from a separate inline if/else chain in
Creature.SendMeTo that duplicates GetQuestStatusFor and does not call it.

That chain also uses a different enum: the spawn packet appends a CreatureState
(QuestFinishable), while the live update sends a QuestStateOpcode
(QuestCompleted). Both paths therefore need the Kill Collector check, and having
two copies of the same decision in two enums is why the first fix looked correct
and did nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Records that the icon must be set in both Creature.SendMeTo (CreatureState) and
QuestsInterface (QuestStateOpcode), because SendMeTo holds an inline copy of the
quest-state decision and never calls the shared method - patching one alone
compiles, reads correctly and does nothing.

Also notes the asymmetric refresh (sweep on appear, direct push on clear) and adds
icon cases to the manual checklist, including the false negative that cost time:
with AccumulatedKills == ClaimedKills there is nothing unclaimed and no icon is
the correct result.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Interaction printed a chat line and nothing else. It now opens the standard NPC
dialogue, showing the collector's flavour text plus the outcome - reward granted,
nothing owed, or already maxed.

This does not need a Quest row. The generic interact packet already supports a
plain-text entry: bit 32 of the menu-items mask followed by the string, the same
shape the dye merchant uses to refuse service. An earlier note in open-items
claimed the window required SendQuestDoneInfo and therefore real Quest and
Character_quest rows; that was wrong and is corrected.

Flavour comes from the NPC's own creature_texts row where one exists, with a
generated hint as fallback. Only 21 of 132 collectors have a text row, and those
rows are truncated mid-sentence in the source data, so most show the fallback.

Also records why this is not implemented as a quest at all. RoR uses repeatable
quests; retail did not, and the difference is behavioural rather than cosmetic -
quest objectives only count kills made after acceptance, which would lose the
retroactive credit the client's own tip text describes and the handover specified.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
It documents a local development account and how to reset its password. That is
useful in a private working copy and has no place in a public repository.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Only 21 collectors had a creature_texts row, and those rows are truncated
mid-sentence in the published data, so most showed a generated fallback hint.

RoR's API cannot supply the text. quest.description is the literal placeholder
"Initial text for kill collectors is set in creature_texts", journalEntry is
empty, tomeOfKnowledgeEntry.description is only "You have met <name>", and there
is no creature-text query.

The client has it. Each collector's Tome of Knowledge "Noteworthy Person" entry is
a record in the section-2 string table inside data.myp (8764b86c/d321fe71),
indexed by tok_infos.Index - the mapping already used to match all 132 collectors.
Book markup is stripped and the third-person attribution line removed, leaving the
NPC's own words, which are thematically the collector's ask rather than generic
biography: Grimilda Mughammer talks about the smell of dead squigs, Barin
Grimbeard about spites reeking of elf magic.

111 rows added, the 21 existing rows left untouched, inserts guarded so re-running
is safe. Lands in creature_texts so CreatureService.GetCreatureText picks it up
with no code change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Kill Collectors are staying as they were in retail rather than moving to RoR's
repeatable-quest implementation. That decision has one data consequence: the caps
seeded from RoR's live API are RoR balancing values, 20-60 scaled by chapter, and
only 12 of 132 matched retail.

RoR's own patch notes of 11 March 2024 state all live retail collectors required
60, and their 14 July 2025 notes record moving Tier 4 away from "all 60", which
only makes sense if 60 was the original. All 132 are now 60.

Conflicting evidence is recorded rather than hidden: the current RoR wiki says 50,
and some retail-era guides give 50 for specific collectors. KillCap is
per-collector data precisely so that stays a row edit.

XP per kill is deliberately left at RoR's values and flagged as the least
retail-faithful part of the data. The retail formula was never recovered - period
reports give roughly 70 XP per kill in one early Dwarf area and a ceiling near 15k
in another, which is not enough to rebuild one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant