Skip to content
Open
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 178 additions & 7 deletions scripts/Killtracker.lic → scripts/killtracker.lic
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,15 @@
contributors: Nisugi
game: Gemstone
tags: hunting, combat, tracking, gemstones, jewels, dust, klocks, data
version: 2.11
version: 2.12

Change Log:
v2.12 (2026-05-24)
- Added separate codex-eligible search counter (weekly_codex_searches)
- Gem and codex eligibility are gated independently
- Added codex tracking for onslaught creatures (10/week cap)
- Added ;kt codex report command and codex section in summary
- Codex finds submitted to external spreadsheet when enabled
v2.11 (2026-05-24)
- Fix for capitalization in dust found messaging
v2.10 (2025-09-21)
Expand Down Expand Up @@ -122,6 +128,10 @@ module Killtracker
$killtracker[:weekly_counts] ||= {}
$killtracker[:jewel_found_this_week] ||= false
$killtracker[:debug_eligibility] ||= false
$killtracker[:codex_found] ||= {}
$killtracker[:weekly_codex] ||= 0
$killtracker[:weekly_codex_searches] ||= 0
$killtracker[:searches_since_codex] ||= 0
end

def self.create_backup(reason = "manual")
Expand Down Expand Up @@ -330,16 +340,22 @@ module Killtracker
total_this_week = $killtracker[:weekly_ascension_searches].to_i
weekly_dust = $killtracker[:weekly_dust].to_i
weekly_gemstone = $killtracker[:weekly_gemstone].to_i
weekly_codex = $killtracker[:weekly_codex].to_i
weekly_codex_searches = $killtracker[:weekly_codex_searches].to_i

$killtracker[:weekly_counts][:"week_#{finished_week}_ascension_searches"] = total_this_week
$killtracker[:weekly_counts][:"week_#{finished_week}_dust"] = weekly_dust
$killtracker[:weekly_counts][:"week_#{finished_week}_gemstone"] = weekly_gemstone
$killtracker[:weekly_counts][:"week_#{finished_week}_codex"] = weekly_codex
$killtracker[:weekly_counts][:"week_#{finished_week}_codex_searches"] = weekly_codex_searches

$killtracker[:monthly_ascension_searches] += total_this_week

$killtracker[:weekly_ascension_searches] = 0
$killtracker[:weekly_gemstone] = 0
$killtracker[:weekly_dust] = 0
$killtracker[:weekly_codex] = 0
$killtracker[:weekly_codex_searches] = 0
$killtracker[:jewel_found_this_week] = false
$killtracker[:last_week_reset] = $killtracker[:cached_reset_time]

Expand Down Expand Up @@ -406,6 +422,8 @@ module Killtracker
$killtracker[:weekly_gemstone] = 0
$killtracker[:monthly_gemstones] = 0
$killtracker[:weekly_dust] = 0
$killtracker[:weekly_codex] = 0
$killtracker[:weekly_codex_searches] = 0
Comment on lines +438 to +439

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Don't zero weekly_codex_searches unless you also rebuild it.

