Mortal界面美化、功能增强

美化界面,允许自定义背景、牌背等,添加恶手率、牌效计算等

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name				Mortal GUI Appeareance Improvement
// @name:zh				Mortal界面美化、功能增强
// @name:zh-CN			Mortal界面美化、功能增强
// @name:zh-TW			Mortal界麵美化、功能增強
// @description			Improve the appearance of mortal killerducky GUI
// @description:zh		美化界面,允许自定义背景、牌背等,添加恶手率、牌效计算等
// @description:zh-CN	美化界面,允许自定义背景、牌背等,添加恶手率、牌效计算等
// @description:zh-TW	美化界麵,允許自定義背景、牌背等,添加噁手率、牌效計算等
// @version				2.0.1
// @namespace			Mortal Appearance
// @author				CiterR
// @icon				https://mjai.ekyu.moe/favicon-32x32.png
// @match				*://mjai.ekyu.moe/killerducky/*
// @grant				GM_addStyle
// @grant				GM_setValue
// @grant				GM_getValue
// @grant				unsafeWindow
// @license 			MIT
// ==/UserScript==

/*
--------------------------- BUG ---------------------------
☑1.暗杠dora显示问题,和.float元素div的overflow有关
☑2.牌效计算时提前计入了未开宝指
☑3.牌效悬浮窗没响应鼠标移出事件时导致的驻留

-------------------------- TODO ---------------------------
  1.牌效不计算7对
  2.计算改良(性能允许情况)
  3.计算一向听的好型率
☑4.优化计算恶手率的启动方式
  5.添加吃碰牌时的牌效计算
*/

//--------------------------------------------  CSS Part should start here  --------------------------------------------//

function mortalAddStyle() {
    let css = `
	/*All the URL in this script shouldn't provide to users*/

    .grid-main {
      background-position: center;/*桌布居中*/
      /*background-position-x: 0px;/*水平调整*/
      /*background-position-y: 0px;/*垂直调整*/
      /*background-size: 145%; /*桌布缩放控制*/
      border-radius: 15px;
      border: 2px solid pink;
    }/*添加桌布*/

    .grid-info {
      border: 2px;
      border-color: white;
      border-style: solid;
      border-radius: 24px;
      background: #93adae;
      z-index: 3; /*和牌顶3D模拟配合使用*/
    }/*中央信息板 */

    .killer-call-img {
      position: relative;
      top: 50px;
      scale: 1.2;
    }/*Mortal外观调整*/

	html {
	  height: 98%;
	}/*避免滚动条*/

    body {
      /*background: white;*/
	  background: linear-gradient(90deg, #2351ff8a, #0bfff7, #fff, #e7eaa7c9, #ff3e4f);
      height: 98%;
    }/*网页颜色更改*/

	.outer {
	  margin-left: -100px;
	}/*主页面左偏置*/

	.opt-info {
	  margin-left: 90px;
	}/*指示栏偏置*/

	.opt-info table {
	  border-radius: 15px;
	  background: #74abb6;
	  box-shadow: 4px -4px 6px 1px #f6f6f6;
	}/*指示栏样式调整*/

    /* img[src="media/Regular_shortnames/back.svg"]{
      content: url('');
    }/*牌背设置*/

    .grid-hand {
      background: hsl(0deg 0% 100% / 0%);
    }/*对手手牌区透明化*/

    .grid-hand-p3 {
      height: 530px;
    }/*上家鸣牌位置调整*/

    .grid-hand-p0-container{
      background: hsl(0deg 0% 100% / 0%);
      scale: 1.15;
      width: 555px;   /*手牌宽度减少,避免穿模,可能会引发问题*/
	  position: relative;
	  left: -15px;
	  top: 50px;
    }/*手牌区透明化及放大调整*/

    .tileImg{
      border-radius: 4px;
      /*border-top: 3px groove #bbc9d9;/*牌顶3D模拟,效果不好*/
    }/*麻将牌修改:圆角*/

    .killer-call-bars > svg > rect, .discard-bars > svg > rect {
      rx: 2px;
    }/*绿条切割矩形:圆角*/

    main{
      /*scale: 1.2;*/
      top: 50px;
      position: relative;
    }/*主页面放大*/

    .info-doras {
      scale: 1.4;
    }/*Dora显示加大*/

    .info-round {
      background: hsl(192.97deg 17.21% 42.16%);
      border-color: transparent;
      border-radius: 15px;
    }/*场次切换器*/

    .info-this-round-modal{
      background: hsl(190deg 100% 20%);
      border-width: 3px;
      border-radius: 10px;
      border-style:solid;
      border-color:unset;
    }/*对局报告器*/

    .close {
      background: red;
      scale: 1.2;
      border: 0px;
      border-radius: 50%;
      right: 5px;
      width: 20px;
      height: 20px;
    }/*对局报告器关闭按钮*/

    .killer-call-bars{
      scale: 1.5;
      position: relative;
      left: 20px;
      top: 20px;
      border-radius: 20px;
      background:hsl(190deg 31.45% 58.49%);
	  box-shadow: 5px 5px 6px 1px #f6f6f6;
    }/*何切栏放大*/

    .killer-call-bars > svg > text:nth-child(2) {
      fill: #f72727;
    }/*第一选标红*/

    /*.killer-call-bars > svg > rect[x="4.5"], rect[x="14.5"], rect[x="24.5"] {
      stroke: red;
    }
    .killer-call-bars > svg {
      height: 116px;
    }/*第一选红框显示*/

    .sidebar{
      margin-left: 60px;
      justify-content: flex-start;
      align-content; center;
      flex-direction: column;
    }
    .sidebar > * {
      margin:5px;
    }/*右侧栏样式*/

    .controls {
      background: hsl(190deg 49.75% 89.34% / 36%);
      border-radius: 20px;
      height: 325px;
	  box-shadow: 5px 5px 6px 1px #f6f6f6;
    }/*右侧控制板*/

    .controls > * {
      margin: 5px;
      color: black;
      border-color: white;
      border-radius: 15px;
      background: #74abb6;
	  width: 115px;
    }/*控制板按钮样式*/

    .tileImg:hover {
        background: #cdcbcb;
    }/*悬浮选牌*/

    .modal, button {
      border-radius: 10px;
    }/*选项、关于窗口*/

	#about-modal {
    background: linear-gradient(45deg, hsl(190deg 100% 20%), hsl(190 100% 30% / 1), hsl(190 100% 40% / 1));
	}/*关于窗口背景*/

	.newSetting {
		height: 50px;
		width: 150px;
	}/*新添加按钮调整*/

    .opt-info table .tileImg {
		width: calc(var(--tile-img-width)*0.7);
		height: auto;
		position: relative;
		top: 5px;
    }/*调整Mortal候选牌大小*/

    .wider-table td {
		height: 36px;
		padding-top: 2px;
		padding-bottom: 2px;
    }/*配合上一条缩窄排版*/

    #about-body-0 > li:last-child > span {
        display: none;
    }
    #about-body-0 > li:last-child:after {
      content: '如有BUG,关闭此脚本 / Disable Script When BUG';
    }/*声明修改*/
 `
    GM_addStyle(css)
}
//--------------------------------------------  CSS Part should end here  --------------------------------------------//


