// ==UserScript==
// @name HWM_roulette_upd
// @namespace Мифист
// @author Мифист
// @version 1.2
// @description Пропатченная рулетка
// @match https://www.heroeswm.ru/roulette.php*
// @match https://*.lordswm.com/roulette.php*
// @run-at document-end
// @grant none
// @license MIT
// @noframes
// ==/UserScript==
(function(view) {
'use strict';
clearTimeout(view.Timer);
if (!Element.prototype.scrollIntoViewIfNeeded) {
Element.prototype.scrollIntoViewIfNeeded = Element.prototype.scrollIntoView;
}
// =========================
const DEV_ID = '5781303';
const PATH = '/roulette.php';
const MODULE_NAME = 'HWM_roulette_upd';
const SYMBOL = Symbol.for(`__${DEV_ID}__`);
const modules = view[SYMBOL] || (view[SYMBOL] = {});
modules[MODULE_NAME] = '1.0';
const {
$, $$,
fetch, clamp, wait, memoize,
attempt, parseNode, getElemIndex
} = (document[`__utils_${DEV_ID}`] || (() => {
const $ = (selector, ctx = document) => ctx.querySelector(selector);
const $$ = (selector, ctx = document) => [...ctx.querySelectorAll(selector)];
const getElemIndex = elem => [...elem.parentNode.children].indexOf(elem);
const clamp = (min, val, max = Infinity) => Math.max(min, Math.min(val, max));
const wait = (sec = 1) => new Promise((resolve) => setTimeout(resolve, 1e3 * sec));
function attempt(that, callback, thisArg) {
if (thisArg == null) thisArg = that;
return that ? callback.call(thisArg, that) : null;
}
function parseNode(html, callback) {
let elem = document.createElement('div');
elem.innerHTML = html;
elem = elem.firstElementChild.cloneNode(true);
callback && callback.call(elem, elem);
return elem;
}
function memoize(fn) {
const cache = new Map();
return (x) => cache.get(x) || cache.set(x, fn(x)).get(x);
}
function fetch({ url, method = 'GET', type = 'document', body = null }) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.responseType = type;
xhr.onload = () => {
const {status} = xhr;
if (status === 200) return resolve(xhr.response);
const er = new Error(`Error with status ${status}`);
er.status = status;
reject(er);
};
xhr.onerror = () => {
const {status} = xhr;
const er = new Error(`HTTP error with status ${status}`);
er.status = status;
reject(er);
};
xhr.send(body);
});
}
fetch.get = url => fetch({ url });
fetch.post = (url, data) => fetch({ url, method: 'POST', body: data });
return {
$, $$,
fetch, clamp, wait, memoize,
attempt, parseNode, getElemIndex
};
})());
const formatNum = (num) => num.toLocaleString('en');
const parseNum = (num) => String(num).replaceAll(',', '') >> 0;
const activeElem = (elem) => elem.classList.add('__active');
const inactiveElem = (elem) => elem.classList.remove('__active');
const reduceBets = (bets, key = 'value') => bets.reduce((a, b) => a + b[key], 0);
// =========================
let locked = false;
const RED_NUMBERS = [1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36];
const [MIN, MAX, CASH] = getMinMaxCash();
function getMinMaxCash(context = document) {
const script = context.forms[0].previousElementSibling;
const {text} = script;
const reg = /bet\.value [><] (\d+)/g;
const [cash, min] = [...text.matchAll(reg)].map(match => +match[1]);
const max = +text.match(/maxsum = (\d+)/)[1];
return [min, max, cash];
}
function checkMoney(value) {
if (roulCash < value) return !!roulAlert.notEnoughMoney();
if (roulBets.sum + value > roulBets.MAX) return !!roulAlert.outOfRange();
return true;
}
function hwmBetToObject(elem) {
const id = elem.lastElementChild.textContent;
const value = parseNum(elem.firstElementChild.textContent);
return { id, value };
}
function renderApp() {
return /*html*/`
<main id="container">
${templates.Tools()}
${templates.Roul()}
${templates.Wheel()}
${templates.Details()}
${templates.Bets()}
${templates.Games()}
${templates.Density()}
${templates.Alert()}
<div id="roul-tip"></div>
<div id="roul-mark">
<div>roulette</div>
by <a href="/pl_info.php?id=${DEV_ID}">Мифист</a>
</div>
</main>
`;
}
const templates = {
Games() {
return /*html*/`
<section id="roul-games" class="roul-box">
<div class="roul-games__info roul-box">
<div class="roul-games__bets roul-box-body ui-scroll"></div>
<footer class="roul-games__footer roul-box-footer"></footer>
</div>
<div class="roul-games__content roul-box-body ui-scroll">
<div class="roul-games__body"></div>
<b id="load_prev_games" title="Загрузить последние 24 игры">+</b>
</div>
</section>
`;
},
Density(context = document) {
const table = $$('table.wb', context).pop().firstElementChild;
const allSum = table.lastElementChild.textContent.split(/\s/)[0];
const items = [...table.children].slice(1, -1).map(row => {
const id = row.lastElementChild.textContent;
const val = row.firstElementChild.textContent;
return /*html*/`
<div class="roul-box-row">
<span class="roul-bet-key">${roulID.get(id)}</span>
<span class="roul-bet-value">${val}</span>
</div>
`;
});
return /*html*/`
<section id="roul-density" class="roul-box">
<header class="roul-box-header">
Плотность ставок
<button id="roul-density__upd">↻</button>
</header>
<div class="roul-box-body ui-scroll">${items.join('')}</div>
<footer class="roul-box-footer">
Всего:
<span class="roul-sum">${allSum}</span>
</footer>
</section>
`;
},
Details() {
return /*html*/`
<section id="roul-details" class="roul-box">
<div id="roul-cash">Баланс: <span class="roul-sum">${formatNum(CASH)}</span></div>
<div id="roul-minbet">Минимальная ставка: <span class="roul-sum">${formatNum(MIN)}</span></div>
<div id="roul-maxbet">Макс. сумма ставок: <span class="roul-sum">${formatNum(MAX)}</span></div>
<div>До спина: <span id="roul-timer">00:00</span></div>
</section>
`;
},
Bets() {
return /*html*/`
<section id="roul-bets" class="roul-box">
<header class="roul-bets__tabs">
<div class="roul-bets__tab __active">Ставки (стол)</div>
<div class="roul-bets__tab">Ставки (принято) <span id="roul-bets__count">0 / 0</span></div>
</header>
<div class="roul-box">
<div class="roul-bets__body roul-box-body ui-scroll"></div>
<footer class="roul-bets__footer roul-box-footer">
<div class="roul-bets__apply">
<div class="roul-bets__action" data-action="cancel">×</div>
<div class="roul-bets__action" data-action="accept">✓</div>
</div>
Всего: <span class="roul-sum">0</span>
</footer>
</div>
<div class="roul-box" hidden>
<div class="roul-bets__body roul-box-body ui-scroll"></div>
<footer class="roul-bets__footer roul-box-footer">Всего: <span class="roul-sum">0</span></footer>
</div>
</section>
`;
},
Alert() {
return /*html*/`
<div id="roul-alert" data-action="hide">
<div id="roul-alert__inner">
<div id="roul-alert__content"></div>
<button id="roul-alert__ok" data-action="hide">OK</button>
</div>
</div>
`;
},
Wheel() {
const numbers = '0-28-9-26-30-11-7-20-32-17-5-22-34-15-3-24-36-13-1-00-27-10-25-29-12-8-19-31-18-6-21-33-16-4-23-35-14-2'.split('-');
const angle = 360 / numbers.length / 360;
const rotate = ind => `style="--turn: ${+(angle * ind).toFixed(3)}turn"`;
const getSegment = (num, ind) => {
return /*html*/`<div class="wheel__segment" data-num="${num}" ${rotate(ind)}></div>`;
};
return /*html*/`
<section id="wheel">
<div id="wheel__main">
<div id="wheel__segments">${numbers.map(getSegment).join('')}</div>
<div id="wheel__rotor">
${'<div class="wheel__line"></div>'.repeat(4)}
<div id="wheel__turret"></div>
</div>
</div>
<div id="wheel__ball"></div>
<div id="wheel__info">
<div id="wheel__number"></div>
<div id="wheel__score">Выигрыш: </div>
</div>
</section>
`;
},
Tools() {
function getCoin(val) {
return /*html*/`<b class="roul-coin" data-coin="${val}" data-action="setMultiplier"></b>`;
}
const coins = [5, 25, 50, 100, 250, 500, '1e3', 'MIN', 'MAX'];
return /*html*/`
<section id="roul-tools">
<div class="roul-tools__item">${coins.map(getCoin).join('')}</div>
<div class="roul-tools__item">
<button class="roul-tools__btn" data-action="rebet" title="Повторить последние ставки">←</button>
</div>
</section>
`.replace('" data-coin="100', ' __active" data-coin="100');
},
Roul() {
return /*html*/`
<section id="roulette">
${this.RoulInside()}
${this.RoulOutside()}
${this.Chips()}
</section>
`;
},
RoulInside() {
const getColor = id => RED_NUMBERS.includes(id) ? 'red' : 'black';
const getZero = id => getCell(id).replace('black', 'zero');
function getCell(id) {
const className = `roul-area roul-cell roul-num roul-${getColor(id)}`;
return /*html*/`
<div class="${className}" data-id="Straight up ${id}">
<b class="roul-value">${id}</b>
<b class="roul-fish"></b>
</div>
`;
}
function getColumn(order) {
return getCell('&').replace('num roul-black', 'x3 roul-column')
.replace('Straight up &', `${order} Column`)
.replace('&', '2 to 1');
}
const coloredCellsHTML = [...Array(12)].map((x, i) => {
const start = i * 3 + 1;
const cells = [start + 2, start + 1, start].map(getCell);
return /*html*/`<div class="roulette__col">${cells.join('')}</div>`;
}).join('');
const zerosHTML = ['00', '0'].map(getZero).join('');
const columnsHTML = ['3rd', '2nd', '1st'].map(getColumn).join('');
return /*html*/`
<div class="roulette__section">
<div class="roulette__col">${zerosHTML}</div>
${coloredCellsHTML}
<div class="roulette__col">${columnsHTML}</div>
</div>
`;
},
RoulOutside() {
const defaultClasses = ['area', 'outside', 'x3'].map(x => `roul-${x}`);
function getDozen(order) {
const classes = [...defaultClasses, 'roul-dozen'];
return /*html*/`
<div class="${classes.join(' ')}" data-id="${order} Dozen">
<b class="roul-value">${order[0]}<sup>${order.slice(1)}</sup> 12</b>
<b class="roul-fish"></b>
</div>
`;
}
function getSection(id) {
const value = id === '1-18 Half' ? '1 to 18' : id === '19-36 Half' ? '19 to 36' : id;
const classes = [...defaultClasses];
if (/R|B/.test(id[0])) classes.push(`roul-${id.toLowerCase()}`);
return /*html*/`
<div class="${classes.join(' ')}" data-id="${id}">
<b class="roul-value">${value}</b>
<b class="roul-fish"></b>
</div>
`;
}
const dozens = ['1st', '2nd', '3rd'];
const others = ['1-18 Half', 'EVEN', 'RED', 'BLACK', 'ODD', '19-36 Half'];
return /*html*/`
<div class="roulette__section">
${dozens.map(getDozen).join('')}
${others.map(getSection).join('')}
</div>
`;
},
Chips() {
function chip(id, num = 0, mod = 'n') {
const className = `roul-chip roul-chip${num} roul-chip-${mod}`;
return /*html*/`<b class="${className}" data-id="${id}"></b>`;
}
function getSplits(start) {
return chip(`Split ${start}, ${start + 3}`, 4)
+ chip(`Split ${start - 1}, ${start + 2}`, 6)
+ chip(`Split ${start - 2}, ${start + 1}`, 8)
+ chip(`Split ${start - 1}, ${start}`, 5, 'h')
+ chip(`Split ${start - 2}, ${start - 1}`, 7, 'h');
}
function getCorners(start) {
const nums1 = [start - 1, start, start + 2, start + 3].join(', ');
const nums2 = [start - 2, start - 1, start + 1, start + 2].join(', ');
return chip(`Corner ${nums1}`, 5) + chip(`Corner ${nums2}`, 7);
}
function getStreetAndSixline(start) {
return chip(`Street ${start - 2}-${start}`, 9, 'h')
+ chip(`Sixline ${start - 2}-${start + 3}`, 9);
}
const colsHTML = [...Array(11)].map((x, i) => {
const start = i * 3 + 3;
return /*html*/`
<div class="roul-chips__col">
${getSplits(start)}
${getCorners(start)}
${getStreetAndSixline(start)}
</div>
`;
}).join('');
return /*html*/`
<div id="roul-chips">
<div class="roul-chips__col">
${chip('Split 3, 00', 4)}
${chip('Numbers 2, 00, 3', 5)}
${chip('Split 2, 00', 0, 'x').replace('>', ' style="top: 4.5em;">')}
${chip('Split 0, 2', 0, 'x').replace('>', ' style="top: 6.3em;">')}
${chip('Split 0, 1', 8)}
${chip('Numbers 0, 1, 2', 7)}
${chip('Numbers 0, 00, 1, 2, 3', 9)}
${chip('Split 0, 00', 0, 'h').replace('>', ' style="top: 50%;">')}
${chip('Numbers 0, 00, 2').replace('>', ' style="top: 50%;">')}
</div>
${colsHTML}
<div class="roul-chips__col">
${chip('Split 35, 36', 5, 'h')}
${chip('Split 34, 35', 7, 'h')}
${chip('Street 34-36', 9, 'h')}
${chip('Red Snake', 9)}
</div>
</div>
`;
},
};
const roulID = {
get(id) { return this[id] || id; },
'Numbers 0, 00, 1, 2, 3': 'Top Line',
'Numbers 0, 1, 2': 'Basket 1',
'Numbers 0, 00, 2': 'Basket 2',
'Numbers 2, 00, 3': 'Basket 3',
'Split 0, 00': 'Row',
'Split 2, 00': 'Split 00, 2',
'Split 3, 00': 'Split 00, 3',
'1-18 Half': '1 to 18',
'19-36 Half': '19 to 36',
};
roulID.keys = Object.keys(roulID).slice(1);
// =========================
const logError = {
create: (status, msg) => Object.assign(new Error(msg), { status }),
disconnect(er, data) {
const text = {
'-1': 'Произошла деавторизация; рулетка не доступна',
0: 'Потеряно соединение с интернетом',
}[er.status] || 'Что-то пошло не так :)';
this.show(er, text);
data && this.handleData(data);
},
show({message}, userText) {
console.error(message);
roulAlert.show(/*html*/`
<div id="roul-log">
<p>${message}</p>
<p>${userText}</p>
</div>
`);
},
handleData(data) {
const log = $('#roul-log');
const link = parseNode(`<a href="#">${data.text}</a>`);
log.appendChild(link);
link.onclick = (e) => {
link.onclick = null;
e.preventDefault();
data.callback();
};
}
}
// =============== [[ FORM ]]
const formData = ((form) => {
!form.parlay_dec && setParlayDec(document.body);
function setParlayDec(ctx) {
const gameId = ctx.innerHTML.match(/inforoul\.php\?id=(\d+)/)[1];
const inputHTML = `<input name="parlay_dec" value="${+gameId + 1}">`;
form.appendChild(parseNode(inputHTML));
}
function isEquals(bet1, bet2) {
const a = [bet1.id, bet1.value].toString();
const b = [bet2.id, bet2.value].toString();
return a === b;
}
function betToPostData(bet) {
const data = new FormData(form);
data.set('bettype', bet.id);
data.set('bet', bet.value);
return data;
}
function sendRedSnake(self, snakeBet) {
if (!snakeBet.initialized) {
snakeBet.initialized = true;
const keys = '9, 12|16, 19|27, 30|1|5|14|23|32|34'.split('|');
snakeBet.keys = keys.map((x, i) => {
return i < 3 ? `Split ${x}` : `Straight up ${x}`;
});
}
const betVal = snakeBet.value / 12 >> 0;
const bets = snakeBet.keys.map((id) => {
const value = betVal * (1 + +id.startsWith('Sp'));
return { id, value, count: 0, onLoadEnd: Function.prototype };
});
return new Promise(function next(resolve, reject) {
if (locked) bets.splice(0);
if (!bets.length) {
snakeBet.onLoadEnd(snakeBet.keys.length ? 2 : 1);
return resolve();
}
const bet = bets.pop();
return self.send(bet).then(status => {
if (status === 1) {
snakeBet.keys.splice(snakeBet.keys.indexOf(bet.id), 1);
} else if (!bet.count++) bets.push(bet);
return wait(0.2).then(() => next(resolve, reject));
}).catch(reject);
});
}
return {
get seconds() {
return form.minutes.value * 60 + +form.seconds.value;
},
get restSeconds() {
return clamp(0, 300 - this.seconds);
},
get gameId() {
return form.parlay_dec.value;
},
async send(bet) {
if (bet.id === 'Red Snake') return await sendRedSnake(this, bet);
const doc = await fetch.post(form.action, betToPostData(bet))
if (!doc.URL.includes(PATH)) {
throw logError.create(-1, 'Authorization error');
}
const table = $('table.wb:nth-child(2)', doc);
const test = isEquals(bet, hwmBetToObject(table.rows[1]));
const status = test ? 1 : 2;
bet.onLoadEnd(status);
return status;
},
update(newForm) {
form = document.importNode(newForm, true);
!form.parlay_dec && setParlayDec(newForm.closest('body'));
}
};
})(document.forms[0].cloneNode(true));
// =========================
const initialBets = [...$('table.wb:nth-child(2)').rows]
.slice(1, -1)
.map(hwmBetToObject);
const container = parseNode(renderApp());
$$('link', document.head).forEach((link) => {
const reg = /(sweetalert|top_basic)\.css/;
return reg.test(link.href) && link.remove();
});
parseNode('<style id="roul-CSS"></style>', function() {
this.append(/*css*/`
@keyframes roulWin {
0% {filter: brightness(1);}
100% {filter: brightness(1.1);}
}
@keyframes roulSpin {
100% {transform: rotate(1turn);}
}
/* === GLOBAL === */
:root {
font-size: 10px;
overflow-y: visible;
}
body.txt {
overflow-y: visible;
}
main *,
::before,
::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
[hidden] {
display: none !important;
}
/* === COMMON === */
.ui-scroll {
overflow-x: hidden;
overflow-y: auto;
}
.ui-scroll::-webkit-scrollbar {
width: 6px;
background-color: #eee;
}
.ui-scroll::-webkit-scrollbar-thumb {
background-color: #ccc;
}
.ui-scroll::-webkit-scrollbar-thumb:hover {
background-color: #aaa;
}
.ui-scroll::-webkit-scrollbar-thumb:active {
background-color: gray;
}
.roul-bet-key {
padding: 0 0.5em;
border-right: var(--border);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 11.5em;
}
.roul-bet-value {
padding: 0 0.5em;
text-align: center;
color: brown;
flex: 1;
}
.roul-box {
background-color: var(--bg);
box-shadow: var(--shadow);
overflow: hidden;
}
.roul-box-body {
background-color: inherit;
}
.roul-box-body:empty {
display: flex;
justify-content: center;
align-items: center;
}
.roul-box-body:empty::after {
content: "No bets";
color: gray;
}
.roul-box-row {
line-height: 1.8;
display: flex;
background-color: inherit;
box-shadow: var(--shadow);
}
.roul-box-row:nth-child(odd) {
background-color: #efebe4;
}
.roul-box-header,
.roul-box-footer {
line-height: 2;
position: relative;
padding: 0 0.5em;
background-color: #e9e0c6;
box-shadow: var(--shadow);
overflow: hidden;
}
.roul-box-footer {
margin-top: -1px;
text-align: right;
background-color: #d9e4d2;
}
.roul-box-body:empty + footer {
visibility: hidden;
}
.roul-sum {
color: brown;
}
/* === CONTAINER === */
#container {
--bg: #e9e4dc;
--shadow: 0 0 0 1px #aaa;
--border: 1px solid #aaa;
--highlight-color-1: #d7c4a4;
--highlight-color-2: #dfd6c8;
--highlight-color-3: #eaebb9;
--selected-bet: linear-gradient(to right, var(--highlight-color-3), transparent);
font-family: Arial, Helvetica, sans-serif;
font-size: 1.6rem;
max-width: 102rem;
min-width: 64rem;
position: relative;
margin: 1rem auto 0;
color: #333;
user-select: none;
}
#container.__locked > section {
filter: grayscale(.3);
pointer-events: none;
}
/* === TOOLS === */
#roul-tools {
width: 100%;
max-width: 84rem;
display: flex;
justify-content: space-between;
column-gap: 3em;
position: absolute;
left: calc(50% - 42rem);
top: 1rem;
}
#container.__locked > #roul-tools {
filter: grayscale(1);
}
.roul-tools__item {
display: flex;
column-gap: 0.2em;
}
.roul-coin,
.roul-tools__btn {
font-size: 1em;
width: 3em;
height: 3em;
display: inline-block;
position: relative;
border-radius: 50%;
cursor: pointer;
}
.roul-coin {
display: flex;
justify-content: center;
align-items: center;
color: #172533;
background-color: #333;
box-shadow: inset 0 0 0 0.5rem currentColor, inset 0 0 0 0.6rem #cfcf89;
opacity: .7;
}
.roul-coin:nth-child(1) {
color: #88a06d;
}
.roul-coin:nth-child(2) {
color: #a89632;
}
.roul-coin:nth-child(3) {
color: #bc6d0b;
}
.roul-coin:nth-child(4) {
color: #8d2525;
}
.roul-coin:nth-child(5) {
color: #62258d;
}
.roul-coin:nth-child(6) {
color: #0044a5;
}
.roul-coin:nth-child(7) {
color: #115b68;
}
.roul-coin:hover {
transform: scale(1.2);
z-index: 2;
}
.roul-coin.__active {
opacity: 1;
pointer-events: none;
}
.roul-coin::before {
content: "";
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
border: .2em dashed #eee;
border-radius: inherit;
box-shadow: 0 0 0 1px #555, 0 0 4px 1px #111;
}
.roul-coin::after {
content: attr(data-coin);
font-family: Consolas, monospace;
font-size: .95em;
color: #eee;
}
.roul-tools__btn {
background-color: #f3e9d1;
border: 2px solid #999;
border-color: #999 #666 #666 #999;
}
.roul-tools__btn:hover,
.roul-tools__btn:focus {
background-color: #eadec0;
}
.roul-tools__btn:active {
transform: scale(.9);
}
.roul-tools__btn::after {
content: "";
position: absolute;
top: 2px; right: 2px; bottom: 2px; left: 2px;
border: 1px dashed #999;
border-radius: inherit;
}
/* === ROULETTE === */
#roulette {
font-size: 2rem;
width: 42em;
display: flex;
flex-direction: column;
align-items: center;
position: absolute;
left: calc(50% - 42rem);
top: 9.5rem;
color: #8191a2;
pointer-events: none;
}
#roulette::before {
content: "";
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: -1em;
background-color: var(--bg);
border: 2px solid #afa18e;
border-top-left-radius: 1.5em;
}
.roulette__section {
display: flex;
flex-wrap: wrap;
}
.roulette__section:first-child {
height: 10.8em;
position: relative;
z-index: 2;
}
.roulette__section:nth-child(2) {
width: calc(100% - 6em);
height: 8em;
}
.roulette__col {
display: flex;
flex-direction: column;
}
.roulette__col:nth-last-child(2) {
position: relative;
z-index: 2;
}
.roul-area {
position: relative;
background-color: var(--bg);
box-shadow: 0 0 0 1px #8d8d8d;
cursor: pointer;
pointer-events: auto;
}
.__target {
background-color: #dfd6c8;
}
.roul-area:hover {
background-color: #d7c4a4;
}
.roul-x3.__target,
.roul-x3:hover {
color: #eadecf;
background-color: #c4a97e;
}
.roul-cell {
flex: 1;
width: 3em;
display: flex;
justify-content: center;
align-items: center;
}
.roul-black {
color: #222 !important;
}
.roul-red {
color: #b43b3b !important;
}
#winner {
background-color: var(--highlight-color-3);
animation: roulWin .3s alternate 4;
}
.roul-value {
pointer-events: none;
}
.roul-column > .roul-value {
transform: rotate(-90deg);
}
/* outside */
.roul-outside {
width: 6em;
display: flex;
justify-content: center;
align-items: center;
}
.roul-dozen {
width: 12em;
}
.roul-outside > .roul-value {
font-size: 1.1em;
}
.roul-dozen > .roul-value {
font-size: 1.5em;
}
/* chips */
#roul-chips {
height: 10.8em;
display: flex;
position: absolute;
left: 0;
right: 0;
top: 0;
}
.roul-chips__col {
width: 3em;
height: 100%;
position: relative;
}
.roul-chip {
width: 0.8em;
height: 0.8em;
position: absolute;
right: -0.4em;
margin-top: -0.4em;
background-color: transparent !important;
pointer-events: auto;
cursor: pointer;
z-index: 4;
}
.roul-chip-h {
width: auto;
left: 0; right: 0;
}
.roul-chip4 {
top: 0;
}
.roul-chip5, .roul-chip6 {
top: 33.333%;
}
.roul-chip7, .roul-chip8 {
top: 66.666%;
}
.roul-chip9 {
top: 100%;
z-index: 5;
}
.roul-chip4, .roul-chip6, .roul-chip8 {
height: 3.6em;
margin-top: 0;
}
.roul-chip[data-id^="Cor"],
.roul-chip[data-id^="Six"] {
z-index: 6;
}
.roul-chip-x {
height: 1em;
margin-top: -0.5em;
}
.roul-chips__col:first-child > .roul-chip {
z-index: 7;
}
.roul-fish,
.roul-chip::after {
--size: 1.5em;
font-size: 0.8em;
width: var(--size);
height: var(--size);
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: calc(50% - var(--size) / 2);
left: calc(50% - var(--size) / 2);
color: #eee;
background-color: #1c405f;
border: .2em dashed;
border-radius: 50%;
box-shadow: 0 0 0 1px #333, 0 0 3px 1px #111;
pointer-events: none;
opacity: 0;
z-index: 4;
}
.roul-fish::after,
.roul-chip::after {
content: "$";
font-weight: normal;
}
.roul-area:hover > .roul-fish,
.__target > .roul-fish,
.roul-chip:hover::after,
.roul-chip.__target::after {
opacity: 1;
}
/* zero */
.roul-zero {
color: #55a867;
border-top-left-radius: 3rem;
}
.roul-zero:last-child {
border-top-left-radius: 0;
border-bottom-left-radius: 3rem;
}
/* disable */
#container.__locked .roul-area,
#container.__locked .roul-chip {
pointer-events: none;
}
/* === WHEEL === */
#wheel {
--diameter: 26rem;
--radius: calc(var(--diameter) / 2);
width: var(--diameter);
height: var(--diameter);
position: absolute;
left: -23rem;
top: -8rem;
text-align: center;
color: #eee;
border-radius: 50%;
filter: drop-shadow(0 0 2px #333);
pointer-events: none;
z-index: 100;
}
#wheel::before {
content: "";
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: -9%;
border-radius: inherit;
background-color: #6b432f;
background-image: radial-gradient(50% 50%, #5d2e18 91%, #271a07 92%, #6b432f 93%);
box-shadow: inset -2px -2px 4px #fff9;
}
#wheel.__spining {
transition: transform 2.05s linear;
}
#wheel.__spinending {
transition: transform 5.1s cubic-bezier(.35, .9, .7, 1);
}
#wheel__main {
--turn: 0;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
border-radius: inherit;
transition: inherit;
transform: rotate(calc(1turn * var(--turn)));
}
/* segments */
#wheel__segments {
height: 100%;
position: relative;
}
#wheel__segments::before,
#wheel__segments::after {
content: "";
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
border-radius: 50%;
}
#wheel__segments::before {
margin: -4px;
border: 1px solid #8b7b74;
box-shadow: inset 0 0 0 6px #bfb997, inset 0 0 0 8px #e3e0ca;
z-index: 2;
}
#wheel__segments::after {
margin: 12%;
background-color: rgba(10, 10, 10, .5);
border: 1px solid #e6cc9a;
box-shadow: inset 0 0 0 1px #e4d6bb;
}
.wheel__segment {
--size: calc(var(--diameter) / 12);
--size2: calc(var(--size) / 2);
width: var(--size);
height: 50%;
position: absolute;
left: calc(50% - var(--size2));
top: 0;
color: #222;
border: 0 solid transparent;
border-width: var(--radius) var(--size2) 0;
border-top-color: currentColor;
transform: rotate(var(--turn));
transform-origin: bottom;
}
.wheel__segment:nth-child(odd) {
color: #ba3535;
}
.wheel__segment:nth-child(1),
.wheel__segment:nth-child(20) {
color: #43914b;
}
.wheel__segment.__active {
color: #5f9ea0;
}
.wheel__segment::after {
content: attr(data-num);
font-family: Arial, Helvetica, sans-serif;
font-size: var(--size2);
width: inherit;
position: absolute;
left: -1em;
top: calc(-1 * var(--radius) + 1em);
color: #eee;
}
/* rotor */
#wheel__rotor {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: 28%;
background-color: #633d2b;
background-image: radial-gradient(circle, #6b4838 50%, #1e0c03);
border: 1px solid #e6cc9a;
border-radius: inherit;
box-shadow: inset 0 0 0 1px #eee0c6;
z-index: 2;
}
.wheel__line {
height: 1px;
position: absolute;
left: 0.2em;
right: 0.2em;
top: 0.2em;
bottom: 0.2em;
margin: auto;
background-color: black;
box-shadow: 0 0 2px gray;
}
.wheel__line:nth-child(2) {
transform: rotate(90deg);
}
.wheel__line:nth-child(3) {
transform: rotate(45deg);
}
.wheel__line:nth-child(4) {
transform: rotate(135deg);
}
#wheel__turret {
width: 40%;
height: 40%;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
background-image: radial-gradient(50% 50%, #ebd5ab 40%, #9c8a63 60%, #e9d09e 80%, #998a6c 90%);
box-shadow: 0 0 .4rem 1px #383838;
border-radius: 50%;
}
#wheel__turret::before,
#wheel__turret::after {
content: "";
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: 14%;
border: 0.3rem dashed #b49f82;
border-radius: inherit;
}
#wheel__turret::after {
margin: 38%;
background-color: #e2d9c5;
border: 1px solid #b2a89a;
box-shadow: inset 0 0 2px #666;
}
/* ball */
#wheel__ball {
--turn: 0;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: 4%;
transform: rotate(calc(-1turn * var(--turn)));
transition: margin .6s ease-out;
z-index: 5;
}
.__spining > #wheel__ball {
transition: inherit;
will-change: transform;
}
.__result #wheel__ball {
margin: 20%;
transition: margin .4s ease-out;
}
#wheel__ball::after {
--size: calc(var(--radius) / 9);
content: "";
width: var(--size);
height: var(--size);
display: inline-block;
background-color: white;
border-radius: 50%;
box-shadow: inset 1px 1px 3px 2px #999;
filter: drop-shadow(2px 3px 4px black);
}
/* info */
#wheel__info {
font-size: calc(var(--radius) / 10);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: 30%;
border-radius: inherit;
opacity: 0;
visibility: hidden;
transition: opacity .5s, visibility .5s;
}
.__result #wheel__info {
opacity: 1;
visibility: visible;
}
#wheel__number {
font-size: 3.5em;
}
#wheel__score {
font-size: 1.2em;
line-height: 1.4;
}
#wheel__score::after {
content: attr(data-score);
display: block;
color: #e3c59d;
}
/* === MINI WHEEL === */
#wheel-wrap {
width: 9rem;
height: 6rem;
position: absolute;
left: calc(100% - 3rem);
top: 0;
background-color: var(--bg);
outline: 1px solid black;
overflow: hidden;
visibility: hidden;
}
#wheel.mini-wheel {
--diameter: 30rem;
left: -4rem;
top: 0.5rem;
filter: none;
transform: rotate(-30deg);
}
/* === DETAILS === */
@keyframes roulTimerBlink {
from {
transform: scale(1);
filter: opacity(1);
}
to {
transform: scale(1.1);
filter: opacity(0.6);
}
}
#roul-details {
font-family: Consolas, monospace;
line-height: 1.3em;
position: absolute;
left: -22rem;
top: 22rem;
padding: 0.3em 0.6em;
}
#roul-cash {
color: #416e90;
}
#roul-maxbet {
margin-bottom: 0.4rem;
padding-bottom: 0.4rem;
border-bottom: 1px solid #aaa;
}
#roul-timer {
display: inline-block;
color: #59908e;
}
#roul-timer.__blink {
color: #ca0000;
animation: roulTimerBlink .5s linear infinite alternate;
}
/* === BETS === */
#roul-bets {
width: 34rem;
position: absolute;
left: -22rem;
top: 34rem;
}
.roul-bets__tabs {
line-height: 2;
display: flex;
position: relative;
}
.roul-bets__tab {
flex: 1 auto;
padding: 0 0.5em;
color: gray;
background-color: #dcd7c8;
box-shadow: var(--shadow);
cursor: pointer;
}
.roul-bets__tab:hover {
background-color: #e3ddca;
}
.roul-bets__tab.__active {
color: inherit;
background-color: #e9e0c6;
pointer-events: none;
}
#roul-bets__count {
font-family: Consolas, monospace;
font-size: 0.8em;
min-width: 4.3em;
float: right;
text-align: right;
pointer-events: none;
}
.roul-bets__body {
height: 17.3rem;
}
.roul-bets__apply {
display: flex;
float: left;
position: relative;
left: -0.5em;
}
.roul-bets__action {
--d: 0deg;
--s: 30%;
--l: 90%;
width: 2em;
text-align: center;
background-color: #eee;
background-color: hsl(var(--d, 180deg), var(--s), var(--l));
box-shadow: var(--shadow);
cursor: pointer;
}
.roul-bets__action[data-action="accept"] {
--d: 150deg;
}
.roul-bets__action:hover {
--s: 40%;
--l: 85%;
}
.roul-bet.__active {
background-image: var(--selected-bet);
}
.roul-bet.__win {
background-image: var(--selected-bet);
animation: roulWin .3s alternate 4;
}
.roul-bet__action {
width: 2em;
text-align: center;
background-color: #eee;
box-shadow: var(--shadow);
cursor: pointer;
}
.roul-bet__action:hover {
background-color: #fff;
}
.roul-bet__action[data-action="remove"] {
color: brown;
}
.roul-bet__status {
width: 2em;
position: relative;
text-align: center;
}
[data-status="0"] > .roul-bet__status::after {
content: "";
width: 0.9em;
height: 0.9em;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
border: 2px solid royalblue;
border-right-color: transparent;
border-radius: 50%;
animation: roulSpin .75s linear infinite;
}
[data-status="1"] > .roul-bet__status::after {
content: "✓";
color: green;
}
[data-status="2"] > .roul-bet__status::after {
content: "✗";
display: block;
color: brown;
cursor: pointer;
}
/* === GAMES === */
#roul-games {
--size: 3rem;
height: calc(var(--size) * 8 - 1px);
position: absolute;
left: calc(100% - 3rem);
top: 8rem;
overflow: visible;
pointer-events: auto !important;
z-index: 100;
}
#roul-games::before,
#roul-games::after {
content: "";
width: 1px;
position: absolute;
left: var(--size);
top: 0;
bottom: 0;
background-color: #ccc;
}
#roul-games::after {
left: calc(var(--size) * 2);
}
.roul-games__content {
height: 100%;
}
.roul-games__content::before {
content: "";
width: 4px;
position: absolute;
right: 100%;
top: 0;
bottom: 0;
}
.roul-games__body {
width: calc(var(--size) * 3);
min-height: 100%;
overflow: hidden;
background-color: inherit;
}
.roul-game {
font: inherit;
width: var(--size);
height: var(--size);
line-height: var(--size);
position: relative;
float: left;
text-align: center;
text-decoration: none;
color: #222;
background-color: inherit;
outline: 1px solid #ccc;
z-index: 2;
}
.roul-game:empty {
pointer-events: none;
}
.roul-game:nth-child(3n+2) {
color: #4aab55;
}
.roul-game:nth-child(3n) {
color: #b20000;
}
.roul-game.__active {
background-color: #eadbae;
}
.roul-game.__loading {
color: transparent;
}
.roul-game.__loading::after {
content: "";
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: 8px;
border: 2px solid royalblue;
border-right-color: transparent;
border-radius: 50%;
animation: roulSpin .75s linear infinite;
}
/* info */
.roul-games__info {
width: max-content;
min-width: 24rem;
height: 100%;
position: absolute;
right: calc(100% + 4px);
top: 0;
opacity: .97;
visibility: hidden;
}
.roul-games__info.__shown {
visibility: visible;
}
.roul-games__info::before {
content: "";
width: 4px;
position: absolute;
left: 100%;
top: 0;
bottom: 0;
}
.roul-games__bets {
height: calc(100% - var(--size) + 1px);
}
.roul-game-bet {
line-height: var(--size);
}
.roul-game-bet.__selected {
background-image: var(--selected-bet);
}
.roul-game-bet__value {
color: inherit;
}
.roul-game-bet__value::before {
content: attr(data-bet);
color: brown;
}
.roul-game-bet__value::after {
content: attr(data-prize);
color: green;
}
.roul-games__footer {
padding: 0;
padding-left: 3rem;
text-align: left;
}
.roul-games__footer > .roul-game-bet {
background-color: inherit;
pointer-events: none;
}
#roul-game-repeat {
width: 2.9rem;
line-height: 3rem;
position: absolute;
left: 0;
top: 0;
text-align: center;
background-color: white;
cursor: pointer;
}
#load_prev_games {
font-weight: normal;
width: 2rem;
line-height: 2rem;
position: absolute;
right: .4rem; bottom: .4rem;
text-align: center;
background-color: white;
box-shadow: 0 0 0 1px #999;
cursor: pointer;
}
/* === DENSITY === */
#roul-density {
min-width: 20rem;
position: absolute;
left: calc(100% - 12rem);
top: 34rem;
pointer-events: auto !important;
z-index: 2;
}
#roul-density > header {
display: flex;
align-items: center;
justify-content: space-between;
column-gap: 0.5em;
}
#roul-density__upd {
width: 2rem;
height: 2rem;
margin-left: 0.6em;
background-color: white;
border: 1px solid #555;
border-radius: 50%;
cursor: pointer;
}
#roul-density__upd.__loading {
animation: roulSpin .4s linear infinite;
}
#roul-density > div {
height: 26rem;
}
/* === TIP === */
#roul-tip {
line-height: 3.4rem;
position: fixed;
left: 0; top: 0;
padding: 0 1rem;
background-color: #eee;
border: var(--border);
box-shadow: 0 0 1px #777;
overflow: hidden;
pointer-events: none;
opacity: .95;
z-index: 102;
}
#roul-tip:empty {
display: none;
}
#roul-tip::after {
content: attr(data-value);
display: inline-block;
margin-left: 1rem;
padding-left: 1rem;
border-left: inherit;
color: brown;
}
/* === MARK === */
#roul-mark {
line-height: 1.4;
position: absolute;
left: 50%;
top: 51rem;
margin-right: -50%;
transform: translateX(-50%);
text-align: center;
pointer-events: none;
opacity: 0.3;
}
#roul-mark:hover {
opacity: 1;
transition: opacity .2s .2s;
}
#roul-mark > div {
font-family: serif;
font-size: 2.2em;
color: #ffeab6;
letter-spacing: 0.15em;
text-shadow: 0 0 2px black, 0 0 2px black;
text-transform: uppercase;
}
#roul-mark > a {
font: inherit;
color: #824242;
pointer-events: auto;
text-decoration: none;
}
#roul-mark > a:hover {
text-decoration: underline;
}
/* === ALERT === */
#roul-alert {
font-size: 1.1em;
position: fixed;
top: 0; right: 0; bottom: 0; left: 0;
background-color: rgba(127, 127, 127, .7);
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: opacity 0.25s, visibility 0.25s;
z-index: 105;
}
#roul-alert.__shown {
opacity: 1;
visibility: visible;
pointer-events: auto;
}
#roul-alert__inner {
max-width: 32em;
margin: auto;
padding: 1em;
color: #eee;
background-color: #444;
border: 1px solid gray;
border-top: none;
box-shadow: 0 0 6px #666;
overflow: hidden;
transform: perspective(50em) rotateX(-40deg);
transform-origin: 50% 0;
transition: transform 0.15s ease-out;
cursor: default;
}
#roul-alert.__shown #roul-alert__inner {
transform: perspective(50em) rotateX(0);
}
#roul-alert__content {
line-height: 1.5;
padding: 1em;
margin-bottom: 1em;
text-align: center;
background-color: #555;
border: 1px dashed gray;
}
#roul-alert__ok {
float: right;
padding: .4em 2em;
text-align: center;
color: #eee;
background-color: #8a766f;
border: 1px solid #bbb;
outline: 1px solid #444;
outline-offset: -3px;
cursor: pointer;
}
#roul-alert__ok:hover {
background-color: #9e8c86;
}
#roul-alert__ok:focus {
border-color: #dabe99;
}
#roul-log {
text-align: justify;
}
#roul-log > p:first-child {
color: lightcoral;
}
#roul-log > a {
display: inline-block;
color: inherit;
margin-top: 1em;
}
@media screen and (max-width: 1480px) {
#roul-tools {
font-size: 0.9em;
width: auto;
padding: 0 0.5em;
left: 0;
}
#roulette {
font-size: 1.7rem;
top: 26em;
left: -0.7em;
transform: rotate(90deg);
transform-origin: top;
}
.roul-num > .roul-value {
transform: rotate(-90deg);
}
#wheel {
left: 42rem;
top: 10rem;
}
#wheel-wrap {
left: 74rem;
top: 3rem;
}
#roul-details {
left: 38rem;
top: 42rem;
}
#roul-bets {
left: 38rem;
top: 54rem;
}
#roul-games {
left: 74rem;
top: 11rem;
}
#roul-density {
left: 74rem;
top: 45.3rem;
}
#roul-mark {
left: calc(80rem + 14vw);
top: 2rem;
}
}
@media screen and (max-width: 1160px) {
#roul-mark {
left: 79rem;
top: 36.3rem;
}
}
`);
container.prepend(this);
$('body > center').replaceWith(container);
});
// =============== [[ CASH ]]
const roulCash = ((target) => {
let cash = CASH;
const goldNode = modules.HWM_new_header
? attempt($('.header-res'), el => el.lastChild)
: ($('#ResourceAmount') || $('#top_res_table td:nth-child(2)'));
return {
get value() {
return cash - roulBets.sum;
},
set value(val) {
cash = val;
this.update(val);
},
update(val) {
const value = formatNum(val || this.value);
target.textContent = value;
if (goldNode) goldNode.textContent = value;
},
valueOf() {
return this.value;
}
};
})($('#roul-cash > span'));
// =============== [[ DENSITY ]]
const roulDensity = ((target) => {
const {children} = target;
const button = children[0].firstElementChild;
let isLoading = false;
return {
__init__() {
button.addEventListener('click', this.update);
},
clear() {
children[1].innerHTML = '';
},
update() {
if (isLoading) return;
isLoading = true;
button.classList.add('__loading');
fetch.get(PATH).then((doc) => {
if (!doc.URL.includes(PATH)) {
throw logError.create(-1, 'Authorization error');
}
const elems = [...parseNode(templates.Density(doc)).children];
children[1].replaceWith(elems[1]);
children[2].replaceWith(elems[2]);
isLoading = false;
button.classList.remove('__loading');
}).catch(handleError);
function handleError(er) {
isLoading = false;
button.classList.remove('__loading');
return logError.disconnect(er);
}
}
};
})($('#roul-density'));
// =============== [[ BETS ]]
const roulBets = ((target) => {
const tabElems = [...target.firstElementChild.children];
const contElems = [...target.children].slice(1);
const bodyElems = contElems.map(el => el.firstElementChild);
const sumElems = contElems.map(el => el.lastElementChild.lastElementChild);
const bets1 = [];
const bets2 = [];
const getBets = ind => !ind ? bets1 : bets2;
let tabIndex = 0;
class Bet {
constructor(id, value) {
createBet.call(this, 0, {id, value});
}
static create({id, value}) {
return parseNode(/*html*/`
<div class="roul-bet roul-box-row">
<div class="roul-bet__action" data-action="remove">×</div>
<div class="roul-bet__action" data-action="multiply">×2</div>
<div class="roul-bet__key roul-bet-key">${roulID.get(id)}</div>
<div class="roul-bet__value roul-bet-value">${formatNum(value)}</div>
</div>
`);
}
static get activeBet() {
return $('.__active', bodyElems[0]);
}
refresh(val) {
this.value += val;
this.target.lastElementChild.textContent = formatNum(this.value);
roulCash.update();
updateBetsSum();
}
remove() {
this.target.remove();
roulAPI.unselect(this.id);
bets1.splice(bets1.indexOf(this), 1);
roulCash.update();
updateBetsSum();
}
multiply(meta) {
if (meta) {
const min = this.id === 'Red Snake' ? roulBets.MIN * 12 : roulBets.MIN;
if (~~(this.value / 2) < min) return this.remove();
} else if (!checkMoney(this.value)) return;
this.refresh(meta ? -this.value / 2 >> 0 : this.value);
this.select();
}
select() {
attempt(Bet.activeBet, inactiveElem);
activeElem(this.target);
this.target.scrollIntoViewIfNeeded(false);
}
}
class Bet2 {
constructor(id, value, status = 0) {
createBet.call(this, 1, {id, value, status});
}
static create({id, value, status}) {
return parseNode(/*html*/`
<div class="roul-bet roul-bet2 roul-box-row" data-status="${status}">
<div class="roul-bet__status"></div>
<div class="roul-bet__key roul-bet-key">${roulID.get(id)}</div>
<div class="roul-bet__value roul-bet-value">${formatNum(value)}</div>
</div>
`);
}
win() {
this.target.classList.add('__win');
this.target.scrollIntoViewIfNeeded(false);
}
submit() {
this.target.dataset.status = this.status = 0;
return formData.send(this);
}
onLoadEnd(status) {
this.target.dataset.status = this.status = status;
recount();
}
}
function createBet(ind, data) {
Object.assign(this, data);
this.target = [Bet, Bet2][ind].create(data);
getBets(ind).push(this);
bodyElems[ind].prepend(this.target);
}
function clearBets(ind = 0) {
getBets(ind).splice(0);
bodyElems[ind].innerHTML = '';
sumElems[ind].textContent = '0';
}
function updateBetsSum(ind = 0) {
const sum = reduceBets(getBets(ind));
sumElems[ind].textContent = formatNum(sum);
}
function recount() {
const {completed} = this.data;
const countElem = tabElems[1].firstElementChild;
countElem.textContent = `${completed.length} / ${bets2.length}`;
}
return {
__init__() {
recount = recount.bind(this);
target.addEventListener('click', (e) => {
e.stopPropagation();
if (locked) return;
const trg = e.target;
if (trg.matches('.roul-bets__tab')) {
this.switchTab(getElemIndex(trg));
return;
}
if (trg.matches('.roul-bets__action')) {
this[trg.dataset.action]();
return;
}
if (trg.matches('.roul-bet__action')) {
const bet = this.find('target', trg.parentNode);
bet[trg.dataset.action](e.ctrlKey);
return;
}
if (trg.matches('[data-status="2"] > .roul-bet__status')) {
const bet = this.find('target', trg.parentNode, bets2);
return bet.submit().catch(er => {
bet.onLoadEnd(2);
return logError.disconnect(er);
});
}
});
if (!initialBets.length) return;
initialBets.forEach((bet) => new Bet2(bet.id, bet.value, 1));
this.switchTab(1);
updateBetsSum(1);
recount();
},
MIN,
MAX,
lastBets: [],
get data() {
return {
get completed() {
return bets2.filter((bet) => bet.status === 1);
},
get failed() {
return bets2.filter((bet) => bet.status === 2);
}
};
},
get sum() {
return reduceBets(bets1) + reduceBets(bets2);
},
get multiplier() {
const multiplier = roulTools.activeCoinValue;
const value = multiplier === 'MAX'
? this.MAX - this.sum
: multiplier === 'MIN'
? this.MIN
: +multiplier;
return clamp(1, value, roulCash);
},
switchTab(ind) {
if (tabIndex === ind) return;
tabIndex = ind;
inactiveElem(tabElems[ind ^ 1]);
activeElem(tabElems[ind]);
contElems[0].hidden = !!ind;
contElems[1].hidden = !ind;
},
find(key, value, bets = bets1) {
return bets.find(bet => bet[key] === value);
},
add() {
const bet = new Bet(...arguments);
bet.refresh(0);
return bet;
},
cancel() {
this.clearTableBets();
roulCash.update();
},
accept() {
const bets = bets1.map(bet => new Bet2(bet.id, bet.value));
const snakeIndex = bets.findIndex(bet => bet.id === 'Red Snake');
snakeIndex > 0 && bets.unshift(bets.splice(snakeIndex, 1)[0]);
this.clearTableBets();
this.switchTab(1);
updateBetsSum(1);
return this.acceptSequentially(bets).catch((er) => {
bets.splice(0).forEach((bet) => bet.onLoadEnd(2));
logError.disconnect(er);
return er;
});
},
acceptSequentially(bets) {
return new Promise(function next(resolve, reject) {
if (locked) bets.splice(0).forEach((bet) => bet.onLoadEnd(2));
if (!bets.length) return resolve(roulBets.data.failed);
const bet = bets.pop();
return bet.submit()
.then(() => wait(0.2))
.then(() => next(resolve, reject))
.catch((er) => {
bet.onLoadEnd(2);
return reject(er);
});
});
},
clearTableBets() {
clearBets(0);
roulAPI.clear();
},
clear() {
clearBets(1);
this.clearTableBets();
recount();
},
updateMinMax(MIN, MAX) {
Object.assign(this, { MIN, MAX })
$('#roul-minbet').lastChild.textContent = formatNum(MIN);
$('#roul-maxbet').lastChild.textContent = formatNum(MAX);
}
};
})($('#roul-bets'));
function mergeBets(bets, callback) {
const data = new Map;
bets.forEach((bet) => {
const bet2 = data.get(bet.id);
if (!bet2) return data.set(bet.id, bet);
bet2.value += bet.value;
callback && callback(bet2, bet);
});
return [...data.values()];
}
function rebet(bets) {
const sum = bets.reduce((a, b) => a + b.value, 0);
if (!(sum && checkMoney(sum))) return false;
roulBets.switchTab(0);
let lastBet = null;
bets.forEach((bet, i) => {
lastBet = roulBets.find('id', bet.id);
if (lastBet) return lastBet.refresh(bet.value);
roulAPI.select(bet.id);
lastBet = roulBets.add(bet.id, bet.value);
});
lastBet.select();
return true;
}
// =============== [[ GAMES ]]
const roulGames = ((target) => {
let activeGame = null;
const infoElem = target.firstElementChild;
const bodyElem = target.lastElementChild.firstElementChild;
const [betsElem, footer] = infoElem.children;
footer.appendChild(parseNode(getBetTemplate({
id: 'Итого',
value: 0,
prize: 0,
})));
const allSumElem = footer.firstElementChild.lastElementChild;
const toggleInfo = infoElem.classList.toggle.bind(
infoElem.classList,
'__shown'
);
const getDefaultGameData = elem => ({
gameId: elem.search.slice(4),
number: elem.textContent,
totalBet: 0,
totalPrize: 0
});
function updateSum(game) {
allSumElem.dataset.bet = formatNum(game.totalBet);
allSumElem.dataset.prize = formatNum(game.totalPrize);
}
function getBetTemplate(bet) {
const value = formatNum(bet.value);
const prize = formatNum(bet.prize);
const selected = bet.prize ? ' __selected' : '';
return /*html*/`
<div class="roul-game-bet roul-box-row${selected}">
<div class="roul-game-bet__key roul-bet-key">${roulID.get(bet.id)}</div>
<div class="roul-game-bet__value roul-bet-value" data-bet="${value}" data-prize="${prize}"> / </div>
</div>
`;
}
function hwmGameToObject(table, data) {
const bets = [];
const rows = table.children;
for (let i = 1; i < 30; i++) {
const row = rows[i];
if (i === 1) {
if (row.textContent.startsWith('Все')) break;
i++;
continue;
}
if (row.childElementCount === 3) {
data.totalBet = parseNum(row.firstElementChild.textContent);
data.totalPrize = parseNum(row.lastElementChild.textContent);
break;
}
const nodes = row.children;
const id = nodes[2].textContent;
const value = parseNum(nodes[0].textContent);
const prize = parseNum(nodes[3].textContent);
bets.push({ id, value, prize });
}
data.bets = !bets.length ? null : mergeBets(bets, (bet1, bet2) => {
bet1.prize += bet2.prize;
});
return data;
}
return {
__init__() {
let isLoading = false;
target.addEventListener('mouseleave', this.hide.bind(this));
bodyElem.addEventListener('mouseover', (e) => {
e.stopPropagation();
const trg = e.target;
trg.matches('.roul-game[href]') && this.showGame(trg);
});
bodyElem.nextElementSibling.addEventListener('click', (e) => {
const button = e.currentTarget;
if (isLoading) return;
isLoading = true;
const add = ([gameId, number]) => this.add({gameId, number});
this.loadPrevGames().then((games) => {
button.remove();
bodyElem.innerHTML = '';
games.reverse().forEach(add);
}).catch((er) => {
isLoading = false;
logError.disconnect(er);
});
});
const repeatButton = parseNode(
'<div id="roul-game-repeat" title="Повторить ставки">←</div>'
);
footer.prepend(repeatButton);
repeatButton.addEventListener('click', () => {
if (!activeGame || locked) return;
attempt(activeGame.__gameData.bets, rebet);
});
},
get lastGame() {
return attempt($('a[href]', bodyElem), elem => {
return elem.__gameData || getDefaultGameData(elem);
});
},
hide() {
attempt(activeGame, inactiveElem);
activeGame = null;
toggleInfo(false);
},
add(data) {
const {gameId, number} = data;
const create = parseNode.bind(null, '<a class="roul-game"></a>');
const elems = [...Array(3)].map(create);
const elem = elems[RED_NUMBERS.includes(+number) ? 2 : !!+number ^ 1];
elem.textContent = number;
elem.href = `/inforoul.php?id=${gameId}`;
elem.target = '_blank';
bodyElem.parentNode.scrollTop = 0;
bodyElem.prepend(...elems);
if (data.hasOwnProperty('bets')) elem.__gameData = data;
},
async showGame(elem) {
attempt(activeGame, inactiveElem);
activeGame = elem;
const gameData = elem.__gameData
|| await this.loadGame(elem).catch(er => er);
if (gameData instanceof Error) {
elem.classList.remove('__loading');
logError.disconnect(gameData);
return;
}
if (elem !== activeGame) return;
if (gameData.bets) {
betsElem.innerHTML = gameData.bets.map(getBetTemplate).join('');
updateSum(gameData);
} else betsElem.innerHTML = '';
activeElem(elem);
toggleInfo(true);
},
async loadGame(elem) {
elem.classList.add('__loading');
const data = getDefaultGameData(elem);
const doc = await fetch.get(`/inforoul.php?id=${data.gameId}`);
const table = $('table.wbwhite > tbody', doc);
elem.classList.remove('__loading');
return (elem.__gameData = hwmGameToObject(table, data));
},
async loadPrevGames() {
const doc = await fetch.get('/allroul.php');
return $$('table.wb > tbody > tr', doc).slice(1).map(row => {
const gameId = row.firstElementChild.firstElementChild.search.slice(4);
const number = row.lastElementChild.textContent.trim();
return [gameId, number];
});
}
};
})($('#roul-games'));
// =============== [[ ROULETTE ]]
const roulAPI = ((target, data) => {
const targets = document.getElementsByClassName('__target');
const styles = [0, 0].map(() => document.createElement('style'));
container.firstElementChild.after(...styles);
const getID = el => el.dataset.id;
const getCSS = ind => `{background-color: var(--highlight-color-${ind})}`;
const getHighlightedCSS = memoize(id => {
return getSelectors(id).join(',') + getCSS(1);
});
function getSelectors(id) {
const items = data[id].items;
return items.map(item => `[data-id="${getID(item)}"]`);
}
function reselectTargetItems() {
const selectrors = Array.from(targets, el => getSelectors(getID(el)));
const css = !selectrors.length ? '' : selectrors.join(',') + getCSS(2);
styles[0].textContent = css;
}
return {
target,
data,
find: (id) => data[id],
highlight(id) {
styles[1].textContent = getHighlightedCSS(id);
},
unhighlight() {
styles[1].textContent = '';
},
select(id) {
data[id].target.classList.add('__target');
reselectTargetItems();
},
unselect(id) {
data[id].target.classList.remove('__target');
reselectTargetItems();
},
clear() {
styles[0].textContent = styles[1].textContent = '';
[...targets].forEach(el => el.classList.remove('__target'));
}
};
})($('#roulette'), {});
const roulette = ((target, that) => {
const elems = $$('.roul-area');
['00', '0'].forEach(num => setData(elems.shift()));
function setData(target, isOutside = false) {
const {id} = target.dataset;
const factor = id[0] === 'S' ? 36 : /Col|Doz/.test(id) ? 3 : 2;
const dispatcher = isOutside ? outsideData[id] : (() => [target]);
that[id] = createData(id, factor, target, dispatcher);
}
function createData(id, factor, target, dispatcher) {
return {
id,
factor,
target,
get items() {
return dispatcher();
}
};
}
function dispatch(callback) {
let items = null;
return () => items || (items = callback());
}
const insideElems = elems.slice(0, 36);
const outsideElems = elems.slice(36);
const boundSlice = (...values) => elems.slice.bind(insideElems, ...values);
const boundFilter = fn => elems.filter.bind(insideElems, fn);
const not = fn => (...args) => !fn(...args);
const orderIsEqual = (div, rem) => (x, i) => i % div === rem;
const isEven = orderIsEqual(2, 1);
const isRed = el => RED_NUMBERS.includes(+el.dataset.id.slice(12));
const outsideData = {
'1st Column': dispatch(boundFilter(orderIsEqual(3, 2))),
'2nd Column': dispatch(boundFilter(orderIsEqual(3, 1))),
'3rd Column': dispatch(boundFilter(orderIsEqual(3, 0))),
'1st Dozen': dispatch(boundSlice(0, 12)),
'2nd Dozen': dispatch(boundSlice(12, 24)),
'3rd Dozen': dispatch(boundSlice(24)),
'1-18 Half': dispatch(boundSlice(0, 18)),
'EVEN': dispatch(boundFilter(isEven)),
'RED': dispatch(boundFilter(isRed)),
'BLACK': dispatch(boundFilter(not(isRed))),
'ODD': dispatch(boundFilter(not(isEven))),
'19-36 Half': dispatch(boundSlice(18))
};
roulAPI.__init__ = function() {
insideElems.forEach(elem => setData(elem));
outsideElems.forEach(elem => setData(elem, true));
initChips(this.data);
target.addEventListener('mouseover', (e) => {
e.stopPropagation();
const trg = e.target;
const {id} = trg.dataset;
id && this.highlight(id);
if (id && trg.matches('.__target')) setElemMoveHandler(id, trg);
else roulTip.hide();
});
target.addEventListener('mouseleave', (e) => {
e.stopPropagation();
roulTip.hide();
this.unhighlight();
});
target.addEventListener('click', (e) => {
e.stopPropagation();
if (locked) return;
const elem = e.target.closest('[data-id]');
if (!elem) return;
const meta = e.ctrlKey;
const {id} = elem.dataset;
const {multiplier} = roulBets;
const isMaxCoin = roulTools.activeCoinValue === 'MAX';
const isSnake = id === 'Red Snake';
const minSnakeBet = roulBets.MIN * 12;
const bet = roulBets.find('id', id);
if (!bet) {
if (meta) return;
const value = isSnake
? clamp(minSnakeBet, multiplier * (isMaxCoin ? 1 : 12))
: clamp(roulBets.MIN, multiplier);
if (!checkMoney(value)) return;
roulBets.add(id, value).select();
return after.call(this);
}
if (!testBet.call(this, isSnake ? minSnakeBet : roulBets.MIN)) return;
bet.refresh(meta ? -multiplier : multiplier);
bet.select();
after.call(this);
function testBet(min) {
if (!meta) return checkMoney(multiplier);
if (!(isMaxCoin || bet.value - multiplier < min)) return true;
bet.remove();
roulTip.hide();
this.unselect(id);
}
function after() {
this.select(id);
roulBets.switchTab(0);
setElemMoveHandler(id, elem);
elem.onmousemove(e);
}
});
function setElemMoveHandler(id, elem) {
elem.onmousemove = (e) => {
elem.matches('.__target') && roulTip.show(e, id);
};
elem.onmouseleave = () => {
elem.onmouseleave = elem.onmousemove = null;
};
}
};
return that;
})(roulAPI.target, roulAPI.data);
function initChips(that) {
const factors = {
Split: 18,
Street: 12,
Numbers: 12,
Corner: 9,
Sixline: 6
};
function getItems(nums) {
return nums.map(num => that[`Straight up ${num}`].target);
}
function add(target, id, factor, nums) {
let items = null;
that[id] = {
id,
factor,
target,
get items() {
return items || (items = getItems(nums));
}
};
}
function streetline(target, id, factor, start) {
const nums = [...Array(factor === 6 ? 6 : 3)].map((x, i) => start - i);
return add(target, id, factor, nums);
}
const chips = $$('.roul-chip');
add(chips.pop(), 'Red Snake', 3, [1, 5, 9, 12, 14, 16, 19, 23, 27, 30, 32, 34]);
chips.forEach((chip) => {
const {id} = chip.dataset;
const key = id.split(' ', 1)[0];
const nums = id.match(/\d+/g);
switch (key) {
case 'Street':
case 'Sixline':
return streetline(chip, id, factors[key], +nums.pop());
case 'Split':
case 'Corner':
case 'Numbers':
return add(chip, id, nums.length === 5 ? 7 : factors[key], nums);
}
});
}
// =============== [[ TIP ]]
const roulTip = ((target) => {
const htmlElem = document.documentElement;
const getClientWidth = () => htmlElem.clientWidth;
return {
move(x, y) {
target.style.transform = `translate(${~~x}px, ${~~y}px)`;
},
show(e, id) {
target.textContent = roulID.get(id);
target.dataset.value = formatNum(roulBets.find('id', id).value);
const maxX = getClientWidth() - target.offsetWidth - 5;
const maxY = view.innerHeight - target.offsetHeight;
const x = Math.min(e.clientX + 10, maxX);
const y = Math.min(e.clientY + 10, maxY);
this.move(x, y);
},
hide() {
if (this.shown) target.textContent = '';
},
get shown() {
return !!target.offsetWidth;
}
};
})($('#roul-tip'));
// =============== [[ WHEEL ]]
const roulWheel = ((target) => {
let isLoading = false;
let isNumDefined = false;
let prevSegment = null;
const segments = [...$('#wheel__segments').children];
const altSegments = segments.map((el) => el.cloneNode());
const wheelClasses = target.classList;
const wheelMain = target.firstElementChild;
const wheelStyle = wheelMain.style;
const ballStyle = wheelMain.nextElementSibling.style;
const infoElem = target.lastElementChild;
const fix3 = (num) => +num.toFixed(3);
const getTurn = (style) => parseFloat(style.getPropertyValue('--turn') || '0');
const setTurn = (style, turn) => style.setProperty('--turn', fix3(turn));
const recalcTurn = (style, plus) => setTurn(style, plus + getTurn(style));
const resetTurnToMin = (style) => setTurn(style, getTurn(style) % 1);
const getSegment = memoize(number => {
const index = segments.findIndex(el => el.dataset.num === number);
const [r, g, b] = ['#ba3535', '#43914b', '#222'];
return {
number,
color: !+number ? g : RED_NUMBERS.includes(+number) ? r : b,
turn: getTurn(segments[index].style),
segment: segments[index],
altSegment: altSegments[index]
};
});
const wrapper = parseNode(/*html*/`
<div id="wheel-wrap">
<div id="wheel" class="mini-wheel">
<div id="wheel__main">
<div id="wheel__segments"></div>
</div>
</div>
</div>
`);
const miniWheel = wrapper.firstElementChild;
const miniWheelStyle = miniWheel.firstElementChild.style;
miniWheel.firstElementChild.firstElementChild.append(...altSegments);
target.after(wrapper);
function calcBallTurn() {
const turn = getTurn(wheelStyle);
const val = ~~turn + fix3(Math.ceil(turn) - turn);
setTurn(ballStyle, val - prevSegment.turn);
}
function waitForResult(endTime) {
if (isNumDefined) return stoping();
[wheelStyle, ballStyle].forEach((style) => recalcTurn(style, 2));
!(isLoading || endTime - Date.now() > 0) && defineNumber();
setTimeout(waitForResult, 2e3, endTime);
}
async function defineNumber() {
isLoading = true;
const {gameId} = formData;
const doc = await fetch.get(`/inforoul.php?id=${gameId}`).catch(er => er);
if (doc instanceof Error) {
return logError.disconnect(doc, {
text: 'Попробовать снова (закончить раунд)?',
callback() {
roulAlert.hide();
defineNumber();
}
});
}
const elem = $('td.wbwhite > div:last-child', doc);
if (!elem) return (isLoading = false);
isNumDefined = true;
const number = elem.textContent.split(' ').pop();
prevSegment = getSegment(number);
console.log(`${gameId}: ${number}`);
}
function stoping() {
wheelClasses.add('__spinending');
miniWheel.classList.add('__spinending');
recalcTurn(wheelStyle, 2 + Math.random() * 0.2);
calcBallTurn();
setTurn(miniWheelStyle, 2 - prevSegment.turn);
wrapper.style.visibility = 'visible';
wait(5).then(roulWheel.endSpin);
}
function spinAfter(game) {
const scoreElem = infoElem.lastElementChild;
scoreElem.hidden = !game.bets;
scoreElem.dataset.score = formatNum(game.totalPrize);
roulGames.add(game);
roulCash.value += game.totalPrize;
wait(3.5).then(next);
function next() {
return loadNextGame().then(startGame).catch(er => {
logError.disconnect(er, {
text: 'Попробовать снова (начать следующую игру)?',
callback() {
roulAlert.hide();
next();
}
});
});
}
}
return {
onGameStart() {
wrapper.style.visibility = '';
setTurn(miniWheelStyle, 0);
[prevSegment.segment, prevSegment.altSegment].forEach(inactiveElem);
},
startSpin(endTime) {
locked = true;
isNumDefined = isLoading = false;
document.title = 'Roulette | spining . . .';
container.classList.add('__locked');
wheelClasses.add('__spining');
roulBets.clearTableBets();
roulBets.switchTab(1);
setTimeout(waitForResult, 50, endTime);
},
endSpin() {
wheelClasses.remove('__spining', '__spinending');
miniWheel.classList.remove('__spinending');
[wheelStyle, ballStyle].forEach(resetTurnToMin);
[prevSegment.segment, prevSegment.altSegment].forEach(activeElem);
document.title = `Roulette | Number ${prevSegment.number}`;
infoElem.firstElementChild.textContent = prevSegment.number;
infoElem.style.backgroundColor = prevSegment.color;
container.classList.add('__result');
endGame(prevSegment.number, spinAfter);
}
};
})($('#wheel'));
// =============== [[ ALERT ]]
const roulAlert = ((target) => {
const body = target.firstElementChild.firstElementChild;
const button = body.nextElementSibling;
function onCloseHandler({key}) {
return key === 'Escape' && this.hide();
}
return {
__init__() {
onCloseHandler = onCloseHandler.bind(this);
target.addEventListener('click', (e) => {
e.stopPropagation();
const trg = e.target;
const {action} = trg.dataset;
action && this[action]();
});
},
show(html) {
body.innerHTML = html;
target.classList.add('__shown');
document.addEventListener('keydown', onCloseHandler);
setTimeout(button.focus.bind(button), 50);
},
hide() {
target.classList.remove('__shown');
document.removeEventListener('keydown', onCloseHandler);
},
notEnoughMoney() {
this.show('Недостаточно денег!');
},
outOfRange() {
const sum = `<b style="color:gold">${formatNum(roulBets.MAX)}</b>`;
this.show(`Сумма ставок не может превышать ${sum}`);
}
};
})($('#roul-alert'));
// =============== [[ TOOLS ]]
const roulTools = ((target) => {
let activeCoinValue = '100';
return {
__init__() {
target.addEventListener('click', (e) => {
e.stopPropagation();
if (locked) return;
const trg = e.target;
const {action} = trg.dataset;
this[action] && this[action](trg);
});
},
get activeCoin() {
return $('.roul-coin.__active', target);
},
get activeCoinValue() {
return activeCoinValue;
},
setMultiplier(coin) {
attempt(this.activeCoin, inactiveElem);
activeElem(coin);
activeCoinValue = coin.dataset.coin;
},
rebet() {
return rebet(roulBets.lastBets);
}
};
})($('#roul-tools'));
// =============== [[ TIMER ]]
const roulTimer = createTimer($('#roul-timer'), {
onTick(timer) {
document.title = `Roulette | ${timer}`;
timer.value <= 10 && timer.target.classList.add('__blink');
},
onStop(timer) {
timer.target.classList.remove('__blink');
roulWheel.startSpin(timer.endTime);
}
});
function createTimer(target, props) {
const format = (num) => num > 9 ? num : `0${num}`;
const getValue = (sec) => Math.max(0, sec - 10);
let playState = false;
let startTime = 0;
let endTime = 0;
let timeVal = 0;
let timerId = 0;
function stringify(value) {
const min = format(value / 60 >> 0);
const sec = format(value % 60);
return `${min}:${sec}`;
}
function clear() {
playState = false;
clearTimeout(timerId);
}
function ticking() {
timeVal = getValue((endTime - Date.now()) / 1e3 >> 0);
target.textContent = stringify(timeVal);
props.onTick(that);
timerId = setTimeout(ticking, 1e3);
if (!timeVal) {
clear();
props.onStop(that);
}
}
function resetTimeStamps() {
startTime = Date.now();
endTime = startTime + formData.restSeconds * 1e3;
}
const that = {
target,
get value() {
return timeVal;
},
get endTime() {
return endTime;
},
start() {
if (playState) return;
if (!startTime) resetTimeStamps();
playState = true;
ticking();
},
pause() {
return playState && clear();
},
reset() {
resetTimeStamps();
this.pause();
this.start();
},
toString() {
return stringify(this.value);
}
};
return that;
}
// =========================
function endGame(number, callback) {
const numElem = roulette[`Straight up ${number}`].target;
numElem.id = 'winner';
const bets = mergeBets(roulBets.data.completed);
const game = {
number,
gameId: formData.gameId,
totalBet: reduceBets(bets),
totalPrize: 0,
bets: null
};
if (!bets.length) return callback(game);
game.bets = bets.map((bet) => ({
id: bet.id,
value: bet.value,
prize: ~~attempt(roulette[bet.id], data => {
const test = data.items.includes(numElem);
test && bet.win();
return test ? bet.value * data.factor : 0;
})
}));
game.totalPrize = reduceBets(game.bets, 'prize');
callback(game);
}
function startGame() {
locked = false;
attempt(roulGames.lastGame.bets, bets => (roulBets.lastBets = [...bets]));
attempt($('#winner'), el => el.removeAttribute('id'));
container.classList.remove('__result', '__locked');
roulBets.clear();
roulDensity.clear();
roulTimer.reset();
roulWheel.onGameStart();
}
function loadNextGame() {
return fetch.get(PATH).then((doc) => {
if (!doc.URL.includes(PATH)) {
throw logError.create(-1, 'Authorization error');
}
const [min, max, cash] = getMinMaxCash(doc);
roulBets.MAX !== max && roulBets.updateMinMax(min, max);
roulCash.value = cash;
formData.update(doc.forms[0]);
});
}
// =========================
container.addEventListener('mouseover', (e) => {
e.stopPropagation();
const trg = e.target;
if (!trg.matches('.roul-bet-key')) return;
const key = trg.textContent;
const id = roulID.keys.find(k => roulID[k] === key) || key;
const roulElem = roulette[id].target;
roulAPI.highlight(id);
trg.onmouseleave = () => {
trg.onmouseleave = null;
roulAPI.unhighlight();
};
});
document.addEventListener('keydown', (e) => {
if (locked || !/Numpad[1-9]/.test(e.code)) return;
e.preventDefault();
roulTools.setMultiplier($(`.roul-coin:nth-child(${e.code.slice(6)})`));
});
// Чтобы не забивать асинхронный стек и память
// глупым тяжеловесным админским кодом
if (!modules.HWM_new_header) stopHeartWhenNeeded();
function stopHeartWhenNeeded() {
if (!view.hasOwnProperty('top_line_draw_canvas_heart')) return;
const target = $('#health_amount');
const nativeSetTimeout = view.setTimeout;
const hwmHeartBeatFnName = 'run_top_line_heart_timer';
view.setTimeout = function(handler, delay, ...args) {
if (!handler) return nativeSetTimeout(null);
if (handler.name === hwmHeartBeatFnName) return heartBeat(...arguments);
return nativeSetTimeout(handler, delay, ...args);
};
function heartBeat() {
if (target.textContent === '100') {
view.setTimeout = nativeSetTimeout;
return nativeSetTimeout(null);
} else return nativeSetTimeout(...arguments);
}
}
// =========================
roulTools.__init__();
roulBets.__init__();
roulGames.__init__();
roulDensity.__init__();
roulAlert.__init__();
roulAPI.__init__();
roulTimer.start();
})(document.defaultView);