對 Minecraft 的 Dynmap 做一些小美化

AurLemon發表於2024-09-02

介紹

Dynmap 是 Minecraft 中以網頁 Web 形式呈現地圖的模組,和 BlueMap 等類似。我自己倒是 Dynmap 用多了感覺更習慣一些就一直用下去了,雖然如今 BlueMap 之類的確實更先進。

LiveAtlas

LiveAtlas 是 Dynmap 的第三方皮膚擴充套件,下載好後直接匯入 dynmap/web 資料夾覆蓋即可。

配置


這是下載後的檔案結構,從 repo 來看這個專案用的 Yarn + Vue,開啟 index.html 看看。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <link rel="preload" href="https://fonts.gstatic.com/s/raleway/v22/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVvaorCIPrE.woff2" as="font" crossorigin="anonymous">
    <link rel="preload" href="https://fonts.gstatic.com/s/raleway/v22/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVsEpbCIPrE.woff2" as="font" crossorigin="anonymous">

    <meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">

	<meta name="apple-mobile-web-app-capable" content="yes" />
	<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />

    <meta name="theme-color" content="#222222">
	<link rel="manifest" href="./live-atlas/favicons/site.webmanifest">
	<link rel="icon" href="./live-atlas/favicons/favicon.svg">
    <link rel=”mask-icon” href="./live-atlas/favicons/mask.svg" color="#cccccc">
    <link rel="apple-touch-icon" sizes="180x180" href="./live-atlas/favicons/apple-touch-icon.png">

    <meta name="keywords" content="minecraft, map, dynamic, liveatlas" />
	<meta name="description" content="Minecraft Dynamic Map" />

    <title>Minecraft Dynamic Map - LiveAtlas</title>

    <!-- Remove this if you are using the servers config below -->
    <script src="./standalone/config.js"></script>

    <script>
        window.liveAtlasConfig = {
        	// By default LiveAtlas looks for a dynmap standalone/config.js file
            // This configuration can be used instead to support Pl3xmap and Squaremap installations as well as multiple servers (external webserver required)
            // To configure multiple servers, see https://github.com/JLyne/LiveAtlas/wiki/Configuring-Multiple-Servers.

            // Example Squaremap internal webserver configuration
            // servers: {
            //   squaremap: {
            // 	   squaremap: window.location.pathname
            //   },
            // },

            // Example Pl3xmap internal webserver configuration
            // servers: {
            //   pl3xmap: {
            // 	   pl3xmap: window.location.pathname
            //   },
            // },

            // Example Overviewer configuration
            // servers: {
            //   overviewer: {
            // 	   overviewer: window.location.pathname
            //   },
            // },

            // Example Dynmap internal webserver configuration without using standalone/config.js
            // servers: {
            //   dynmap: {
            //     dynmap: {
            //       configuration: 'standalone/dynmap_config.json?_={timestamp}',
            //       update: 'standalone/dynmap_{world}.json?_={timestamp}',
            //       sendmessage: 'standalone/sendmessage.php',
            //       login: 'standalone/login.php',
            //       register: 'standalone/register.php',
            //       tiles: 'tiles/',
            //       markers: 'tiles/'
            //     }
            //   },
            // },

            // Example multiple Dynmap servers on external webserver configuration
            // servers: {
            //   creative: {
            //     label: 'Creative',
            //     dynmap: {
            //         configuration: 'http://dynmap.local/standalone/creative/MySQL_configuration.php',
            //         update: 'http://dynmap.local/standalone/creative/MySQL_update.php?world={world}&ts={timestamp}',
            //         sendmessage: 'http://dynmap.local/standalone/creative/MySQL_sendmessage.php',
            //         login: 'http://dynmap.local/standalone/creative/MySQL_login.php',
            //         register: 'http://dynmap.local/standalone/creative/MySQL_register.php',
            //         tiles: 'http://dynmap.local/standalone/creative/MySQL_tiles.php?tile=',
            //         markers: 'http://dynmap.local/standalone/creative/MySQL_markers.php?marker='
            //     }
            //   },
            //   survival: {
            //     label: 'Survival',
            //     dynmap: {
            //         configuration: 'http://dynmap.local/standalone/survival/MySQL_configuration.php',
            //         update: 'http://dynmap.local/standalone/survival/MySQL_update.php?world={world}&ts={timestamp}',
            //         sendmessage: 'http://dynmap.local/standalone/survival/MySQL_sendmessage.php',
            //         login: 'http://dynmap.local/standalone/survival/MySQL_login.php',
            //         register: 'http://dynmap.local/standalone/survival/MySQL_register.php',
            //         tiles: 'http://dynmap.local/standalone/survival/MySQL_tiles.php?tile=',
            //         markers: 'http://dynmap.local/standalone/survival/MySQL_markers.php?marker='
			//     }
            //   },
            // },

            // These messages are used throughout LiveAtlas and can be translated here
            // If a message you want to translate isn't here, it is likely controlled by dynmap itself
            // see https://github.com/webbukkit/dynmap/wiki/Configuration.txt
            messages: {
            	chatNoMessages: 'No chat messages yet...',
                chatTitle: 'Chat',
                chatLogin: 'Please login to send chat messages',
                chatSend: 'Send',
                chatPlaceholder: 'Type your chat message here...',
                chatErrorUnknown: 'Unexpected error while sending chat message',
                chatErrorDisabled: 'Chat is not enabled',
            	serversHeading: 'Servers',
                markersHeading: 'Markers',
                markersSearchPlaceholder: 'Search markers...',
                markersSkeleton: 'No markers exist for the current world',
                markersSetSkeleton: 'This marker set is empty',
                markersSearchSkeleton: 'No matching markers found',
                markersUnnamed: '(Unnamed marker)',
                worldsSkeleton: 'No maps have been configured',
                playersSkeleton: 'No players are currently online',
                playersTitle: 'Click to center on player\nDouble-click to follow player',
                playersTitleHidden: 'This player is currently hidden from the map\nDouble-click to follow player when they become visible',
                playersTitleOtherWorld: 'This player is in another world.\nClick to center on player\nDouble-click to follow player',
                playersSearchPlaceholder: 'Search players...',
                playersSearchSkeleton: 'No matching players found',
                followingHeading: 'Following',
                followingUnfollow: 'Unfollow',
                followingTitleUnfollow: 'Stop following this player',
                followingHidden: 'Currently hidden',
                linkTitle: 'Copy link to current location',
                loadingTitle: 'Loading...',
                locationRegion: 'Region',
                locationChunk: 'Chunk',
                contextMenuCopyLink: 'Copy link to here',
                contextMenuCenterHere: 'Center here',
                toggleTitle: 'Click to toggle this section',
                mapTitle: 'Map - Use the arrow keys to pan the map',
                layersTitle: 'Layers',
                copyToClipboardSuccess: 'Copied to clipboard',
                copyToClipboardError: 'Unable to copy to clipboard',

                loginTitle: 'Login/Register',
                loginHeading: 'Existing User',
                loginUsernameLabel: 'Username',
                loginPasswordLabel: 'Password',
                loginSubmit: 'Login',
                loginErrorUnknown: 'Unexpected error while logging in',
                loginErrorDisabled: 'Logging in is disabled on this server',
                loginErrorIncorrect: 'Incorrect username or password',
                loginSuccess: 'Logged in successfully',

                registerHeading: 'New User',
                registerDescription: `Enter your username and password, along with your registration code.

                        You can get a registration code by running /dynmap webregister in-game.`,
                registerConfirmPasswordLabel: 'Confirm Password',
                registerCodeLabel: 'Registration Code',
                registerSubmit: 'Register',
                registerErrorUnknown: 'Unexpected error during registration',
                registerErrorDisabled: 'Registration is disabled on this server',
                registerErrorVerifyFailed: 'The entered passwords do not match',
                registerErrorIncorrect: 'Registration failed, please check the entered details are correct',

                logoutTitle: 'Logout',
                logoutErrorUnknown: 'Unexpected error while logging out',
                logoutSuccess: 'Logged out successfully',

                closeTitle: 'Close',
                showMore: 'Show more'
            },

            ui: {
            	// If true, player markers will always be displayed in front of other marker types
            	playersAboveMarkers: true,

                // Whether to enable the player list search box
                playersSearch: true,

                // Use more compact pre-2.0 player marker style
                compactPlayerMarkers: false,

                // Disable the map right click menu
                disableContextMenu: false,

                // Disable the markers button and list
                disableMarkerUI: false,

                // Custom URL to redirect to when logging in is required
                // This URL will need to handle the login process and redirect users back to LiveAtlas
                customLoginUrl: null
            },

            // Config version. Do not modify.
            version: 1
        };
    </script>

    <style>
        /* Theme colours */
        :root {
            --background-base: #222222; /* Foreground UI elements */
            --background-dark: #121212; /* Body/Splash screen/Shadows */
            --background-light: #363636; /* Scrollbars/inputs */
            --background-error: #771616; /* Errors */
            --background-marker: var(--background-dark); /* Map markers */

            --background-disabled: #555555; /* Disabled controls */
            --background-hover: #363636; /* :hovered buttons/menu items */
            --background-active: #6d6d6d; /* Button :active */
            --background-selected: #BDBDBD; /* Selected buttons/menu items */

            --outline-focus: #eeeeee; /* :focus outline */

            --border-radius: 0.5rem;
            --border-color: #333333; /* Control borders */
            --border-error: #cc0e0e;
            --box-shadow: 2px 2px 0px #111111;

            --text-base: rgba(255, 255, 255, 0.7); /* Normal text */
            --text-emphasis: rgba(255, 255, 255, 0.87); /* Chat messages/:focus inputs */
            --text-subtle: rgba(255, 255, 255, 0.5); /* Skeletons/secondary text */
            --text-disabled: var(--text-subtle); /* Disabled controls */
            --text-marker: var(--text-base); /* Map markers */

            --text-hover: var(--text-base); /* Text in :hover buttons */
            --text-active: var(--text-base); /* Text in :active buttons */
            --text-selected: var(--background-base); /* Text in selected buttons */
            --text-shadow: 0.1rem 0.1rem #000000; /* Text in selected buttons */

            --text-night: #ddffff; /* Clock time at night */
            --text-day: #ffdd33; /* Clock time in day */
        }

        @keyframes fade {
            from {
                opacity: 0;
            }

            to {
                opacity: 1;
            }
        }

        * {
            scrollbar-width: thin;
            scrollbar-color: var(--background-light) transparent;
        }

        *:hover, *:focus-within {
            scrollbar-color: var(--background-hover) transparent;
        }

        *::-webkit-scrollbar {
            width: 0.7rem;
        }

        *::-webkit-scrollbar-track {
            background: transparent;
        }

        *::-webkit-scrollbar-thumb {
            background-color: var(--background-light);
            border: 2px solid #000000;
            border-radius: 2rem;
            transition: background 1s ease-in;
            padding-right: 0.2rem;
        }

        *:hover::-webkit-scrollbar-thumb, *:focus-within::-webkit-scrollbar-thumb {
          background-color: var(--background-hover);
        }

        *::-webkit-scrollbar-button {
            display: none;
        }

        html, body {
            background-color: var(--background-dark);
            height: 100%;
            width: 100%;
            margin: 0;
            padding: 0;
	        overscroll-behavior: none;
        }

        html {
            font-size: 62.5%;
        }

        body {
            font-family: Raleway,
            system-ui,
            -apple-system,
            'Segoe UI',
            Roboto,
            Helvetica,
            Arial,
            sans-serif,
            'Apple Color Emoji',
            'Segoe UI Emoji';
            color: var(--text-base);
            text-shadow: var(--text-shadow);
            letter-spacing: 0.02rem;
        }

        noscript {
            color: var(--text-base);
            font-size: 1rem;
            font-family: sans-serif;
            text-align: center;
            line-height: 1.25;
        }

        #splash, noscript {
            position: fixed;
            top: 0;
            left: 0;
            bottom: 0;
            right: 0;
            transition: 0.3s opacity linear;
            z-index: 100;
            background-color: var(--background-dark);
            cursor: wait;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            font-size: 1.6rem;
            padding: 4rem;
        }

        #splash[hidden] {
            display: none;
        }

        #splash__spinner {
            margin-top: 4rem;
            animation: fade 0.5s linear 1s;
            animation-fill-mode: both;
        }

        #splash__error {
            margin-top: 2rem;
            transition: opacity 0.5s ease-in;
            display: flex;
            flex-direction: column;
            text-align: center;
            max-width: 60rem;
        }

        #splash__error-message {
            font-family: monospace;
            background-color: var(--background-error);
            padding: 1rem 1.5rem;
            border-radius: var(--border-radius);
            margin-bottom: 1rem;
        }

        #splash__error[aria-hidden=true] {
            opacity: 0;
        }

        #app {
            font-size: 1.6rem;
            height: 100%;
        }
    </style>
    <script type="module" crossorigin src="./live-atlas/assets/index.a82d2095.js"></script>
    <link rel="modulepreload" href="./live-atlas/assets/vendor.d0ab50b1.js">
    <link rel="stylesheet" href="./live-atlas/assets/index.1de34b3a.css">
  </head>
  <body>
    <div id="splash">
        <svg id="splash__logo" width="200" height="200" viewBox="0 0 268.83 266.53" aria-hidden="true" fill="rgba(255, 255, 255, 0.7)">
            <path d="M5.235.662C2.153.606 0 2.945 0 6.912v216.577c0 5.288 3.828 11.41 8.582 13.725l57.835 28.165c4.755 2.316 8.582-.078 8.582-5.367V43.434c0-5.288-3.827-11.41-8.582-13.727L8.582 1.544C7.394.965 6.262.681 5.235.662zm32.283 135.96c14.394 0 26.062 11.669 26.062 26.063 0 14.394-24.167 59.55-26.062 57.654-1.854 1.854-26.063-43.26-26.063-57.654 0-14.394 11.669-26.063 26.063-26.063zM202.388 1.013l57.833 28.165c4.755 2.315 8.583 8.437 8.583 13.726v216.58c0 5.29-3.828 7.683-8.583 5.367l-57.833-28.164c-4.755-2.316-8.583-8.438-8.583-13.727V6.38c0-5.289 3.828-7.682 8.583-5.367zM172.012.39c-1.051-.035-2.209.191-3.426.709l-68.342 29.053c-4.867 2.07-8.786 7.993-8.786 13.282V260.01c0 5.29 3.919 7.88 8.786 5.811l68.342-29.053c4.867-2.07 8.786-7.991 8.786-13.28V6.91c0-3.967-2.204-6.417-5.36-6.521zm-36.949 41.216c14.394 0 26.063 11.668 26.063 26.062 0 14.394-24.168 59.55-26.063 57.655C133.209 127.177 109 82.063 109 67.668c0-14.394 11.669-26.062 26.063-26.062z"/>
            <path d="M48.573 162.689a11.056 11.056 0 0 1-11.056 11.056 11.056 11.056 0 0 1-11.056-11.056 11.056 11.056 0 0 1 11.056-11.056 11.056 11.056 0 0 1 11.056 11.056zM146.12 67.669a11.056 11.056 0 0 1-11.057 11.056 11.056 11.056 0 0 1-11.056-11.056 11.056 11.056 0 0 1 11.056-11.056 11.056 11.056 0 0 1 11.056 11.056z"/>
        </svg>

        <svg id="splash__spinner" width="38" height="38" viewBox="0 0 38 38" stroke="#fff" aria-label="LiveAtlas is loading">
            <g transform="translate(1 1)" stroke-width="2" fill="none">
                <circle stroke-opacity=".5" cx="18" cy="18" r="18"/>
                <path d="M36 18c0-9.94-8.06-18-18-18">
                    <animateTransform attributeName="transform" type="rotate" from="0 18 18" to="360 18 18" dur="1s" repeatCount="indefinite"/>
                </path>
            </g>
        </svg>

        <div id="splash__error" aria-hidden="true">
            <span id="splash__error-message" role="alert"></span>
            <span id="splash__error-retry" aria-live="polite"></span>
        </div>
    </div>

    <noscript>
        <strong>LiveAtlas requires JavaScript to work.<br />Please enable it to continue.</strong>
    </noscript>

    <main id="app" aria-hidden="true"></main>
    

    <script>
        window.addEventListener('load', () => {
            if(!window.liveAtlasLoaded) {
                document.getElementById('splash__error').setAttribute('aria-hidden', 'false');
                document.getElementById('splash__error-message').innerText = 'Required LiveAtlas files are missing or failed to load.\nPlease reinstall LiveAtlas.';
                document.getElementById('splash__spinner').style.visibility = 'hidden';
            }
        });
    </script>
  </body>