//--------------------------------------------  Extra Functions should start here  --------------------------------------------//

/*全局变量*/
const standardTileHeight = 20;	//牌张大小常数
const standardTileWidth = standardTileHeight / 4 * 3;
let timer = null;	//消抖定时器

function listenerAdder(strips) { //给出高度条相对百分比
	let maxStripHeight = 1;
	strips.forEach(e=>{
		if(e.getAttribute('width') !== '20') {
			maxStripHeight = Math.max(e.getAttribute('height'), maxStripHeight);
		}
	});

	strips.forEach(e=>{
		if (e.getAttribute('width') !== '10')	return;

		const showHoverWin = ()=>{	//对高度条设置鼠标悬浮响应事件
			let p0Element = document.querySelector(".opt-info > table:last-child tr:nth-of-type(2) > td:last-child");
			let p0 = parseFloat(p0Element.innerText) / 100;
			let normProb = e.getAttribute('height') / maxStripHeight; 	//归一化公式 n(x)=sqrt(x/p0)
			let realProb = p0 * (normProb ** 2); 						//逆操作还原
			let pos = e.getBoundingClientRect();

			let tooltip = document.createElement('div');
			tooltip.className = 'hoverInfo';
			tooltip.style.position = 'absolute';
			tooltip.style.backgroundColor = '#7dbcc980';
			tooltip.style.border = '1px solid white';
			tooltip.style.padding = '5px';
			tooltip.style.borderRadius = '5px';		// 设置悬浮窗的样式 Hover window styles
			tooltip.textContent = (realProb * 100).toFixed(2) + '%';
			tooltip.style.top = `${pos.y - 40}px`;
			tooltip.style.left = `${pos.x - 25}px`;
			e.style.opacity = '0.6';
			document.body.appendChild(tooltip); // 将悬浮窗添加到页面中

			const deleteTooltip = ()=>{			// 给悬浮窗绑定鼠标移出事件
				e.style.opacity = '1';
				tooltip.remove(); 				// 移除悬浮窗
				e.removeEventListener('mouseout', deleteTooltip);
			}
			e.addEventListener('mouseout', deleteTooltip);
		}

		e.addEventListener('mouseover', showHoverWin);
	});
};

function mortalOptionColorize(errTolerance = [ 1, 5, 10, -1 ]) { //最后一个参数-1,为绝对值恶手,>0为比值恶手
	let actionTable = document.querySelector(".opt-info > table:last-child");
	let actionTrList = actionTable.querySelectorAll("tr");

	let actionCardList = new Array();	// 第一个是无用项
	let possibilityList = new Array();

	let lastTr = actionTrList[actionTrList.length - 1];
	lastTr.querySelector("td:first-child").style.borderBottomLeftRadius = "15px";
	lastTr.querySelector("td:last-child").style.borderBottomRightRadius = "15px";
	// 设置表格底部圆角

	actionTrList.forEach(e=>{
		let cardAct = e.querySelector("td:first-child > span");
		let action, card;
		if (cardAct != null) {
			action = cardAct.textContent.substring(0, 1);		//获取牌操作
		}

		let cardImg = e.querySelector("td:first-child > span > img");
		if (cardImg != null) {
			let cardURL = cardImg.getAttribute('src');
			card = cardURL.substring(
				cardURL.lastIndexOf('/')+1, cardURL.lastIndexOf('.')); //获取出牌选择
		}

		actionCardList.push(action + card);

		let possibilityTr = e.querySelector("td:last-child");
		if (possibilityTr.textContent != 'P') {
			possibilityList.push(possibilityTr.textContent);// 获取概率数据
		}
	});

	//获取玩家选择和Mortal一选
	let actionCard = new Array();
	let mainActionSpan = document.querySelectorAll(".opt-info > table:first-child span");
	mainActionSpan.forEach(e=>{
		let action = e.textContent.substring(0, 1);//操作
		let card;
		let cardImg = e.querySelector('img');
		if (cardImg != null) {
			let cardURL = cardImg.getAttribute('src');
			card = cardURL.substring(cardURL.lastIndexOf('/')+1, cardURL.lastIndexOf('.'));//牌张
		}
		actionCard.push(action + card);
	});

	let possibilityPlayer = 0;
	let playerSelect = 0;
	//给玩家选择进行标记
	for (let i = 1; i < actionCardList.length; i++) {
		if (actionCardList[i] == actionCard[0]) {
			actionTrList[i].style.background = "rgb(171, 196, 49)";
			possibilityPlayer = parseFloat(possibilityList[i - 1]);
			playerSelect = i - 1;
			break;
		}
	}

	//判断恶手并标红
	let fatalErr = parseFloat(errTolerance[0]);
	let normalErr = parseFloat(errTolerance[1]);
	let arguableErr = parseFloat(errTolerance[2]);
	let fatalErrEdge = parseFloat(errTolerance[3]);
	let pRatio= parseFloat(possibilityPlayer) / parseFloat(possibilityList[0]);
	let colorChoice = -1;	//分别为红0、橙1、蓝2,及上方标记的黄绿-1

	if (actionCard[0] != actionCard[1]) {
		if (fatalErrEdge < 0) {		//绝对值恶手
			if 			(possibilityPlayer < fatalErr) 			colorChoice = 0;
			else if 	(possibilityPlayer < normalErr) 		colorChoice = 1;
			else if 	(possibilityPlayer < arguableErr)		colorChoice = 2;
		} else if (fatalErrEdge > 0) {	//比值恶手
			if 			(possibilityPlayer < fatalErrEdge) 		colorChoice = 0; //权重过小,直接判断恶手
			else if 	(pRatio < fatalErr) 					colorChoice = 0;
			else if 	(pRatio < normalErr) 					colorChoice = 1;
			else if 	(pRatio < arguableErr) 					colorChoice = 2;
		}
	}

	let playerSelectInMain = document.querySelectorAll('.discard-bars-svg > rect[width="20"]');
	 switch (colorChoice) {
		case 0 :
			actionTrList[playerSelect + 1].style.background = "red";
			playerSelectInMain.forEach(e=>{ e.style.fill = "red"; });
			break;
		case 1 :
			actionTrList[playerSelect + 1].style.background = "#ff5a00";
			playerSelectInMain.forEach(e=>{ e.style.fill = "#ff5a00"; });
			break;
		case 2 :
			actionTrList[playerSelect + 1].style.background = "blue";
			playerSelectInMain.forEach(e=>{ e.style.fill = "blue"; });
			break;
	 }
}