This method recomputes weekly_codex, but not weekly_codex_searches. After ;kt fix find count, the codex summary added in this PR will show 0 current-week codex searches until more kills arrive, even if the previous value was correct.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/killtracker.lic` around lines 425 - 426, Don't reset
$killtracker[:weekly_codex_searches] unless you also rebuild it; remove the line
that sets $killtracker[:weekly_codex_searches] = 0 or replace it with logic that
recomputes weekly_codex_searches from the same data used to rebuild
$killtracker[:weekly_codex]. Locate the assignments to
$killtracker[:weekly_codex] and $killtracker[:weekly_codex_searches] and either
only zero/recompute weekly_codex or add code to aggregate existing kill
records/events into $killtracker[:weekly_codex_searches] (same
time-window/bucketing logic used for weekly_codex) so the count remains correct
after running the fix.

$killtracker[:monthly_ascension_searches] = 0
$killtracker[:jewel_found_this_week] = false

Expand All @@ -432,6 +450,14 @@ module Killtracker
end
end

$killtracker[:codex_found].each do |key, _|
next unless key.is_a?(Integer)
local = tz.to_local(Time.at(key))
if local.strftime("%U").to_i == current_week && local.year == current_year
$killtracker[:weekly_codex] += 1
end
end

$killtracker[:weekly_counts].each do |week_key, searches|
next unless week_key.to_s.include?('ascension_searches')
next unless searches.is_a?(Integer)
Expand All @@ -448,6 +474,7 @@ module Killtracker
update_eligibility
respond("Counters successfully recalculated.")
respond(" Weekly gems: #{$killtracker[:weekly_gemstone]}, Monthly gems: #{$killtracker[:monthly_gemstones]}")
respond(" Weekly codex: #{$killtracker[:weekly_codex]}")
respond(" Jewel found this week: #{$killtracker[:jewel_found_this_week]}")
rescue => e
respond("Error during backfill: #{e.message}")
Expand All @@ -465,6 +492,7 @@ module Killtracker
respond(";kt gemstones report - find report broken down by week")
respond(";kt jewel report - find report of all jewels")
respond(";kt dust report - find report of all dust")
respond(";kt codex report - find report of all codex")
respond(";kt fix find count - fixes monthly/weekly gemstone count in summary")
respond(";kt save - force search data to be saved to file")
respond(";kt backup - create a manual backup of current data")
Expand All @@ -480,15 +508,19 @@ module Killtracker
begin
gems_total = $killtracker[:jewel_found].size
dust_total = $killtracker[:dust_found].size
codex_total = $killtracker[:codex_found].size

weekly_searches = $killtracker[:weekly_ascension_searches].to_i
monthly_searches = calculate_monthly_eligible_searches
gems_this_week = $killtracker[:weekly_gemstone].to_i
dust_this_week = $killtracker[:weekly_dust].to_i
codex_this_week = $killtracker[:weekly_codex].to_i
codex_searches_this_week = $killtracker[:weekly_codex_searches].to_i
gems_this_month = $killtracker[:monthly_gemstones].to_i

since_last_gem = $killtracker[:searches_since_jewel].to_i
since_last_dust = $killtracker[:searches_since_dust].to_i
since_last_codex = $killtracker[:searches_since_codex].to_i

total_searches_for_gems = $killtracker[:jewel_found].values
.map { |ev| ev[:searches_week].to_i }
Expand All @@ -498,10 +530,18 @@ module Killtracker
.map { |ev| ev[:searches_week].to_i }
.sum

avg_per_gem = gems_total > 0 ? (total_searches_for_gems.to_f / gems_total).round : 0
avg_per_dust = dust_total > 0 ? (total_searches_for_dust.to_f / dust_total).round : 0
total_searches_for_codex = $killtracker[:codex_found].values
.map { |ev| ev[:searches_week].to_i }
.sum

avg_per_gem = gems_total > 0 ? (total_searches_for_gems.to_f / gems_total).round : 0
avg_per_dust = dust_total > 0 ? (total_searches_for_dust.to_f / dust_total).round : 0
avg_per_codex = codex_total > 0 ? (total_searches_for_codex.to_f / codex_total).round : 0

remaining_gems = [0, 3 - gems_this_month].max
remaining_gems = [0, 3 - gems_this_month].max
remaining_codex = [0, CODEX_WEEKLY_CAP - codex_this_week].max

codex_status = codex_currently_eligible? ? "Eligible (#{codex_this_week}/#{CODEX_WEEKLY_CAP})" : "Ineligible (#{codex_this_week}/#{CODEX_WEEKLY_CAP})"

# Determine weekly status with gem number
if currently_eligible?
Expand Down Expand Up @@ -535,8 +575,16 @@ module Killtracker
[" This Week", dust_this_week],
[" Avg Searches/Dust", avg_per_dust.with_commas],
[],
["Codex Found (all)", codex_total.with_commas],
[" This Week", codex_this_week],
[" Codex Searches This Week", codex_searches_this_week.with_commas],
[" Remaining This Week", remaining_codex],
[" Status", codex_status],
[" Avg Searches/Codex", avg_per_codex.with_commas],
[],
["Since Last Gem", since_last_gem.with_commas],
["Since Last Dust", since_last_dust.with_commas],
["Since Last Codex", since_last_codex.with_commas],
]

table = Terminal::Table.new(
Expand Down Expand Up @@ -607,6 +655,35 @@ module Killtracker
end
end

def self.codex_report
begin
events = $killtracker[:codex_found].sort_by { |key, _| key.to_i }

total_searches_for_codex = events.map { |_, ev| ev[:searches_week].to_i }.sum

rows = events.map do |key, ev|
time_str = format_time_eastern(key.to_i)
searches = (ev[:searches_week] || 0).with_commas
since_last = (ev[:searches_since] || 0).with_commas
creature = ev[:creature] || ""
room = ev[:room] || ""
name = ev[:name] || ""
[time_str, searches, since_last, creature, room, name]
end

title = "Detailed Codex Report: #{$killtracker[:codex_found].size} Codex over #{total_searches_for_codex.with_commas} Eligible Searches"
table = Terminal::Table.new(
title: title,
headings: ["Time", "Week Searches", "Since Last Codex", "Creature", "Room", "Name"],
rows: rows
)

respond table.to_s
rescue => e
respond "Error generating codex report: #{e.message}"
end
end

def self.gemstones_report(weeks_back = nil)
begin
tz = get_eastern_tz
Expand All @@ -629,6 +706,14 @@ module Killtracker
since: ev[:searches_since]
)
end
$killtracker[:codex_found].each do |key, ev|
next unless key.is_a?(Integer) && ev.is_a?(Hash)
combined << ev.merge(
timestamp: key,
type: "Codex",
since: ev[:searches_since]
)
end

events_by_week = combined.group_by do |ev|
tz.to_local(Time.at(ev[:timestamp])).strftime("%U").to_i
Expand Down Expand Up @@ -751,6 +836,8 @@ module Killtracker
ev = $killtracker[:dust_found][key]
when "jewel"
ev = $killtracker[:jewel_found][key]
when "codex"
ev = $killtracker[:codex_found][key]
end
return unless ev

Expand Down Expand Up @@ -801,6 +888,16 @@ module Killtracker
end
respond(" Sent #{sent_dust} dust records")

respond("Sending found codex...")
sent_codex = 0
$killtracker[:codex_found].each do |timestamp, _|
next unless timestamp.is_a?(Integer)
if send_to_sheet("codex", timestamp)
sent_codex += 1
end
end
respond(" Sent #{sent_codex} codex records")

respond("Sending complete.")
respond("View the data at: https://docs.google.com/spreadsheets/d/1IOLs8AGRR45Kr6Y9nz6CXlMVBKYR7cHLaz0jjAbjMv0")
respond("")
Expand Down Expand Up @@ -902,6 +999,18 @@ module Killtracker
eligible
end

def self.codex_currently_eligible?
eligible = $killtracker[:weekly_codex].to_i < CODEX_WEEKLY_CAP

if $killtracker[:debug_eligibility]
echo "Codex Eligibility Check:"
echo " Weekly codex: #{$killtracker[:weekly_codex]} / #{CODEX_WEEKLY_CAP}"
echo " Eligible: #{eligible}"
end

eligible
end

def self.update_eligibility
begin
@eligibility_file = File.join(DATA_DIR, XMLData.game, "jewel_eligibility.yaml")
Expand Down Expand Up @@ -967,13 +1076,14 @@ module Killtracker
errors = []

[:weekly_ascension_searches, :monthly_ascension_searches, :searches_since_jewel,
:searches_since_dust, :monthly_gemstones, :weekly_gemstone, :weekly_dust].each do |key|
:searches_since_dust, :monthly_gemstones, :weekly_gemstone, :weekly_dust,
:weekly_codex, :weekly_codex_searches, :searches_since_codex].each do |key|
if $killtracker[key] && $killtracker[key] < 0
errors << "#{key} is negative: #{$killtracker[key]}"
end
end

[$killtracker[:jewel_found], $killtracker[:dust_found]].each do |events|
[$killtracker[:jewel_found], $killtracker[:dust_found], $killtracker[:codex_found]].each do |events|
next unless events
events.each do |timestamp, _data|
unless timestamp.is_a?(Integer)
Expand All @@ -1000,6 +1110,10 @@ module Killtracker
errors << "Weekly gemstone exceeds maximum: #{$killtracker[:weekly_gemstone]}"
end

if $killtracker[:weekly_codex].to_i > CODEX_WEEKLY_CAP
errors << "Weekly codex exceeds maximum: #{$killtracker[:weekly_codex]}"
end

errors
end

Expand All @@ -1016,6 +1130,8 @@ module Killtracker

FOUND_DUST = %r{<pushBold/>You notice a scintillating mote of gemstone dust on the ground and gather it quickly\.}i
FOUND_GEMSTONE = %r{<pushBold/> \*\* A glint of light catches your eye, and you notice an? <a exist="\d+" noun="\w+">(?<n>[^<]+)</a> at your feet! \*\*}
FOUND_CODEX = %r{Among the remains, you find an? <a exist="\d+" noun="codex">(?<n>[^<]+)</a>, which falls alongside your feet\.}
CODEX_WEEKLY_CAP = 10 unless const_defined?(:CODEX_WEEKLY_CAP)
ASCENSION_CREATURES = Regexp.union(
%r{armored battle mastodon},
%r{black valravn},
Expand Down Expand Up @@ -1064,6 +1180,16 @@ module Killtracker
%r{merrow oracle}
)

ONSLAUGHT_CREATURES = Regexp.union(
%r{battle-worn Empyrean captain},
%r{branded goliath diviner},
%r{burly goliath engineer},
%r{haze-shrouded goliath diviner},
%r{masked goliath plunderer},
%r{radiant-eyed goliath auramancer},
%r{tawny armor-clad pegasus}
)

def self.parse_downstream(line)
case line
when FOUND_GEMSTONE
Expand All @@ -1089,6 +1215,26 @@ module Killtracker
REPORT_QUEUE.push(report)
REPORT_QUEUE.push(["send jewel report", key]) if $killtracker[:submit_finds]

when FOUND_CODEX
key = Time.now.to_i
name = Regexp.last_match[:n]
room = Room.current.id.to_s

create_backup("pre_codex_find")

$killtracker[:weekly_codex] += 1
$killtracker[:codex_found][key] = {
searches_since: $killtracker[:searches_since_codex],
searches_week: $killtracker[:weekly_ascension_searches],
Comment on lines +1246 to +1249

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Persist the codex search counter here, not the gem search counter.

Line 1228 stores weekly_ascension_searches into codex events, but codex eligibility is now tracked independently. Once a character is gem-ineligible but still codex-eligible, detailed codex reports, averages, and sheet exports will all undercount because they read ev[:searches_week] from this record.

Suggested fix
       $killtracker[:codex_found][key] = {
         searches_since: $killtracker[:searches_since_codex],
-        searches_week: $killtracker[:weekly_ascension_searches],
+        searches_week: $killtracker[:weekly_codex_searches],
         name: name,
         room: room,
         creature: $killtracker[:creature]
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/killtracker.lic` around lines 1225 - 1228, The codex event is
currently recording the gem search counter
($killtracker[:weekly_ascension_searches]) into ev[:searches_week] which
miscounts codex-only eligibility; change the stored value to the codex counter
($killtracker[:weekly_codex]) so that the codex record created at
$killtracker[:codex_found][key] uses $killtracker[:searches_since_codex] and
$killtracker[:weekly_codex] (keep the existing keys searches_since and
searches_week).

