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

Details

Date Uploaded
3 days ago
Downloads
333
Size
3.5MB

CombatPanel 1.17.8

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.17.8 is a documentation fix: the code and behaviour are identical to 1.17.7. It corrects the "not published" wording in the 1.17.7 note and records the publication (the uploaded package is byte-identical to the local build; 1.17.2-1.17.6 were never published on their own, their content is all in 1.17.7). If 1.17.7 is already installed, there is nothing to update for. Below: what 1.17.7 actually changed. 1.17.7 stops a projectile weapon from always reading 100%. ProjectileAPI.OnProjectileHit's second argument - what the projectile actually hit - was ignored, so a wall, a floor and a ceiling each counted as a hit: in one run the turbo plasma gun read 529 fired / 529 hit for one player and 92 / 92 for another, both exactly 100%, and the player's own hit rate was lifted to ~82.5% with it. Only an impact on an enemy counts now, under the same "scenery is not a target" rule the damage path already uses (a container lock or a door lock never counts). Hits still arrive from the damage records as a second, independent source, so nothing is lost - only the miscounts. 1023 assertions; reports/release-1.17.7.md. Published as HawChow-CombatPanel-1.17.7. Below: 1.17.6. 1.17.6 stops deferring EWC weapons outright, and pins a weapon row's name. The last release read all four EWC streams, but BeginFire still skipped every EWC-managed weapon unconditionally, so those rows had exactly one source - EWC itself. The moment EWC says nothing about a (player, slot), the row can only read 0/0/0, which says nothing about whether the weapon was fired (that is what the HEL gun, the Desert Eagle and the turbo plasma gun did in the last run). The deferral is conditional now: EWC's cumulative totals stay the authority where it reports, and where it never has, the rays this build can actually see are counted and reconciled with EWC's own totals by max (Applied remembers what was already handed over, so nothing doubles). A shot whose rays cannot be seen is not estimated: no pellet count is invented from ammunition spent, because that would print a fabricated 0% hit rate - the row stays empty with a log line saying it is unobserved. The streams that were only held and never applied (FireShotAPI shots, ProjectileAPI and damage-record hits) now reach the panel while EWC stays silent, marked as an estimate, and every counted shot is registered as the attack under way so an explosion that follows it can be attributed - the 19.3-point blast this build used to drop had nothing to attribute it to. One display fix rides along: a row's name is set only by a source that really named that weapon, so a passive probe of a bot's backpack can no longer rename the row after the run ended - one archive said HEL左轮手枪 while the board drawn from it said 手枪. 1019 assertions; reports/release-1.17.6.md. Not published. Below: 1.17.5. 1.17.5 reads all four ExtraWeaponCustomization streams, and archives a session's second result screen too. EWC's accuracy API does not cover every weapon it manages: StatDisplay reads four streams (AccuracyAPI, FireShotAPI, ProjectileAPI, DamageAPI) and this mod subscribed to one, which is why a weapon such as the turbo plasma gun sat at 0/0/0 pellets and 0.0 damage for a whole run. All four are read now and reconciled by max, so one shot reported by two streams still counts once, and vanilla explosion damage (ReceiveExplosionDamage), which had no hook at all, has one - the payload carries no owner, so it is credited through a short-lived "last attack" cache and never guessed at. A second defect is fixed from the log and the disk together: the archive's one-file-per-screen latch keyed on the page pointer alone, while the game keeps its result pages alive for the whole session - so the second failed run of a session wrote no file and logged nothing (two failure screens in one Survival new session left a single json, and not the run in the screenshot). The key is the page plus this machine's run counter. 1009 assertions; reports/release-1.17.5.md. Not published. Below: 1.17.4. 1.17.4 fixes why a client saw only itself, and stops the board covering the host's own button. The client log behind this release showed the exchange knowing the host from the roster while receiving not one packet all session — the in-level table was the header plus itself. Two causes, both in the sync layer this mod added on top of the game's channel: (1) sending used to wait for the peer to answer, and with that return path dead nothing ever arrived; nothing gates delivery now, which is what the proven mods on this game do (SenderMode.OtherPlayers pushes without asking). (2) the receive prefix could be swallowed by another mod — the same client carried four prefixes on that method, and mine may have been last; it now reads first (Priority.High). A third, real defect is fixed too: the handshake had a rule for a peer whose run counter goes backwards, but the snapshot cache — a second latch on the same counter — did not, so a peer that restarted was refused for the rest of the session (a controlled experiment in the suite pins this down). On the result screens, the host's own "send every prisoner back to the lobby" is a vanilla button this mod failed to measure, and an unmeasured button used to mean a strip running to the bottom of the screen; every button on the page is measured now, and when none can be read the strip still stops at 0.66 of the window. 1002 assertions. Not published. Below: 1.17.3 and 1.17.2.