function createButtonBox(){
	let settingOption = document.querySelector('.options-div');
	let buttonBox = document.createElement('div');
	buttonBox.style.display = 'flex';
	buttonBox.className = 'buttonBox-div';
	buttonBox.style.flexWrap = 'wrap';
	buttonBox.style.width = '500px';
    buttonBox.style.justifyContent = 'space-evenly';
	settingOption.appendChild(buttonBox);
}

function backgroundSetting(){
	let buttonBox = document.querySelector('.buttonBox-div');
	let setBackgroundButton = document.createElement('button');
	let backgroundURL = GM_getValue('backgroundPicUrl', 'https://backgroundURL.example');
	let backgroundImg = document.createElement('img');
	setBackgroundButton.className = 'newSetting';
	buttonBox.appendChild(setBackgroundButton);		//插入按钮
	setBackgroundButton.textContent = '修改背景图';
	setBackgroundButton.addEventListener('click', ()=>{
		let inputURL = prompt('输入背景图URL', backgroundURL);
		if (inputURL !== null) {
			backgroundURL = inputURL.trim();
			backgroundImg.src = backgroundURL;
			GM_setValue('backgroundPicUrl', backgroundURL); //存储背景图链接
		}
		document.querySelector('.grid-main').style.backgroundImage = `url(${backgroundURL})`;
	});
	document.querySelector('.grid-main').style.backgroundImage = `url(${backgroundURL})`;
	backgroundImg.src = backgroundURL;						//设置被存储好的背景
	backgroundImg.style.maxWidth = '200px';
	backgroundImg.style.maxHeight = '200px';
    backgroundImg.style.marginTop = '30px';
	backgroundImg.style.justifySelf = 'center';
	backgroundImg.onload = ()=>{ document.querySelector('.options-div').appendChild(backgroundImg); }
	backgroundImg.onerror = ()=> {
		console.log('Can not to load background pic');
		document.querySelector('.grid-main').style.background = 'green';
	}
}

function tileBackSetting(){
	let buttonBox = document.querySelector('.buttonBox-div');
	let setTileBackButton = document.createElement('button');
	let tileBackURL = GM_getValue('tileBackPicURL', 'https://tilebackURL.example');
	let tileBackImg = document.querySelectorAll('img[src="media/Regular_shortnames/back.svg"]');
	setTileBackButton.className = 'newSetting';
	buttonBox.appendChild(setTileBackButton);			//插入按钮
	setTileBackButton.textContent = '设置牌背';
	setTileBackButton.addEventListener('click', ()=>{
		let inputURL = prompt('输入牌背URL', tileBackURL);
		if (inputURL !== null) {
			tileBackURL = inputURL.trim();
			GM_setValue('tileBackPicURL', tileBackURL);		//存储牌背链接
		}
		let tilebackStyle = `img[src="media/Regular_shortnames/back.svg"]{
      						content: url('${tileBackURL}');	}`
		GM_addStyle(tilebackStyle);
	});
	if (tileBackURL == 'https://tilebackURL.example') return;
    let tilebackStyle = `img[src="media/Regular_shortnames/back.svg"]{
      					content: url('${tileBackURL}');		}`
    GM_addStyle(tilebackStyle);						//使用CSS添加
}

function logoSetting(){
	let buttonBox = document.querySelector('.buttonBox-div');
    let setLogoButton = document.createElement('button');
	let logoURL = GM_getValue('logoURL', 'https://logoURL.example');
	setLogoButton.className = 'newSetting';
	buttonBox.appendChild(setLogoButton);		//插入按钮
	setLogoButton.textContent = '修改形象图';
	setLogoButton.addEventListener('click', ()=>{
		let inputURL = prompt('输入形象图URL', logoURL);
		if (inputURL !== null) {
			logoURL = inputURL.trim();
			GM_setValue('logoURL', logoURL); //存储形象图链接
		}
		document.querySelector('.killer-call-img').src = `url(${logoURL})`;
	});
    if (logoURL !== 'https://logoURL.example') {
        let logoStyle = `
		.killer-call-img {
	  	content: url('${logoURL}');
      	position: relative;
      	top: 50px;
      	scale: 1.2;
    	}`;
		GM_addStyle(logoStyle);
    }
}