name: name,
room: room,
creature: $killtracker[:creature]
}
report = ['found codex', $killtracker[:creature], $killtracker[:weekly_codex], $killtracker[:searches_since_codex]]
$killtracker[:searches_since_codex] = 0
REPORT_QUEUE.push(report)
REPORT_QUEUE.push(["send codex report", key]) if $killtracker[:submit_finds]

when FOUND_DUST
key = Time.now.to_i
room = Room.current.id.to_s
Expand All @@ -1110,7 +1256,14 @@ module Killtracker
maybe_reset_monthly_counter
name = Regexp.last_match[:creature]
$killtracker[:creature] = name
if ASCENSION_CREATURES.match?(name)

is_ascension = ASCENSION_CREATURES.match?(name)
is_onslaught = ONSLAUGHT_CREATURES.match?(name)

# Ascension and onslaught creatures both drop gems and dust.
# Codex only drops from onslaught creatures.
# Gem and codex eligibility are gated independently.
if is_ascension || is_onslaught
# Always count dust searches (dust has no limits)
$killtracker[:searches_since_dust] += 1

Expand All @@ -1124,6 +1277,16 @@ module Killtracker
elsif $killtracker[:debug_eligibility]
echo "Search not counted for gems - currently ineligible (#{name})"
end

# Onslaught creatures additionally contribute to codex tracking
if is_onslaught
if codex_currently_eligible?
$killtracker[:weekly_codex_searches] += 1
$killtracker[:searches_since_codex] += 1
elsif $killtracker[:debug_eligibility]
echo "Search not counted for codex - currently ineligible (#{name})"
end
end
end
end
line
Expand Down Expand Up @@ -1173,10 +1336,16 @@ module Killtracker
Lich::Messaging.stream_window("Found a gemstone in #{week} searches. (#{creature}) - Since last Jewel: (#{jewel})", "speech")
save
update_eligibility
when "found codex"
_, creature, weekly_codex, since = report
Lich::Messaging.stream_window("Found a codex after #{since} searches. (#{creature}) - Week Total: (#{weekly_codex}/#{CODEX_WEEKLY_CAP})", "speech")
save
when /send dust report/
send_to_sheet("dust", report[1])
when /send jewel report/
send_to_sheet("jewel", report[1])
when /send codex report/
send_to_sheet("codex", report[1])
end
end

Expand Down Expand Up @@ -1210,6 +1379,8 @@ module Killtracker
Killtracker.jewel_report
when /dust report/
Killtracker.dust_report
when /codex report/
Killtracker.codex_report
when /gemstones? report(?:\s+(\d+))?$/
weeks = $1 ? $1.to_i : nil
Killtracker.gemstones_report(weeks)
Expand Down
Loading