fix(rust): the last fixes' small ones (W3 W6 W7/W11 W8 W10 W13-W18)
Stage 2 of PLAN_LAST_FIXES (docs#366). - W7/W11: an "is it loaded" refusal is confirmed live before it is given. New liveStatus.js asks the sidecar once (boot.refreshOne, as a poll tick would), at most once per server per 5 s and waiting at most 3 s, then reads the stored status again. Used by Economics (step and staff page), Backpacks, Dangerous Treasures, Guarded Crate, RaidableBases, ZoneDomes, TruePVE and RunicNPC. - W3: a world call answered `already-out` (the bridge half comes in stage 3) succeeds as called 0, saying a Travelling Vendor is already out. - W6: "A locked crate dropped by a Chinook is being hacked." - W8: a RaidableBases profile's audit row names RaidableBases, not "@data". - W10: the backpack sizes picker lists each size once, naming its servers. - W13: a RaidableBases name both servers describe differently shows each server's own description. - W14: each RaidableBases note after the first starts its own sentence. - W15: a staff deposit refused at Economics' limit answers 409. - W16: a payment's failedNote explains only the reasons that happened. - W17: the sync state counts player grants that did not land (waitingToLand), and the Permissions page says "in sync · 1 waits for their player to join" instead of plain "in sync". - W18: Guarded Crate's kept note drops the "30 minutes" it could not know. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -114,11 +114,22 @@ function shapeCatalogue(catalogue) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The plugin a path belongs to, by its first segment, or its file name for a
|
||||
* file at the root. A RaidableBases profile's virtual path names the plugin
|
||||
* after its `@data/` prefix, so the audit row says RaidableBases (W8).
|
||||
*/
|
||||
function pluginOf(path) {
|
||||
const text = String(path)
|
||||
const parts = text.split('/')
|
||||
if (parts[0] === '@data' && parts.length > 2) return parts[1]
|
||||
return parts.length > 1 ? parts[0] : text.replace(/\.json$/i, '')
|
||||
}
|
||||
|
||||
/** Whether this file is the bridge's own config, by the name the plugin gave. */
|
||||
function isBridgeConfig(path, self) {
|
||||
if (!self) return false
|
||||
const plugin = String(path).includes('/') ? String(path).split('/')[0] : String(path).replace(/\.json$/i, '')
|
||||
return plugin.toLowerCase() === String(self).toLowerCase()
|
||||
return pluginOf(path).toLowerCase() === String(self).toLowerCase()
|
||||
}
|
||||
|
||||
/** The locked keys for a file: three of them in our own config, none anywhere else. */
|
||||
@@ -279,6 +290,7 @@ module.exports = {
|
||||
isPlausiblePath,
|
||||
shapeCatalogue,
|
||||
shapeFile,
|
||||
pluginOf,
|
||||
isBridgeConfig,
|
||||
lockedKeysFor,
|
||||
lockedChanges,
|
||||
|
||||
@@ -23,6 +23,7 @@ const db = require('./economics.db')
|
||||
const eventsDb = require('../events/events.db')
|
||||
const servers = require('../servers/servers.model')
|
||||
const { pluginError, transportError } = require('../../eventLeases')
|
||||
const live = require('../../liveStatus')
|
||||
|
||||
const log = core.logger('economics')
|
||||
|
||||
@@ -168,7 +169,8 @@ async function adjust({ serverId, steamId, op, amount, key }) {
|
||||
const server = await servers.getForCalling(serverId)
|
||||
if (!server) return { ok: false, status: 404, message: 'No such server' }
|
||||
|
||||
const econ = await forServer(server.id)
|
||||
// Confirmed live before it refuses: the stored status lags a restart (W7).
|
||||
const econ = await live.confirmed(server, () => forServer(server.id), (e) => !e.loaded)
|
||||
if (!econ.loaded) return { ok: false, status: 409, reason: 'economics-missing', message: MISSING }
|
||||
|
||||
const result = await client.economicsAdjust(server, { key, steamId, op, amount: checked.value })
|
||||
@@ -187,9 +189,11 @@ async function adjust({ serverId, steamId, op, amount, key }) {
|
||||
}
|
||||
|
||||
log.warn('Economics refused an adjustment', { server: server.id, op, reason: data.reason })
|
||||
// The game saying no is a 409, as `insufficient` is above: the request was
|
||||
// well formed, and the balance it met refused it (W15).
|
||||
return {
|
||||
ok: false,
|
||||
status: data.reason === 'economics-missing' ? 409 : 400,
|
||||
status: data.reason === 'economics-missing' || data.reason === 'at-limit' ? 409 : 400,
|
||||
reason: data.reason || null,
|
||||
message: pluginError(data, `${name} refused the adjustment`),
|
||||
}
|
||||
|
||||
@@ -162,11 +162,19 @@ function shapeSync(row) {
|
||||
}
|
||||
}
|
||||
|
||||
// A grant to a player the plugin made and did not find when it read it back
|
||||
// (`steamId:permission`). On Carbon that is a Steam id the server has never
|
||||
// seen: it lands when they first join, because every sync pushes the whole
|
||||
// set. "In sync" alone would hide it (W17).
|
||||
const notLanded = report && Array.isArray(report.notLanded) ? report.notLanded : []
|
||||
const waitingToLand = notLanded.filter((entry) => /^\d{17}:/.test(String(entry))).length
|
||||
|
||||
return {
|
||||
serverId: row.serverId,
|
||||
state: row.state,
|
||||
dirty: Boolean(row.dirty),
|
||||
inSync: Boolean(row.desiredHash) && row.desiredHash === row.syncedHash && row.state === 'ok',
|
||||
waitingToLand,
|
||||
lastAttemptAt: row.lastAttemptAt,
|
||||
lastOkAt: row.lastOkAt,
|
||||
importedAt: row.importedAt || null,
|
||||
|
||||
Reference in New Issue
Block a user