Skip to content

Major accessibility overhaul: redesigned menus, column painting, info cards, DLC support, and dozens of fixes#106

Draft
aaronr7734 wants to merge 213 commits intoAccessMods:masterfrom
aaronr7734:next-pr
Draft

Major accessibility overhaul: redesigned menus, column painting, info cards, DLC support, and dozens of fixes#106
aaronr7734 wants to merge 213 commits intoAccessMods:masterfrom
aaronr7734:next-pr

Conversation

@aaronr7734
Copy link
Copy Markdown
Collaborator

This is a big one. Four rounds of changes spanning most of the mod's feature surface — schedule and assign menus rebuilt from scratch, column painting generalized across all supported tables, universal Alt+I info cards, a full tile info overhaul, and a ton of DLC features (Biotech gene inspection, mechanitor control groups, growth moments, Royalty permits, Ideology roles, Odyssey fishing zones). There are also significant improvements to the game setup flow, bill configuration, policy editors, quest system, colonist bar navigation, and the scanner.

Bug fixes touch everything from soft-locks on Biotech dialogs to keyboard layout issues, Escape key isolation, slider direction consistency, and speech sanitization. A detailed changelog and updated documentation will be included before this PR leaves draft.

Add a complete multi-select system that lets blind players select
multiple pawns and issue group commands, eliminating the need to
individually select and command each pawn one at a time.

Selection mechanics:
- Alt+Shift+Left/Right: contiguous selection (extend to adjacent pawns)
- Alt+Space: toggle individual pawns in/out of selection
- Alt+Ctrl+Space: select all colonists / clear all selection
- Alt+Ctrl+F1-F5: save selection to persistent group (survives save/load)
- Alt+F1-F5: recall saved group

Navigation in multi-select mode:
- Comma/period, Alt+Left/Right, Alt+1-0, Alt+Up/Down all move focus
  without changing the selection set
- Camera does not follow focus navigation
- Focused pawn announced with selected/not-selected status and job
- Ctrl+Alt reorder blocked during multi-select

Group commands:
- R key drafts/undrafts using vanilla InheritInteractionsFrom behavior
  (only toggles pawns with same state as first pawn)
- G key always shows selected pawns' gizmos in multi-select mode
- Gizmo grouping via GroupsWith/MergeWith mirrors vanilla GizmoGridDrawer
- Gizmo execution propagates to grouped gizmos via InheritInteractionsFrom
- Right bracket (]) context menu works automatically via Find.Selector
- "Go here (formation)" option added for line formation movement

Line formation placement:
- Two-point placement with Space bar (first point, second point)
- Enter to confirm, Escape to cancel
- Pawns distribute evenly along the line in colonist bar order
- Uses RCellFinder.BestOrderedGotoDestNear for valid destinations

Action feedback (job-diff system):
- Snapshots pawn jobs + queue counts before action, compares after
- Announces using shorter-list logic:
  "Everyone drafted" / "Everyone except Bob, Sarah drafted" /
  "Only Carol, Dave drafted"
- Detects targeting mode activation for attack commands
- Post-targeting summary in TargetingPatch for Squad Attack

Pawn info shortcuts with multi-select:
- Alt+H/M/N/K/G/B: cursor pawn takes priority, then pawn picker menu
- Alt+C: pawn picker menu for camera jump
- Menus support typeahead search

Persistent groups (MultiSelectGroupComponent):
- GameComponent with 5 slots, auto-discovered by RimWorld
- Saves/loads pawn references via Scribe_Collections LookMode.Reference
- Filters to current map on recall, announces unavailable pawns

Localization:
- Uses game translation keys where available (GoHere, Everyone)
- Properly strips XML tags via implicit string cast on TaggedString
…ment

Replace manual line distribution math with the game's own controller,
which handles duplicate-cell prevention, mech command range checks,
exit map handling, and proper job creation via PawnGotoAction.
- Change permadeath from toggle to two radio button options (Up/Down)
- Add MenuHelper for navigation (respects WrapNavigation setting)
- Add TypeaheadSearchHelper for storyteller and difficulty search
- Add Home/End navigation for all three sections
- Add position announcements (X of Y)
- Auto-select first item on first visit to each section
- Simplify mode announcements (removed verbose help text)
Add accessible custom difficulty settings navigation during character
creation. When selecting "Custom..." difficulty, press Enter to open a
2-level menu for editing all difficulty settings.

Features:
- Navigate sections with Up/Down, Enter to drill into settings
- Adjust sliders with Left/Right arrows
- Toggle checkboxes with Enter/Space
- Typeahead search in both sections and settings
- Alt+R to jump directly to playstyle reset section
- Escape to go back or exit to difficulty selection