1.17.3 fixes "the settlement board shares, the in-level table does not". The cause was neither the display nor the transport: it was the handshake layer this mod added on top of them. Greetings had a cap (one every 2 s, five in total) and a peer that had not answered by the last one was written off for the whole run — so if the other machine was still on the loading screen during those ten seconds, or a greeting was dropped, the two exchanged nothing at all, which from either screen is indistinguishable from a build that cannot share. Greetings now only slow down instead of stopping (2 s for the first ten seconds, then every 10 s), and whether rows are sent is decided by whether the peer has ever proved it runs this mod (EverAnswered) rather than by whether it spoke in the last twelve seconds — the old rule silently cut off a guest that could still receive perfectly well. The host check on incoming rows was widened too, so a roster that knows who the host is no longer depends on the peer's own flag. Two once-per-level log lines (squad sync outbound: / squad sync inbound:) now say which direction is blind. 1001 assertions. Not published. Behind this, 1.17.2's EWC accuracy repair and the plate key below. 1.17.1 in-level plate: the in-level table's own dark plate is now [Layout] PanelOpacity (default 0.28, was a hard-coded 0.48). 0 lets the game show through untouched, 1 is a solid plate; the hairline frame is scaled by the same ratio so a lighter plate does not end up as an outline around nothing. It is read on every draw, so an edit or a hot reload is on screen the next frame, and both the startup line and the [HOTRELOAD] line print panelOpacity= so the value in force is visible in the log. The settlement board keeps its own approved plate and is not affected by this key. Behind that, 1.17.0's multiplayer exchange: a headless SquadExchange with every machine greeting every human peer, an answer re-sent on the cadence instead of once, and a peer whose run counter came back smaller believed after three refused packets.

1.17.2 fixes EWC weapon hit rates reading —. ExtraWeaponCustomization publishes one accuracy update when a weapon fires and a separate one when a pellet lands — and that second update carries hits with a shot count of zero. The previous build read every update as one complete shot and returned early whenever it carried no shot, so the hit half was thrown away: shots climbed, hits never did. This build reads EWC's own cumulative per-weapon totals instead (they are already updated before the callback fires), so both halves land, a cancelled shot's negative count is consumed as the correction it is, and each weapon locks onto one of EWC's three levels so pellets and hits are always the same kind of counter. A hit this machine saw and EWC never counted still reaches the row, reconciled by max bounded by the shots EWC says were fired — it can only fill a gap, never inflate. Also here: a client whose host runs no copy of this mod measures its own blows locally (min(damage, health left), marked ~), because the game never sends a machine its own damage back — that is what puts a client's own damage, share and kill credit back on its own screen — and in that topology the squad share is summed from the rows the exchange actually reached and marked as an estimate rather than shown as —. 995 assertions.

Test status. 1.17.1 has been run in game by the author and the first look is good — the table reads against the level, the settlement board is unchanged and the plate key takes effect on save. Multiplayer feedback is still open: the three squad topologies (a host with this mod, a host without it, and a squad of two with this mod behind a host without it) are asserted headlessly and have one earlier live log to back the transport, but a real 4-player run is what settles them. Anything you find goes to qingju.me or the voice channel below, and this page will be updated with what comes back.

1.17.2 test status. Two fixes are in this build and neither has a live run behind it yet: the EWC accuracy repair (the empty hit-rate column) and the local damage fallback for a client whose host runs no copy of this mod. The EWC fix rests on EWC 4.13.4's own decompiled semantics plus headless assertions, not on an in-game Deagle test; the fallback is the same split Dinorush's StatDisplay makes, and it refuses every case where the blow is already accounted for. A 1.17.2 live log will settle both, and this page will be updated with what comes back.

~ 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.17.2(本地,未发布)

EWC 武器命中率恒为空是本版主线:InitShot 与 AddHit 是两次独立回调(命中那次 Count = 0),旧实现按「每次回调 = 一发完整结果」读并提前返回,命中被整段丢弃。现改读 EWC 自身的累计统计(Shots / FullShots 的 Count/Hits/Crits),每把武器在第一次回调时锁定一层并保持,CancelShot 的负增量作为更正消费;本地观测按 (玩家, 槽位) 记账并用 max 对账(只填缺、不翻倍)。另修:弱点读取改用记录里的 limbID(旧代码用错误的类型转换,永远读不到);无模组主机下客机新增本地输出端伤害兜底(min(这一击伤害, 剩余生命),标 ~),该拓扑的小队占比不再恒为 —。995 条断言通过,记录见 reports/release-1.17.2.md。验证边界:EWC 修复由 EWC 自身反编译语义与无头断言支撑,尚未在装了 EWC 的沙鹰上实机复现;无模组主机的客机兜底也待实机联机确认。

1.17.1(已发布)

局内面板底板改为可配置:[Layout] PanelOpacity(默认 0.28,旧值是写死的 0.48)。981 条面板断言通过,包校验 VERIFY: PASS,记录见 reports/release-1.17.1.md。1.17.0 的多人同步重写见 reports/release-1.17.0.md。

测试状态:作者已实机初步测试,观感与底板开关正常;多人联机反馈待补——三种联机拓扑目前只有无头断言与更早一次实机日志支撑,真正的 4 人局结论以回传反馈为准,收到后会更新本页。

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

.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、PanelOpacity
[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.