GTFO
You are viewing a potentially older version of this package. View Latest Version
Install with App

Details

Date Uploaded
3 days ago
Downloads
334
Size
3.5MB

CombatPanel 1.16.2

Standalone GTFO BepInEx IL2CPP combat statistics. Only dependency: BepInEx-BepInExPack_GTFO-3.2.3.

The post-run influence board on a failed expedition, sitting under the vanilla 任务失败 heading The settlement board on a successful expedition: the 说明 line under 评价 says what each sentence was earned with, in the player's own figures

1.16.2 local success-screen fix: observe the native SetActiveBlink completion for the sector background, skull and optional title. Version 1.16.1 incorrectly treated this activation-based blink as an alpha tween; that render-state gate could hold the board indefinitely. No timeout bypass is used. F8 retains completed reveal state. Verified: 920 panel tests, 9 watcher tests and an isolated native BlinkIn probe; full settlement rendering and physical F8 input still await gameplay confirmation.

~ on scan efficiency is intentional for the current bot callback path: the host can identify the bot through its scan action, but scan uses are still grouped by a time window. Human scanner events use a different path. This update does not remove an estimate marker without changing its measurement basis.

One plugin, both halves of the run. During an expedition it draws a native-looking table: each player's damage and its share, friendly fire, hit and weakspot rates and pellets. After the run — success or failure — it replaces the vanilla recap with an influence board whose rows are fields and whose columns are players, ranked by a score out of 100, with a place, a sentence, the four gear slots and ten scored metrics each: total damage, stagger interrupts, kills by weight class, revives, weakspot rate, hit rate, supply efficiency, scan efficiency, lone survivor and deaths. Team help leads the default weights, so the squad's most useful member outranks its best shooter, and a player who contributed nothing wears the 「战地游客」 / "Battlefield tourist" sentence. Everything is read from the game's own events, and anything that cannot be read shows — instead of a guess.

Feedback: qingju.me | Voice channel: Xiaoheihe voice — both also at the end of this page.

HUD

Native-looking table during an expedition, all columns shown by default, F8 to toggle:

Current in-level combat table with damage, share, friendly fire and pellet statistics

Player | Damage Share | Friendly fire | Accuracy | Weakspot rate | Pellets total hit weak

Values inside a merged column sit side by side, one space apart and with no slash between them: Damage/Share reads 1,234.6 32.1%, Pellets read 120 45 12 in total / hit / weakspot order.