Refactored difficulty settings into shared DifficultySettingsHelper
used by both character creation and in-game storyteller selection,
removing ~550 lines of duplicate code.
- Add MapSize and StartingSeason to unified field menu with (Advanced) label
- Sync slider values from game on init (fixes navigation starting from wrong position)
- Use MenuHelper for navigation respecting user settings
- Add TypeaheadSearchHelper for field search
- Add Home/End navigation
- Support all 7 slider values for Rainfall/Temperature/Population/LandmarkDensity
- Add dev mode options: 5% coverage with (dev) marker, extra map sizes
- Use game translation strings for all labels and warnings
- Add warnings for 100% coverage, large maps, and winter start
- Values clamp instead of wrap
When using PageUp/PageDown to cycle through pawns in the scanner,
the announcement now includes the pawn's current activity (e.g.,
"cooking four lavish meals", "sleeping"). Respects the existing
ShowPawnActivityOnMap setting.
- Add FactionsNavigationState for faction list and add menu navigation
- Tab/Shift+Tab toggles between World Params and Factions sections
- Up/Down/Home/End navigates faction list with typeahead search
- Alt+A opens add faction menu, Delete removes selected faction
- Faction announcements include full description with xenotype info
- Block Enter/Escape appropriately to prevent unintended page navigation
- Fix storyteller screen to show position on initial entry
When the cursor moves to a door or when cycling through doors in the
scanner, the announcement now includes whether the door is open or
closed (e.g., "wooden door (open)" or "autodoor (closed)").
- Add fish species and population to key 2 (terrain info) when Odyssey DLC
  is active, matching what sighted players see in the mouseover readout
- Add colons after stat labels in terrain info (beauty:, cleanliness:, path cost:)
- Update light info (key 4) to show percentage with translated label
  (e.g., "light: 50% (Lit)") matching sighted player display
- Add temperature label to light info for consistency
- Create FishingZoneMenuState for keyboard navigation of fishing zone settings
- Support repeat mode, target count, minimum population, and fish species list
- Add tooltips from game UI for fish population and minimum population fields
- Integrate with inspection system via TabRegistry and InspectionTreeBuilder
- Register in KeyboardHelper to properly block conflicting keys
- Add InfoCardState check to yield control when viewing fish info cards
- Use rounded fish population values for consistency with game display
The game plays a sound when drafting and undrafting pawns via the UI.
Now the mod's R key shortcut plays the same sound.
Previously, activating a Dialog_NodeTree option that navigates to a
sub-dialog (like 'Request trade caravan' -> trader type selection)
would immediately close the dialog instead of showing the sub-menu.

Now correctly checks DiaOption's resolveTree, link, and linkLateBind
fields to determine whether to close the dialog or refresh to show
the new dialog node's options.
…controls

Ability Targeting:
- When targeting a psycast, announces ability name, range, AOE radius, psyfocus cost, and neural heat
- Press R during targeting to check distance to cursor and line of sight status
- Press T during targeting to hear which pawns are in the area of effect (AOE abilities only)
- Clear error messages for out of range, no line of sight, psychic immunity, and missing targets
- Support for two-phase abilities like Skip (target selection then destination selection)
- World map targeting support for abilities like Farskip

Neural Heat Gizmo:
- Renamed "Psychic Entropy" to "Neural Heat" (the player-facing term)
- Gizmo now shows limiter state: "Neural heat: 0/80, Psyfocus: 39%, Limiter: ON"
- Press Enter on the gizmo to toggle the neural heat limiter on/off

Ability Gizmo Improvements:
- Psycast gizmos now announce their costs when browsing: "Vertigo pulse (M). Costs 2% psyfocus, requires 50%, Neural heat: 30"
- Shows both the psyfocus cost (consumed when casting) and minimum required (band threshold)

Bug Fixes:
- Fixed rich text tags appearing in letter/message announcements (e.g., "(*Name)Mikaela(/Name)" now shows as "Mikaela")
- Fixed targeting using mouse position instead of accessibility cursor position
…criptions

Animal Attack Target (Odyssey DLC):
- Announce range from master when entering targeting mode
- Press R during targeting to check distance to cursor and range status
- Pre-validate range before executing, with clear error on out-of-range
- Keep targeting open on range failure so user can adjust cursor and retry
- Detect attack commands by icon match against game's AttackTargetTexture
- Use game's Pawn_TrainingTracker.AttackTargetRange const for range value

Ability Gizmo Descriptions:
- Psycast gizmos now include the spell description after name and cost
- Pull description from ability.def.description instead of Command.Desc
  (which is only populated during mouse hover rendering)
- Reorder announcement: name, hotkey, cost, then description

Dialog Tree Navigation:
- Fix dialog option close logic to not depend on resolveTree flag
…and destination phase tracking

- Detect all ability verbs via IAbilityVerb interface (not just Verb_CastAbility),
  supporting Verb_AbilityShoot and modded ability verbs automatically