</html>

index.html 裡面的內容都預留好了配置口,比如翻譯資訊、CSS,需要的話直接覆蓋修改即可。以下是筆者自己的翻譯資訊:

            messages: {
            	chatNoMessages: '沒人發資訊喵。',
                chatTitle: '聊天',
                chatLogin: '只有登入了才能發訊息喵~',
                chatSend: '傳送',
                chatPlaceholder: '聊天資訊輸在這',
                chatErrorUnknown: '不知道為什麼訊息發不出去',
                chatErrorDisabled: '聊天未啟用',
            	serversHeading: '伺服器',
                markersHeading: '標記',
                markersSearchPlaceholder: '搜尋標記',
                markersSkeleton: '當前世界沒有標記點存在',
                markersSetSkeleton: '這個標記點是空的',
                markersSearchSkeleton: '無匹配結果',
                markersUnnamed: '(未命名標記)',
                worldsSkeleton: '沒有地圖被配置',
                playersSkeleton: '現在沒人線上喵!',
                playersTitle: '單擊以定位玩家\n雙擊以跟隨玩家',
                playersTitleHidden: '該玩家在當前地圖上隱藏\n雙擊可在玩家可見時跟隨',
                playersTitleOtherWorld: '該玩家在另一個世界\n點選以定位到玩家\n雙擊以跟隨玩家',
                playersSearchPlaceholder: '搜尋玩家…',
                playersSearchSkeleton: '找不到匹配的玩家',
                followingHeading: '跟隨中',
                followingUnfollow: '取消跟隨',
                followingTitleUnfollow: '停止跟隨該玩家',
                followingHidden: '當前隱藏',
                linkTitle: '獲取當前座標的網址',
                loadingTitle: '載入中…',
                locationRegion: '區域',
                locationChunk: '區塊',
                contextMenuCopyLink: '複製連結',
                contextMenuCenterHere: '定位到此處',
                toggleTitle: '點選以切換此部分',
                mapTitle: '地圖 - 使用方向鍵移動地圖',
                layersTitle: '圖層',
                copyToClipboardSuccess: '已複製到剪貼簿',
                copyToClipboardError: '無法複製到剪貼簿',

                loginTitle: '登入',
                loginHeading: '已有賬戶登入',
                loginUsernameLabel: '使用者名稱',
                loginPasswordLabel: '密碼',
                loginSubmit: '登入',
                loginErrorUnknown: '登入時出現未知錯誤',
                loginErrorDisabled: '登入已禁用',
                loginErrorIncorrect: '使用者名稱或密碼錯誤',
                loginSuccess: '登入成功喵!!',

                registerHeading: '新使用者註冊',
                registerDescription: `分別輸入你的使用者名稱、密碼和註冊碼喵!

                        註冊碼需要在伺服器中獲取。在遊戲中輸入/dynmap webregister可以獲得註冊碼。`,
                registerConfirmPasswordLabel: '確認密碼',
                registerCodeLabel: '註冊碼',
                registerSubmit: '註冊',
                registerErrorUnknown: '註冊時出現未知錯誤',
                registerErrorDisabled: '註冊已禁用',
                registerErrorVerifyFailed: '前後輸入的密碼不一致,笨蛋。',
                registerErrorIncorrect: '註冊失敗了,檢查下輸入內容的是否有誤喵',

                logoutTitle: '登出',
                logoutErrorUnknown: '登出時出現未知錯誤',
                logoutSuccess: '已登出',

                closeTitle: '關閉',
                showMore: '顯示更多'
            }

CSS 也是按需修改,筆者還是比較喜歡淺色的()所以自己的幾個站點風格都比較接近,感興趣的可以去 repo 看程式碼。

CDN

筆者給衛星地圖配置的域名是透過 CDN 的,服務商是騰訊雲,反代總擔心效能和頻寬都不夠,而且每次這麼多圖片請求壓力也比較大,考慮到衛星地圖看的人不多就上了 CDN。騰訊雲國內流量是 19 元 100 G,用個半年沒啥問題,每天頂多 100 M()

相關文章