Fix/artefact container getters - #2130
Closed
mikhailRo wants to merge 40 commits into
Closed
Conversation
…rgences Squashed source patch set built up while porting the Dead Air Revolution 2 mod (a Shadow of Chernobyl mod on a heavily modified proprietary engine) onto stock OpenXRay. Full investigation history, root causes, and per-fix build/verify notes are in HANDOFF-DeadAir-OpenXRay.md. Highlights: - Crash fixes: spatial-tree octant OOB on level load, A-Life registry THROW asserts on load/unload, ambient-sound R_ASSERT relaxation. - Restored engine features Dead Air's original build had but stock OpenXRay lacks: full Torch scripting API, artefact dynamic-property setters, several missing script exports, render cvars for the dynamic post-process script (DOF/vibrance/lens/SSS/color-grade), native crash stack traces. - Rendering: NaN postprocess constants causing load-time corruption (scramble + dim variants), spot-light cookie texture zeroing dynamic lighting. - Data-vs-enum divergences between Dead Air's data-driven slot/state layout and OpenXRay's hardcoded enums (5 instances): HUD-state enum renumbering, duplicate key bindings (torch, night vision), inventory slot routing (grenades/pistols/binoculars), and backpack slot/class mismatch. Fixed by realigning enums or loose data to match, per-case, never both. - UI/input: Escape key dialog-stacking/render-desync fixes, PDA update crash guard. See HANDOFF-DeadAir-OpenXRay.md for the complete, numbered write-up (28 sections) of each bug: symptom, root cause, fix, and build/verify status. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Port Dead Air Revolution 2 to OpenXRay: 28 fixes for engine/data dive…
The equip-slot condition indicators (progess_bar_sidearm, progess_bar_backpack) are authored in actor_menu_16.xml but the inventory_lists table in CUIActorMenu::InitializeUniversal never wired them up via SetConditionIndicator, unlike every other single-item slot (knife/pistol/automatic/outfit/helmet). CUIDragDropListEx::Update() only refreshes m_condition_indicator when it's non-null, so these two slots' bars stayed at their unset default (empty) while equipped, and only looked correct again once the item returned to the ruck's grid list, which uses the separate per-cell UICellItem condition bar path. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fix empty durability bar on backpack/sidearm equip slots
CActor::currentFOV() set the camera FOV directly to pWeapon->GetZoomFactor() * 0.75, i.e. it treated the weapon's ironsight/scope zoom_factor ltx value as an already-computed target FOV in degrees. Dead Air's weapon configs use zoom_factor as a divisor against the base FOV instead (base ironsight ~1.8, optical scopes ~6-20, matching the "Nx zoom" convention used throughout every weapon .ltx). With the old formula, an ironsight zoom_factor of 1.8 produced an actual camera FOV of ~1.35 degrees (g_fov=67.5 default) for every single weapon on RMB aim, regardless of which gun - explaining the "magnification way too strong / camera hopping" report across AK-74U, PM, and TOZ-66 alike (hopping was normal weapon sway made huge by the near-zero FOV, not a separate camera bug). Changed the formula to (g_fov / zoom_factor) * 0.75, which divides down from the base FOV as the data assumes. Kept the existing *0.75 tail so the change is a single targeted operator fix rather than an unrelated tuning pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fix ADS FOV formula: zoom_factor is a divisor, not an absolute FOV
ui_actor_state_wnd::UpdateActorInfo() (UIActorStateInfo.cpp) bound the "stamina_state" bar in actor_menu_16.xml to conditions.GetPower(), i.e. the actor's stamina/power stat. But Dead Air repurposed that bar's tooltip text (st_ui_stamina_sensor_inv is localized as "Satiety", body text entirely about hunger) without updating the C++ binding to match, so the bar visually behaved like stamina (recovering just from standing still/waiting) while being labeled and described as satiety - exactly backwards from the real, already- correct satiety model (CActorCondition::m_fSatiety only decays over time and only rises via ChangeSatiety(), called when eating food). Rebound the bar to conditions.GetSatiety(). Also marked CActorCondition::GetSatiety() const so it's callable through the const CActorCondition& already used in this function, matching GetPower()'s existing const qualification. Stamina itself is unaffected and still shown correctly elsewhere (UIMotionIcon::SetPower() drives the HUD power/sprint meter). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…kable The previous fix (dont_stack=false on ammo_base, see prior commit) made drag-to-combine work for ammo, but it shares its mechanism with every other place an item enters a list - including the RMB "split stack" action, which spawns a second same-type ammo item that then gets auto-picked-up back into the same bag and immediately re-merged with the original, undoing the split. Added a one-shot suppression flag on CInventoryItem (FSuppressAutoStackOnce / SetSuppressAutoStackOnce / ConsumeSuppressAutoStackOnce) that CUICellContainer::FindSimilar checks (and clears) when considering a candidate, exposed to Lua as itm:suppress_auto_stack_once(). itms_manager.script's inv_item_split_ammo now calls it on the original item right after halving its count, so only that one re-insertion is exempted from auto-merging - explicit drag-and-drop combining still works normally afterward, and every other auto-stack-on-pickup case (food, drugs, etc.) is untouched. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Log evidence showed the boolean suppression flag never got cleared for the item that stays behind after a split: ClearSuppressAutoStackOnce() only ran once the *placed* item reached SetItem(cell_pos), but the original half's UI cell is never re-inserted after a split (its count is just mutated in place), so it would have stayed permanently unmergeable for the rest of the session. Replaced the boolean flag with a 2-second time-based expiry (m_dwSuppressAutoStackUntil vs Device.dwTimeGlobal) on CInventoryItem. This naturally covers the whole split-then-repick-up sequence (including AddSimilar being called more than once per placement) without needing to track a "was it placed" event at all, and it can't get stuck on. Diagnostic Msg() logging left in for one more verification round - not for merge, will be stripped once confirmed working. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fighting the generic inventory-list auto-stack path (dont_stack=false + CUICellContainer::AddSimilar) for ammo turned out fragile: it fires on every list insertion (drag, pickup, split re-pickup alike), called AddSimilar multiple times per placement, and produced inconsistent merges. Dead Air already ships a purpose-built, dedicated ammo consolidation system for exactly this - gamedata/scripts/ ammo_aggregation.script's actor_on_item_take - which combines same- section fragments up to each ammo type's authored box_size and releases the emptied entities, entirely separate from generic UI grouping. It was just never enabled (whole body commented out). Reverted the ammo_base dont_stack=false data fix (loose gamedata, not tracked here) and the now-unneeded UIDragDropListEx.cpp changes back to stock. Kept the CInventoryItem time-based suppression mechanism (SetSuppressAutoStackOnce/IsSuppressingAutoStack, added previously) since it's still needed - exposed a read-only is_suppressing_auto_stack() Lua getter for it so ammo_aggregation.script can skip a just-split item when collecting merge candidates, the same way the UI code used to. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CUIActorMenu::DropItemOnAnotherItem() - Dead Air's native hook that detects "item A was dropped directly onto item B" and forwards to the Lua CUIActorMenu_OnItemDropped callback (which already has fully correct, working handlers for ammo/consumable/map-tablet combining in itms_manager.script) - resolved the target cell via PickCell(old_owner->GetDragItemPosition()). GetDragItemPosition() returns the dragged ghost icon's tracked top-left corner (cursor position plus a fixed offset captured at drag start, from wherever within the icon the player originally grabbed it), not the cursor position itself. Cell lookup needs to know which cell is under the cursor right now, not under the icon's corner - those only coincide when the player happens to grab the icon near its own top-left corner. For any other grab point the computed cell was frequently off by one, so _citem resolved to nullptr (or the wrong item), the Lua drop-on-item callback silently never fired, and the drop fell through to a plain reposition instead of combining - this is the root cause of "combining ammo/consumables only works sometimes" across everything tested today. Switched to GetUICursor().GetCursorPosition() directly, matching the same absolute coordinate space PickCell()/GetAbsolutePos() already expect (confirmed by how CUIDragDropListEx::Update() compares the raw cursor position against absolute window rects the same way). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Splitting an ammo stack was still triggering full aggregation consolidation of *other*, unrelated ammo stacks as a side effect - the per-item suppression flag only protected the specific original stack from being re-absorbed, but ammo_aggregation.script's job is to sweep and consolidate everything of that type, so any other pre-existing fragments still got glued together whenever the split's spawned half was auto-picked-up and triggered a consolidation pass. Replaced with a blanket time gate in the scripts instead (loose gamedata, not tracked here): itms_manager.script now stamps a global last_ammo_split_time on split, and ammo_aggregation.script's actor_on_item_take skips its entire consolidation pass for 2 seconds after any split, regardless of which item's pickup triggered it - so a split only ever affects the specific stack being split. That makes the CInventoryItem::SetSuppressAutoStackOnce/ IsSuppressingAutoStack pair (and their Lua bindings) dead code with no remaining callers - removed rather than left unused. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fix/satiety and ammo fixes
…closing F is bound to kUSE (world interact) but the engine's default keybinding table also maps it as a secondary keyboard bind for kUI_ACCEPT. Inside the inventory grid, CUICellItem::OnKeyboardAction treats kUI_ACCEPT on a hovered cell as a double-click, so pressing F while hovering an item equipped/unequipped it instead of closing the screen. CUIActorMenu::OnKeyboardAction now intercepts kUSE first and closes the window (same as Escape/I), before the event can reach the focused cell item. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fix: F key in inventory/container windows equips/unequips instead of …
…ion list Dead Air's authored actor_menu_item_16.xml has a Reliability row and condition/durability rows in wpn_params and outfit_info that the ported CUIWpnParams/CUIOutfitInfo never had members for. Also wires up the per-part malfunction list (already fully implemented in loose Lua as items_condition.script) into the tooltip description text. - UIWpnParams.h/.cpp: add Reliability functor+bar and Condition icon/label/value, reading GetConditionToShow() and ui_wpn_params.GetReliability (previously unused). - UIOutfitInfo.h/.cpp: add the same Condition display for suits/helmets. - UIItemInfo.cpp: BuildItemDescriptionText() appends the st_condition_type malfunction bullet list for weapons with a nonzero GetWeaponConditionType(). - xrServer_Objects_ALife_Items.h/.cpp: add m_weapon_condition_type to CSE_ALifeItemWeapon so coc_treasure_manager.script's loot-spawn roll (previously silently dropped) actually reaches the live object via STATE_Read/Write, guarded on remaining bytes for old spawn data. - Weapon.cpp (net_Spawn): copy the ALife seed value only when the item has no prior session history (client_data empty), so it doesn't clobber a value net_Load already restored from a save. - inventory_item.cpp (save/load): persist m_weapon_condition_type in the session save format as a marker+value pair rather than a bare trailing field, since weapon subclasses always append more data after this point even in old saves — a plain "bytes remaining" check can't tell old format from new here. Old saves verified to still load correctly. Two further blockers (loose Lua, no C++ rebuild) had to be fixed before the malfunction-roll-on-jam path actually worked end-to-end; see HANDOFF-DeadAir-OpenXRay.md for the full writeup: - bind_gr_gun.script: typo (ggun_binder -> gravi_gun_binder) was silently aborting axr_main's on_game_start() dispatch for every mod script, not just its own. - bind_stalker_ext.script: actor_on_weapon_jammed was missing a parameter, so the native callback's auto-prepended self arg shifted the real weapon argument out of the declared param list. User-confirmed in-game: tooltip and repair screen now show/persist malfunctions correctly across reload, and old saves still load fine. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nd-malfunctions Fix weapon/suit tooltip missing reliability, durability, and malfunct…
Dead Air's weapon addon icon overlays (scope/etc.) are driven by numbered "Nicon_layer" keys (1icon_layer, 1icon_layer_x, ...) read in CUIInventoryCellItem's constructor. The loop started at index 0 and bailed out via `break` as soon as a numbered key didn't exist, but no config in gamedata ever defines "0icon_layer" - every weapon variant starts numbering at 1. So the loop always broke on the very first check and the layer icon was never created for any item. This made scope attachment look broken: the weapon itself (3D model, zoom, HUD) is swapped via a separate Lua-driven mechanism (dxr_scopes.script cloning the weapon into a new section) and worked fine, but the small scope icon overlay on the inventory icon never appeared. Silencer/launcher addons were unaffected since they use an older, separate native engine mechanism (CWeaponMagazined addon flags + CUIWeaponCellItem eSilencer/eLauncher icons) that doesn't go through this loop at all. Fix: start the loop at i = 1 to match the data convention.
Fix: scope icon overlay never rendered on weapon inventory icons
The condition row's caption passed the raw localization key
("ui_inv_af_condition") instead of translating it, unlike every sibling
row in the same function.
The restore/immunity/weight effects were always read fresh from the
static ltx template via pSettings, ignoring Dead Air's runtime artefact
tuning (CArtefact::Set*Power/SetAdditionalWeight/SetArtefactImmunity,
used for IAM quality-tier mutations). Artefacts whose real effect
magnitudes were set dynamically at spawn showed stale/zero data in the
tooltip. SetInfo() now prefers the live CArtefact instance's values
when the item is one, falling back to static config otherwise.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nd-effects Fix artefact tooltip: untranslated condition caption and stale effects
Regular (non-trader) stalkers in Dead Air are flagged <barter_mode>1</barter_mode> in their character profile and are supposed to exchange goods for goods only, never for money: the trade screen shows a single exchange button and the deal goes through as soon as what the player offers is worth at least as much as what he is taking. Stock OpenXRay has no notion of barter at all, so every NPC opened the ordinary money trade screen with separate Buy/Sell buttons. - specific_character: parse and expose <barter_mode>, mirroring <mechanic_mode>. - CUIActorMenu: create the trade_barter_button that Dead Air's actor_menu XML already defines (optional, so layouts without it still load); in trade mode show either the barter button or the buy/sell pair depending on the partner's flag. - OnBtnPerformBarter: compares the same two totals the trade bars already display and, when the offer covers the ask, transfers both directions with CTrade::TransferItem(..., bFree=true) so no money changes hands. Otherwise it shows the existing trade_dont_make message. - TransferItems gained a defaulted bFree parameter; the money trade paths are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Dead Air: restore goods-for-goods barter with ordinary NPCs
…ect ratio UICore::get_xml_name() picked the plain (non-widescreen) XML file whenever is_widescreen() was false, and only tried "<name>_16.xml" otherwise. Dead Air / DeadZone only maintains the "_16" layouts: the plain XMLs still in the archives are stale leftovers from the mod's base (different skin, missing nodes, and in several cases entire features the mod replaced - e.g. ui_mm_faction_select.xml is still the old CoC faction picker, actor_menu.xml has no backpack/sidearm/binocular slots, ui_mm_main.xml has no <background_words> so it hard-errors in CUIXmlInitBase::InitWindow). Picking by aspect ratio meant any 4:3/5:4 resolution silently swapped in a broken UI. Also fixes a related bug in the same function: when building the "_16" name for an extension-less caller, the stock code appended "_16" to the bare name without ever adding ".xml" first, so the follow-up FS.exist() probe could never find the file for those callers. Now always prefers "<name>_16.xml" if it exists, regardless of aspect ratio, and falls back to the plain file only when no "_16" variant exists - identical behavior to stock for every file that has no widescreen variant.
Fix UI layout selection: prefer the maintained "_16" XML at every asp…
12 checkboxes/sliders in ui_mm_opt_16.xml (r__actor_body, r2_sss_intensity/ enable, r2_lumasharpen, r2_fxaa, r2_technicolor, r2_vignette, r2_lenswater, r2_lensdirt, r2_reflections, hud_draw_info, hud_draw_map) referenced console variables stock OpenXRay never registered, so Apply silently failed and the UI always reverted to off. Registered all of them (storage-only, matching the existing missing-render-cvars fix pattern) so settings persist. r__actor_body additionally gets real behavior: the actor is normally excluded from its own color-pass and shadow-map rendering entirely (never needed before, since the local player never saw their own body). When enabled: - color pass: renders the actor's base body visual (legs-only when an outfit's actor_visual is worn, by design — the separate first-person HUD hands/arms are used instead of a body head/hands) - shadow map: additionally renders a full-detail stand-in visual (the same model regular NPCs use) with its bone transforms retargeted from the actor's live pose, so the cast shadow has a complete head+arms silhouette that matches the actor's real animation instead of the legs-only geometry or a static pose - attachments (torch/headlamp, radio) render for the shadow but not the color pass, since the legs-only model has no head to visually mount the headlamp on InventoryOwner.cpp also stops skipping CAttachmentOwner rendering for the locally-controlled actor (previously dead code, since the actor was never rendered at all) while still avoiding a double-drawn weapon (already handled separately by the HUD system). Known remaining issue: camera clips through the torso while climbing ladders. Not fixed — see HANDOFF-DeadAir-OpenXRay.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fix options-menu cvars that never saved; add player-body visibility
The §44 first-person body render force-draws the actor's own visual every frame, but had no way to know when the camera pose made that body clip the view (a camera-to-bone distance heuristic was tried and proved unworkable, since that distance barely changes between normal standing and climbing). Add a minimal cross-DLL query instead: IGameObject::climbing() (default false in CGameObject, overridden in CActor to read the existing mcClimb movement-state flag). The render DLL's PHASE_NORMAL body force-injection now skips the draw outright while climbing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fix actor body clipping the camera while climbing a ladder
itms_manager.script's actor_on_item_take_from_ground handler already plays the inv_take sound and shows a lower-left PDA news popup with the item's name, but was dead: GameObject::eTakeItemFromGround was registered for script binding but never invoked from any C++ call site. Fire it from both real ground-pickup call sites in Actor_Feel.cpp (PickupModeUpdate and PickupModeUpdate_COD), right after the existing SendPickUpEvent call. Deliberately not hooked into the shared GE_OWNERSHIP_TAKE network-event handler in Actor_Events.cpp: that same event also fires when existing inventory items re-materialize on level load/save-restore, which would have spammed the notification for the player's whole inventory. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tion Fire native ground-pickup event so Dead Air's item-take news/sound fires
… shadow
Follow-up to feat/actor-body-visibility-options (§44) and
fix/ladder-climb-body-clip (§45). Two independent bugs, ten passes to
fully root-cause (see HANDOFF-DeadAir-OpenXRay.md §47 for the full
investigation, including three dead ends kept for the record):
1. Body/shadow popped into view facing the wrong way when turning the
camera fast while standing still. Root cause: CActor::g_cl_Orientate's
pre-existing mcTurn animation-catchup lerp (model orientation chases
camera yaw over ~1s), invisible before the actor's own body was ever
rendered. Added IGameObject::turning_in_place() cross-DLL query
(same pattern as climbing()) and skip the body/shadow draw while it's
active.
2. Shadow showed a duplicate/doubled silhouette, then legs-only, then a
fixed-size blob across successive fix attempts — three distinct real
bugs stacked on top of each other:
- A data race: 3 parallel sun-cascade threads sharing one mutable
shadow-model instance. Fixed with a per-context_id instance array.
- That fix exposed a second race in CModelPool::Create (now callable
concurrently). Fixed with a Lock around just the creation call.
- CHUDManager::Render_First (the original, pre-§44 "weapon shadow"
code) has always also drawn the actor's own legs-only base visual
unconditionally, redundant with the new full-detail shadow stand-in.
Added IRender::actor_body_shadow_active() cross-DLL query to skip
it when the replacement is active.
- add_Visual's internal CalculateBones(TRUE) call was silently
discarding the retargeted pose one line after it was set (an
accidental early-out from the old shared-instance code had been
masking this). Fixed by calling CalculateBones ourselves first, so
our retargeted pose becomes the last write.
Engine-built (0 errors) after every change; user-confirmed in-game for
both symptoms.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fix actor-body render/shadow quirks: camera-turn pop-in, doubled/blob…
- ls_header/ls_tip_number/ls_tip_N text was rendering as a raw untranslated placeholder key and never changed between loads (gamedata loadscreen.script had get_tip_number() hardcoded to return 2, and ls_tip_number was missing from the string table -- both fixed in loose gamedata, not here). - Repositioned loading_header/loading_tip_number/loading_tip so the text no longer overlapped the progress bar. - loading_progress lacked under_background="0" (unlike the ClearSky/ SoC variants), so it defaulted to drawing UNDER the full-screen background photo layer, which alpha-blended over it -- this is why the bar looked hazy/desaturated and why it got more noticeable later in loading (the growing white fill blended more visibly than the static black background). Fixed by drawing it on top. - Measured the background texture's baked-in decorative "groove" bar pixel-by-pixel (ui_actor_loadgame_screen.dds) and repositioned/ resized loading_progress to sit exactly inside it instead of floating above it. - Brightened the tip text color (103,103,103 -> 200,200,200) for legibility against the busy background art. Applies to both the 4:3 (LoadingScreenXML) and 16:9 (LoadingScreenXML16x9) Call of Pripyat-style hardcoded loading screen variants in UILoadingScreenHardcoded.h. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…gressbar Fix loading-screen tip text and progress bar for Dead Air's art
…eline r__color_base_r/g/b and r2_vibrance_val were registered as storage-only cvars (to silence console spam) but never consumed anywhere, so the options-menu R/G/B/Saturation sliders had zero visual effect regardless of value. - CRender::SetPostProcessParams (r2.cpp) now folds the color-base/add cvars in as an offset on top of CCameraManager's per-frame postprocess baseline, the same way multiple .ppe effectors already blend against that baseline. - r4_rendertarget_phase_combine.cpp now sends the real r2_vibrance_val to the combine shader's vibrance constant instead of a hardcoded zero (aberration/lumasharpen stay pinned to zero -- nothing drives those). - Narrowed the four sliders' registered cvar range from the spam-fix's -10000..10000 placeholder to 0..1 / -1..1, matching SPPInfo::SColor's packing and Dead Air's own script-set values. User-confirmed in-game: R/G/B/Saturation sliders now visibly affect the rendered image.
Wire options-menu color correction and saturation into the render pip…
…munities) An earlier patch added set_artefact_weight/set_artefact_additional_weight and 9 per-hit-type set_artefact_*_immunity setters for Dead Air runtime artefact tuning, but never the matching getters. itms_manager.script:container_add() (dropping an artefact into a container item) calls the getters, which did not exist, causing a Lua attempt to call method (a nil value) error and occasional crash. Adds the 11 corresponding get_artefact_* bindings, mirroring the setters: weight reuses the existing generic Weight() getter, additional_weight reads CArtefact::AdditionalInventoryWeight(), and the 9 immunities read CHitImmunity::GetHitImmunity() via CArtefact::m_ArtefactHitImmunities.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.