function optInfoSwitch(){
	let buttonBox = document.querySelector('.buttonBox-div');
	let mortalOptionSwitch = document.createElement('button');
	let mortalOpt = document.querySelector('.opt-info');
	let outer = document.querySelector('.outer');
	let state = GM_getValue('mortalOptionState', true);
	mortalOptionSwitch.className = 'newSetting';
	buttonBox.appendChild(mortalOptionSwitch);	//插入按钮
	if (!state) {	//初始化按钮对应的状态
		mortalOptionSwitch.textContent = '开启Mortal选项面板';
		mortalOpt.style.display = 'none';
		outer.style.marginLeft = '0px';
	} else {
		mortalOptionSwitch.textContent = '关闭Mortal选项面板';
		mortalOpt.style.display = 'initial';
		outer.style.marginLeft = '-100px';
	}
	mortalOptionSwitch.addEventListener('click', ()=>{
			state = !state;
			if (!state) {
				mortalOptionSwitch.textContent = '开启Mortal选项面板';
				mortalOpt.style.display = 'none';
				outer.style.marginLeft = '0px';
			} else {
				mortalOptionSwitch.textContent = '关闭Mortal选项面板';
				mortalOpt.style.display = 'initial';
			outer.style.marginLeft = '-100px';
		}
		GM_setValue('mortalOptionState', state);	//存储状态
	});
}

function fullScreenEnlarge(){
    let scaleArray = GM_getValue('scaleStr', '1.2, 1.35');
	let scale = scaleArray.split(',');
	let defaultScale = parseFloat(scale[0]);
	let fullScreenScale = parseFloat(scale[1]);

	addEventListener('keydown', (e)=>{				//进入全屏放大
		if (e.key === 'F11') {
			event.preventDefault();
			document.documentElement.requestFullscreen();
		}
	});
	addEventListener('fullscreenchange',()=>{
		let mainInFull = document.querySelector('main');
		if (!document.fullscreen) {					//退出全屏重置
			mainInFull.style.scale = `${defaultScale}`;
			mainInFull.style.top = '50px';
		} else {
			mainInFull.style.scale = `${fullScreenScale}`;
			mainInFull.style.top = '110px';
		}
	});
	document.querySelector('.killer-call-img').addEventListener('click', ()=>{	//快捷全屏
		if (!document.fullscreen){
			document.documentElement.requestFullscreen();
		} else {
			document.exitFullscreen();
		}
	});
}

function createStripsHoverWindow() {
	let bars = document.querySelector('#discard-bars');	//设置监听svg监听
	let observer = new MutationObserver((mutationList, observer)=>{
		let strips = bars.querySelectorAll('.discard-bars-svg>rect');
		listenerAdder(strips);
	})
	if (bars === null) {
		console.log('SelectorError!');
	} else {
    	observer.observe(bars, {childList: true, subtree: true});		//当svg被重置时更新选择器strips
	}

	let callBars = document.querySelector('.killer-call-bars');
	let observerAdviser = new MutationObserver((mutationList, observerAdviser)=>{
        mutationList.forEach(e=>{
			if (e.type === 'childList') {
				let stripsAdviser = document.querySelectorAll('.killer-call-bars>svg>rect');
				listenerAdder(stripsAdviser);
				let remainWindow = document.querySelectorAll(".hoverInfo");
				remainWindow.forEach(w=>{ w.remove() }); //svg更新时清空浮窗
			}
        });
    });
	observerAdviser.observe(callBars, {childList: true});
}

function startMortalOptionObserver(errTolerance) {
	let optState = GM_getValue('mortalOptionState', true);
//	if (!optState) return;		//关闭状态不设置监听
	let optInfo = document.querySelector('.opt-info');
	let observerInfo = new MutationObserver(
		(mutationList, observerInfo)=>{
			mortalOptionColorize(errTolerance);
		}
	);	//设置mortal选项更新监听
	observerInfo.observe(optInfo, {childList: true});
}

function setCustomErrTolerance() {
	let buttonBox = document.querySelector('.buttonBox-div');
	let setErrToleranceButton = document.createElement('button');
	let errToleranceStr = GM_getValue('errToleranceStr', '1, 5, 10, -1');
	let errTolerance = errToleranceStr.split(',');

	setErrToleranceButton.className = 'newSetting';
	buttonBox.appendChild(setErrToleranceButton);		//插入按钮
	setErrToleranceButton.textContent = '自定义恶手率';
	setErrToleranceButton.addEventListener('click', ()=>{
		let explainText ='输入恶手率组合,四个参数 (刷新后生效)\n' +
									'x4=-1为绝对模式,低于权重直接判定\n' +
									'x4> 0为比值模式,与一选相除再判定'
		let inputStr = prompt(explainText, errToleranceStr);
		if (inputStr !== null) {
            let input = inputStr.replace(',',','); //替换中文逗号
            let numArray = input.split(',');
            let newErrTolerance = numArray.map(Number);
			if (newErrTolerance.length !== 4) {
                alert('参数数量不一致!');
                return;
            }
			GM_setValue('errToleranceStr', inputStr); 	//存储恶手率字符串
            errToleranceStr = inputStr;
		}
	});
	return errTolerance;
}

function addTableRow(table, str, value) {
    const tr = table.insertRow();
    let cell = tr.insertCell();
    cell.textContent = `${str}`;
    cell = tr.insertCell();
    cell.textContent = `${value}`;
}

function setMainAreaEnlarge() {
	let buttonBox = document.querySelector('.buttonBox-div');
	let scaleButton = document.createElement('button');
	let scaleStr = GM_getValue('scaleStr', '1.2, 1.35');
	let scaleArray = scaleStr.split(',');

    document.querySelector('main').style.scale = `${scaleArray[0]}`;//应用放大

	scaleButton.className = 'newSetting';
	buttonBox.appendChild(scaleButton);		//插入按钮
	scaleButton.textContent = '界面放大倍数';
	scaleButton.addEventListener('click', ()=>{
		let explainText ='输入放大倍数组合,四个参数 (刷新后生效)\n' +
									'第一个参数,非全屏状态的放大倍数\n' +
									'第二个参数,全屏状态下的放大倍数'
		let inputStr = prompt(explainText, scaleStr);
		if (inputStr !== null) {
            let input = inputStr.replace(',',','); //替换中文逗号
            let numArray = input.split(',');
            let newScaleArray = numArray.map(Number);
			if (newScaleArray.length !== 2) {
                alert('参数数量不一致!');
                return;
            }
			GM_setValue('scaleStr', inputStr); 	//存储倍数字符串
            scaleStr = inputStr;
		}
	});
	return scaleArray;
}

