Implement the Kill Collector mechanic - #178
Open
Chris443C wants to merge 10 commits into
Open
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Kill Collectors were cosmetic only: a
CreatureTitle, aCreatureStateand someFigLeafData, 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
kill_collector_definitionskill_collector_targetscharacters_kill_collectorMigrations 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.AddKillis unchanged and remains a separate concern.KillCollectorServicebuilds 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 againstcreature_protosscoped to each collector's own zones:Deliberate decisions
docs/kill-collector.md.CompletionTokEntryis 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, sodocs/kill-collector.mdcarries 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.SendMeToholds an inline copy ofGetQuestStatusFor's if/else chain and never calls the method, so patching onlyQuestsInterfacecompiles, 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 fromgameobject_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