Underground 2 Revived
Play Need for Speed: Underground 2 (2004) in Chrome and Firefox with WebGPU graphics, Web Audio, up to 120 fps widescreen, controller support, free roam and Online races with friends. Fan project, not
翻譯準備中
What started as a Discord bot answering “when does the boss respawn?” grew into a four-component raid intelligence platform: a desktop cockpit over the game, a privacy-first engine on every raider's machine, a hub that merges every viewpoint of every fight, and a website that remembers all of it.
翻譯準備中
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
Wolf Pack EQ — bot · wolfpack.quest · Mimic
The guild platform for Wolf Pack on Project Quarm (EverQuest emu, Luclin era):
Timer data sourced from PQDI.cc. Architecture map + release playbook: CLAUDE.md. Component versions live in each package.json; builds on the Releases page.
One-click installer: wolfpack.quest/mimic (stable) · beta channel
Electron desktop app; bundles its own Node runtime — nothing else to install. The installer is not code-signed, so Windows SmartScreen will warn: More info → Run anyway (in Edge: ⋯ → Keep → Keep anyway). Auto-updates in place after that.檢視完整 README ↗The guild platform for Wolf Pack on Project Quarm (EverQuest emu, Luclin era):
| Component | What it is | Where it runs |
|---|---|---|
| Discord bot | Raid timers, parse aggregation, DKP/loot, onboarding, the /api/agent/* ingest surface | Railway, ships from main |
| wolfpack.quest | Guild tracker site — /raid hub, parses, buffs, PvP, leaderboards, character pages, officer admin | Vercel, ships from main |
| Wolf Pack Mimic | Desktop client for raid members — log parser + overlays + uploads, auto-updating | Member PCs, releases from this repo |
Timer data sourced from PQDI.cc. Architecture map + release playbook: CLAUDE.md. Component versions live in each package.json; builds on the Releases page.
☕ Tips: if this saves your raid some grief, you can buy the maintainer a coffee at ko-fi.com/davehess.
One-click installer: wolfpack.quest/mimic (stable) · beta channel
Electron desktop app; bundles its own Node runtime — nothing else to install. The installer is not code-signed, so Windows SmartScreen will warn: More info → Run anyway (in Edge: ⋯ → Keep → Keep anyway). Auto-updates in place after that.
What you get:
<Name>Quarmy.txt export in your EQ folder (enable Zeal's Export data on /camp); gear, AAs and factions sync, powering accurate cast bars + MGB detection. Bank and coin are stripped on your machine and never uploaded/tells history — synced privately to wolfpack.quest/meeqclient.ini; restore on any machineAfter install: sign in with Discord (Step 1 of onboarding), pick your EQ folder, launch. The dashboard's Setup Checklist tells you if anything needs attention (logging on, Zeal connected, export-on-camp, …). The local dashboard lives at http://localhost:7779.
The original log-tail agent — no GUI, no overlays, same uploads. Double-click RUN-FIRST-for-Node.js.bat once per machine, then Parser.bat. Dashboard serves on http://localhost:7777.
Same RUN-FIRST-for-Node.js.bat flow as above, one time per machine.
Open a normal PowerShell or Command Prompt window in the repo folder:
npm install
copy .env.example .env
notepad .env # fill in your Discord token and channel IDs
npm start
Tip: the bot auto-registers slash commands on every startup — no separate deploy step needed.
Double-click Parser.bat in the repo folder. That's it. No PowerShell execution policy to fight with — the .bat file handles everything.
Parser watches every recently-active EQ log file simultaneously (no need to pick a character) and uploads encounter data to the bot while you raid.
First run — auto-detects your EQ directory across all drives (C:\, D:\, E:\, etc.) looking for EverQuest, EQ, TAKP, TAKP2.2, and standard install paths. Asks for the bot URL and your personal upload key — run /token in Discord to mint one (Mimic users get this automatically when they sign in). There is no shared guild token any more; each key is yours alone and can be revoked without affecting anyone else. It then copies itself into your EQ directory — which is typically already excluded from Windows Defender — and offers three startup options:
| Option | What it does |
|---|---|
| Run automatically | Registers a Windows Task Scheduler task — Parser starts silently in the background every time you log into Windows. No window, no clicks needed. |
| Desktop shortcut | Adds a "Parser" shortcut to your desktop — double-click it before a raid. |
| Start menu | Adds "Parser" under Start → All Apps. |
After first run the repo copy is no longer needed. Everything (Parser.bat, start-logsync.ps1, wolfpack-logsync\index.js, logsync.config.json) lives inside your EQ directory. Shortcuts and the scheduled task point there directly.
Subsequent runs:
What gets watched: any eqlog_*_pq.proj.txt modified in the last 30 days. Characters active in the last hour show in green with *. Idle log files sit at zero cost until that character logs in.
Privacy: all filtering is local. Officer chat, tells, group chat, and custom channels are dropped before any upload — they never leave your machine. Guild chat (/gu) and raid chat (/rs) are forwarded to read-only Discord channels (#in-game-guild-chat, #in-game-raid-chat). Only combat events from detected boss encounters are uploaded to the parse endpoint.
Flags (pass via start-logsync.ps1 directly if needed):
| Flag | Description |
|---|---|
-DryRun | Parse locally, print summaries — nothing uploaded |
-StaleAfterDays 7 | Tighten the "recently active" window (default: 30) |
-EqDir "D:\TAKP" | Override EQ path without re-prompting |
-Setup | Re-run the startup wizard (change auto-start/shortcut preference) |
-Remove | Remove the scheduled task and any shortcuts |
-Reset | Forget all saved config and start over |
#raid-mobs (main channel)Four fixed message slots, always edited in place — never re-posted:
| Slot | Content |
|---|---|
| 1 | 📊 Active Cooldowns (all expansions) |
| 2 | 🌅 Spawning in the Next 24 Hours |
| 3 | 📅 Daily Raid Summary (resets midnight) |
| 4 | Thread links (one message, all 5 expansions) |
#raid-mobs)One thread per expansion. Each thread contains:
| Thread | Env Var |
|---|---|
| Classic | CLASSIC_THREAD_ID |
| Kunark | KUNARK_THREAD_ID |
| Velious | VELIOUS_THREAD_ID |
| Luclin | LUCLIN_THREAD_ID |
| PoP | POP_THREAD_ID |
HISTORIC_KILLS_THREAD_IDReceives midnight daily summaries, zone kill cards when bosses respawn, and archived /announce messages.
ONBOARDING_THREAD_IDHosts the public Quick Start instructions and the encrypted opt-out registry (salted SHA-256 hashes — no plaintext user IDs).
PARSES_LOG_THREAD_IDEvery /parse submission is archived here as a JSON embed. This is the source of truth for parse data and survives Railway volume wipes — the bot rebuilds parses.json from this thread on every startup.
The tables below cover the core raid-timer / parse / PvP set. The bot has 83 slash commands total — run /raidbosshelp in Discord for the live, complete reference. Whole areas live only there: DKP balances and wishlists, loot bidding, Plane of Hate tracking, /who lookups and roster tools, raid attendance ticks, feedback and suggestions, and the officer backfill/recovery set.
| Command | Description |
|---|---|
/kill <boss> | Record a kill, start the respawn timer, post a zone kill card in the expansion thread |
/unkill <boss> | Clear a kill record, remove the boss from the zone kill card |
/updatetimer <boss> <time> | Override the next-spawn time (e.g. "3d4h30m", "Expires in 3 Days, 4 Hours") |
/timers [zone] [filter] | View spawn timers — filter by zone (autocomplete, 33+ zones) or status |
/board | Post or in-place refresh all 4 main-channel slots and all expansion thread boards |
/cleanup | Remove duplicate/stale messages, re-anchor boards to earliest copies |
/restore <links...> | Rebuild kill state from any Active Cooldowns or Daily Summary message links |
| Command | Description |
|---|---|
/parse <data> | Submit an EQLogParser "Send to EQ" DPS parse — boss auto-detected from the header |
/parseboss <boss> <data> | Submit a parse with explicit boss selection |
/parsestats <boss> | DPS scoreboard and raidwide metrics for a boss across all stored kills |
/parseaoe <data> | Submit an AoE parse combining damage within a 5-minute window (max damage per player) |
/parsenight [public] | Full-night DPS summary across every kill tonight |
/raidnight | Open tonight's raid parse thread with a live rolling scoreboard |
/raidreview [date] [preview] | Post or refresh a night's Raid Night Review in that night's thread (officer only; posts itself ~45 min after midnight) |
/mystats <character> | Per-character DPS stats — kills, avg DPS, peak DPS, per-boss breakdown (ephemeral) |
/mystatsall <character> | Same as /mystats but aggregates across the full main + alt family |
/parseleaderboard | Post/update a pinned leaderboard in the parse log thread (officer only) |
EQLogParser "Send to EQ" format:
<Boss> in <N>s, <X>K/<X>M Damage @<X>K, 1. Player = <X>K@<X> in <X>s | ...
| Command | Description |
|---|---|
/announce time:<when> [boss:<name>] [zone:<zone>] [note:<text>] | Create a raid announcement, a thread, and a Discord event |
/addtarget <boss> | Add a boss to the active announce thread's target list |
/removetarget <boss> | Remove a boss from the target list |
/adjusttime <time> | Update the raid time in the announce thread and Discord event |
/adjustdate <date> | Update the raid date (e.g. "Friday", "4/30") |
Time formats: "8:30 PM", "Thursday 9pm", "tomorrow 8pm", "8:30 PM EST", "in 2 hours"
The announce thread contains a live control panel. Use the Cancel Event button to cancel and archive.
| Command | Description |
|---|---|
/pvpkill <mob> | Record a PVP mob kill — timer pulled from bosses.json, card posted to PVP_KILLS_THREAD_ID |
/pvpspawn <mob> | Clear a PVP mob timer when it spawns; ephemeral reply with "Alert PVP" button to rally the pack |
/pvpunkill <mob> | Remove a PVP kill record without sending an alert |
/quake [time] | Schedule a quake ("now", "9pm", "in 2 hours") — resets all PVP mob timers, creates a Discord event |
/pvprole [silent] | Toggle your @PVP role; without silent, posts a wolf announcement to the PVP channel |
/pvpalert <zone> | Ping @PVP with a howl message; other users click 🐺 Howl! to join |
| Command | Description |
|---|---|
/addboss <pqdi_url> | Scrape a PQDI.cc NPC page, add to bosses.json, refresh the board |
/removeboss <boss> | Remove a boss, clear its kill state, refresh the board |
| Command | Description |
|---|---|
/raidbosshelp | Full command reference (ephemeral) |
/onboarding | Show the Wolf Pack welcome message again, or toggle your opt-out preference |
When a new member joins, the bot sends them a welcome message (DM, or falls back to the onboarding thread) covering the three pillars of coordination: accountability, timing, and announcements. Buttons let them indicate intent (PVP, organizer, or attendee) and see tailored follow-up.
/onboarding at any time.ONBOARDING_THREAD_ID and reloaded on every startup.DISCORD_CLIENT_ID)bot and applications.commands
Manage Eventsis required for Discord Scheduled Events created by/announceand/quake.
Manage Rolesis required for/pvproleto add and remove the @PVP role.Server Members Intent is required for the member-join onboarding messages.
.env.example under the Variables tabdata/state.json is a within-process cache, not memory across deploys — production runs with no volume mounted and every deploy starts from a fresh file. Durability comes from three other places instead: Discord itself (parses, boards and roster live in threads and are re-read on boot), the message-ID environment variables in step 9 (so anchors survive a restart), and Supabase bot_kv for anything keyed per-night or per-fight. Anything you store only in state.json will be gone after the next deploycp .env.example .env
nano .env # fill in your values
docker-compose up -d
docker-compose logs -f
# To update:
git pull && docker-compose down && docker-compose up -d --build
Same flow as Bot install above: double-click RUN-FIRST-for-Node.js.bat
once to install Node.js, then in the repo folder run npm install, copy
.env.example to .env and fill it in, and npm start.
The bot builds its own Discord layout, so on a brand-new server there is nothing to create by hand and no ID to copy.
#raid-mobs channel for you, the invite needs Manage Channels (the second number in that section).
If you would rather make #raid-mobs yourself, do that now and skip the permission.DISCORD_TOKEN is the only variable the bot needs to find its footing; it works
out its own application ID, and your server's ID when it is in just one server. Leave GUILD_PROVISION unset.#raid-mobs (if there is no channel of that name, or raid-timers / boss-timers), posts its four pinned cards
(Active Cooldowns, Spawning in the Next 24 Hours, Daily Raid Summary and the expansion thread links), then opens
one thread per expansion (Classic, Kunark, Velious, Luclin, PoP) and a Historic Kills thread. Each expansion thread
starts with its own cooldown card and its kill-button boards. The boot log ends with one line that begins
[provision] and says what it found, made and could not make. This needs the database migrations applied first, or no
Supabase variables at all. If the [provision] line says kv=unknown nothing was built; once the database is up run
/setup discord provision with dry_run set to false./setup discord status (you need Manage Server or the officer role). It shows where every
ID came from, which ones point at something that was deleted, which permissions the bot lacks (by name), and the
few things only a person can do: set the suggestions forum, follow the patch-notes channel, and the Raid-Helper IDs./setup discord export for two private files: guild/discord.json (commit it, IDs are not
secrets) and a discord.env block you can paste into your host's variables./setup discord provision (a dry run until you set dry_run to false) can
also make the other channels (create_channels), and optional groups such as pvp, voice, loot, ,
, , , and (). The same switches exist as variables:
, , ,
( stops members posting in channels the bot made) and . They are all in ./board and /cleanup still work as before; the first /board replaces each placeholder card with the live one.
/restore <link1> [<link2> ...]
Paste links to any combination of Active Cooldowns cards and Daily Raid Summary messages. The most recent nextSpawn per boss wins. Parse data is recovered automatically from the Parse Logs thread on startup.
| Variable | Description |
|---|---|
DISCORD_TOKEN | Bot token from Discord Developer Portal |
DISCORD_CLIENT_ID | Application ID |
DISCORD_GUILD_ID | Server ID |
TIMER_CHANNEL_ID | #raid-mobs main channel ID |
CLASSIC_THREAD_ID | Classic expansion thread ID |
KUNARK_THREAD_ID | Kunark expansion thread ID |
VELIOUS_THREAD_ID | Velious expansion thread ID |
LUCLIN_THREAD_ID | Luclin expansion thread ID |
POP_THREAD_ID | PoP expansion thread ID |
HISTORIC_KILLS_THREAD_ID | Historic Kills thread ID |
PARSES_LOG_THREAD_ID | Parse Logs thread ID (parse data source of truth) |
ONBOARDING_THREAD_ID | Onboarding thread ID (quick-start instructions + opt-out registry) |
ALLOWED_ROLE_NAMES | Comma-delimited role names (e.g. Pack Member,Officer,Guild Leader) |
The table above is the minimum for the raid-timer bot. The tables here cover the
common cases; .env.example documents every variable (load-shed flags,
overlay tuning, encryption keys, timeouts) — treat it as the authoritative list.
Required for the modern platform — parse aggregation, wolfpack.quest, DKP/loot, and the Mimic/agent ingest surface. Without these the bot still runs as a bare timer, but the analytics, character pages, and uploads are dark.
| Variable | Description |
|---|---|
SUPABASE_URL | Supabase project URL — backs parses, characters, DKP, live state, and every wolfpack.quest surface |
SUPABASE_SERVICE_ROLE_KEY | Service-role key (bypasses RLS; server-side only — never ship to a client) |
SUPABASE_GUILD_ID | Guild tag written to all rows (default: wolfpack) |
WOLFPACK_AGENT_TOKEN | Agent-side only. Where the standalone Parser reads your personal upload key (wpms_…, minted by /token). The bot does not read this — since 2026-06-04 the ingest surface accepts only per-user session tokens and rejects a shared secret outright. Mimic stores your key itself and ignores this variable |
PORT | HTTP port for the agent API + /health (Railway sets this automatically) |
OPENDKP_* | OpenDKP connection (raids URL, Cognito client, credentials) for DKP sync and loot auctions — see .env.example |
| Variable | Description |
|---|---|
SUMMARY_MESSAGE_ID | Active Cooldowns message in main channel |
SPAWNING_TOMORROW_MESSAGE_ID | Spawning Tomorrow message |
DAILY_SUMMARY_MESSAGE_ID | Daily Summary message |
THREAD_LINKS_MESSAGE_ID | Thread links message |
CLASSIC_BOARD_IDS | Comma-delimited board panel message IDs for Classic thread |
KUNARK_BOARD_IDS | Kunark board IDs |
VELIOUS_BOARD_IDS | Velious board IDs |
LUCLIN_BOARD_IDS | Luclin board IDs |
POP_BOARD_IDS | PoP board IDs |
CLASSIC_COOLDOWN_ID | Active Cooldowns card at top of Classic thread |
KUNARK_COOLDOWN_ID | Kunark cooldown card ID |
VELIOUS_COOLDOWN_ID | Velious cooldown card ID |
LUCLIN_COOLDOWN_ID | Luclin cooldown card ID |
POP_COOLDOWN_ID | PoP cooldown card ID |
| Variable | Default | Description |
|---|---|---|
DEFAULT_TIMEZONE | America/New_York | IANA timezone for time parsing and midnight tasks |
ARCHIVE_CHANNEL_ID | — | Channel to receive archived raid event summaries |
BOSS_OUTPUT_CHANNEL_ID | — | Channel where bosses.json is posted after /addboss or /removeboss |
RAID_CHAT_CHANNEL_ID | #raid-chat | Channel for /raidnight + auto raid-night threads (known-id default, then TIMER_CHANNEL_ID) |
EVENT_CHAT_CHANNEL_ID | #event-chat | Channel for off-night guild-event threads (roll loot, no DKP) |
PVP_KILLS_THREAD_ID | — | Thread where /pvpkill posts kill cards and timers are tracked |
PVP_CHANNEL_ID | — | Channel for PVP alerts, quake alerts, and spawn notifications |
PVP_THREAD_ID | — | Thread for PVP alerts (takes priority over PVP_CHANNEL_ID) |
PVP_ROLE | PVP | Name of the Discord role to ping for PVP alerts and spawn notifications |
AUDIT_TRAIL_THREAD_ID | — | Thread for audit log entries with undo buttons (officer-only undo) |
Runs every 5 minutes. For each boss with an active kill:
Run at midnight in DEFAULT_TIMEZONE (default: Eastern):
/announce messages to Historic Kills, then delete originals133 bosses across Classic (15), Kunark (17), Velious (35), Luclin (46), and PoP (20, locked until 2026-10-01).
bosses.json schema:
{
"id": "lord_nagafen",
"name": "Lord Nagafen",
"zone": "Nagafen's Lair",
"expansion": "Classic",
"timerHours": 162,
"nicknames": ["naggy", "nag", "nagafen"],
"emoji": "🐉",
"pqdiUrl": "https://www.pqdi.cc/npc/32040"
}
Valid expansion values: Classic, Kunark, Velious, Luclin, PoP
Boss data is hot-reloaded on every command — /addboss and /removeboss take effect immediately without a restart.
With Docker, sync back after
/addboss:docker cp quarm-raid-timer-bot:/app/data/bosses.json ./data/bosses.json
One bot for everything (a new guild's setup, 2026-10-06). The raid-timer bot and the raid-voice
bot (Bristlebane) are ONE Discord application for a new guild; Wolf Pack runs them as two only because
Bristlebane came later. Invite it with scopes bot applications.commands and permissions
2252135193504768:
https://discord.com/oauth2/authorize?client_id=<your application id>&scope=bot+applications.commands&permissions=2252135193504768
| Permission | Why |
|---|---|
| View Channels | See the channels it posts to and the raid voice channel |
| Send Messages | Kill cards, spawn alerts, boards, PVP alerts, onboarding messages, Bristlebane's join notice |
| Send Messages in Threads | Expansion threads, raid-night threads, the roster and parse threads |
| Create Public Threads | Create announce and raid-night threads |
| Manage Threads | Rename event threads, archive them at midnight, wake an archived expansion thread |
| Embed Links | Rich embeds with PQDI links |
| Attach Files | Parse and leaderboard images, exports |
| Read Message History | Fetch messages to edit in place |
| Manage Messages | Delete kill cards on respawn; clean up at midnight; /removespam |
| Pin Messages | Pin the parse leaderboard (Discord split this out of Manage Messages); pin the four cards when GUILD_PROVISION_PIN=1 |
| Manage Events | Create/delete Discord Scheduled Events for /announce and /quake |
| Manage Roles | Add/remove @PVP role via /pvprole (the bot's role must sit above @PVP) |
| Connect | Join the raid voice channel |
| Speak | Spoken callouts and loot calls |
| Use Voice Activity | A bot has no push-to-talk; without this it cannot be heard |
| Change Nickname | Bristlebane's [REC] name while a member who opted in is in the channel |
Add Manage Channels only if you want the bot to create channels itself (GUILD_PROVISION_CREATE_CHANNELS, or the
hub channel on a brand-new server): the permission number then becomes 2252135193504784. It is the one
permission here that is about the server rather than a channel, and the bot asks for it by name before it tries.
What the setup step checks. Before it writes anywhere, the bot checks that it can see the channel, send
messages and embeds, read history, send in and create threads, manage threads and manage messages there (and pin,
if you turned pinning on), and tells you by name what is missing instead of failing half way through. /setup discord status shows the same gaps at any time, with the invite link carrying the exact number. It never asks for
Manage Roles to set a layout up; that stays only for /pvprole.
Add Create Invite + Use Activities (2252684949318657 in total) only if the raid screen is run as a
Discord Activity. Privileged intents: Server Members (member sync) — on; Message Content — only
for the patch-notes mirror (MESSAGE_CONTENT_INTENT=1); Presence — off. Mentioning @PVP needs the
role set to "Allow anyone to @mention this role"; the bot does not get Mention Everyone.
Open source under the GNU Affero General Public License v3 or later (AGPL-3.0-or-later). Read it, change it, fork it, run it for your guild — free, no time limit, no asking.
The one obligation: if you modify it and let other people use your version over a network, you must offer them your source (AGPL §13). That is the point of this license rather than a permissive one — your fixes come back to everyone instead of dying in a private fork.
Nothing here is for profit. Money, where any moves, is cost recovery for infrastructure and time — donations, or sharing the cost of a deployment someone else runs for you. There is no paid tier and no feature behind a paywall. Hosting a modified version commercially without publishing your changes needs a separate commercial license: open an issue titled "Commercial license".
Binding text: LICENSE. Plain-language version, including what happens to a pull
request: docs/LICENSING.md.
rulesannouncehatelivedeathrollmimicoptionalGUILD_PROVISION_CREATE_CHANNELSGUILD_PROVISION_OPTIONALGUILD_PROVISION_SKIPGUILD_PROVISION_LOCKreadonlyGUILD_PROVISION_PIN.env.exampleGUILD_PROVISION=adopt to let it find the pieces you have not set by name, or
create to make what is missing. /setup discord provision with dry_run:false on a hand-configured server fills
every platform anchor you left unset; read the dry run first.node scripts/provision-discord.js --dry-run does the same job from your own
computer, needing only DISCORD_TOKEN, and writes guild/discord.json. Drop --dry-run to apply it.