async function errCalculate(errTolerance) {
	let fatalErrCnt = 0;
    let normalErrCnt = 0;
	let arguableErrCnt = 0;
/*  感谢脚本Mortal Killer Plus作者sabertaz的数据获取思路
	const urlParams = new URLSearchParams(window.location.search);
    const dataURL = urlParams.get("data");
    const response = await fetch(dataURL);
    const data = await response.json();
    const reviewData = data.review;					*/

	async function waitReview() {
	  return new Promise((resolve) => {
		const check = setInterval(() => {
		  if (unsafeWindow.MM.GS.fullData.review) {
			clearInterval(check); // 停止轮询
			resolve(unsafeWindow.MM.GS.fullData.review); // 返回数据
		  } }, 500);	//间隔500ms
	  });
	}
	const reviewData = await waitReview();	//由killerducky作者挂载的Debug信息

    for (const kyokus of reviewData.kyokus) {
      for (const curRound of kyokus.entries) {
        const mismatch = !curRound.is_equal;
        const pPlayer = curRound.details[curRound.actual_index].prob * 100;
		const pMortal = curRound.details[0].prob * 100;
		if (mismatch && parseFloat(errTolerance[3]) < 0) {	//绝对值恶手
			if (pPlayer <= parseFloat(errTolerance[0])) fatalErrCnt++;
			if (pPlayer <= parseFloat(errTolerance[1])) normalErrCnt++;
			if (pPlayer <= parseFloat(errTolerance[2])) arguableErrCnt++;
		} else if (mismatch && parseFloat(errTolerance[3]) > 0) {	//比值恶手
			const pRate = parseFloat(pPlayer) / parseFloat(pMortal);
			if (pPlayer <= parseFloat(errTolerance[3])) {
				fatalErrCnt++;
				normalErrCnt++;
				arguableErrCnt++;
				continue;
			}
			if (pRate <= parseFloat(errTolerance[0])) fatalErrCnt++;
			if (pRate <= parseFloat(errTolerance[1])) normalErrCnt++;
			if (pRate <= parseFloat(errTolerance[2])) arguableErrCnt++;
		}
      }
    }


    const totalReviewed = reviewData.total_reviewed;

    const fatalErrRate = ((fatalErrCnt / totalReviewed) * 100).toFixed(2);
    const fatalErrStr = `${fatalErrCnt}/${totalReviewed} = ${fatalErrRate}%`;
    const normalErrRate = ((normalErrCnt / totalReviewed) * 100).toFixed(2);
    const normalErrStr = `${normalErrCnt}/${totalReviewed} = ${normalErrRate}%`;
	const arguableErrRate = ((arguableErrCnt / totalReviewed) * 100).toFixed(2);
    const arguableErrStr = `${arguableErrCnt}/${totalReviewed} = ${arguableErrRate}%`;

	let metadataTable = document.querySelector(".about-metadata table:first-child");
	let errRateZH = " 恶手率";
	if (parseFloat(errTolerance[3]) < 0) errRateZH = "% 恶手率";
	addTableRow(metadataTable, `${errTolerance[0]}${errRateZH}`, fatalErrStr);
	addTableRow(metadataTable, `${errTolerance[1]}${errRateZH}`, normalErrStr);
	addTableRow(metadataTable, `${errTolerance[2]}${errRateZH}`, arguableErrStr);
}

function addDoraFlash(doraIndicators, state) { //同时负责上一次dora特效的关闭:state=0
	let doras = new Array();
	doraIndicators.forEach(e =>{
		let doraStr = '';
		switch(e[1]) {
			case 'z':
				if(parseInt(e[0]) < 5) {
					doraStr = `${ parseInt(e[0]) % 4 + 1 }z`; //东南西北
				} else {
					doraStr = `${ (parseInt(e[0]) - 4 ) % 3 + 5}z`; //白发中
				} break;
			default:
				if (parseInt(e[0]) === 0) {
					doraStr = `6${e[1]}`;//赤宝牌的特例
				} else {
					doraStr = `${ parseInt(e[0]) % 9 + 1 }${e[1]}`;
				} break;
		}
		doras.push(doraStr);
	});

	if(state) doras.push('0m', '0p', '0s');
	for (const dora of doras) {
		let doraStyle;
		if (state) {
			doraStyle = `
				.tileDiv:has(img[src="media/Regular_shortnames/${dora}.svg"]) {
					position: relative;
					overflow: hidden;
					border-radius: 5px;
				}

				.tileDiv:has(img[src="media/Regular_shortnames/${dora}.svg"])::after {
					content: '';
					position: absolute;
					inset: -40%;
					background: linear-gradient(45deg, rgba(255,255,255,0) 40%, rgba(255, 255, 255, 0.7), rgba(255,255,255,0) 60%);
					animation: doraFlash 2s infinite;
					transform: translateY(-100%);
					z-index: 1; /*解决失焦问题*/
				}

				@keyframes doraFlash {
				  to {
					transform: translateY(100%);
				  }
				}`;
		} else { //关闭伪元素显示和动画
			doraStyle = `
				.tileDiv:has(img[src="media/Regular_shortnames/${dora}.svg"]){
					overflow: visible;
				}
				.tileDiv:has(img[src="media/Regular_shortnames/${dora}.svg"])::after {
					content: none;
					background: transparent;
					animation: none;
				}`;
		}
		GM_addStyle(doraStyle);
	}

	if (typeof addDoraFlash.executed === "undefined" || !addDoraFlash.executed) {
        addDoraFlash.executed = false;
		let rotatedDoraFix = `
			.pov-p0 > div:has(.rotate) {
				height: var(--tile-width);
				align-self: flex-end;
			}
			.pov-p0 > div > .rotate {
				transform: rotate(90deg) translate(calc(-1 * var(--tile-height)), 0px);
			}
			/*自家鸣牌立直调整*/

			.pov-p1 > div:has(.rotate) {
				width: var(--tile-width);
				align-self: flex-end;
			}/*下家鸣牌立直调整*/

			.pov-p2 > div:has(.rotate) {
				height: var(--tile-width);
			}
			.grid-discard-p2 > div:has(.rotate) {
				align-self: flex-end;
			}/*对家鸣牌立直调整*/

			.pov-p3 > div:has(.rotate) {
				width: var(--tile-width);
			}
			.grid-discard-p3 > div:has(.rotate) {
				align-self: flex-end;
			}/*上家鸣牌立直调整*/
			.tileDiv:has(.tileImg.rotate.float) {
				overflow:visible;
			}/*修复自杠Dora4显示*/
			`;
		GM_addStyle(rotatedDoraFix);
    }
}

