ёбанные удаления версия хуй знает какая
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user