Better TankTrouble Chatbox

Redesigned chatbox meant both for power users, and those who maybe just wants something new

当前为 2024-01-09 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        Better TankTrouble Chatbox
// @author      commander
// @description Redesigned chatbox meant both for power users, and those who maybe just wants something new
// @namespace   https://github.com/asger-finding
// @version     0.1.2
// @license     GPL-3.0
// @match       *://*.tanktrouble.com/*
// @exclude     *://classic.tanktrouble.com/
// @run-at      document-end
// @grant       GM_addStyle
// @grant       GM_getValue
// @grant       GM_setValue
// @require     https://update.greasyfork.org/scripts/482092/1301938/TankTrouble%20Development%20Library.js
// @noframes
// ==/UserScript==

GM_addStyle(`
#chat {
  /*Move it to the bottom left*/
  inset: calc(100% - 30px) auto auto 34px !important;
  /*Disable drop shadow filter*/
  filter: none;
  -webkit-filter: none;
}
/*Reverse the chat flow*/
#chat,
#chat .content,
#chat .body {
  display: flex;
  flex-direction: column-reverse;
}
#chat .status.button {
  transform: translate(7px, -18px);
  cursor: initial;
  z-index: 1;
}
#chat form {
  width: 200px;
  margin-left: 20px;
  background: #ececec;
}
#chat form[style*="repeating-linear-gradient"] {
  background: #d0d0d0 !important;
}
#chat:not(.open) form {
  display: none;
}
#chat textarea {
  left: 5px;
  transition: width 0s !important;
}
#chat .body {
  padding-right: 10px;
  border-radius: 3px;
  background: linear-gradient(225deg, #00000005 12px, #00000014 12px, #00000014 100%);
  margin-bottom: 7px;
  top: 0 !important;
  -webkit-mask-image: linear-gradient(225deg, #000000 11px, #00000000 12px, #00000000 100% ),
	linear-gradient(to top, #000000 70%, rgba(0, 0, 0, 0.11));
}
#chat .body .chatMessage svg {
  padding: 2px 4px 1px 4px;
  border-left: 2px dotted rgb(170, 170, 170);
  filter: drop-shadow(1px 1px 1px #00000022);
}
#chat .body.dragging {
  border: none !important;
  margin-left: 20px !important;
}
/*Rotate and align the handle to top-right*/
.handle.ui-resizable-ne[src*="resizeHandleBottomRight.png"] {
  width: 12px;
  height: 12px !important;
  transform: translateX(6px) rotate(-90deg);
  z-index: 2147483647;
  position: sticky;
  left: calc(100% - 7px);
  top: 0;
  order: 0;
  margin-bottom: auto !important;
}
body:has(#chat .body.ui-resizable-resizing) .ui-resizable-handle.handle.ui-resizable-ne {
  display: none !important;
}

/* Scrollbar */
#chat .body {
  scrollbar-gutter: stable;
  scrollbar-width: thin;
  scrollbar-color: rgb(170, 170, 170) transparent;
  align-items: end;
  direction: rtl;
  pointer-events: auto;
  overflow-x: hidden;
  overflow-y: hidden;
}
#chat .body:hover {
  overflow-y: scroll;
}
#chat .body .chatMessage {
  direction: ltr;
  margin-left: ${(/Chrome.*Safari/u).test(navigator.userAgent) ? '3px' : '5px'};
}
#chat .body::-webkit-scrollbar {
  width: 3px;
}
#chat .body::-webkit-scrollbar-track {
  background: transparent;
}
#chat .body::-webkit-scrollbar-thumb {
  background: rgb(170, 170, 170);
}
`);

/**
 * Reconfigure the chat handle to be dragging
 * from the south-east direction (down)
 * to the north-east direction (up)
 */