function startDoraObserver(doraCheck_ms = 1500) {	//事实上网页上挂载了window.MM.GS.gs.dora,但貌似无法监听
    let preDoraIndicator = new Array();
    const checkInterval = doraCheck_ms; 	//每隔x毫秒查询dora指示牌
    const interval = setInterval(() => {
		let doraInfo = document.querySelectorAll('.info-doras > div > img');
		let doraIndicator = new Array();
		doraInfo.forEach(e=>{
			let cardURL = e.getAttribute('src');//获取doraIdr
			let doraStr = cardURL.substring(cardURL.lastIndexOf('/')+1, cardURL.lastIndexOf('.'));
			if (doraStr !== 'back')		doraIndicator.push(doraStr);
		});

		if (!(function(doraIndicator, preDoraIndicator) { //数组相等的匿名函数
				if (doraIndicator.length !== preDoraIndicator.length) return false;
				for (let i = 0; i < doraIndicator.length; i++) {
					if (doraIndicator[i] !== preDoraIndicator[i]) { return false; }};
				return true;
			})(doraIndicator, preDoraIndicator)) {
			addDoraFlash(preDoraIndicator, false);//清除上一次的dora特效;
			addDoraFlash(doraIndicator, true);
			//console.log(preDoraIndicator, '----Updated To--->', doraIndicator);
			preDoraIndicator = [];	//保存当前dora
			doraIndicator.forEach(d=>{ preDoraIndicator.push(d); });
		}
    }, checkInterval);
}

function startEfficencyCalc(calcDelay_ms = 800) {
	let effEnable = GM_getValue('effEnable', true);
	if (!effEnable) return;

	const calcDelay = (mutationsList) => {
		let effHover = document.querySelectorAll('.eff-hover');
		effHover.forEach((e)=>{ e.remove(); });	//清除未正确移除的浮窗
  		if (mutationsList.length <= 1) return;	//非摸牌更新
		if (timer) clearTimeout(timer); 		//等待时间过短
		timer = setTimeout(()=>{
			timer = null;
			calcEfficency();			//延迟后计算牌效,跳过快速浏览
		}, calcDelay_ms);
	};

	const svgBarsDetector = new MutationObserver((mutations, observer) => {
        const target = document.querySelector('.discard-bars-svg');
        if (target) {
			const startCalc = new MutationObserver(calcDelay);
			startCalc.observe(target, { childList: true, subtree: false });
            svgBarsDetector.disconnect();
        }
    });
    svgBarsDetector.observe(document.body, { childList: true, subtree: true }); //等待bars-svg加载
}

function calcEfficency() { //负责函数调用
	let cardInfo = getCardInfo();
	if(!cardInfo) return;			//不摸牌不计算
	let shantenCnt = shanten(cardInfo.handset);
	if(shantenCnt === -1) return;	//和牌返回

	let ukeireSet = kiruEfficency(cardInfo.handset, cardInfo.seenTiles);
	addEffCardset(ukeireSet, shantenCnt);
}

function getCardInfo() {
	let handcard = unsafeWindow.MM.GS.gs.hands[unsafeWindow.MM.GS.heroPidx];
	let tsumocard = unsafeWindow.MM.GS.gs.drawnTile[unsafeWindow.MM.GS.heroPidx];
	if (!tsumocard) return;	//没有自摸牌
	handcard.push(tsumocard);
	let handset = new Array(5).fill().map(() => new Array(10).fill(0));	//矩阵化手牌
	handcard.forEach(e=>{
		let idx = Math.floor(e / 10), idy = e % 10;	//添菜idy
		if (idx === 5) {
			idx = idy;
			idy = 5;
		}	//处理红五
		handset[idx][idy]++;
	});

	let seenTiles = new Array(5).fill().map(() => new Array(10).fill(0));	//已现牌组,不包括手牌
	let calls = unsafeWindow.MM.GS.gs.calls;
	let discardPond = unsafeWindow.MM.GS.gs.discardPond;
	let doraIdr = unsafeWindow.MM.GS.gs.doraIndicator;

	let doraInfo = document.querySelectorAll('.info-doras > div > img');
	let doraCnt = 0;
	doraInfo.forEach(e=>{
		let cardURL = e.getAttribute('src');
		let doraStr = cardURL.substring(cardURL.lastIndexOf('/')+1, cardURL.lastIndexOf('.'));
		if (doraStr !== 'back') doraCnt++;
	});	//获取dora数量,修正计入未开指示牌问题

	for (let card of calls) {
		if (typeof card !== 'number') continue;
		let idx = Math.floor(card / 10), idy = card % 10;
		if (idx === 5) {
			idx = idy;
			idy = 5;
		}
		seenTiles[idx][idy]++;
	}
	for (let ply of discardPond) {
		ply.forEach(e=>{
			let idx = Math.floor(e.tile / 10), idy = e.tile % 10;
			if (idx === 5) {
				idx = idy;
				idy = 5;
			}
			seenTiles[idx][idy]++;
		})
	}
	for (let i = 0; i < doraCnt; i++) {
		let idr = doraIdr[i];
		let idx = Math.floor(idr / 10), idy = idr % 10;
		if (idx === 5) {
			idx = idy;
			idy = 5;
		}
		seenTiles[idx][idy]++;
	}

	for (let i = 1; i <= 4; i++) handset[i][0] = seenTiles[i][0] = i;
	return { handset: handset, seenTiles: seenTiles };
}

