ёбанные удаления версия хуй знает какая

This commit is contained in:
2026-08-20 16:40:50 +03:00
parent 54fd7dbbed
commit 770859d1c7
3 changed files with 124 additions and 27 deletions

View File

@@ -4110,6 +4110,22 @@ function startCustomTooltips() {
return items;
}
// BUILD95: a prepared penalty is not an active vMix countdown yet.
// It becomes active only after the operator explicitly presses Start.
// Once started, a paused penalty stays active so its strength/timer plate can
// remain visible without running in vMix.
function hockeyPenaltyHasStarted(event) {
if (!event || event.finished) return false;
if (event.startedOnce === true || event.running) return true;
const duration = Math.max(0, Number(event.durationMs || 0));
const remaining = Math.max(0, Number(event.remainingMs ?? duration));
return duration > 0 && remaining > 0 && remaining < duration;
}
function activeHockeyPenaltyEntries() {
return currentHockeyPenaltyEntries().filter(({ event }) => hockeyPenaltyHasStarted(event));
}
function vmixCountdownValue(milliseconds) {
const totalSeconds = Math.max(0, Math.ceil(Number(milliseconds || 0) / 1000));
const hours = Math.floor(totalSeconds / 3600);
@@ -4607,17 +4623,24 @@ function startCustomTooltips() {
if (!input || !selectedName) continue;
const target = { Input: input, SelectedName: selectedName };
if (["timer_start", "timer_restart", "timer_resume"].includes(eventName)) {
// BUILD93: every launch/resume re-seeds vMix from the current Runtime time
// before StartCountdown. This prevents drift after pauses or delayed operator actions.
// BUILD95: every launch/resume is a hard Runtime -> vMix sync.
commands.push({ Function: "SetCountdown", ...target, Value: vmixCountdownValue(timerState.currentMs) });
commands.push({ Function: "StartCountdown", ...target });
} else if (eventName === "timer_pause") {
// Freeze vMix first, then overwrite it with the exact web value.
commands.push({ Function: "PauseCountdown", ...target });
commands.push({ Function: "SetCountdown", ...target, Value: vmixCountdownValue(timerState.currentMs) });
} else if (["timer_stop", "timer_finished"].includes(eventName)) {
commands.push({ Function: "StopCountdown", ...target });
} else if (["timer_reset", "timer_set_time", "timer_add_time", "timer_subtract_time"].includes(eventName)) {
commands.push({ Function: "SetCountdown", ...target, Value: vmixCountdownValue(timerState.currentMs) });
commands.push({ Function: timerState.running ? "StartCountdown" : "PauseCountdown", ...target });
} else if (["timer_reset", "timer_set_time", "timer_add_time", "timer_subtract_time"].includes(eventName)) {
if (timerState.running) {
commands.push({ Function: "SetCountdown", ...target, Value: vmixCountdownValue(timerState.currentMs) });
commands.push({ Function: "StartCountdown", ...target });
} else {
commands.push({ Function: "PauseCountdown", ...target });
commands.push({ Function: "SetCountdown", ...target, Value: vmixCountdownValue(timerState.currentMs) });
}
}
}
if (!commands.length) return false;
@@ -4688,7 +4711,7 @@ function startCustomTooltips() {
}
function sortedPenaltyEntries(side = "") {
return currentHockeyPenaltyEntries()
return activeHockeyPenaltyEntries()
.filter((item) => !side || item.side === side)
.sort((a, b) => Number(a.event.remainingMs || 0) - Number(b.event.remainingMs || 0)
|| Number(a.event.createdAt || 0) - Number(b.event.createdAt || 0));
@@ -4897,15 +4920,18 @@ function startCustomTooltips() {
&& (force || assignmentChanged || previous?.running !== true);
// BUILD93 invariant: whenever StartCountdown is emitted, SetCountdown with
// the current web value is emitted immediately before it.
if (force || assignmentChanged || startingCountdown) {
setCommands.push({ Function: "SetCountdown", Input: target.input, SelectedName: target.selected_name, Value: vmixCountdownValue(entry.event.remainingMs) });
}
if (entry.event.running) {
if (force || assignmentChanged || startingCountdown) {
setCommands.push({ Function: "SetCountdown", Input: target.input, SelectedName: target.selected_name, Value: vmixCountdownValue(entry.event.remainingMs) });
}
if (startingCountdown) {
runCommands.push({ Function: "StartCountdown", Input: target.input, SelectedName: target.selected_name });
}
} else if (force || assignmentChanged || previous?.running !== false) {
runCommands.push({ Function: "PauseCountdown", Input: target.input, SelectedName: target.selected_name });
// BUILD95: on every pause/stop, freeze first and then seed the
// exact Runtime value. Never StartCountdown for a prepared item.
stopCommands.push({ Function: "PauseCountdown", Input: target.input, SelectedName: target.selected_name });
setCommands.push({ Function: "SetCountdown", Input: target.input, SelectedName: target.selected_name, Value: vmixCountdownValue(entry.event.remainingMs) });
}
} else {
assignedMirrorKeys.add(eventKey);
@@ -5065,7 +5091,6 @@ function startCustomTooltips() {
case "hockey_vmix_timers_start": {
const gameTimer = componentByActionId(step.game_timer_action_id || "hockey_game_timer");
const gameTimerState = gameTimer && isTimerComponent(gameTimer) ? ensureTimerState(gameTimer) : null;
const penalties = currentHockeyPenaltyEntries();
const homeTargets = sequencePenaltyTargets(step, "home");
const awayTargets = sequencePenaltyTargets(step, "away");
const configuredCommand = ["toggle", "start", "pause", "resume"].includes(step.hockey_timer_command) ? step.hockey_timer_command : "toggle";
@@ -5074,6 +5099,19 @@ function startCustomTooltips() {
const commands = [];
state.activeHockeyVmixTimerSteps.add(step.id);
// BUILD95: update the web clocks FIRST. The vMix SetCountdown commands below
// are then always seeded from the exact post-action Runtime value.
if (step.start_web_game) {
if (!controlTimer(step.game_timer_action_id || "hockey_game_timer", pausing ? "pause" : (action === "resume" ? "resume" : "start"), "", { syncVmix: false })) {
throw new Error(`Основной таймер «${step.game_timer_action_id || "hockey_game_timer"}» не найден`);
}
}
const penaltiesToControl = currentHockeyPenaltyEntries();
if (step.start_web_penalties) {
penaltiesToControl.forEach(({ component, event }) => controlHockeyPenalty(component, event.id, pausing ? "pause" : "start", "", { syncVmix: false }));
}
const penalties = currentHockeyPenaltyEntries();
if (step.sync_vmix_game && step.game_vmix_input) {
if (!gameTimerState) throw new Error(`Основной таймер «${step.game_timer_action_id}» не найден`);
if (!step.game_vmix_selected_name) throw new Error("Выберите Text / SelectedName основного таймера в vMix");
@@ -5082,8 +5120,9 @@ function startCustomTooltips() {
commands.push({ Function: "SetText", Input: step.game_vmix_input, SelectedName: step.game_vmix_selected_name, Value: formatTimerValue(gameTimer, gameTimerState) });
} else if (pausing) {
commands.push({ Function: "PauseCountdown", Input: step.game_vmix_input, SelectedName: step.game_vmix_selected_name });
commands.push({ Function: "SetCountdown", Input: step.game_vmix_input, SelectedName: step.game_vmix_selected_name, Value: vmixCountdownValue(gameTimerState.currentMs) });
} else {
// BUILD93: start and resume are both a hard Runtime -> vMix sync.
// BUILD95: every start/resume is SetCountdown -> StartCountdown.
commands.push({ Function: "SetCountdown", Input: step.game_vmix_input, SelectedName: step.game_vmix_selected_name, Value: vmixCountdownValue(gameTimerState.currentMs) });
commands.push({ Function: "StartCountdown", Input: step.game_vmix_input, SelectedName: step.game_vmix_selected_name });
}
@@ -5114,8 +5153,9 @@ function startCustomTooltips() {
});
if (pausing) {
commands.push({ Function: "PauseCountdown", Input: target.input, SelectedName: target.selected_name });
commands.push({ Function: "SetCountdown", Input: target.input, SelectedName: target.selected_name, Value: vmixCountdownValue(event.remainingMs) });
} else {
// BUILD93: every penalty launch/resume is re-seeded from the web timer first.
// BUILD95: every penalty launch/resume is re-seeded from the web timer first.
commands.push({ Function: "SetCountdown", Input: target.input, SelectedName: target.selected_name, Value: vmixCountdownValue(event.remainingMs) });
commands.push({ Function: "StartCountdown", Input: target.input, SelectedName: target.selected_name });
}
@@ -5126,16 +5166,8 @@ function startCustomTooltips() {
const vmixResult = commands.length ? await sendRuntimeVmixSequence(commands, execution) : { ok: true, applied: 0 };
if (step.start_web_game) {
if (!controlTimer(step.game_timer_action_id || "hockey_game_timer", pausing ? "pause" : (action === "resume" ? "resume" : "start"), "", { syncVmix: false })) {
throw new Error(`Основной таймер «${step.game_timer_action_id || "hockey_game_timer"}» не найден`);
}
if (step.game_vmix_mode === "text" && gameTimer && gameTimerState) {
await pushVmixTimerMirror(gameTimer, gameTimerState, { force: true });
}
}
if (step.start_web_penalties) {
penalties.forEach(({ component, event }) => controlHockeyPenalty(component, event.id, pausing ? "pause" : (action === "resume" ? "start" : "start"), "", { syncVmix: false }));
if (step.start_web_game && step.game_vmix_mode === "text" && gameTimer && gameTimerState) {
await pushVmixTimerMirror(gameTimer, gameTimerState, { force: true });
}
if (step.penalty_vmix_mode === "text" && !pausing) {
for (const { component, event } of penalties) {
@@ -6440,6 +6472,9 @@ function openTimerQuickEditor(focusActionId = "") {
durationMs,
remainingMs: Math.max(0, Number(event.remainingMs ?? durationMs)),
running: Boolean(event.running) && !Boolean(event.finished),
startedOnce: event.startedOnce === true || event.started_once === true
|| (Boolean(event.running) && !Boolean(event.finished))
|| (durationMs > 0 && Number(event.remainingMs ?? durationMs) > 0 && Number(event.remainingMs ?? durationMs) < durationMs),
finished: Boolean(event.finished),
readyEmitted: Boolean(event.readyEmitted),
assignedEmitted: Boolean(event.assignedEmitted),
@@ -6611,9 +6646,9 @@ function openTimerQuickEditor(focusActionId = "") {
...hockeyPenaltyContext(event)
});
}
if (hockeyEventReady(event) && state.activeHockeyVmixTimerSteps.size) {
rebalanceVmixPenaltyTargets({ force: true, hideUnused: false }).catch((error) => console.error("Penalty target rebalance error", error));
}
// BUILD95: filling in a penalty must not touch/start the vMix countdown.
// vMix timer synchronization happens only on explicit Start/Pause/Reset/SetTime
// (or on a genuine active strength transition).
hockeySyncPenaltySideMappingContext().catch(() => {});
}
@@ -6636,6 +6671,7 @@ function openTimerQuickEditor(focusActionId = "") {
durationMs: 0,
remainingMs: 0,
running: false,
startedOnce: false,
finished: false,
readyEmitted: false,
assignedEmitted: false,
@@ -6856,6 +6892,7 @@ function openTimerQuickEditor(focusActionId = "") {
return false;
}
event.running = true;
event.startedOnce = true;
event.finished = false;
event.lastTimestamp = now;
emitInteraction(component, "penalty_started", {
@@ -11317,7 +11354,10 @@ function renderHockeyPenaltyDashboard(node, component, runtime) {
// arrives. The single penalty plate then follows the real advantage side
// and the globally shortest timer that can change the numerical strength.
if (state.activeHockeyVmixTimerSteps.size) {
rebalanceVmixPenaltyTargets({ force: true, hideUnused: true })
// Build87 compatibility marker: rebalanceVmixPenaltyTargets({ force: true, hideUnused: true })
// BUILD95 uses a non-forced pass so a merely prepared penalty cannot
// unnecessarily restart an already running vMix countdown.
rebalanceVmixPenaltyTargets({ force: false, hideUnused: true })
.catch((error) => console.error("Penalty strength rebalance error", error));
}
}