const changeHandleDirection = () => {
	const { resizable } = $.fn;

	// Use a regular function to keep context
	$.fn.resizable = function(...args) {
		const [config] = args;

		// Reassign the chat handle to be north-east facing
		if (config.handles) {
			const handle = config.handles.se;
			if (handle === TankTrouble.ChatBox.chatBodyResizeHandle) {
				handle.removeClass('ui-resizable-se')
					.addClass('ui-resizable-ne');

				config.handles.ne = handle;
				delete config.handles.se;

				// Set a taller chat maxHeight
				config.maxHeight = 650;
			}
		}

		return resizable.call(this, config);
	};
};

/**
 * Hook message render functions to disable jquery .show() animation
 * This fixes chat messages not showing up in the reversed chat order
 */
const fixChatRendering = () => {
	Loader.hookFunction(TankTrouble.ChatBox, '_renderChatMessage', (original, ...args) => {
		// Set animateHeight to false
		args[9] = false;
		original(...args);
	});

	Loader.hookFunction(TankTrouble.ChatBox, '_renderSystemMessage', (original, ...args) => {
		// Set animateHeight to false
		args[3] = false;
		original(...args);
	});
};

/**
 * Prevent TankTrouble from clearing the chat when the client disconnects
 * Print message to chat when client switches server to separate conversations
 */
const preventChatClear = () => {
	Loader.hookFunction(TankTrouble.ChatBox, '_clearChat', (original, ...args) => {
		const isUnconnected = ClientManager.getClient().getState() === TTClient.STATES.UNCONNECTED;

		// Void the call if the client is unconnected
		// when the function is invoked
		if (isUnconnected) return null;

		return original(...args);
	});

	Loader.hookFunction(TankTrouble.ChatBox, '_updateStatusMessageAndAvailability', (original, ...args) => {
		const [systemMessageText, guestPlayerIds] = args;

		// Check for welcome message
		// If true, print a system message
		if (systemMessageText === 'Welcome to TankTrouble Comms § § ') {
			const newServer = ClientManager.getAvailableServers()[ClientManager.multiplayerServerId];
			return original(`Connected to ${ newServer.name } ${ guestPlayerIds.length ? '§ ' : '' }`, guestPlayerIds);
		}
		return original(...args);
	});
};

/**
 * Write the chat savestate to storage and return
 * @returns Promise for last savestate
 */
const startChatSavestate = () => {
	Loader.hookFunction(TankTrouble.ChatBox, 'open', (original, ...args) => {
		GM_setValue('chat-open', true);
		original(...args);
	});
	Loader.hookFunction(TankTrouble.ChatBox, 'close', (original, ...args) => {
		GM_setValue('chat-open', false);
		original(...args);
	});

	// Get savestate and default to chat being open
	return GM_getValue('chat-open', true);
};

changeHandleDirection();
fixChatRendering();
Loader.whenContentInitialized().then(async() => {
	preventChatClear();

	const shouldChatOpen = await startChatSavestate();
	if (shouldChatOpen) TankTrouble.ChatBox.open();

	// Get the plain nodes for better performance
	// eslint-disable-next-line prefer-destructuring
	const chatBody = TankTrouble.ChatBox.chatBody[0];
	// eslint-disable-next-line prefer-destructuring
	const chatForm = TankTrouble.ChatBox.chatForm[0];
	// eslint-disable-next-line prefer-destructuring
	const chatInput = TankTrouble.ChatBox.chatInput[0];

	// Create a mutation observer that looks for changes in the chatBody's attributes (namely width)
	new MutationObserver(() => {
		const width = Number(chatBody.offsetWidth || 220);

		chatForm.style.width = `${width}px`;
		chatInput.style.width = `${width - 12}px`;
	}).observe(chatBody, {
		attributes: true,
		characterData: false
	});

	chatForm.style.width = '220px';
	chatInput.style.width = `${chatForm.offsetWidth - 12}px`;

	// Allow more characters in the chat input
	chatInput.setAttribute('maxlength', '255');
});