function breakdown(A, depth) {
	if (depth >= 4) return 0;		//四张孤张剪枝
	let ret = 0, i = 1;
	while (i <= 9 && !A[i]) i++;	//定位第一张
	if (i > 9) return 0;			//空白返回
	if (i + 2 <= 9 && A[i] && A[i + 1] && A[i + 2] && A[0] != 4) {
		A[i]--; A[i + 1]--; A[i + 2]--;
		ret = Math.max(ret, breakdown(A, depth) + 2100);
		A[i]++; A[i + 1]++; A[i + 2]++;
	}
	else {
		if (i + 2 <= 9 && A[i] && A[i + 2] && A[0] != 4) {
			A[i]--; A[i + 2]--;
			ret = Math.max(ret, breakdown(A, depth) + 1001);
			A[i]++; A[i + 2]++;
		}
		if (i + 1 <= 9 && A[i] && A[i + 1] && A[0] != 4) {
			A[i]--; A[i + 1]--;
			ret = Math.max(ret, breakdown(A, depth) + 1001);
			A[i]++; A[i + 1]++;
		}
	}

	if (A[i] >= 3) {
		A[i] -= 3;
		ret = Math.max(ret, breakdown(A, depth) + 2100);
		A[i] += 3;
	}
	if (A[i] >= 2) {
		A[i] -= 2;
		ret = Math.max(ret, breakdown(A, depth) + 1010);
		A[i] += 2;
	}
	A[i]--;
	ret = Math.max(ret, breakdown(A, depth + 1));
	A[i]++;
	return ret;
}

function shantenStandard(S) {
	let analysis = 0, cardnum = 0;
	for (let A of S) {
		let ret = 0;
		for (let i = 1; i <= 9; i++) {
			ret += A[i];
			cardnum += A[i];
		}
		if (!ret) continue;	//该类牌空
		analysis += breakdown(A, 0);
	}

	let block = Math.floor(analysis % 1000 / 100);
	let pair = Math.floor(analysis % 100 / 10);
	let dazi = analysis % 10;

	block += Math.floor((14 - cardnum) / 3);	//处理鸣牌
	if (pair > 1) {
		dazi += pair - 1;
		pair = 1;
	}	// 对搭转换
	while (block + dazi > 4 && dazi > 0) dazi--;	// 4N+2规则

	return 8 - (2 * block + dazi + pair);
}

function shantenChiitoi(S) {
	let pair = 0;
	for (let A of S) {
		for (let i = 1; i <= 9; i++) {
			if (A[i] >= 2) pair++;
		}
	}
	return 6 - pair;
}

function shanten(S) { return Math.min(shantenStandard(S), shantenChiitoi(S)); }

function ukeire(S, curShanten) {
	let vaildcard = new Array(5).fill().map(() => new Array(10).fill(0));	//进张
	for (let i = 0; i <= 4; i++) vaildcard[i][0] = i;						//初始化

//	let curShanten = shanten(S);	/*外部传入向听数节省调用时间 */
	for (let i = 1; i <= 3; i++) {	//数牌
		for (let j = 1; j <= 9; j++) {
			let k = j - 2, acc = 0;
			while (k < 1) k++;
			while (k <= j + 2) {
				if (k > 9) break;
				acc += S[i][k++];
			}
			if (!acc) continue;	//不摸孤张
			S[i][j]++;
			if (shanten(S) < curShanten) vaildcard[i][j]++;
			S[i][j]--;
		}
	}
	for (let j = 1; j <= 7; j++) {	//字牌
		if (!S[4][j]) continue;
		S[4][j]++;
		if (shanten(S) < curShanten) vaildcard[4][j]++;
		S[4][j]--;
	}
	return vaildcard;
}

function kiruEfficency(S, seen) {
	let ret = [];
	let curShanten = shanten(S);
	for (let i = 1; i <= 4; i++) {
		for (let j = 1; j <= 9; j++) {
			if (!S[i][j]) continue;
			let pai, num = j.toString();
			switch (i) {
				case 1: pai = num + "m"; break;
				case 2: pai = num + "p"; break;
				case 3: pai = num + "s"; break;
				case 4: pai = num + "z"; break;
			}

			S[i][j]--;
			if (shanten(S) == curShanten) {	//出牌向听不减
				let vaild = ukeire(S, curShanten);
				let left = tileleft(S, vaild, seen);
				let vaildstr = convertToStr(vaild);
				ret.push({ pai: pai, left: left, ukeStr: vaildstr, uke: vaild });
			}
			S[i][j]++;
		}
	}
	ret.sort((a,b)=> b.left.leftNor - a.left.leftNor);
	return ret;
}

function tileleft(S, uke, seen) {
	let leftNor = 0, leftPure = 0;
	for (let i = 1; i <= 4; i++) {
		for (let j = 1; j <= 9; j++) {
			if (!uke[i][j]) continue;
			leftNor += 4 - S[i][j] - seen[i][j];
			leftPure += 4 - S[i][j];
		}
	}
	return { leftNor, leftPure };
}

function convertToStr(S) {
	let str = '';
	for (let i = 1; i <= 4; i++) {
		let acc = 0;
		for (let j = 1; j <= 9; j++) {
			let tmp = S[i][j];
			acc += tmp;
			while (tmp--) str += j.toString();
		}
		if (!acc) continue;
		switch (i) {
			case 1: str += 'm'; break;
			case 2: str += 'p'; break;
			case 3: str += 's'; break;
			case 4: str += 'z'; break;
		}
	}
	return str;
}