- Add self-cast feedback: abilities with targetRequired=false now announce
  "Casting {name}" instead of producing no feedback
- Track destination phase for dual-target abilities (Skip): R key reports
  distance from selected target using destination comp's range
- Handle touch/zero-range abilities: announce "Touch range" instead of
  misleading range values, skip range pre-validation
- Add range and effect radius to ability gizmo descriptions (after cost,
  before description), with world map and self-cast variants
- Add affected pawns announcement for world-targeting AOE abilities (Farskip)
- Fix location-targeting AOE abilities (Solar Pinhole) showing misleading
  "No pawns in radius" on target confirmation
- Use verb.EffectiveRange for range calculations (respects equipment bonuses)
- Separate cost stats with periods for natural screen reader pauses
- Move hotkey to end of ability announcements
- Add animal attack range to gizmo description
- Clean up stale I key documentation from Abilities CLAUDE.md
Check if announcement already ends with a period before appending
period-separated sections for attack range and ability info.
Toggle gizmos previously only announced label and ON/OFF state.
Now includes the tooltip description that sighted players see on
hover, giving screen reader users the same context (e.g., "Animals
attack: OFF. While this is on, mastered animals are sent forth to
attack distant targets. While it is off, they remain near and guard
the master.").
- Add Predator column and sort predators to top (closes AccessMods#74)
- Replace mod-added columns (Age, BodySize, Health, Pregnant) with
  vanilla columns (ManhunterOnDamage, ManhunterOnTameFail)
- Add column tooltips announced only on left/right navigation, not
  up/down, for both wildlife and animals menus
- Add jump-to-animal on Enter for wildlife Name column
- Fix Hunt/Tame columns showing empty text by using Yes/No translations
… tree enrichments

Add Alt+I keyboard shortcut across all major menus and states to open
info cards for the currently selected item. This brings RimWorld's
hyperlink-based info card system to screen reader users who previously
had no way to access it.

Info card access points (15 contexts):
- Map cursor: opens info card for thing/terrain at cursor position,
  with float menu selection when multiple things occupy the same cell
- Architect menu: opens info card for selected building designator
- Plant selection menu: info card for selected plant def
- Storage settings: info card for selected ThingDef filter item
- Bills menu: info card for selected bill's product
- Bill config: info card for the bill's produced item
- Gizmo navigation: handles Command_Ability (ability def),
  Designator_Build (building def with stuff), and fallback to owner
- Colony inventory: info card for selected inventory item
- Research menu: info card for selected research project
- Research detail: info card for project, unlocked items (things/recipes)
- Colony animals menu: info card for selected animal
- Wildlife menu: info card for selected wild animal
- Float menus: extracts Def from shownItem/iconThing/revalidateClickTarget
- Info card itself: nested info cards via vanilla's hyperlink system

Nested info card navigation:
- State stack (SavedCardState) preserves cursor position, expansion
  state, and visible items when drilling into a linked def
- Escape pops back to the outer card; final Escape closes entirely
- Multi-hyperlink stat entries present a float menu for selection
- closingFromAccessibility flag prevents PostClose from interfering
  with stack-managed transitions
- onlyOneOfTypeAllowed set to false to allow multiple Dialog_InfoCard
  instances on the WindowStack simultaneously

Info card tree improvements:
- Stat entry labels now include hyperlink def names inline (sighted
  users see clickable links; screen reader users now hear the names)
- "Inspectable" hint appended to announcements for stat entries that
  have hyperlinks, guiding users to press Alt+I
- Stat expandability checked upfront via GetExplanationText; stats
  with no explanation are marked non-expandable instead of showing
  "No items to show" on expand
- Empty-children edge case now corrects IsExpandable dynamically

Typeahead search fix (all menus):
- Alt+key combos no longer accidentally trigger typeahead search input
- Added !Event.current.alt guard to ~15 typeahead handlers across
  UnifiedKeyboardPatch, BuildingInspectPatch, StorageSettingsMenuPatch,
  PlantSelectionMenuState, WindowlessFloatMenuState, and others
- Info card typeahead switched from ev.character to KeyCode-based input
  (matching all other menus), with progressive backspace deletion and
  Up/Down match navigation during active search

Pawn data improvements:
- Xenotype genes now carry GeneDef reference for future info card use
- Melanin skin color genes get descriptive shade names (very light,
  light, fair, medium, tan, brown, dark brown) based on luminance
  calculation, replacing the generic "Skin color" label
- Health capacity tooltips now use HealthCardUtility.GetPawnCapacityTip
  (showing impactors: hediffs, body parts, genes) instead of the
  always-empty capacityDef.description
- Capacity tooltip text split into individual tree lines per impactor
- Skill passion labels changed from * / ** to readable text:
  "(Minor passion)" / "(Major passion)"

Permits system overhaul (Royalty DLC):
- Shows ALL permits per faction matching vanilla's PermitsCardUtility
  layout, not just the pawn's granted permits
- Status reflects actual state: Granted, Granted (on cooldown),
  Available (X points), Locked (requires prerequisite/title)
- Adds current title, unused permit points, and royal favor per faction
- Required title shown in permit detail expansion

Bug fixes:
- Zone rename category comparison uses literal "Rename" instead of
  translated string (fixes potential locale mismatch)
- Removed duplicate MenuHelper.GetLevelSuffix call in research detail
- Research detail announcements trim trailing punctuation before
  appending state/position info
- BillsMenuState passes ProducedThingDef to FloatMenuOption for
  info card extraction
- KeyboardHelper.IsBlockingPanelActive includes InfoCardState
- StorageSettingsMenuPatch returns early when InfoCardState is active
- ArchitectMenuPatch skips input when InfoCardState is active
- InfoCardDataExtractor extracts worldObject, hediff, titleDef,
  faction, and stuff fields for comprehensive title generation
- InfoCardTreeBuilder generates proper titles for WorldObject, Hediff,
  AbilityDef, RoyalTitleDef, Faction, and ThingDef+stuff combinations
Apply the same skin color descriptors (very light, light, fair, medium,
tan, brown, dark brown) from InfoCardDataExtractor to the unborn baby
gene tree. Skin colors now route through a dedicated DescribeSkinShade
method using perceptual luminance (0.299r + 0.587g + 0.114b) instead
of the generic brightness-based labels (medium-dark, etc.). Hair and
other cosmetic colors retain their existing hue-based descriptions.
Sighted players see fertility in the mouse-over tooltip but it was
missing from the key 2 terrain announcement. Now shows fertility
right after the terrain name, e.g. "Rich soil (140% fertility)".
- Move pregnancy approach menu after Relationship line instead of end
- Use game's i18n methods (GetLabel/GetDescription) for pregnancy approach
- Add CanEverProduceChild check with translated reason
- Consolidate duplicate certainty into single item with change rate
- Use translated labels (IdeoRoles, NoRoleAssigned, Effects, Certainty)
- Fix cursor jumping after role/pregnancy actions via parent walk-up
- Allow ITab_Genes through Hidden tab filter for gene inspection
- Add Alt+I to open info cards for gear, hediffs, and genes
- Show "Inspectable." suffix on items with available info cards
- Set LinkedDef on gene tree nodes for info card navigation
…tech DLC)

Integrates the Biotech DLC's "Try Romance" feature into the accessible
Social tab tree with keyboard navigation. Viable targets show success
chance percentages, Enter initiates romance, and Alt+I opens a factor
breakdown via StatBreakdownState. Gated behind Biotech DLC, pawn
eligibility, and Full inspection mode. Includes duplicate job guard
and Dialog_MessageBox passthrough for relationship warning dialogs.
Rearchitect starting site selection to share the same navigation
experience as the in-game F8 world map. World gen now supports the
scanner, Z search, number keys 1-5, 3D compass navigation, and
biome descriptions announced on biome change.

- Add WorldNavContext enum (InGame/WorldGen) to WorldNavigationState
- Add BiomeDescriptionTracker for MUD-style biome descriptions
- Add StartingSiteContext for world-gen-only features (I-menu, R random,
  Ctrl+arrows biome jump, faction warnings, tile validation)
- Rewrite StartingSitePatch to route through shared WorldNavigationState
- Add context guards to caravan/quest methods (InGame only)
- Relax ProgramState.Playing checks for scanner search and tile info keys
- Guard EARLY BLOCK and F8 handler with InGame context
- Remove StartingSiteNavigationState (migrated to shared system)
- Remove dead code: CycleToNext/PreviousSettlement, CheckOffRoadOrRiver,
  wasOnRoad/wasOnRiver fields, lastAnnouncedInfo field,
  BiomeDescriptionTracker.SetCurrentBiome, ValidateTileForSettlement
…ing filtering

Replace static DefDatabase recipe query with vanilla's ingredient-aware
filtering (PotentiallyMissingIngredients, Worker.AvailableReport, HasHediff)
so the operations list dynamically shows/hides recipes based on available
items on the map.

Modernize HealthTabState with MenuHelper navigation (Home/End, wrapping),
TypeaheadSearchHelper for recipe and body part lists, and streamlined
announcements without redundant instructional text.

Fix Enter key leak where ActivateAction continued running after OnActivate
closed the inspection state, causing a spurious "No items to inspect"
announcement.
…rgeting

Items like sentience catalyst and mech serums use CompUsable/CompTargetable
targeting which was silently ignored by the mod. Now announces targeting start
using the game's own mouse-attached label text, provides error feedback on
invalid targets, and handles multi-phase targeting (e.g., select colonist
then select animal).
aaronr7734 and others added 30 commits April 9, 2026 04:55
Gizmo navigation now supports two new interaction types accessible to
screen reader users:

- Right bracket on gizmos with RightClickFloatMenuOptions opens the
  options in WindowlessFloatMenuState, mirroring vanilla's grouped gizmo
  option merging from GizmoGridDrawer. Covers designators ("Designate
  All"), Command_Hide subclasses, and MechanitorControlGroupGizmo.

- Enter on Gizmo_Slider subclasses (fuel, hemogen, etc.),
  Gizmo_PruningConfig, and MechCarrierGizmo enters a slider adjustment
  mode. The Heat and Psyfocus gizmo uses right bracket instead since
  Enter already toggles the neural heat limiter. Left/Right arrows step
  one increment, Shift+Left/Right steps 5x. Plays SoundDefOf.DragSlider
  on each step to match vanilla audio feedback.

Announcements now include discoverability hints and the current
psyfocus target value alongside the existing heat/psyfocus status.

Also fixes input priority so WindowlessFloatMenuState takes keyboard
focus when opened over an active GizmoNavigationState.
- Reorder list item announcements to put content first, then type, then
  date, so screen reader users can stop listening once they've heard
  what they need.
- Change Alt+L and Alt+M filter toggle announcements from "Letters
  filter on/off" to "Showing/hiding letters" and "Showing/hiding
  messages" for clarity.
- Announce the Alt+L and Alt+M hotkey hints on tab entry so users know
  how to toggle filters.
- Reorder tab switch announcements so the tab name is spoken first,
  then the current item, then the hotkey hint.
MinifiedThing overrides LabelNoCount to delegate to InnerThing, but
does not override LabelNoParenthesis. The scanner used LabelNoParenthesis
for clean announcements (no health/quality suffixes), so every minified
item was announced as the generic "minified thing" from the MinifiedThing
def — hiding Royalty monument markers, uninstalled beds, tables, and any
other minified buildings from both the 'z' search and page navigation.

Add a GetNoParenthesisLabel helper that unwraps MinifiedThing and returns
the inner thing's label. Apply it in both ScannerItem Thing constructors
and in RefreshLabel (which re-derives the label before every announcement
and would otherwise clobber the constructor-set label).
Adds a Shift+T shortcut that announces the current time speed, actual vs
target ticks per second, and percent of requested speed, mirroring
RimWorld's internal TPS counter formula. When a combat threat forces the
game to normal speed, the announcement includes a "slowed by threat"
clause so the speed-name and TPS numbers don't look inconsistent.

In-game messages are also emitted on both OFF-to-ON and ON-to-OFF
transitions of TimeSlower.ForcedNormalSpeed, but only when the user's
set speed is faster than Normal (otherwise the slowdown has no
observable effect). Messages are auto-announced by the existing
NotificationAccessibilityPatch.

Also replaces hardcoded English labels in TimeAnnouncementState with
RimWorld's vanilla translation keys where they exist (ClockTime,
ClockDate, Weather, AM, PM, Period1Day/PeriodDays) so non-English
players hear more of the announcement in their own language. Labels
without vanilla keys (Season, Days passed, error strings) remain in
English.

TimeControlAccessibilityPatch exposes LocalizedSpeedName as an internal
static helper so PerformanceAnnouncementState can reuse the single
TimeSpeed-to-display-name mapping instead of duplicating the switch.
- Change PrismConfig from 16 bytes to 1 byte to match Prism v0.11+ header
- Remove obsolete platformPointer field
- Add debug logging for config version

Fixes prism_init returning null context on all platforms.
fix: correct PrismConfig struct size for v0.11+ API
User-visible improvements:

* New "All" top-level category at the very top of the scanner. Lists
  every scanner item on the map in one closest-first list. Useful for
  "show me the closest thing of any kind near me right now."

* Every category also gets an "All" subcategory by default. Pressing
  Ctrl+PgDn now lands on the All view of each category, and Shift+PgDn
  drills into the specialized buckets when you want to filter further.

* Pawns are split by faction relationship: Colonists, Prisoners, Slaves,
  Guests, Hostile, Player Mechs, Hostile Mechs. Raids during friendly
  visits no longer mix raiders and visitors into one bucket. Visiting
  traders, quest lodgers, and allied raid help all live in Guests.

* Mud, moss, riverbank, lava, volcanic rock, flesh, space, and many
  other natural terrain types now show up in the scanner. The terrain
  filter no longer relies on hardcoded English defNames — it uses
  fertility and pathCost so DLC and modded terrain are caught
  automatically.

* Dead pawns and destroyed items are now detected mid-navigation and
  removed silently with an "Item no longer exists" notice, instead of
  being announced as still alive until you switch categories.

* Standing on a rich-soil patch, in your bedroom, or on a stockpile
  zone now correctly reads "here" instead of pointing miles away at
  the geometric center of the area. Distance to area-backed items
  uses the nearest cell.

* Multi-region terrain (e.g. 63 separate marble patches as one item)
  now describes the specific area you're on as "10x10, here, area 1
  of 63" — matching the format used when you Alt+PgDn through them —
  instead of misleading totals like "290 tiles, here".

* Page Up/Down preserves your position when the list re-sorts. Moving
  the cursor a few tiles or watching things shift positions no longer
  shuffles the list and resets you to the closest item. You stay on
  whatever you were looking at, and the next press advances normally.

* The list also re-sorts on every navigation press to catch moving
  things (pawns, animals) updating their positions in real time. This
  one is marked experimental in the code and may be reverted if it
  feels worse during play.

* Sort tie-breaking is now stable, fixing a bug where two items at
  exactly equal distance (e.g. rich soil and dandelions both 11 tiles
  south) would swap places on every press, trapping the user bouncing
  between them.

Refactor / code health:

* Extracted four new helpers from the scanner to eliminate duplication
  and god-method clutter:
    - ScannerLabelBuilder — label formatting (was duplicated 3x)
    - ScannerDirectionHelper — compass math (was duplicated 2x)
    - ScannerSearchEngine — generic match-type filter (was duplicated
      4x across map/world × update/refresh)
    - ScannerCategorySchema — declarative category structure +
      ScannerBuckets lookup helper, replacing 150+ lines of manual
      "new ScannerCategory(...) / new ScannerSubcategory(...)" boilerplate

* CollectMapItems is now driven by ScannerCategorySchemas.All, so
  adding a new category is a one-line schema entry instead of editing
  five places.

* Switched in-place List.Sort to OrderBy.ToList() in the cursor-aware
  re-sort paths. List.Sort is documented as unstable, which is what
  caused the tied-item bouncing bug above. OrderBy is stable.

* Added an AddTo helper that centralizes the "specialized + All"
  pattern so every item added to a specialized subcategory also lands
  in the category's All subcategory automatically.
The combat triage summary was recently expanded to list every capacity
that wasn't at 100%, which produces noisy announcements when chronic or
minor capacities are affected. Restore the previous behavior of showing
only the three capacities that matter most for combat decisions — sight,
manipulation, and moving — so the readout stays focused on actionable
triage information.
Integrate the PositionalFootsteps mod into RimWorld Access as an
event-driven spatial audio system. Pawns produce footstep sounds when
entering new tiles via a Harmony Postfix on TryEnterNextPathCell,
replacing the original polling MapComponent for better performance.

Features:
- Terrain-aware human footsteps (dirt, stone, wood, metal, carpet,
  bridge, water, snow)
- Animal footsteps (light and heavy categories by body size)
- Mechanoid footsteps
- Stereo panning based on screen position relative to camera
- Distance-based volume attenuation with near/mid/far field zones
- Low-pass filter for distant sounds, reverb for enclosed rooms
- Zoom-level volume scaling
- Per-category volume sliders (human, animal, mechanoid)
- Master "Enable Sound Effects" toggle for vanilla-experience players
- All settings accessible via the keyboard-navigable options menu
- 80 OGG sound files deployed as loose assets for ContentFinder

Based on original work by OlegTheSnowman (https://github.com/OlegTheSnowman)

Co-authored-by: OlegTheSnowman <OlegTheSnowman@users.noreply.github.com>
Unify terrain sound mapping using TerrainAudioHelper's 100+ defName
dictionary instead of the footstep mod's 8 substring patterns. Metal
tiles now get metal footsteps, snow/ice get snow footsteps.

Add wall-based sound occlusion using RimWorld's region/room system:
sounds are muffled through walls via low-pass filter and volume
reduction, with open doors partially restoring clarity. Uses bounded
BFS through regions (max 30) with per-tick listener room caching.

Fix room reverb bug where openness calculation always produced values
≤1.0 but thresholds checked for ≥2.5, making Livingroom and
StoneCorridor presets unreachable. Now uses room.CellCount directly.

Unify cursor terrain sounds with the pawn footstep .ogg pool so both
systems share the same sound files with room-aware reverb.
…stance controls

- Scanner harvestable subcategories now require plants/trees to be fully
  mature (PlantLifeStage.Mature) instead of just past harvestMinGrowth
- Mod list quick toggle (Space) and detail toggle now force-refresh the
  filtered list before announcing, fixing stale "Now on" announcements
- Ctrl+Shift+Left/Right adjusts preset jump distance by 10 tiles
Wire up the existing FootstepPerformanceMode setting to cap footsteps
at 18 per tick, prioritizing selected, drafted, and hostile pawns.
Skip all processing for categories with volume set to 0%.
fix: ensure Alt+H reports healthy colonists clearly
TryEnterNextPathCell has early-return paths where the pawn doesn't
actually move (door waiting, pawn collision, forbidden door). The
postfix now compares position before and after to only trigger
footsteps on actual tile changes.
Small mechs now sound lighter and tinnier while large mechs keep
their deep rumble. Pitch scales inversely with body size using
0.9 * (4.0 / bodySize)^0.4, and a high-pass filter strips bass
from smaller mechs (cutoff from 50Hz at size 4.0 to 400Hz at 0.3).
Play footstep sounds every 2nd tile entry instead of every tile to
prevent audio overlap on fast-moving pawns. Interval may need further
tuning based on playtesting and feedback.
Bind = and - to zoom in and out on the map. Play the game's DragSlider
sound on every zoom step, and announce zoom tier (closest/close/middle/
far/furthest) when crossing into a new tier. Announce "Zoom limit" when
already at min or max.
The previous step1.ogg and step2.ogg clips were loud and bassy. Replace
them with a single lighter clip that gets pitched and filtered per-mech
by body size at playback time.
…andling

Rework the footstep wall-occlusion system to address three issues uncovered
while using it:

1. No zoom coupling. Occlusion was identical at every zoom level, so a
   zoomed-out "radar" view still muffled pawns behind walls as aggressively
   as a zoomed-in tactical view. Now occlusion strength fades from full at
   Close tier to zero by the Middle/Far boundary (RootSize 13.8 to 42), so
   zooming out reveals distant pawns through walls while zooming in keeps
   the muffled-indoors feel.

2. Cursor-on-wall was degenerate. IntVec3.GetRoom() returns null for wall
   cells, and the old code early-returned 1.0 occlusion in that case --
   meaning every pawn sounded fully un-occluded whenever the cursor landed
   on a wall, regardless of actual geometry. Now wall cells gather their
   cardinal-neighbor rooms and regions, so a shared wall lets the listener
   "hear both sides un-occluded" and an exterior wall still muffles distant
   outdoor pawns correctly.

3. Doorway reverb was wrong. Door cells are their own Region but RimWorld
   assigns them a Room that's often the larger connected room, so cursor
   on a doorway between a tiny bedroom and a large hall inherited the
   hall's StoneCorridor preset and boomed. Same bug applied to wall cells.
   Added ResolveReverbRoom() which picks the smallest non-outdoor adjacent
   room for doorway/wall cells, so reverb matches the acoustic volume the
   listener is actually adjacent to.

Also removed the zoom-based footstep volume scaling. It made footsteps
~150% at Closest zoom, which drowned out the rest of the game, and ~30% at
Middle zoom, which made them too quiet to be useful. Zoom now drives only
occlusion, not volume -- footsteps stay at consistent amplitude across all
zoom levels.

Repurpose the existing FootstepZoomScaling setting as a master toggle for
wall occlusion: on means occlusion applies with zoom-based fading, off
means no occlusion whatsoever (all footsteps audible regardless of walls).
UI label updated to "Wall occlusion" / "Footstep Wall Occlusion" to match.
Internal field name preserved to keep existing save-file Scribe keys valid.

Files:
- src/Sounds/CameraZoomUtility.cs: drop all volume-scaling code and the
  camera cache; add GetOcclusionStrength() driven by CameraDriver.RootSize
  (returns 0 when toggle is off, so Lerp(1, raw, 0) = 1 = no occlusion).
- src/Sounds/FootstepSoundBank.cs: replace single cached listener room
  with per-tick cached listener rooms + regions (HashSet); add
  ResolveReverbRoom() and wall-cell neighbor scan; split GetWallOcclusion
  into raw computation + zoom-strength blend at the end.
- src/Sounds/FootstepManager.cs: drop GetZoomVolumeScale() call.
- src/Core/RimWorldAccessSettings.cs + src/UI/WindowlessOptionsMenuState.cs:
  relabel the toggle with new semantics.
RimWorld's CameraDriver.ApplyPositionToGameObject computes the camera's
world-space Y as 15 + t*50, where t is the normalized zoom (0 at Closest,
1 at Furthest). Since the AudioListener is attached to the camera
GameObject, zooming lifts the listener up to 50 units higher. Every
spatialized world sound uses Unity 3D distance attenuation, so the full
game audio mix quieted and re-panned as the player zoomed out -- even
though the player was using zoom as a visual-navigation tool, not an
audio-distance expression. For a screen-reader user who depends on audio
to identify activity across the map, that coupling was hostile: zooming
out to survey the colony made the exact sounds they were trying to hear
fade away.

Fix by hosting a proxy AudioListener on a mod-owned GameObject. The
proxy's X/Z track the camera (so left/right/front/back panning still
reflects what you're looking at) but Y is pinned at 15 -- the Closest-zoom
value, which is the loudest, nearest audio profile the game produces.
Result: ambient sounds, combat, weather, and everything else sound
identical at every zoom level.

Two surgical Harmony patches:

- Postfix on CameraDriver.ApplyPositionToGameObject: lazy-init the proxy
  GameObject on first run (also disables the original AudioListener on
  the camera), then each frame mirror the camera's X/Z and rotation but
  hold Y constant. Uses DontDestroyOnLoad so the proxy survives world
  transitions. Also reparents RimWorld's OneShotSourcesCameraContainer
  (which holds the 16 "on-camera" oneshot voices plus MusicManagerPlay's
  output) from the camera to the proxy -- those sources are meant to be
  coincident with the listener, and leaving them on the camera caused
  intermittent choppiness at max zoom-out as voices danced near Unity's
  virtualization threshold 50 units below the listener.

- Prefix on SoundParamSource_CameraAltitude.ValueFor: returns the same
  pinned Y so sound defs binding to the CameraAltitude param (certain
  ambient/weather sounds) also stop reacting to zoom.

No setting -- this is always on, since the behavior it replaces is
actively harmful for the mod's use case.
Transpile Command.GizmoOnGUIInt so bare letter gizmo hotkeys no longer
fire — users must hold Shift (e.g. Shift+K to build a wall). This frees
bare letters for other accessibility shortcuts.

Draft (Command_ColonistDraft, default R) is exempt: R remains the
vanilla draft/undraft toggle. Players rely on this muscle memory and no
other gizmo shares R on a draftable pawn, so leaving it bare is safe.
The SR announcement and visual badge both skip the "Shift+" prefix for
the draft key.

Shift+T previously announced performance stats; it now falls through so
it can reach gizmos with hotkey T. Performance stats moved to Alt+T.

Screen reader ordering updated: the hotkey is now spoken immediately
after the gizmo title, before descriptions and ability cost/range/
cooldown.
When MapNavigationState.CurrentCameraMode is Pawn (set by comma/period
cycling or colonist-bar selection), place the mod's proxy AudioListener
directly on the focused pawn's DrawPos each frame. Route ScreenPanUtility's
pan/distance math through the listener anchor (not the camera) so the
followed pawn's own footsteps stay dead-center and other pawns pan
relative to the focused pawn's position. Any arrow-key press flips the
mode back to Cursor and the listener falls back to its camera anchor
(existing behavior).

Fixes the practical issue that the camera lags slightly behind a moving
pawn, so pan cues for the followed pawn themselves (and for nearby
activity) drifted in the pawn's walking direction even though the user
was clearly watching that specific pawn.

Implementation:
- AudioZoomDecouplePatch.cs: add ResolveListenerAnchor(cam) helper that
  picks pawn.DrawPos (Y pinned to 15) when CurrentCameraMode == Pawn
  AND Find.Selector.SingleSelectedThing is a spawned pawn on the current
  map; otherwise falls back to the camera anchor. Also expose
  GetListenerWorldPosition() so other modules can align with the same
  anchor.
- ScreenPanUtility.cs: replace TryGetCameraMapPosition with
  TryGetListenerMapPosition, which reads the proxy listener's world XZ
  first, with the old camera lookups kept as fallback.

This behavior may be temporary. The long-term goal of the mod's audio
system is to give a blind player a feel for how pawns pathfind across
the map -- an absolute-position soundscape where walking directions,
distances, and routes are audible. Centering audio on the followed pawn
trades that for a first-person "what the pawn hears" view, which means
the listener loses absolute positional cues and must rely on
environmental sounds or other pawns to orient on the map. The first-
person framing felt right in this session's testing; it may get
reconsidered or made togglable once the broader pathfinding-audibility
system is designed.
…ction

Add two new keyboard shortcuts for faster pawn command flow:

/ (slash): Focus the colonist/mech bar on whichever pawn is standing
under the map cursor. Selects the pawn in-game, jumps the camera, and
syncs the bar position so subsequent comma/period/Alt+arrow navigation
picks up where the cursor pointed. If the cursor isn't on a pawn on
the bar (e.g. an animal or a raider), announces "Not on colonist bar".

[ (left bracket): Execute the top option of the same context menu that
] builds — whatever the first FloatMenuMakerMap entry is for the
currently selected pawn(s) at the cursor. Shift+[ queues the order
instead (KeyBindingDefOf.QueueOrder picks up the shift during action()
invocation, matching vanilla shift-click queueing).

For single-pawn selections, announcement is "PawnName: action" (plus
", Queued" when Shift is held). For multi-select, [ uses the same
per-pawn success/failure wrapping that Enter-on-top-option already
does from the right-bracket menu ("Everyone X", "No one could X",
"Everyone except A X", "Only A X"). That wrapping logic is now
factored into a shared WrapOptionsForMultiSelectFeedback helper so
both bracket handlers stay in sync.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants