Better TankTrouble Chatbox

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

目前为 2023-12-26 提交的版本。查看 最新版本

// ==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/*
// @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');
});