function addEffCardset(ukeireSet, shantenCnt) {
	let effWindow = document.querySelector('.efficency-call-div');
	if (!effWindow) return;		//没有创建窗口则取消
	effWindow.innerHTML = '';	//清空内容

	let shantenText = `${shantenCnt} 向听`;
	if(!shantenCnt) shantenText = '听牌';
	let showShanten = document.createElement('text');
	showShanten.textContent = shantenText;
	showShanten.style.textAlign = 'center';
	showShanten.style.width = '100%';
	showShanten.marginTop = '1%';
	effWindow.appendChild(showShanten);

	for (let ukeInfo of ukeireSet) {
		let pai = ukeInfo.pai;
		let tile = document.createElement('img');
		let leftText = ukeInfo.left.leftNor.toString().padStart(2, '0') + ':'
						+ ukeInfo.left.leftPure.toString().padStart(2, '0');
		let showLeftText = document.createElement('text');
		let wrapDiv = document.createElement('div');

		tile.src = `media/Regular_shortnames/${pai}.svg`;
		tile.className = 'tileImg effTile';
		showLeftText.style.fontSize = 'xx-small';
		showLeftText.style.lineHeight = '2';
		showLeftText.style.marginLeft = '2px';
		showLeftText.textContent = leftText;
		wrapDiv.style.display = 'flex';
		wrapDiv.style.marginLeft = '3%';
		wrapDiv.style.marginTop = '1%';	/*样式设定*/

		tile.addEventListener('mouseover', ()=> {	//添加浮窗
			let effHover = document.createElement('div');

			let hoverPai, cnt = 0;
			for (let i = 1; i <= 4; i++) {
				for (let j = 1; j <= 9; j++) {
					if (!ukeInfo.uke[i][j]) continue;
					switch(i) {
						case 1: hoverPai = j.toString() + 'm'; break;
						case 2: hoverPai = j.toString() + 'p'; break;
						case 3: hoverPai = j.toString() + 's'; break;
						case 4: hoverPai = j.toString() + 'z'; break;
					}
					cnt++;
					let hoverTile = document.createElement('img');
					hoverTile.src = `media/Regular_shortnames/${hoverPai}.svg`;
					hoverTile.className = 'tileImg hoverTile';
					effHover.appendChild(hoverTile);			//向浮窗添加进张
				}
			}

			let posParent = effWindow.getBoundingClientRect();
			let maxWidthcCnt = Math.min(13, cnt);
			let posX = ( posParent.left + posParent.right - (standardTileWidth + 4) * maxWidthcCnt ) / 2;
			let posY = posParent.top - Math.ceil(cnt / 13) * (standardTileHeight + 4) - 10;
			effHover.style.width = `${maxWidthcCnt * (standardTileWidth + 4)}px`;	//.tileImg样式中还有2px的padding
			effHover.style.left = `${posX}px`
			effHover.style.top = `${posY}px`
			effHover.className = 'eff-hover';
			document.body.appendChild(effHover);

			const deleteEffHover = ()=>{	//清除监听器和窗口
				effHover.remove();
				tile.removeEventListener('mouseout', deleteEffHover);
			};
			tile.addEventListener('mouseout', deleteEffHover);
		});

		wrapDiv.appendChild(tile);
		wrapDiv.appendChild(showLeftText);
		effWindow.appendChild(wrapDiv);
	}
}

function addEffWindow() {	//添加牌效窗口
	let buttonBox = document.querySelector('.buttonBox-div');
	let efficencySwitch = document.createElement('button');
	let effEnable = GM_getValue('effEnable', true);

	efficencySwitch.className = 'newSetting';
	buttonBox.appendChild(efficencySwitch);	//插入按钮

	if (!effEnable) {	//初始化按钮对应的状态
		efficencySwitch.textContent = '开启牌效计算';
	} else {
		efficencySwitch.textContent = '关闭牌效计算';
	}

	efficencySwitch.addEventListener('click', ()=>{
		effEnable = !effEnable;
		if (!effEnable) {
			efficencySwitch.textContent = '开启牌效计算';
		} else {
			efficencySwitch.textContent = '关闭牌效计算';
		}
		GM_setValue('effEnable', effEnable);	//存储状态
	});
	if (!effEnable) return;	//不开启牌效计算,则仅添加按钮

	let effDiv = document.createElement('div');
	let killerCallDiv = document.querySelector('.killer-call-div');
	let effCss = `
		.efficency-call-div {
			scale: 1.4;
			width: calc(var(--zoom)*245px);
			height: calc(var(--zoom)*110px);
			background: hsl(190deg 31.45% 58.49%);
			box-shadow: 5px 5px 6px 1px #f6f6f6;
			border-radius: 20px;
			margin-top: 34%;
			margin-left: 14%;
			display: flex;
			flex-wrap: wrap;
			align-content: flex-start;
		}

		.eff-hover {
			position: absolute;
			display: flex;
			flex-wrap: wrap;
			scale: 1.5;
			background: #00c0ff80;
			box-shadow: 0px 0px 5px 5px #0090ff;
			border-radius: 5px;
		}

		.effTile {
			filter: none;
			width: ${standardTileWidth}px;
			height: ${standardTileHeight}px;
			box-shadow: inset 0 0 2px #880000;
			margin-left: 3%;
		}

		.hoverTile {
			filter: none;
			width: ${standardTileWidth}px;
			height: ${standardTileHeight}px;
			box-shadow: inset 0 0 2px #880000;
		}`;
	/* 分别是牌效窗口、进张悬浮窗、牌效张、浮窗张
	   在css字符串内加注释,会有bug,很神奇吧js   */
	GM_addStyle(effCss);

	document.querySelector('.killer-call-img').style.display = 'none';	//关闭logo
	effDiv.addEventListener('click', ()=>{	//代替logo的快捷全屏
		if (!document.fullscreen) document.documentElement.requestFullscreen();
		else document.exitFullscreen();
	});
	effDiv.className = 'efficency-call-div';
	killerCallDiv.appendChild(effDiv);
}

//--------------------------------------------  Extra Functions should end here  --------------------------------------------//


(function() {
	//-------------------------------------------- Main Code should start here  --------------------------------------------//
    'use strict';

	//↓↓↓一系列按钮及其功能
    createButtonBox();
    backgroundSetting();
    tileBackSetting();
    logoSetting();
    optInfoSwitch();
    setMainAreaEnlarge();
	addEffWindow();
	let errTolerance = setCustomErrTolerance();
	//↑↑↑一系列按钮及其功能

	mortalAddStyle();			//应用CSS样式
    fullScreenEnlarge();		//全屏时缩放调整
    createStripsHoverWindow();	//创建绿条悬浮窗
    startMortalOptionObserver(errTolerance);	//mortal选项染色
    errCalculate(errTolerance);	//计算恶手率
	startDoraObserver();		//添加宝牌特效
	startEfficencyCalc();		//启动牌效计算

    //-------------------------------------------- Main Code should end here  --------------------------------------------//
})();