${name}
@@ -258,13 +255,51 @@ class ReplayCanvasEngine {
}
html += '
'
panel.innerHTML = html
- panel.querySelectorAll('.rc-row').forEach((row) => {
+
+ const rows = panel.querySelectorAll('.rc-row')
+ let i = 0
+ for (const [pid, list] of byPlayer) {
+ const row = rows[i++]
+ if (!row) continue
+ const pr = {
+ row,
+ playerId: pid,
+ entities: list,
+ vehEl: row.querySelector('.rc-row-veh'),
+ iconEl: row.querySelector('.rc-type-icon'),
+ statusEl: row.querySelector('.rc-row-status'),
+ shownEntityIndex: list[0].entityIndex,
+ currentEntityIndex: list[0].entityIndex,
+ }
+ this._panelRows.push(pr)
row.addEventListener('mouseenter', () => {
- const ent = this.entities.find((e) => e.entityIndex === Number(row.dataset.entityIndex))
- if (ent && !this._isEntityGone(ent, this.currentTime)) this._setHighlight(Number(row.dataset.playerId))
+ const ent = pr.currentEntityIndex != null
+ ? this.entities.find((e) => e.entityIndex === pr.currentEntityIndex)
+ : null
+ if (ent && !this._isEntityGone(ent, this.currentTime)) this._setHighlight(pid)
})
row.addEventListener('mouseleave', () => this._setHighlight(null))
- })
+ }
+ }
+
+ // Resolve which of a player's spawned vehicles is relevant at time t, and
+ // whether the player is currently alive, down (awaiting respawn), or gone.
+ _playerStateAtTime(entities, t) {
+ for (const e of entities) {
+ const first = e.times[0]
+ const last = e.times[e.times.length - 1]
+ if (t >= first && t <= last && !this._isEntityDead(e, t)) {
+ return { entity: e, dead: false, gone: false }
+ }
+ }
+ let recent = null
+ for (const e of entities) {
+ if (e.times[0] <= t && (!recent || e.times[0] >= recent.times[0])) recent = e
+ }
+ if (!recent) return { entity: entities[0] || null, dead: false, gone: false }
+ const hasFutureSpawn = entities.some((e) => e.times[0] > t)
+ const gone = !hasFutureSpawn && this._isEntityGone(recent, t)
+ return { entity: recent, dead: true, gone }
}
_buildEventList() {
@@ -402,16 +437,25 @@ class ReplayCanvasEngine {
_updatePanelDeathStates() {
const t = this.currentTime
- this.container.querySelectorAll('.rc-row').forEach((row) => {
- const ent = this.entities.find((e) => e.entityIndex === Number(row.dataset.entityIndex))
- if (!ent) return
- const dead = this._isEntityDead(ent, t)
- const gone = this._isEntityGone(ent, t)
- row.classList.toggle('rc-dead', dead)
- row.classList.toggle('rc-gone', gone)
- const status = row.querySelector('.rc-row-status')
- status.textContent = dead || gone ? 'x' : ''
- })
+ if (!this._panelRows) return
+ for (const pr of this._panelRows) {
+ const st = this._playerStateAtTime(pr.entities, t)
+ pr.currentEntityIndex = st.entity ? st.entity.entityIndex : null
+ if (st.entity && st.entity.entityIndex !== pr.shownEntityIndex) {
+ pr.shownEntityIndex = st.entity.entityIndex
+ if (pr.vehEl) pr.vehEl.textContent = st.entity.vehicleName || ''
+ if (pr.iconEl) {
+ const panelIcon = st.entity.miniIcon
+ ? st.entity.miniIcon.replace('mini:', '')
+ : (st.entity.iconKey || 'medium')
+ pr.iconEl.src = `/api/icons/type/${panelIcon}`
+ pr.iconEl.style.display = ''
+ }
+ }
+ pr.row.classList.toggle('rc-dead', st.dead)
+ pr.row.classList.toggle('rc-gone', st.gone)
+ if (pr.statusEl) pr.statusEl.textContent = st.dead || st.gone ? 'x' : ''
+ }
}
_updateBattleLog() {
diff --git a/frontend/src/styles.css b/frontend/src/styles.css
index a23c41c..1a83570 100644
--- a/frontend/src/styles.css
+++ b/frontend/src/styles.css
@@ -524,8 +524,7 @@ h3 {
.rc-layout {
display: grid;
- grid-template-columns: minmax(190px, 230px) minmax(560px, 720px) minmax(190px, 230px);
- width: min(100%, 1210px);
+ grid-template-columns: minmax(160px, 200px) min(720px, 60vh, 92vw) minmax(160px, 200px);
gap: 0.5rem;
align-items: start;
justify-content: center;