Feature/gas station repair - #2
Conversation
Extends the existing gas-station refuel interaction with a repair service priced by current vehicle damage, without cancelling the active taxi shift when the repair primitive triggers BeamNG's vehicle-reset callback (suppressed via a narrow, one-shot, auto-expiring flag scoped to that exact reset event). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
be:reloadVehicle(0) respawns from the vehicle's raw .pc file path rather than its resolved parts config, which left the license plate CEF texture stuck on "NO TEXTURE" after every repair. core_vehicles.setPlateText is the same primitive BeamNG's own Vehicle Config "License Plate" field and its tech/research API use to force the plate texture to regenerate without another respawn, so it's now queued once the post-repair vehicle settles. Also adds a free "REPAIR VEHICLE" button to the cheat/debug menu for testing, mirroring the paid gas-station repair primitive. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Never read anywhere, unlike the trip.fuelCost pattern it mirrored (initialized at trip start and consumed by vehicleHistory.recordRide). Shift-level repair-cost tracking via shiftTracker.recordRepairCost already covers this. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
be:reloadVehicle(0) preserves whatever position/orientation the vehicle had, so repairing while flipped over/on its roof kept it that way. Queues recovery.startRecovering()/stopRecovering() on the vehicle's own Lua VM after reload -- the same pair of calls BeamNG's native "Recover Vehicle" (Home key) uses -- to place it upright at its last tracked safe position, which for a stationary repair is right where it already is. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Tank restoration ran immediately after be:reloadVehicle(0), but the recovery.startRecovering()/stopRecovering() call added afterward is known to refill the tank again, and since it goes through a separate async channel (queueLuaCommand) than the tank-restore's vehicle-bridge call, there was no guaranteed ordering between the two. Restoration is now deferred to the same scannerBecameReady checkpoint already used for the post-repair plate fix, guaranteeing it's applied last. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Both actions run at the same gas station and share the same panel, but neither checked whether the other was already in progress. Buy buttons are now disabled while the other session is active, and purchase()/ purchaseRepair() reject the same case server-side as defense in depth. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
State-lifecycle safety review found that autopilot's resume call after a repair reload was a silent no-op unless the service had been suspended first, so native self-driving would stop without warning after a repair. Suspend autopilot before be:reloadVehicle(0), matching the pattern used by every other reset path, and add a regression test for the underlying suspend/resume no-op behavior. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
080debc to
7767c78
Compare
Reuses the exact recovery primitive the pre-existing godMode reset-survival branch already uses (autopilot:markRouteDirty(), which forces the next autopilot:update() tick to reissue the native route regardless of prior suspend state) instead of suspending autopilot before the reload just to make the existing suspend(false) resume call meaningful. Drops the now-unneeded suspend-before-reload call and its dedicated regression test, since the markRouteDirty -> reissue behavior was already covered by an existing assertion. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The godMode reset-survival branch and the repair-reload settle checkpoint each independently reapplied the delivery cargo-mass modifier with the same conditional logic. Extract it into one shared helper reused by both call sites instead of duplicating it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Could you please check if this PR fits the 4.0.0-rc? I had lotta stuff to do cause of 0.39 release |
Will check it out. |
|
@JamDaBam I am fixing map canvas in the Connected Phone right now, it will be released as 4.0.1-RC |
Pulls in upstream's Release 4.0.0 RC (new UI/CSS design system, AI/autopilot overhaul, updated visual-test baselines). Resolved two conflicts: - scripts/package-mod.ps1: required archive entry count - both branches added files independently (this branch: repairPricing.lua, 60->61; main: aiDriverRoute.lua + icon.jpg, 60->62), combined to 63. - app.html: main restyled the fuel/repair panel's close and buy buttons with the new taxi-action/taxi-action--primary/--tertiary classes; kept this branch's repair-station-aware title and ng-disabled conditions (blocking refuel while a repair is active) alongside main's new classes. Note: tests/lua/combinatorics.lua fails on an autopilot route-trimming assertion after this merge - verified this is a pre-existing bug in upstream main's new AI code (fails identically on plain upstream main in isolation), not something this branch or merge introduced. This branch's own tests passed cleanly before the merge.
|
@noteMASTER11 i merged and tested this feature. |
|
@JamDaBam Need some time to test but it's 4 AM for me, need to sleep after making 4.0.0-RC for more than 7 hours in a raw. Sorry but please wait till tomorrow, I'll test your PR and merge it. This idea was one of the underestimated puzzles from my TODOs and always was throwed back before a bit more valuable features |
|
No problem. Thanks for your awesome work. |
|
@JamDaBam Today GPT5.6-Sol ExtraHigh loses Claude, because it took too much time for me to find and fix the issues instead of your work. Maybe because of overheated context window which includes about 25K lines changes. Are you using Fable5? |
|
@noteMASTER11 |
|
One additional change I'd like to make before this PR is ready: When Realistic Mode is disabled, repairing the vehicle at a gas station should be free, matching the existing non-realistic gameplay behavior. Repair costs should only be charged when Realistic Mode is enabled. I'll add this before considering the feature complete. |
Repair cost/balance checks now only apply when state.realisticMode is true, matching existing non-realistic gameplay behavior for fuel.
…imit Merging main pulled in another branch's new top-level test locals on top of this branch's repair-pricing tests, pushing the flat combinatorics.lua chunk over LuaJIT's 200-active-local cap. Wrap each independent assertion block (verified to have no references outside its own range) in do...end so its locals go out of scope before the next section runs.
|
The feature would be finished and ready for testing. |
|
@JamDaBam Fixing the car consistently caused the game to crash on the 4.0.1 version of the mod. It was tested on different standard maps. The PR will come out with significant edits on my part. The repair caused be:reloadVehicle(0), BeamNG started to recreate the current ETK I-Series (etki/2400_A.pc) and dropped to C++ on the main thread inside finishConstructionGESide. |
|
For repairs, I found a standard implementation of BeamNG 0.39: the "Repair vehicle" item itself calls resetBrokenFlexMesh() and spawn.safeTeleport(..., resetVehicle=true). This path resets physical damage without destroying and recreating Vehicle Lua — it was the native reboot that fell. I switch the mod to the same mechanism and save the fuel level on top of the reset. |
|
Fixed. Will be pushed after merging. |
Add the complete Brazilian Portuguese translation from issue noteMASTER11#7, extend it with PR noteMASTER11#2 repair strings, and mark the package and external UI cache as a local 2026-08-10 test build.
Summary
Adds a vehicle repair service at the existing gas stations, priced by current vehicle damage, alongside the existing realistic-fuel refuel service. Repairing does not cancel the player's active taxi shift, does not leave the vehicle flipped over, does not top off the fuel tank for free, and cannot run at the same time as refueling.
This PR has been carefully reviewed and tested in-game by the author across all the scenarios below.
Changes
Repair at gas stations
freeroam_gasStations.gasStationCenterRadius, hooked into the samerefuelCarWrapper/activityGatherWrapper/M.onActivityAcceptGatherDatawrappers) rather than a second parallel detection system \E2\80\94 one gas station, two services.M.onUpdate, not gated behindstate.active.realisticFuel.requestCurrentDamage) \E2\80\94 so the price charged always matches the vehicle's actual current damage, never a stale HUD value.repairPricing.lua(pure module, unit-tested) converts BeamNG's raw, unboundedbeamstate.damageinto a 0-100% damage value and then into a repair price, both via the same exponential-saturating curve shape already used bydelivery.calculateImpactDamage. Bounds are configurable inconfig.lua's newM.repairtable (minimumRepairPrice,maximumRepairPrice,repairPriceScale,damagePercentScale,minimumRepairableDamagePercent,repairDurationSeconds).shiftTracker.recordRepairCost, mirroring the existingfuelCostpattern) and reduces shift net income.Shift preservation
Repairing damage requires
be:reloadVehicle(0), which fires the same BeamNG reset callback that normally cancels the player's active taxi shift. A one-shot, vehicle-ID-scoped, auto-expiring suppression flag is armed immediately before that call and consumed inhandleVehicleReset\E2\80\94 only the exact reset caused by that specific repair is swallowed; manual player resets, vehicle switches, and resets on any other vehicle fall through to the existing, unmodifiedmainbehavior.Bugs found and fixed during in-game testing
be:reloadVehicle(0)respawns the vehicle from its raw.pcfile path rather than its already-resolved parts config, which left the license plate CEF texture stuck on "NO TEXTURE" after every repair. Fixed by callingcore_vehicles.setPlateText(false, vehicleId)once the vehicle settles \E2\80\94 the same primitive BeamNG's own "License Plate" config field and its tech/research API use to force the plate texture to regenerate without another respawn.be:reloadVehicle(0)preserves whatever position/orientation the vehicle had, including upside-down or on its roof. Fixed by queuingrecovery.startRecovering()/recovery.stopRecovering()on the vehicle's own Lua VM after reload \E2\80\94 the same pair of calls BeamNG's native "Recover Vehicle" (Home key) uses, placing the vehicle upright at its last tracked safe position (which, for a stationary repair, is right where it already was).purchase()/purchaseRepair()as defense in depth.State-lifecycle safety review of the reset suppression
A full audit (code + git-history) of whether preserving TaxiDriver state across the repair-triggered
be:reloadVehicle(0)could leave anything stale \E2\80\94 active trip, queued next-offer, telemetry, cargo mass, autopilot, physical-pickup props \E2\80\94 found the suppression design itself sound (vehicle-ID-scoped, correctly non-one-shot, auto-expiring) and every genuinely vehicle-bound piece of state already reacquired at the "VM settled" checkpoint, except autopilot:autopilot:suspend(vehicle, false)(the resume call fired once the vehicle VM settles) only re-issues the native drive route if the service was actually suspended first \E2\80\94 it's a no-op otherwise, and the repair path never suspends autopilot before reloading the vehicle. So if "AI Driver" mode was engaged and not already suspended at the moment of repair, native self-driving would silently stop with no error or HUD change. Fixed by callingautopilot:markRouteDirty()right after the existing resume call once the vehicle settles \E2\80\94 the exact same primitive the pre-existing godMode reset-survival branch already uses to recover from a reset without ever needing to suspend autopilot first.Active trip and queued-offer state were confirmed safe to preserve as-is: both are pure GE-side data with no vehicle object/VM references.
Architecture review: does this duplicate the existing godMode reset-survival mechanism?
The debug/cheat menu's pre-existing "godMode" toggle also keeps an active shift/trip alive across a vehicle reset, which raised the question of whether repair's dedicated suppression mechanism duplicates it. It does not: godMode is a persisted, global, vehicle-unscoped setting that survives every reset while enabled, with no reconciliation for reload-specific side effects (license plate, fuel tanks, telemetry) because the plain in-place reset/recover it targets never causes them. Repair's mechanism instead has to target one specific, self-caused reset from
be:reloadVehicle(0)\E2\80\94 a heavier respawn that does cause those side effects \E2\80\94 without suppressing any other reset on the same vehicle. Temporarily toggling the real godMode setting to reuse its branch was considered and rejected (it's a persisted, UI-visible setting, and doing so would incorrectly swallow unrelated resets during the repair window). The two mechanisms are kept separate and are mutually exclusive gates inhandleVehicleReset(repair's check returns before godMode is even evaluated).The review did find one genuine, small duplication worth fixing: both godMode's branch and the repair-reload settle checkpoint independently reapplied the delivery cargo-mass modifier with the same conditional logic. Extracted into a single shared
reapplyDeliveryCargoMass(vehicle)helper reused by both call sites \E2\80\94 a pure, behavior-preserving dedup, no functional change.Tests
Added to
tests/lua/combinatorics.lua(all additive, no existing assertions changed):repairPricing.calculateRepairPrice/calculateDamagePercent\E2\80\94 zero/negative/over-range clamping, monotonic increase across damage levels, custom config bounds, nil-safety.No new autopilot test was needed: the
markRouteDirty()\E2\86\92 nextupdate()reissues the native route behavior used to fix the repair-resume gap was already covered by an existing assertion in the stock-AI-routing test.Manual verification
All of the following were tested in-game and confirmed working by the author:
Remaining limitations
minimumRepairPrice,maximumRepairPrice,repairPriceScale, anddamagePercentScaleinconfig.lua'sM.repairtable are initial values; further balancing may be wanted once more real-world damage/price data is collected across different vehicles and crash severities.Additional changes since the initial review
Based on the review and additional testing, the following improvements were added:
Realistic Mode integration
Repair costs are now gated behind
state.realisticMode.$0, no money is deducted, and balance checks are skipped, matching the existing non-realistic gameplay behavior.State lifecycle review
A complete review of the repair-triggered reset confirmed that the reset-suppression logic itself is sound and that vehicle-dependent state is correctly restored after
be:reloadVehicle(0).During the review it initially appeared that AI Driver did not always resume correctly after a repair. Investigation showed that this behavior was not caused by the repair implementation, but by a separate issue in the current experimental AI Driver feature.
The repair implementation still restores AI Driver state by calling
autopilot:markRouteDirty()once the repaired vehicle has settled, reusing the same recovery mechanism already used by the existing godMode reset-survival path.The observed AI Driver behavior was ultimately traced to a separate experimental AI Driver issue, which is tracked independently and is unrelated to this PR.
Architecture review
The repair reset-survival logic was compared against the existing godMode reset-survival implementation.
Although both preserve an active shift across a vehicle reset, they serve different purposes:
be:reloadVehicle(0)call and additionally restores reload-specific state such as license plates, fuel level, telemetry, cargo mass, and AI Driver.The two mechanisms therefore remain separate.
The review did identify one small duplication: both code paths reapplied the delivery cargo-mass modifier using identical logic.
This has been refactored into a shared
reapplyDeliveryCargoMass(vehicle)helper without changing behavior.Additional manual verification
The following scenarios were also verified:
$0, no balance is deducted, and repairs always proceed.Test suite maintenance
Merging
maininto this branch exposed LuaJIT's 200-active-local-per-chunk limit intests/lua/combinatorics.lua, as the repair-pricing tests from this branch and the Connected Phone tests frommaintogether exceeded the limit.The test file was refactored by wrapping independent assertion groups in
do...endblocks so their temporary locals are released immediately after use. This is a pure scoping change only: no assertions were added, removed, reordered, or otherwise modified, and the suite continues to behave identically to before.