Share is that row's weapon enemy damage divided by the expedition's observed team enemy damage (the settlement board's weapon rows work the same way), excluding friendly fire. A player's own Total damage also contains damage their tool dealt (a sentry gun, for instance), so the two guns' shares can add up to less than that total's share. With zero team damage or unavailable non-host team totals, the share part shows —.

Player names use their in-game color and are limited to six text elements. All other text is white; all fields are right-aligned.

Merged columns align field by field

Both the header and the values of a merged column are split into separate fields, each with its own slot, and each field is right-aligned inside its slot. The numbers of a merged column therefore line up vertically from row to row instead of the whole group being pushed around by the longest value. No slash is drawn between the fields: they are held apart by a gap that is a fraction of the glyph size (and shrinks further with the density), which is what keeps 伤害 占比 and 总弹丸 命中 弱点 narrow. The pellet header qualifies "total" only, which is two characters narrower, and a row with no data shows a single — right-aligned in the last slot.

Column spacing (1.8.2)

The space surrounding every cell was cut by more than a third. The whitespace between two neighbouring columns is the padding of both cells plus the gap between them, and it went from 1.067em to 0.625em (PaddingFactor 0.45 → 0.27, GapFactor 0.55 → 0.30, in ems of the glyph size, so all three density levels follow). The four column floors whose header is the widest thing they hold came down with it — friendly fire 3.4 → 2.65em, accuracy and weakspot 4.6 → 3.05em, damage/share 7.0 → 6.0em — and a floor is only a minimum, so a smaller one can never truncate a value: the column still grows to whatever its widest cell measures.

The 总弹丸 命中 弱点 triplet keeps the spacing that was called "about right": its field gap factor is still exactly 0.60 and its column floor is still 6.8em.

Placement: window pixels, zero parallax

The table is drawn with IMGUI in window pixels. IMGUI is composited after every camera and every canvas, and is positioned only against the window, so the table inherits no view motion, no HUD motion and no HUD parallax: nothing in the game can move it, and only a configuration change relocates it. It is also clamped to the window, so no column can be cut off by a screen edge or a parent canvas.

Earlier versions could not have both properties. 1.2.0 converted a screen rectangle through a camera that does not render the HUD canvas (the table was displaced and skewed); 1.3.0 used a canvas of the mod's own that produced no pixels at all; 1.4.0 placed the table inside the game's HUD hierarchy, which renders but sways with the player and clipped its right-hand columns. No reference mod for this game creates its own canvas — they all live in the HUD hierarchy and therefore all inherit that sway.

  • Glyph size is a reference size at 1080p scaled by the window height and the density, so the table looks the same at any resolution.
  • Chinese text uses a system font (Microsoft YaHei UI → Microsoft YaHei → SimHei → SimSun), because IMGUI's built-in font has no CJK glyphs.
  • A column is as wide as its widest cell, measured with the very font atlas IMGUI draws with, never below a per-column floor. Headers and values share one cell box, so their right edges line up by construction; cells cannot overlap.
  • Drawing happens on the repaint pass only and input is never touched, so the table cannot swallow a click.

The result screens: the influence board (1.13.0)

Success and failure use a dark influence matrix: rows are fields, columns are players, ordered by a score out of 100 (one decimal). Names, scores, gear and metrics are grouped with weight and thin section rules; numeric cells share a right edge. Multi-value cells use fully measured / separators. Fresh configurations split kill tiers by default; saved settings are preserved, and merged kills carry tier labels too.

Row Content
#1 name Place and player name, in the player's in-game colour
Score 0-100 only; podium colours distinguish place, with no medal words
Rating A three to six word sentence, unique across the board
Reason What the sentence was earned with, in the player's own figures (damage 95.4, 52.2% of the squad, scanned 6x, 2 first marks); a figure the board could not measure prints the table's dash
Primary / Secondary / Melee / Tool The four gear slots; a support tool is marked support
Primary damage share That weapon's enemy damage, and its share of the squad's enemy damage
Primary hit weak That weapon's hit rate and weakspot rate
Primary pellets That weapon's fired / hit / weakspot pellet counts
(the same three rows for the secondary)
—— metrics, by weight —— Separator
The ten metrics Below

The player's own field is one row, not two: the first row is the place and the name, the second is the score, the third is the sentence, and the fourth says what that sentence was earned with, in the player's own figures. Only then come the four gear slots and the two weapons' figures. (1.9.0 printed the name twice, on a rank strip and again on a row of its own; the duplicate is gone.) The descriptive block is therefore fourteen rows, and with the separator and the ten metrics the board is 25 rows, or 27 with the kill tiers split.

The ten metrics, in descending weight. The squad's outright leader in a metric has its cell tinted gold; a tie tints nobody.

Metric What it reads Weight Normalised
Total damage Enemy damage dealt 1.1 value / squad max
Stagger the number of attacks interrupted (no longer accumulated damage): the hit landed while the enemy's attack was under way and not yet finished with a Light/Heavy react. A melee push that breaks an attack counts too, since a push deals no damage and is judged on the attack state alone. A kill is never an interrupt: the reacts that report a death are rejected, and so is any blow that takes the last of the enemy's health. The same enemy counts once per frame 1.2 value / squad max
Kills minor <120 / elite 120-999 / boss >=1000, graded from the enemy's own HealthMax so a modded enemy is graded by what it is 0.9 weighted value (minor 0.5 / elite 1.0 / boss 1.5) / squad max
Revives the reviver of Interact_Revive.TriggerInteractionAction 1.5 value / squad max
Weakspot rate weakspot pellets / hit pellets 0.7 the rate itself, 0-1
Hit rate hit pellets / fired pellets 0.6 the rate itself, 0-1
Supply efficiency the arithmetic mean of effective / intended over every use, credited to the player who used the pack; pickups never enter the denominator and an overflowing use counts as 0% 1.4 the rate itself, 0-1
Scan efficiency enemies revealed for the first time / sweeps made; a repeat sweep of an already revealed enemy adds nothing for anybody 1.3 display: enemies/attempt; scoring: clamped 0-1
Lone survivor was once the last one standing (not counted on a failure) 0.9 value / squad max
Deaths times downed 0.7 reverse: 1 - value / squad max

Default weights, in that order (the Weights field of CombatPanel-config/scoring.json; the .cfg Scoring/Weights is only a fallback): 1.2,1.8,0.9,0.35,0.55,0.35,1.0,1.1,0.6,0.7. Stagger (1.8) leads — breaking an attack is the rarest contribution — with revives (0.35) and supplies (1.0) lower and the firepower metrics (total damage and stagger) now weighing above every other column, total damage having moved from 1.1 to 1.2; team help still lets the squad's most useful member top its best shooter. Any ten non-negative numbers are accepted.

Score = 100 * sum(weight * role factor * normalised) / sum(weight * role factor), clamped into [0, 100]. When the whole squad ties, the metric awards no gold and normalises to zero, so the score cannot blow up.

The sentence runs from the top of the board down and no two players wear the same one. A player takes the metric they lead the squad in outright that they contributed most to; when somebody above them already wears that sentence they step down their own list to the next one still free, so a player who leads nothing still ends up with a sentence of their own instead of sharing the neutral title with everyone else. A player whose displayed score is exactly 0.0 always wears Battlefield tourist, and that one sentence is deliberately repeatable. The full table is: total damage = Firepower core, stagger = Interrupter, minor kills = Sweeper, elite kills = Giant hunter, boss kills = Dragonslayer, weakspot = Weakspot adept, hit rate = Dead eye, revives = Lifesaver, supply = Quartermaster, scan = Scout, lone survivor = Last stand, deaths = Survivor.

Support kits are lifted. A player whose class tool name (case insensitive substring) matches SupportToolNames in CombatPanel-config/scoring.json (default Bio Tracker,Enemy Scanner,生物追踪仪,Glue Gun,Glue Grenade,C-Foam,胶枪,粘胶,泡沫,Lock Melter,开锁器) gets FirepowerBonus (default 1.15) on total damage and stagger only, and UtilityBonus (default 1.0) on the support metrics. ExcludedTools (default Sentry,哨戒炮,哨戒,Tripmine,Mine,绊雷,地雷) is never a support kit (sentries and tripmines are out), while OtherToolsAreSupport (default true) counts any other non-empty tool. Both bonuses are hard-clamped to 1.3.

The vanilla blocks the board replaces are moved off the page and then switched off, re-applied on every frame the page is up. Deactivating alone is not enough: both result pages animate their content in over several seconds and switch it back on as they go, which is why the artifact inventory kept coming back — the same problem StatDisplay documents in Patches/PagePatches.cs (// HACK - Want to set inactive, but it blinks in...) and works around the same way, by moving the element out of the page. On the success screen the artifact inventory, its summary line, the checkpoint usage and the four prisoner report cards (with the anchors they hang on) all go; on the failure screen the artifact inventory and its summary line. The success screen's two blocks are controlled by Results/ShowSuccessArtifacts (default false = hidden: the crystal/artifact block, its summary line and the checkpoint usage) and Results/ShowSuccessPlayerReports (default false = hidden: the four prisoner report cards and the anchors they hang on); the failure screen's block by Results/HideVanillaFailBlocks. Both success-screen descriptions carry the hint 「建议关闭显示以免影响观感」. Every block is restored exactly when its switch is turned on, without recompiling. No vanilla text object is written into and no report card's text is ever touched.

The failure board does not disappear when title measurement fails. It prefers the title bounds plus a 16px gap. Unavailable geometry uses an explicitly logged fallback at 34% of screen height plus 16px, below the title in the supplied 2560x1440 capture, not a measured edge. Known inactive or transparent headings still delay rendering; missing/unreadable components degrade rather than permanently hiding statistics. Known alpha still controls the whole board. The log reports each gate/fallback state once; transition timing requires gameplay evidence.

Results/ShowImpactBoard = false draws no board at all — there is no older statistics table to fall back to; the result page stays exactly as the game drew it.

Three things only the real game can settle (all guarded, all logged on first fire)

  • Human scans count rising tagging edges, and each scanner carries its own reveal ledger: a reveal is itself a sweep, so a new reveal always records one first mark and always adds one to the sweep count — a row can no longer show a first mark with a zero denominator, or two players both sweeping yet both reading zero. Bot scans are attributed from the bot's own scan action instead: EnemyScanner.BotTag is static and has exactly one caller, Player.PlayerBotActionUseEnemyScanner.UpdateStateScan() (a private instance method whose m_agent is that bot), so the plugin hooks that action, publishes its owner for as long as it runs and clears it on return. The one-metre proximity rule survives only as a fallback when that read fails, and it now reports how many tracker-carrying bots it saw and how close the nearest was instead of failing silently. Callback bursts are still grouped by game tag duration and bot figures still carry ~, because the static callback itself has no owner. Per-scanner first-mark accounting and the new attribution hook still need gameplay evidence.
  • A melee push never damages the enemy and never reaches the melee damage hook: it travels its own path (Dam_SyncedDamageBase.PushDamage -> Dam_EnemyDamageBase.ReceivePushDamage). The hook therefore sits on the push entry point and reads the enemy's attack state before the game processes the push, so a push that breaks an attack under way counts as one interrupt; the same enemy counts once per frame. Whether pushes fire as expected in real fights still needs gameplay evidence.
  • Supply efficiency is measured per use and averaged: ammo reads the pack's own storage around PlayerAmmoStorage.PickupAmmo, health reads the target's health around ReceiveAddHealth, and a remote sender is resolved through SNet.Replication.TryGetLastSender. Self-use counts, pickups never enter the denominator, and a use whose amounts cannot be read is skipped rather than guessed.

Language

General/Language accepts zh (default) and en, plus spellings such as zh-CN, cn and English; anything unrecognized falls back to Chinese. Column headers, result headings and statistic labels all follow it.

Density

General/Density accepts compact (default), normal and spacious. Each level scales row height, cell padding and column gap, and acts as a glyph-size factor:

Value Row height Cell padding Gap Font
compact (default) x0.80 x0.88 x0.50 x0.90
normal x1.00 x1.00 x1.00 x1.00
spacious x1.30 x1.12 x1.25 x1.10

Which fields are shown

The [Columns] section toggles each data column: ShowDamage, ShowFriendlyFire, ShowAccuracy, ShowWeakspot, ShowProjectiles. The player name is the row label and is always drawn. A hidden column contributes neither width nor header, so the table immediately becomes narrower.

Position and size

The table hangs under the native flashlight icon in the right-hand gear column. The icon's screen rectangle is measured through the HUD's own camera, sampled every 0.25 s until it holds still for a second (the HUD animates while a level loads, and a single sample on the first frame lands hundreds of pixels away - that is exactly what earlier versions got wrong), and then frozen: the table is placed against it and never sampled again, so it follows the icon at any resolution without inheriting any motion. It is re-sampled only on a hot reload or a resolution change, and it is clamped to the window so every column is always visible.

[Layout] AnchorToFlashlight = false falls back to PositionX/PositionY, a fraction of the window where a negative value measures from the right/bottom edge. OffsetX/OffsetY is a pixel nudge on the final position, FontSize is the glyph size at 1080p, and [Results] OffsetX/OffsetY moves the result table. All of it is hot reloadable, so position and size can be tuned without leaving the game.

Config hot reload

The plugin watches its own config file and the scoring file. Edit and save BepInEx/config/HawChow.GTFOCombatPanel.cfg or CombatPanel-config/scoring.json while playing and it applies about 0.3 seconds later: language, density, column toggles, font size and position, the ten weights and the support-tool rules all recompute without restarting the game. Each reload logs:

[HOTRELOAD] config reloaded; language=...; density=...; columns=...; the table is rebuilt and repositioned from the new settings.

BepInEx 6 does not watch config files itself, so the plugin uses ConfigWatcher.Watch, which opens two FileSystemWatchers (one for the .cfg, one for the scoring file's directory) sharing a single 300 ms debounce flag, and performs the reload and every Unity object touch on the main thread. Windows' FileSystemWatcher occasionally misses an event; saving the file again applies it.

Statistics

Actual gun/shotgun Fire and pellet ray casts are tracked for the local player and host-controlled bots. Penetration contacts count once per pellet. Accuracy is hits/fired pellets; weakspot rate is weakspot hits/hit pellets, matching Hikaria. Friendly fire does not improve enemy hit rate.

Remote-human host statistics are vanilla-network estimates, marked ~; the original damage packets have no unique pellet ID. Non-host clients only show their own observable data. Damage is actual health lost; source-less explosions are not attributed. Per-weapon damage for unmodded remote players may be misattributed during quick weapon switches.

Results

The success and the failure screen both get the influence board, drawn on top of the page in window pixels, so the vanilla report cards are never rewritten or repositioned. Results/ShowImpactBoard = false draws no board at all — there is no older per-weapon table to fall back to (it was removed); the page stays exactly as the game drew it. When the board is drawn it is scaled down and clamped if it would outgrow the window.

New expeditions reset counters; completion, failure and checkpoint retries retain them.

Verified for 1.15.0: installed-Interop compilation at 0 warnings / 0 errors and 678 regression assertions (665 in 1.14.0). The squad exchange registers a custom snapshot type with the game's own network layer, so a host and a client that both run this mod share the figures a client cannot observe for itself: the local row is never overwritten, an unobserved figure stays a dash, and a host that does not run it exchanges nothing at all. New coverage in 1.13.1: a Steam name carrying TextMeshPro markup (<#fa0>"<#0ff>A1pha<#f00>", read from a live log) is cut to its visible text ("A1pha) instead of the literal tag prefix, <br> cannot split a table row, a name made only of markup prints nothing instead of throwing, a stray angle bracket stays literal, and an unrecognised tag is left alone. Carried from 1.13.0: the success screen's usable strip at 1920x1440, 2560x1440 and 1280x720 with all four players kept and the glyphs scaling into the room; per-weapon attribution that follows the gear category a damage record carries, including a modded weapon with no archetype id of its own and the ambiguous case that falls back instead of guessing; accuracy confidence monotonic across 50/60/70/80/90 percent at one sample and a three-pellet perfect rate that cannot outweigh 80 percent over a hundred; the rescue floor and the sentence it withholds; the previous published weight list migrating while a hand-edited one is left alone; and no unobserved figure ever printing as a fabricated zero, NaN or Infinity. Confirmed in gameplay twice: on 2026-10-14 from a live session log plus a 2560x1440 capture (evidence from 1.12.1, influence board 5 columns x 26 rows (4 players), 757x513 px at (902,506)), and on 2026-10-15 20:06 from a live 1.13.0 session on an r2modman profile (871x603 px at (844,506) of a 2560x1440 window, failure screen) — in both, the failure heading could not be measured (menuCamera=False, rect=(0,0,0,0)) so the logged screen-space fallback is what placed it. Native CJK font creation succeeded (table font: 'Microsoft YaHei UI' loaded from the operating system (CJK capable)). The same 1.13.0 session also credited kills, staggers, downs and lone survivors and logged eleven Fire captured lines. Still unverified: where the success strip lands in a real transition, gear-category attribution for EWC-managed guns, what a client shows when the host does not run the mod, and failure-page alpha synchronisation.

Feedback

Bug reports, feature requests and anything else: https://qingju.me


1.16.2(本地,未发布)

本轮未发布。构建零警告零错误,920 条面板回归、9 条文件监听回归,隔离原生 Blink 探针 NATIVE_BLINK_PASS completed=2/2。完整结算页面和实体 F8 未实测,交付记录见 reports/release-1.16.2.md。

设置表(当前真实存在的键)

.cfg = BepInEx/config/HawChow.GTFOCombatPanel.cfg:

段 键
[General] Language、Density
[Panel] ShowPanel、ToggleKey
[Columns] ShowDamage、ShowFriendlyFire、ShowAccuracy、ShowWeakspot、ShowProjectiles
[Layout] AnchorToFlashlight、PositionX、PositionY、OffsetX、OffsetY、FontSize
[Results] ShowResults、ShowResultOverlay、OffsetX、OffsetY、ShowImpactBoard、ShowSuccessArtifacts、ShowSuccessPlayerReports、HideVanillaFailBlocks
[Scoring] ScoringFile、Weights(回退值)、SupportToolNames、SupportExcludedTools、SupportOtherTools、SupportRoleFirepowerBonus、SupportRoleUtilityBonus、SplitKillTiers、KillMinorMaxHealth、KillEliteMaxHealth、StaggerNeedsAttack

评分文件 CombatPanel-config/scoring.json(与 DLL 同目录,绝不裸放;只在缺失时创建一次,非法时保留上一次有效配置并回落 .cfg,绝不覆盖用户文件,写入原子):Guidance、Weights、EnabledMetrics(null = 全开)、MinorHealth(默认 120)、EliteHealth(默认 1000)、MinorKillWeight / EliteKillWeight / BossKillWeight(三档击杀的相对价值,默认 0.5 / 1 / 1.5,必须单调不减即 小怪 ≤ 精英 ≤ boss;Weights 里的 Kills 是整行权重,三档值是其内部的相对价值,两者相乘)、FirepowerBonus(默认 1.15,≤1.3)、UtilityBonus(默认 1.0,≤1.3)、SupportToolNames[]、ExcludedTools[]、OtherToolsAreSupport(默认 true)、RescueFloor(默认 2)、AccuracyConfidencePellets(默认 20)。

逐条改动

  • 同步(A/B):快照只进独立缓存,本机账本不动;显示走 CombatSync.DisplayRows() 克隆本机行再逐字段合并远端数据(CombatMerge.Project / ApplyRemote),覆盖而非累加,未观测字段不清零,重复注入不翻倍。客机不再用「看到的掉血」估全队总量(EnemySetHealthPatch、ObserveSetHealth、_damageFromHealth 已删除);主机没装模组时客机伤害/份额显示 —(不再是带 ~ 的近似值),装模组的主机一开口其实测总数立刻接管。F8 写回 ShowPanel + ShowResultOverlay 并保存 .cfg。
  • 扫描(C):每个 scanner 带自己的揭示账本,一次揭示本身就是一次扫描,新揭示一定记 1 次首标并补记 1 次扫描次数。
  • 评分(D/E/F):十项默认权重 1.2,1.8,0.9,0.35,0.55,0.35,1.0,1.1,0.6,0.7;火力补偿默认 1.15 且只作用于总伤害与硬直阻止,效用默认 1.0,两者硬夹 1.3 上限。辅助判定:胶枪 / C-Foam / 生物追踪仪 / 开锁器算辅助,哨戒炮与绊雷排除,其余非空工具由 OtherToolsAreSupport 算辅助。每个客户端用自己的 JSON 算自己的分,主机只同步原始计数。
  • 热重载(G):.cfg 与 scoring.json 各一个 watcher,共用 300 ms 去抖,主线程重载。
  • 结算页开关(H/I/J):成功页两个开关改成肯定式、默认关闭——ShowSuccessArtifacts(默认 false = 隐藏红晶体/神器栏、总结行、检查点消耗)与 ShowSuccessPlayerReports(默认 false = 隐藏四张囚犯报告卡及其锚点);ShowImpactBoard = false 时影响板整块不画、没有旧统计表回落(PrepareStatistics 已删除),是否绘制由 OverlayPolicy.Draws 判定。
  • 失败页对齐(K):失败页与成功页共用同一条安全区,任务失败 底部对齐成功页完成块底部(FailureAlignment),用相同的 ResultsRegion.Resolve / ResultsPlacement.TryFitBetween 放板,底部永远在按钮之上,塞不下就不画。
  • 总伤害带占比(L):指标区「总伤害」行写成 1234.5 (23.4%),占比仍沿用 ~ 估算标记。

已知未验证

Verified: 920 panel assertions, 9 file-watcher assertions and the isolated native BlinkIn completion/config-toggle probe. Not gameplay-verified: the full success-screen sequence, physical F8 input on either result page and multiplayer host/client paths. Saved configuration is preserved; ShowResultOverlay=false intentionally hides the board until F8 or a config reload enables it.

Thunderstore development is made possible with ads. Please consider making an exception to your adblock.