Greasy Fork

来自缓存

Greasy Fork is available in English.

Sun:Raise - zombs.io

weeb strikes again

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

// ==UserScript==
// @name         Sun:Raise - zombs.io
// @namespace    http://tampermonkey.net/
// @version      0.90
// @description  weeb strikes again
// @author       rdm
// @match        *://zombs.io/
// @icon         https://media.discordapp.net/attachments/854376044522242059/1067302959765016656/latest.png
// @grant        none
// @license      GNU GPLv3
// @noframes
// ==/UserScript==

/* @Credit
 * i have to give credit where credit is due,
 * most of the images used in this mod, including:
 *   + intro background (#hud-intro::before),
 *   + theme-character (.hud-intro-character)
 * is taken from Arcaea, this mod is themed to look
 * like Arcaea's UI.
 *
 * visit Arcaea at https://arcaea.lowiro.com/ and
 * check out the game on Google's Play Store and
 * Apple's App Store.
 */

// day 1 (0.1) : intro styles done
// day 2 (0.2) : menu styles done
// day 3 (0.3) : ahrc, rb, autoup done + polishing
// day 4 (0.4) : entering unfamiliar territory with scanner
// day 5 (0.41): polishing
// day ? (0.42): beta release
// v0.5        : added resizeable wall blocks
// v0.6        : auto heal & ??????????????????
// v0.7        : unlimited slots base saver
// v0.71       : shop shortcuts
// v0.73       : shop shortcut fixes & added entity preserver
// v0.80       : raid defending tools (in Misc.) and Grouping Grid (courtesy of ABCxFF) added
// v0.90       : multi-auto-heal feature based on Trollers' idea, fix assets (again) & one minor fix

const fontAwesome = document.createElement("script");
fontAwesome.type = "text/javascript";
fontAwesome.src = "https://kit.fontawesome.com/1c239b2e80.js";
document.head.appendChild(fontAwesome);

document.querySelectorAll('.ad-unit, #hud-intro > div.hud-intro-wrapper > h1, #hud-intro > div.hud-intro-footer > a:nth-child(2), #hud-intro > div.hud-intro-footer > a:nth-child(4), #hud-menu-shop > div.hud-shop-grid > a:nth-child(10), #hud-intro > div.hud-intro-wrapper > h2, .hud-intro-left, .hud-intro-guide, .hud-intro > .hud-intro-stone, .hud-intro >.hud-intro-tree, .hud-intro-youtuber, .hud-intro-more-games, .hud-intro-social, .hud-respawn-corner-bottom-left, .hud-respawn-twitter-btn, .hud-respawn-facebook-btn').forEach(el => el.remove());

const css = `
/* @Media */
@media only screen and (min-width: 1200px) {
    #hud-intro {
        zoom: 110%;
    }
}
/* @Root */
:root {
    --menu-background: hsl(29deg 69% 34% / 40%);
    --normal-btn: hsl(44deg 58% 60%);
    --light-hover-btn: hsl(44deg 88% 70%);
}
::-webkit-scrollbar {
	width: 12px;
    height: 0px;
    border-radius: 10px;
	background-color: rgba(0, 0, 0, 0);
}
::-webkit-scrollbar-thumb {
	border-radius: 10px;
	background-image: url(https://cdn.glitch.global/ba7f4151-2a49-416a-985b-56301606ae3d/whiteslider.png?v=1714878503407);
}

/* @Keyframes */
@keyframes parallax-bg {
    0%, 100% {
        background-position: top left;
    }
    50% {
        background-position: bottom right;
    }
}
@keyframes bounce {
    50%   { background-position-y: -10px; }
    100%  { background-position-y: 0px; }
}
@keyframes slide-in-left {
    0% {
        left: -100%;
    }
    100% {
        left: 0px;
    }
}
@keyframes fade-in {
    0% {
        opacity: 0;
    }
    100% {
        opacity: 1;
    }
}

/* @ButtonStyles */
.no-bg {
    display: inline-block;
    height: 40px;
    line-height: 40px;
    padding: 0 20px;
    color: #eee;
    background: none;
    box-shadow: none;
    border: none;
    cursor: pointer;
}
.underline-white {
    border-bottom: 3px solid #eee;
}
.underline-red {
    border-bottom: 3px solid red;
}
.hud-settings-options .underline-red {
    border-bottom: 3px solid red;
}
.btn-important {
    display: inline-block;
    height: 40px;
    line-height: 40px;
    padding: 0 20px;
    color: var(--normal-btn);
    background: none;
    border: none;
    cursor: pointer;
    transition: all 0.15s ease-in-out;
    border-bottom: 3px solid var(--normal-btn);
    box-shadow: inset 0 -7px 9px -7px var(--normal-btn);
}
.btn-important:hover {
    filter: saturate(200%);
}
/* @IntroStyles */
.hud-intro::before {
    background-image: url('https://cdn.glitch.global/ba7f4151-2a49-416a-985b-56301606ae3d/Omatsuri%20Light.webp?v=1714878746625');
    background-size: 200%;
    background-position: top;
    filter: blur(10px);
    animation-name: parallax-bg;
    animation-duration: 400s;
    animation-timing-function: linear;
    animation-iteration-count: infinite;
}
.hud-intro::after {
    background: rgba(0, 0, 0, 0.2);
}
#hud-intro > div.hud-intro-corner-top-left {
    top: 0px;
    left: 0px;
    /* display: none; */
}
.hud-intro-stick {
    display: block;
    position: relative;
    width: 30vw;
    background: #eee;
    height: 30px;
    box-shadow: 0px 5px 10px 2px rgb(0 0 0 / 70%);
    font-weight: 700;
    padding: 4px;
}
.hud-intro-stick::before {
    display: block;
    position: absolute;
    content: "";
    right: -12px;
    top: 3px;
    height: 22.5px;
    width: 22.5px;
    transform: rotate(45deg);
    background: #eee;
}
.hud-intro-stick::after {
    display: block;
    position: absolute;
    content: "";
    right: -11px;
    top: 4px;
    height: 18px;
    width: 18px;
    transform: rotate(45deg);
    background: black;
    border: 2px double #eee;
}
.hud-intro-scan-data {
    width: 30vw;
    min-width: 100%;
    height: fit-content;
    background: rgba(0, 0, 0, 0.4);
    border-bottom-right-radius: 4px;
    padding: 10px;
    zoom: 90%;
    color: #eee;
    max-height: 400px;
    overflow: scroll;
}
.hud-intro-scan-data .hud-intro-scan-data-player {
    position: relative;
    display: block;
    margin: 0 0 8px;
    padding: 0 90px 0 40px;
    height: 20px;
    line-height: 20px;
    font-size: 13px;
    font-family: 'Open Sans', sans-serif;
}
.hud-intro-scan-data .hud-intro-scan-data-player:last-child {
    margin-bottom: 0;
}
.hud-intro-scan-data .hud-intro-scan-data-player.is-header * {
    color: rgba(255, 255, 255, 0.6) !important;
}
.hud-intro-scan-data .hud-intro-scan-data-player .player-rank {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    color: rgba(255, 255, 255, 0.6);
}
.hud-intro-scan-data .hud-intro-scan-data-player .player-name {
    display: block;
    height: 20px;
    line-height: 20px;
}
.hud-intro-scan-data .hud-intro-scan-data-player .player-score {
    position: absolute;
    top: 0;
    bottom: 0;
    right: 50px;
    color: rgba(255, 255, 255, 1);
}
.hud-intro-scan-data .hud-intro-scan-data-player .player-wave {
    position: absolute;
    top: 0;
    bottom: 0;
    right: 0;
    color: rgba(255, 255, 255, 0.7);
}
.hud-intro-scan-data hr {
    border: 1px dashed #eee;
    margin: 20px 0;
}
#hud-intro > div.hud-intro-corner-top-right {
    background: rgba(0, 0, 0, 0.3);
    padding: 15px;
    top: 0px;
    right: 0px;
    border-bottom-left-radius: 4px;
}
#hud-intro > div.hud-intro-corner-top-right::before {
    display: block;
    position: absolute;
    content: "";
    width: 100%;
    height: 2px;
    background: linear-gradient(to right, transparent 5%, rgba(255, 255, 255, 0.9));
    right: 10px;
    bottom: 0px;
}
#hud-intro > div.hud-intro-corner-top-right::after {
    display: block;
    position: absolute;
    content: "";
    width: 15px;
    height: 15px;
    transform: rotate(45deg);
    margin: 10px;
    border: 4px solid white;
    background: black;
    right: 0px;
    bottom: -20px;
    box-shadow: none;
    border-style: double;
}
.hud-intro .hud-intro-footer {
    left: 20px;
    right: unset;
    text-shadow: 0 1px 3px rgb(0 0 0 / 50%);
    z-index: 20 !important;
}
#hud-intro > div.hud-intro-wrapper > div {
    z-index: 31;
    margin: 50px 30vw 0 0;
}
#hud-intro > div.hud-intro-wrapper > div > div {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    width: 380px;
    padding-bottom: 0px;
    padding-top: 37.5px;
}
.hud-intro-server-container {
    display: flex;
    flex-direction: row;
    justify-content: space-around;
    margin-bottom: 10px;
}
.hud-intro-server-decorator {
    width: 0px;
    content: "";
    height: 0px;
    border-bottom: 50px solid #eee;
    border-right: 50px solid transparent;
    position: relative;
    bottom: 0px;
}
#hud-intro > div.hud-intro-wrapper > div > div > div > div > select {
    border-bottom-right-radius: 0px;
    border-top-right-radius: 0px;
}
.hud-intro-name-container {
    display: flex;
    flex-direction: row;
    justify-content: space-around;
    margin-bottom: 5px;
}
.hud-intro-name-decorator {
    width: 0px;
    content: "";
    height: 0px;
    border-top: 50px solid #eee;
    border-right: 50px solid transparent;
    position: relative;
    bottom: 0px;
}
.hud-intro-name-decorator::after {
    content: "";
    position: absolute;
    bottom: 54px;
    left: -200px;
    width: 120px;
    border-bottom: 3px dashed #eee;
}
#hud-intro > div.hud-intro-wrapper > div > div > div > div > input {
    border-bottom-right-radius: 0px;
    border-top-right-radius: 0px;
    padding: 8px 30px 8px 14px;
}
#saved-names {
    position: absolute;
    top: -34px;
    left: -20px;
    box-shadow: none;
    border: none;
    background: none;
    width: 20px;
}
#scan-btn {
    cursor: pointer;
    width: 120px;
    position: relative;
    right: -160px;
    top: -20px;
    height: 120px;
    margin: -60px;
    filter: drop-shadow(7.5px 2.5px 0px rgba(0, 0, 0, 0.5));
}
.hud-intro .hud-intro-form .hud-intro-play {
    width: 150px;
    height: 150px;
    transform: rotate(45deg);
    border: 5px solid white;
    margin-bottom: -20px;
    margin-left: -7.5px;
    margin-right: -150px;
    margin-top: -17.5px;
    background-image: url(https://cdn.glitch.global/ba7f4151-2a49-416a-985b-56301606ae3d/Story_crimsonsolace.webp?v=1714878769815);
    background-size: 150%;
    padding: 0 0 0 0;
    background-position-y: center;
    background-position-x: center;
    transition: all 0.15s ease-in-out;
    z-index: 21 !important;
    position: relative;
    filter: drop-shadow(10px -5px 0px rgba(0, 0, 0, 0.4));
}
.hud-intro .hud-intro-form .hud-intro-play:hover {
    filter: drop-shadow(10px -5px 0px rgba(0, 0, 0, 0.4)) saturate(200%);
    box-shadow: inset 0 0 20px 3px rgb(0 0 0 / 45%);
}
#playspan {
    position: relative;
    font-weight: 900;
    z-index: 22;
    font-size: xx-large;
    text-shadow: 1px 1px 3px black;
    cursor: pointer;
    font-family: 'Open Sans';
    pointer-events: none;
    top: -75px;
    left: 65px;
    margin: -20px -30px;
}
.hud-intro .hud-intro-form .hud-intro-play::after {
    display: block;
    content: "";
    position: absolute;
    top: -15px;
    left: -15px;
    width: 40px;
    height: 40px;
    border-top: 5px solid white;
    border-left: 5px solid white;
    border-top-left-radius: 5px;
    pointer-events: none;
}
#hud-intro > div.hud-intro-wrapper > div > div > label {
    margin: -30px 0 0 0;
}
.hud-intro .hud-intro-decoration {
    display: flex;
    position: absolute;
    bottom: 0px;
    width: 100%;
    pointer-events: none;
}
.hud-intro .hud-intro-decoration > * {
    pointer-events: none;
}
.hud-intro-decoration .hud-intro-left-triangle {
    height: 300px;
    width: 600px;
}
.hud-intro-decoration .hud-intro-left-triangle::before {
    width: 100%;
    height: 100%;
    transform: rotate(30deg);
    content: '';
    display: block;
    position: absolute;
    left: -40%;
    bottom: -50%;
    background-image: linear-gradient( 45deg, rgba(60, 50, 93, 0.8) 25%, rgba(60, 50, 93, 0.7) 25%, rgba(60, 50, 93, 0.7) 50%, rgba(60, 50, 93, 0.8) 50%, rgba(60, 50, 93, 0.8) 75%, rgba(60, 50, 93, 0.7) 75%, rgba(60, 50, 93, 0.7) );
    background-size: 10px 10px;
}
.hud-intro-decoration .hud-intro-right-triangle {
    height: 700px;
    width: 700px;
}
.hud-intro-decoration .hud-intro-right-triangle::before {
    width: 100%;
    height: 100%;
    transform: rotate(-45deg);
    content: '';
    display: block;
    position: absolute;
    right: -50%;
    bottom: -50%;
    color: white;
    background-image: linear-gradient(rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.9)), url(https://cdn.glitch.global/ba7f4151-2a49-416a-985b-56301606ae3d/light2.webp?v=1714878822666);
    background-position-x: 130px;
    filter: drop-shadow(4px 2px 6px black);
}
.hud-intro-decoration .hud-intro-character {
    display: block;
    position: absolute;
    height: 70vh;
    width: 70vh;
    bottom: 7.5vh;
    right: calc(20vw - 35vh);
    cursor: default;
    z-index: 30;
    filter: drop-shadow(15px 15px 0px rgba(0, 0, 0, 0.3));
    background-size: cover;
    background-image: url(https://cdn.glitch.global/ba7f4151-2a49-416a-985b-56301606ae3d/Kanae%20midsummer.webp?v=1714878905694);
    background-repeat: no-repeat;
    animation-name: bounce;
    animation-duration: 10s;
    animation-iteration-count: infinite;
}
.hud-intro-credits {
    position: fixed;
    display: block;
    bottom: 40px;
    left: 20px;
    color: rgba(255, 255, 255, 0.4);
}

/* @UIBuildingOverlay */
.hud-building-overlay {
    background: var(--menu-background);
    border: 3px solid white;
    padding: 12px;
    width: 370px;
}
.hud-building-overlay::after {
    border-top: 6px solid white;
    top: 101%;
}
.hud-building-overlay .hud-tooltip-health .hud-tooltip-health-bar {
    background: #bf6509;
}

/* @UIChatStyles */
.hud-chat {
    resize: vertical;
    max-height: 380px;
    min-height: 75px;
    overflow-y: auto;
    border-radius: 4px 4px 4px 0;
}
.hud-chat .hud-chat-message {
    display: block;
    position: relative;
    width: 90%;
    white-space: unset;
    word-break: break-all;
    overflow: visible;
}
.hud-chat .hud-chat-message strong {
    display: inline-block;
}
.hud-chat .hud-chat-message > small {
    position: absolute;
    right: -12%;
    font-weight: bold;
    opacity: 0.4;
}

/* @UIZoomStyles */
.interaction-wheel {
    display: none;
    height: 250px;
    width: 250px;
    border: 80px solid rgba(0, 0, 0, 0.4);
    border-radius: 125px;
    position: fixed;
    top: calc(50vh - 125px);
    left: calc(50vw - 125px);
}
.tag-is-disabled {
    opacity: 0.4;
    pointer-events: none;
    cursor: no-drop;
}
#next-wheel {
    background: rgba(0, 0, 0, 0.4);
    color: white;
    right: -120px;
    position: absolute;
    top: -80px;
    height: 60px;
    width: 60px;
    border-radius: 50%;
    border: none;
    cursor: pointer;
}
#next-wheel::after {
    content: ' ';
    position: absolute;
    width: 100%;
    height: 100%;
    background-image: url(https://cdn.glitch.global/ba7f4151-2a49-416a-985b-56301606ae3d/Friends_Mutual.webp?v=1714878926203);
    background-size: 90%;
    top: 5%;
    left: 5%;
}
#zoom-mode {
    background: rgba(0, 0, 0, 0.4);
    color: white;
    left: -120px;
    position: absolute;
    bottom: -80px;
    height: 60px;
    width: 60px;
    border-radius: 50%;
    border: none;
    text-align: center;
    padding: 20px 0;
    font-family: 'Hammersmith One';
}
#zoom-controls {
    background: rgba(0, 0, 0, 0.2);
    color: white;
    right: calc(50% - 65px);
    position: fixed;
    top: 74px;
    height: 60px;
    width: 130px;
    border-radius: 4px;
    border: none;
    text-align: start;
    padding: 10px;
    font-family: 'Hammersmith One';
}
.hud-zoom-item {
    width: 48px;
    height: 48px;
    color: white;
    position: absolute;
    transition: all 0.15s ease-in-out;
}
.zoom-reset {
    top: -60px;
    left: calc(50% - 15px);
}
.zoom-up {
    left: -60px;
    top: calc(50% - 15px);
}
.zoom-down {
    top: calc(50% - 15px);
    right: -80px;
}
.zoom-prop {
    font-size: 20px;
    bottom: -80px;
    left: 25%;
}

/* @UIQuickBuy */
.hud-center-toolbar {
    width: 30vw;
    height: 68px;
    z-index: 9999;
    background: rgba(0, 0, 0, 0.4);
    border-bottom-left-radius: 3px;
    border-bottom-right-radius: 3px;
    top: -20px;
    position: relative;
    overflow-x: auto;
    padding: 10px;
    display: flex;
    flex-wrap: nowrap;
    max-width: max-content;
}
.hud-center-toolbar .is-disabled {
    pointer-events: none;
    opacity: 0.4 !important;
}
.hud-center-toolbar .hud-center-toolbar-item {
    position: relative;
    flex: 0 0 auto;
    display: inline-block;
    width: 48px;
    height: 48px;
    line-height: 48px;
    margin: 0 4px;
    text-align: center;
    text-decoration: none;
    background: rgba(255, 255, 255, 0.1);
    color: rgba(0, 0, 0, 0.8);
    border-radius: 4px;
    transition: all 0.15s ease-in-out;
}
.hud-center-toolbar .hud-center-toolbar-item::after {
    content: ' ';
    display: block;
    width: 32px;
    height: 32px;
    margin: 8px auto;
    background-size: contain;
    background-position: center;
    background-repeat: no-repeat;
    opacity: 0.7;
    transition: all 0.15s ease-in-out;
}

/* @UIMisc. */
#joinWithPsk {
    display: none;
    background-color: rgba(0, 0, 0, 0.4);
    padding: 4px 5px;
    border-radius: 8px;
    color: rgb(255, 255, 255);
    width: 30vw;
    height: 40px;
    position: fixed;
    left: 35vw;
    top: 60vh;
}
div > div > a.btn.btn-green.hud-confirmation-accept {
    background: var(--normal-btn);
}

/* @UIMenuShopStyles */
#hud-menu-shop {
    top: calc(50vh - 250px);
    left: calc(50vw - 345px);
    width: 690px;
    height: 500px;
    background: var(--menu-background);
    border: 4px solid white;
    margin: 0 0 0 0;
    padding: 20px 20px 20px 20px;
    z-index: 20;
}
.hud-menu-shop .hud-shop-grid {
    height: 360px;
}
.hud-shop-grid::after {
    font-size: 200px;
    position: absolute;
    top: -30px;
    right: -70px;
    width: 40%;
    height: 40%;
    z-index: -69;
    font-family: "Font Awesome 5 Free";
    font-weight: 10;
    content: "\\f185";
    opacity: 0.1;
    color: white;
}
.hud-menu-shop .hud-shop-grid .hud-shop-item .hud-shop-item-actions .hud-shop-actions-equip {
    background: var(--normal-btn);
}
.hud-menu-shop .hud-shop-grid .hud-shop-item .hud-shop-item-actions .hud-shop-actions-equip:hover, .hud-menu-shop .hud-shop-grid .hud-shop-item .hud-shop-item-actions .hud-shop-actions-equip:active {
    background: var(--light-hover-btn);
}
.hud-menu-shop .hud-shop-grid .hud-shop-item .hud-shop-item-actions .hud-shop-actions-equip.is-disabled {
    background: none;
}
.hud-menu-shop .hud-shop-grid .hud-shop-item[data-item=HatComingSoon] .hud-shop-item-coming-soon {
    background: none;
}

/* @UIMenuPartyStyles */
#hud-menu-party {
    top: calc(50vh - 240px);
    left: calc(50vw - 305px);
    margin: 0;
    width: 610px;
    height: 480px;
    background-color: var(--menu-background);
    border: 4px solid white;
    z-index: 20;
}
.hud-party-members::after {
    font-size: 200px;
    position: absolute;
    bottom: 30px;
    left: -10px;
    width: 40%;
    height: 40%;
    z-index: -69;
    font-family: "Font Awesome 5 Free";
    font-weight: 10;
    content: "\\f185";
    opacity: 0.1;
    color: white;
}
.hud-menu-party .hud-party-members .hud-member-link::before {
    display: block;
    position: absolute;
    content: " ";
    left: 0px;
    bottom: 0px;
    height: 3px;
    width: 30%;
}
#hud-menu-party > div.hud-party-members > div:nth-child(1)::before {
    background: #8473d4;
}
#hud-menu-party > div.hud-party-members > div:nth-child(2)::before {
    background: #d6ab35;
}
#hud-menu-party > div.hud-party-members > div:nth-child(3)::before {
    background: #76bd2f;
}
#hud-menu-party > div.hud-party-members > div:nth-child(4)::before {
    background: #d67820;
}
.hud-party-grid::after {
    font-size: 200px;
    position: absolute;
    bottom: 30px;
    left: -10px;
    width: 40%;
    height: 40%;
    z-index: -69;
    font-family: "Font Awesome 5 Free";
    font-weight: 10;
    content: "\\f185";
    opacity: 0.1;
    color: white;
}
.hud-menu-party .hud-party-grid .hud-party-link.is-active {
    background: var(--normal-btn) !important;
}
.hud-menu-party .hud-party-visibility {
    width: 275.5px;
    margin: 10px 3px 0 0;
    background: var(--normal-btn);
}
.hud-menu-party .hud-party-share {
    width: 395px;
    margin: 0 0 0 5px;
}

/* @UIMenuSettingsStyles */
#hud-menu-settings {
    background-color: var(--menu-background);
    border: 4px solid white;
    margin: 0px;
    top: calc(50vh - 275px);
    left: calc(50vw - 360px);
    width: 720px;
    height: 550px;
    overflow: hidden;
}
.hud-menu-settings::before {
    font-size: 200px;
    position: absolute;
    top: -90px;
    left: calc(50% - 100px);
    width: 40%;
    height: 40%;
    z-index: -69;
    font-family: "Font Awesome 5 Free";
    font-weight: 10;
    content: "\\f185";
    opacity: 0.1;
    color: white;
}
#hud-menu-settings::after {
    display: block;
    position: absolute;
    content: "";
    width: 25px;
    height: 25px;
    transform: rotate(45deg);
    margin: 10px;
    background: white;
    right: -25px;
    top: 73px;
    box-shadow: none;
}
.hud-menu-settings .hud-settings-grid {
    height: 390px;
    margin: 90px 0 0;
    overflow: hidden;
    padding: unset;
}
.hud-settings-page {
    width: 100%;
    height: 100%;
}
.hud-settings-page > span {
    position: relative;
    margin: auto;
    font-size: 17px;
    opacity: 0.7;
}
.hud-settings-page .coming-soon {

}
.hud-settings-options {
    display: inline-block;
    position: relative;
    width: 50%;
    height: 100%;
    background: rgba(0, 0, 0, 0.2);
    padding: 15px;
    overflow: auto;
}
.hud-settings-options > div {
    opacity: 0.8;
    display: block;
    position: relative;
    height: 100px;
    border-bottom: 2px dashed white;
    margin-bottom: 15px;
}
.hud-settings-options h2 {
    font-family: "Open Sans";
    margin: 5px 0 10px 0;
    font-size: 1.4em;
}
.hud-settings-options span {
    display: block;
    position: absolute;
    opacity: 0.7;
    width: 60%;
    -webkit-font-smoothing: antialiased;
}
.hud-settings-options button {
    position: absolute;
    top: calc(50% - 20px);
    right: 35px;
    height: 40px;
    width: 25%;
    background: unset;
    border: none;
    border-bottom: 3px solid white;
    color: white;
    font-size: 18px;
    cursor: pointer;
    transition: all 0.15s ease-in-out;
}
.hud-settings-options a {
    position: absolute;
    top: calc(50% - 10px);
    right: -5px;
    height: 20px;
    width: 20px;
    background: unset;
    opacity: 0.4;
    transition: all 0.15s ease-in-out;
}
.hud-settings-options a::before {
    font-family: "Font Awesome 5 Free";
    content: "\\f054";
    font-size: 20px;
    height: 100%;
    width: 100%;
    display: block;
    font-weight: 900;
}
.hud-settings-options a:hover {
    opacity: 1;
}
.hud-settings-more {
    position: relative;
    display: inline-block;
    width: 50%;
    height: 100%;
    padding: 15px;
    overflow: auto;
}
.hud-settings-more input {
    height: 40px;
    width: 100%;
    background: rgba(0, 0, 0, 0.2);
    border: 2px dashed rgba(255, 255, 255, 0.7);
    border-radius: 4px;
    padding: 5px;
    color: #eee;
    margin: 10px 0;
}
.hud-settings-more span {
    display: block;
    opacity: 0.5;
}
.hint-controls {
    position: absolute;
    display: flex;
    flex-direction: column;
    zoom: 83%;
    width: fit-content;
    height: 84px;
    top: 55px;
    left: 30px;
    overflow: scroll;
}
.hint-controls > li {
    opacity: 0.5;
    -webkit-font-smoothing: antialiased;
    margin: 0 5px 0 0;
}
#select-page {
    display: flex;
    justify-content: space-evenly;
    position: absolute;
    top: 75px;
    right: 0px;
    background-image: linear-gradient(to left, rgba(0, 0, 0, 0.4), transparent);
    box-shadow: 10px 0 rgb(0 0 0 / 40%);
    width: 200px;
    border: 2px solid;
    border-image: linear-gradient(to right, transparent 5%, rgb(163 163 43) 35%, rgb(206 120 55) 95%);
    border-image-slice: 1;
    border-left: none;
    border-right: none;
    border-top: none;
    padding-left: 20px;
}
#page-name {
    display: inline-block;
    -webkit-font-smoothing: antialiased;
    position: relative;
    transform: translate(0px, 9px);
}
/* @UIMenuMoreCustomStyles */
.base-card {
    position: relative;
    display: block;
    width: 100%;
    height: 64px;
    margin: 0 0 10px;
    padding: 10px 10px 10px 10px;
    text-decoration: none;
    background: rgba(255, 255, 255, 0.1);
    cursor: pointer;
    color: #eee;
    border-radius: 3px;
    transition: all 0.15s ease-in-out;
    text-align: left;
}
.base-card:hover {
    background: rgba(255, 255, 255, 0.2);
}
#base-management {
    position: absolute;
    top: 0px;
    width: 95%;
    height: 75%;
    margin: 0px;
    padding-right: 10px;
    overflow: scroll;
}
#base-management > h2 {
    display: inline-block;
    margin: 10px 0 10px;
    font-weight: 600;
    -webkit-font-smoothing: antialiased;
}
#base-management > hr {
    width: 87%;
    opacity: 0.4;
}
#save-config {
    position: absolute;
    top: 285px;
    right: 15px;
}
`;

const styles = document.createElement("style");
styles.type = "text/css";
styles.appendChild(document.createTextNode(css));
document.head.appendChild(styles);

function getClass(DOMClass) {
    return document.getElementsByClassName(DOMClass);
};

function getId(DOMId) {
    return document.getElementById(DOMId);
};

/* @Intro */
getId("hud-intro").insertAdjacentHTML("beforeend", `
    <div class="hud-intro-decoration" id="hud-intro-decoration">
        <a class="hud-intro-left-triangle"></a>
        <a class="hud-intro-right-triangle"></a>
        <a class="hud-intro-character"></a>
    </div>
    <div class="hud-intro-credits">
        <span>Sun<strong>:Raise</strong></span>
        <i class="fas fa-sun"></i>
    </div>
`);
getClass("hud-intro-corner-top-left")[0].insertAdjacentHTML("afterbegin", `
    <a class="hud-intro-stick">Scanner</a>
    <div class="hud-intro-scan-data">
        <span style="display: block;position: relative;opacity: 0.7;text-wrap: wrap;">Scanned data will be shown here once done.</span>
    </div>
`);
getClass("hud-intro-form")[0].insertAdjacentHTML("afterbegin", `
    <div style="margin: 0 10px 0 0;">
        <div class="hud-intro-name-container">
            <a class="hud-intro-name-decorator"><select id="saved-names"></select></a>
        </div>
        <div class="hud-intro-server-container">
            <a class="hud-intro-server-decorator"></a>
        </div>
    </div>
    <div></div>
    <img id="scan-btn" src="https://cdn.glitch.global/ba7f4151-2a49-416a-985b-56301606ae3d/scan.webp?v=1714879196519" onclick="window.sscs();" />
`);
getClass("hud-intro-name-container")[0].insertAdjacentElement("afterbegin", document.querySelector("#hud-intro > div.hud-intro-wrapper > div > div > input"));
getClass("hud-intro-server-container")[0].insertAdjacentElement("afterbegin", document.querySelector("#hud-intro > div.hud-intro-wrapper > div > div > select"));

getClass("hud-intro-play")[0].innerText = "";
document.querySelector("#hud-intro > div.hud-intro-wrapper > div > div > div:nth-child(2)").insertAdjacentElement("afterbegin", document.querySelector("#hud-intro > div.hud-intro-wrapper > div > div > button"));
document.querySelector("#hud-intro > div.hud-intro-wrapper > div > div > div:nth-child(2)").insertAdjacentHTML("beforeend", `
    <span id="playspan">Play</span>
`);

const oldSubmit = game.ui.components.Intro.submitElem;
const newSubmit = oldSubmit.cloneNode(true);
oldSubmit.parentNode.replaceChild(newSubmit, oldSubmit);
game.ui.components.Intro.submitElem = newSubmit;
game.ui.components.Intro.onSubmitClick = function (_0x56b3b8) {
    const realNicknameLength = new Blob([this.nameInputElem.value]).size;
    if (realNicknameLength > 29) return void game.ui.components.Intro.onConnectionError('Your nickname length is too long. Please shorten it/use less special characters.');
    const server = this.ui.getOption(`servers`)[this.serverElem.value];
    localStorage.setItem(`name`, this.nameInputElem.value.trim());
    this.connecting || (
        this.connecting = true,
        getId("playspan").style && (getId("playspan").innerText = "    "),
        this.connectionTimer = setTimeout(function() {
            this.onConnectionError();
        }.bind(this), 15000),
        this.submitElem.innerHTML = '<span\x20class=\x22hud-loading\x22></span>',
        this.errorElem.style.display = `none`,
        this.ui.setOption(`nickname`,this.nameInputElem.value.trim()),
        this.ui.setOption(`serverId`, this.serverElem.value),
        game.network.connect(server)
    );
}
newSubmit.onclick = game.ui.components.Intro.onSubmitClick.bind(game.ui.components.Intro);

game.ui.components.Intro.onConnectionError = function (errorText) {
    errorText ||= `We were unable to connect to the gameserver. Please try another server.`;
    this.connecting = false;
    this.connectionTimer && (
        clearInterval(this.connectionTimer),
        delete this.connectionTimer
    );
    getId("playspan").style && (getId("playspan").innerText = "Play");
    this.serverElem.classList.add('has-error');
    this.errorElem.style.display = 'block';
    this.errorElem.innerText = errorText;
}

game.ui.components.Intro.checkForPartyInvitation = function () {
    if (document.location.hash && !(document.location.hash.length < 2)) {
        var subString = document.location.hash.substring(2).split('/'),
            serverId = subString[0],
            partyShareKey = subString[1];
        if (serverId && partyShareKey) {
            this.serverElem.setAttribute(`disabled`, `true`);
            document.querySelector("#hud-intro > div.hud-intro-wrapper > div > div > div:nth-child(1) > div.hud-intro-server-container > a").style.opacity = '0.4';
            this.serverElem.querySelector('option[value=\x22' + serverId + '\x22]').setAttribute(`selected`, 'true');
            this.partyShareKey = partyShareKey;
            /*             Game.currentGame.network.addEnterWorldHandler(function (e) {
                if (e.allowed && !this.reconnectKey) {
                    var packet = {
                        'name': `JoinPartyByShareKey`,
                        'partyShareKey': this.partyShareKey
                    };
                    Game.currentGame.network.sendRpc(packet);
                }
            }); */
        };
    }
};
game.ui.components.Intro.checkForPartyInvitation();

(function() {
    if (!localStorage.savedNames) return;
    let id = 0;
    const selectElem = getId("saved-names"),
          names = [],
          items = JSON.parse(localStorage.savedNames).map(nameUnfiltered => {
              const name = window.filterXSS(nameUnfiltered);
              names.push(nameUnfiltered);
              return `<option>${name}</option>`;
          }).join('\n');
    selectElem.innerHTML = items;
})();

getId("saved-names").addEventListener('change', (e) => { getClass("hud-intro-name")[0].value = e.target.selectedOptions[0].innerText; });

game.network.addEnterWorldHandler((e) => {
    if (!e.allowed) {
        try {
            getId("playspan").innerText = "Play";
            game.ui.components.Intro.submitElem.innerText = '';
        } catch {};
    };
    console.log(e);

    !localStorage.savedNames && (localStorage.savedNames = JSON.stringify([]));
    const willBeSaved = e.effectiveDisplayName,
          storage = JSON.parse(localStorage.savedNames);
    if (storage.find(i => i == willBeSaved) === undefined) {
        storage.push(willBeSaved);
        localStorage.savedNames = JSON.stringify(storage);
    }
});

document.getElementsByClassName('hud-party-tag')[0].setAttribute('maxlength', 49);
document.getElementsByClassName('hud-intro-name')[0].setAttribute('maxlength', 29);

window.sscs = (selected = getClass("hud-intro-server")[0].value) => {
    const server = game.options.servers[selected];
    const hostname = server.hostname;
    const url = `wss://${hostname}:443/`;
    let hasSentData = false;

    document.querySelector("#hud-intro > div.hud-intro-corner-top-left > div").innerHTML = `
        <strong class="hud-loading" style="display: block;margin: auto;"></strong>
        <span style="display: block;margin: 10px 0 5px;position: relative;text-align: center;opacity: 0.7;">This may take 10-20 seconds.</span>
    `;

    const iframe = document.createElement('iframe');
    iframe.src = `https://zombs.io/`;
    iframe.style.display = 'none';
    document.body.append(iframe);

    game.network.connectionOptions = { ipAddress: server.ipAddress };
    game.network.connected = true;

    iframe.addEventListener("load", () => {
        const _game = iframe.contentWindow.game;
        _game.network.connectionOptions = { ipAddress: server.ipAddress };
        _game.network.connected = true;
        const ws = new WebSocket(url);
        ws.binaryType = "arraybuffer";
        ws.isclosed = false;
        const loadLbPacket = () => {
            for (let i = 0; i < 30; i++) ws.send(new Uint8Array([3, 17, 123, 34, 117, 112, 34, 58, 49, 44, 34, 100, 111, 119, 110, 34, 58, 48, 125])); // move packet
            ws.send(new Uint8Array([7, 0])); // ping
            ws.send(
                new Uint8Array(
                    [9,6,0,0,0,126,8,0,0,108,27,0,0,146,23,0,0,82,23,0,0,8,91,11,0,8,91,11,0,0,0,0,0,32,78,0,0,76,79,0,0,172,38,0,0,120,155,0,0,
                     166,39,0,0,140,35,0,0,36,44,0,0,213,37,0,0,100,0,0,0,120,55,0,0,0,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,134,6,0,0]
                )
            ); // metrics
        };
        ws.onopen = (data) => {
            ws.network = new game.networkType();
            ws.send(new Uint8Array([7, 0]));
            ws.sendPacket = (e, t) => {
                let enc;
                if (e == 6) {
                    enc = _game.network.codec.encode(e, t);
                } else {
                    enc = ws.network.codec.encode(e, t);
                }
                !ws.isclosed && ws.send(enc);
            };
            ws.onRpc = (data) => {
                if(data.name === "SetPartyList") {
                    ws.parties = data.response;
                };
                if(data.name === "Leaderboard") {
                    if (ws.b4) {
                        window.appSsrs({ population: ws.pop, leaderboard: data, parties: ws.parties, server: server });
                        hasSentData = true;
                        ws.close();
                        return;
                    };
                    loadLbPacket();
                    ws.b4 = true;
                };
            };
            ws.onmessage = msg => {
                const dataArray = new Uint8Array(msg.data);
                let data;
                if (dataArray[0] == 5) {
                    ws.network.codec.decodePreEnterWorldResponse = buffer => buffer;
                    const _data = ws.network.codec.decode(msg.data);
                    data = _game.network.codec.decodePreEnterWorldResponse(_data);
                    data = {opcode: 5, ...data};
                } else data = ws.network.codec.decode(msg.data);
                switch(data.opcode) {
                    case 5:
                        ws.sendPacket(4, { displayName: `${new Date().toLocaleString()}`, extra: data.extra });
                        break;
                    case 4:
                        if (!data.allowed) {
                            const lbData = {name: "Leaderboard", response: [{name: "Server full.", uid: 727, rank: 0, score: 0, wave: 0}], opcode: 9};
                            window.appSsrs({ population: 32, leaderboard: lbData, parties: [], server: server });
                            hasSentData = true;
                        } else {
                            ws.sendPacket(6, {});
                            ws.sendPacket(3, { left: 1, up: 1 });
                            ws.sendPacket(3, {mouseMovedWhileDown: 0});
                            ws.sendPacket(3, {space: 0});
                            ws.sendPacket(3, {space: 1});
                            ws.pop = data.players;
                        }
                        break;
                    case 9:
                        ws.onRpc(data);
                        break;
                };
            };
            ws.onclose = () => {
                ws.isclosed = true;
                if (!hasSentData) {
                    const lbData = {name: "Leaderboard", response: [{name: "Unable to access.", uid: 727, rank: 0, score: 0, wave: 0}], opcode: 9};
                    window.appSsrs({ population: null, leaderboard: lbData, parties: [], server: server });
                    hasSentData = true;
                }
                iframe.remove();
            }
        };
    });
};
window.appSsrs = res => {
    console.log(res);
    if (res.population !== null) {
        getClass("hud-intro-server")[0].value = res.server.id;
        getClass("hud-intro-server")[0].selectedOptions[0].innerText = `${res.server.name} [${res.population}/32]`;
    }
    const leaderboard = res.leaderboard.response;
    const ssrs = getClass("hud-intro-scan-data")[0];
    ssrs.innerHTML = `
    <p>Population: ${res.population === null ? "Cannot fetch" : res.population}</p>
    <div>
    ${leaderboard.map(lb => {
        return `
        <div class="hud-intro-scan-data-player">
            <span class="player-rank">#${lb.rank + 1}</span>
            <strong class="player-name">${window.filterXSS(lb.name)}</strong>
            <span class="player-score">${lb.score.toLocaleString("en")}</span>
            <span class="player-wave">${lb.wave.toLocaleString("en")}</span>
        </div>
        `;
    }).join("\n")}
    </div>
    <hr />
    <div>
    ${res.parties.map(p => {
        return `
        <div class="hud-intro-scan-data-player">
            <span class="player-rank">${p.memberCount}</span>
            <strong class="player-name">${window.filterXSS(p.partyName)}</strong>
            <span class="player-score">${p.isOpen ? "Public" : "Private"}</span>
            <span class="player-wave">${p.partyId}</span>
        </div>
        `;
    }).join("\n")}
    </div>
    `;
};

/* @UI */
// @Chat
game.ui.components.Chat.chatResize = function() {
    this.messagesElem.style.height = (this.componentElem.offsetHeight - 40) + "px";
    this.componentElem.scrollTop = this.messagesElem.scrollHeight;
}
game.ui.components.Chat.blockedNames = [];
game.ui.components.Chat.emojiList = {
    // static emojis
    hmm: "https://cdn.discordapp.com/emojis/724365641963929611.png?size=48",
    pog: "https://cdn.discordapp.com/emojis/721070353337811026.png?size=48",
    pepehands: "https://cdn.discordapp.com/emojis/733406770139103293.png?size=48",
    pepeEyes: "https://cdn.discordapp.com/emojis/869573233794486323.gif?size=48",
    pepeHappy: "https://cdn.discordapp.com/emojis/801475958883614811.png?size=48",
    sadge: "https://cdn.discordapp.com/emojis/826530556974989344.png?size=48",
    ha: "https://cdn.discordapp.com/emojis/782756472886525953.png?size=48",
    kekw: "https://cdn.discordapp.com/emojis/748511358076846183.png?size=48",
    pogEyes: "https://cdn.discordapp.com/emojis/786979080406564885.png?size=48",
    appalled: "https://cdn.discordapp.com/emojis/830880294881853530.png?size=48",
    pogYou: "https://cdn.discordapp.com/emojis/790293794716516430.png?size=48",
    pogChag: "https://cdn.discordapp.com/emojis/831156303497134090.png?size=48",
    pogey: "https://cdn.discordapp.com/emojis/790293759861719050.png?size=48",
    weirdChamp: "https://cdn.discordapp.com/emojis/757553915389673502.png?size=48",
    monkaS: "https://cdn.discordapp.com/emojis/757179783573405766.png?size=48",
    yep: "https://cdn.discordapp.com/emojis/758356179477987339.png?size=48",

    // animated emojis / gif
    weirdButOkay: "https://cdn.discordapp.com/emojis/831156194247966782.gif?size=48",
    pogpogpogpog: "https://cdn.discordapp.com/emojis/869580566096379974.gif?size=48",
    wooyeah: "https://cdn.discordapp.com/emojis/791008461420888084.gif?size=48",
    idk: "https://cdn.discordapp.com/emojis/882513306164805642.gif?size=48",
};
game.ui.components.Chat.blockPlayer = function(name) {
    game.ui.components.PopupOverlay.showConfirmation(`Are you sure you want to block ${window.filterXSS(name)}?`, 3500, () => {
        this.blockedNames.push(name);
        for (let msg of Array.from(document.getElementsByClassName("hud-chat-message"))) {
            if (msg.childNodes[1].innerText === ' ' + name) {
                let bl = msg.childNodes[0];
                bl.innerHTML = "Unblock";
                bl.style.color = "blue";
                bl.onclick = () => this.unblockPlayer(name);
            };
        };
    }, () => {});
};
game.ui.components.Chat.unblockPlayer = function(name) {
    this.blockedNames.splice(this.blockedNames.indexOf(name), 1);
    for (let msg of Array.from(document.getElementsByClassName("hud-chat-message"))) {
        if (msg.childNodes[1].innerText === ' ' + name) {
            let bl = msg.childNodes[0];
            bl.innerHTML = "Block";
            bl.style.color = "red";
            bl.onclick = () => this.blockPlayer(name);
        };
    };
};
game.ui.components.Chat.onMessageReceived = function(e) {
    if (this.blockedNames.includes(e.displayName) || window.chatDisabled) return;
    let a = this,
        b = window.filterXSS(e.displayName),
        c = window.filterXSS(e.message)
    .replace(/(?:f|F)uck/gi, `<img src="https://cdn.discordapp.com/emojis/907625398832070667.png?size=80" height="16" width="18" style="margin: 1px 0 0 0;"></img>`)
    .replace(/s[3e]x+/gi, `<img src="https://cdn.discordapp.com/emojis/953759638350872666.gif?size=80&quality=lossless" height="16" width="18" style="margin: 1px 0 0 0;"></img>`)
    .replace(/n+[i1]+gg+[a@]+/i, `<img src="https://cdn.discordapp.com/emojis/902742239996936226.webp?size=80" height="16" width="17" style="margin: 1px 0 0 0;"></img>`);
    let arr = c.split(':');

    for (let i = 1; i < arr.length; i += 2) {
        if (!this.emojiList[arr[i]]) arr = [c];
        else arr[i] = `<img src="${this.emojiList[arr[i]]}" height="16" width="18" style="margin: 1px 0 0 0;"></img>`;
    }

    let d = a.ui.createElement(`<div class="hud-chat-message"><a href="javascript:void(0);" style="color: red;margin: 0 5px 0 0;display: inline-block;">Block</a><strong> ${b}</strong>: ${arr.join(" ")}<small>${getClock()}</small></div>`);
    d.children[0].onclick = () => game.ui.components.Chat.blockPlayer(b);
    a.messagesElem.appendChild(d);
    a.messagesElem.scrollTop = a.messagesElem.scrollHeight;
}
new ResizeObserver(game.ui.components.Chat.chatResize.bind(game.ui.components.Chat)).observe(getId("hud-chat"));
Game.currentGame.network.emitter.removeListener("PACKET_RPC", Game.currentGame.network.emitter._events.PACKET_RPC[1]);
Game.currentGame.network.addRpcHandler("ReceiveChatMessage", game.ui.components.Chat.onMessageReceived.bind(game.ui.components.Chat));

// @Zoom
getId('hud').insertAdjacentHTML('beforeend', `
<div class="interaction-wheel">
    <a class="hud-zoom-item zoom-reset" onclick="game.zoom.resetZoom();"><i class="fa fa-undo fa-lg" style="font-size: 30px;"></i></a>
    <a class="hud-zoom-item zoom-up" onclick="game.zoom.zoomOut();"><i class="fa fa-arrow-up fa-2x" style="font-size: 30px;"></i></a>
    <a class="hud-zoom-item zoom-down" onclick="game.zoom.zoomIn();"><i class="fa fa-arrow-down fa-2x" style="font-size: 30px;"></i></a>
    <a class="hud-zoom-item zoom-prop">1.0x</a>
    <a id="zoom-mode">Button</a>
    <a id="zoom-controls">
        <strong>N</strong> to zoom in <br>
        <strong>M</strong> to zoom out
    </a>
    <a id="next-wheel" onclick="game.zoom.toggleZoomOnScroll();"></a>
</div>
<input id="joinWithPsk" type="tel" placeholder="insert PSK..." class="btn">
`);
game.zoom = {
    dimension: 1,
    zoomonscroll: !!parseInt(localStorage.zoomonscroll) || false,
    upd: function() {
        getClass('zoom-prop')[0].innerText = `${this.dimension.toFixed(1)}x`;
        this.dimension = Math.max(0.1, this.dimension);

        const renderer = Game.currentGame.renderer;
        let canvasWidth = window.innerWidth * window.devicePixelRatio;
        let canvasHeight = window.innerHeight * window.devicePixelRatio;
        let ratio = canvasHeight / (1080 * this.dimension);
        renderer.scale = ratio;
        renderer.entities.setScale(ratio);
        renderer.ui.setScale(ratio);
        renderer.renderer.resize(canvasWidth, canvasHeight);
        renderer.viewport.width = renderer.renderer.width / renderer.scale + 2 * renderer.viewportPadding;
        renderer.viewport.height = renderer.renderer.height / renderer.scale + 2 * renderer.viewportPadding;
    },
    onWindowResize: function(e) {
        if (this.zoomonscroll && e) {
            if (e.deltaY > 0) this.dimension += 0.02;
            else if (e.deltaY < 0) this.dimension -= 0.02;
        }
        this.upd();
        game.ui.components.PlacementOverlay.onResize?.();
    },
    zoom: function(val) {
        this.dimension = val;
        this.upd();
    },
    toggleZoomOnScroll: function() {
        this.dimension -= 0.02;
        this.zoomonscroll = !this.zoomonscroll;
        localStorage.zoomonscroll = this.zoomonscroll | 0;
        const zoomMode = document.querySelector("#zoom-mode");
        const zoomBtns = [...document.getElementsByClassName('hud-zoom-item')];
        if (!this.zoomonscroll) {
            this.resetZoom();
            for (let i of zoomBtns) i.classList.remove('tag-is-disabled');
            zoomMode.innerText = "Button";
        } else {
            zoomMode.innerText = "Scroll";
            for (let i of zoomBtns) i.classList.add('tag-is-disabled');
        }
    },
    zoomOut: function() {
        this.dimension += 1;
        this.zoom(this.dimension);
    },
    zoomIn: function() {
        this.dimension -= 1;
        this.zoom(this.dimension);
    },
    resetZoom: function() {
        this.dimension = 1;
        this.zoom(this.dimension);
    }
};
game.zoom.onWindowResize();
window.onresize = game.zoom.onWindowResize.bind(game.zoom);
window.onwheel = game.zoom.onWindowResize.bind(game.zoom);

/* @Party */
game.ui.components.MenuParty.loaded = false;
game.ui.components.MenuParty.serverPopulation = 0;
game.ui.components.MenuParty.closedParties = null;
game.ui.components.MenuParty.shareKeyLog = null;
game.ui.components.MenuParty.partyMenu = getClass("hud-menu-party")[0];
game.ui.components.MenuParty.partyTabs = getClass("hud-party-tabs")[0];
game.ui.components.MenuParty.partyMembers = getClass("hud-party-members")[0];
game.ui.components.MenuParty.partyGrid = getClass("hud-party-grid")[0];
game.ui.components.MenuParty.init = function() {
    if (this.loaded) return;
    this.loaded = true;

    this.partyMembers.style.display = "block";
    this.partyGrid.style.display = "none";

    const privateTab2 = document.createElement("a");
    privateTab2.className = "hud-party-tabs-link";
    privateTab2.id = "privateTab2";
    privateTab2.innerHTML = "Closed Parties";

    this.closedParties = document.createElement("div");
    this.closedParties.className = "hud-private hud-party-grid";
    this.closedParties.id = "privateHud2";
    this.closedParties.style.display = "none";

    this.partyTabs.appendChild(privateTab2);
    this.partyMenu.insertBefore(this.closedParties, getClass("hud-party-actions")[0]);


    const privateTab = document.createElement("a");
    privateTab.className = "hud-party-tabs-link";
    privateTab.id = "privateTab";
    privateTab.innerHTML = "Key Logs";

    this.shareKeyLog = document.createElement("div");
    this.shareKeyLog.className = "hud-private hud-party-grid";
    this.shareKeyLog.id = "privateHud";
    this.shareKeyLog.style.display = "none";

    this.partyTabs.appendChild(privateTab);
    this.partyMenu.insertBefore(this.shareKeyLog, getClass("hud-party-actions")[0]);

    privateTab.addEventListener("click", e => {
        for (let menu of [this.partyMembers, this.partyGrid, this.closedParties, this.shareKeyLog]) {
            menu.style.display == "block" && (menu.style.display = "none");
        }

        for (let i of getClass("hud-party-tabs-link")) i.className = "hud-party-tabs-link";
        privateTab.className = "hud-party-tabs-link is-active";

        this.shareKeyLog.style.display = "block";
    })

    privateTab2.addEventListener("click", e => {
        for (let menu of [this.partyMembers, this.partyGrid, this.closedParties, this.shareKeyLog]) {
            menu.style.display == "block" && (menu.style.display = "none");
        }

        for (let i of getClass("hud-party-tabs-link")) i.className = "hud-party-tabs-link";
        privateTab2.className = "hud-party-tabs-link is-active";

        this.closedParties.style.display = "block";
    })

    getClass("hud-party-tabs-link")[0].addEventListener("click", e => {
        this.shareKeyLog.style.display = "none";
        privateTab.className = "hud-party-tabs-link";

        this.closedParties.style.display = "none";
        privateTab2.className = "hud-party-tabs-link";
    })

    getClass("hud-party-tabs-link")[1].addEventListener("click", e => {
        this.shareKeyLog.style.display = "none";
        privateTab.className = "hud-party-tabs-link";

        this.closedParties.style.display = "none";
        privateTab2.className = "hud-party-tabs-link";
    })
}
game.ui.components.MenuParty.onSetPartyList = function(parties) {
    this.serverPopulation = 0;
    for (let party of parties) this.serverPopulation += party.memberCount;
    document.getElementsByClassName("hud-party-server")[0].innerHTML = `${this.serverPopulation}/32<small id="serverRegion"></small>`;
}
game.ui.components.MenuParty.onPartyShareKey = function(e) {
    const psk = e.partyShareKey,
          lnk = `https://zombs.io/#/${game.options.serverId}/${psk}/`;
    this.shareKeyLog.innerHTML += `
        <div style="display:inline-block;margin:10px 5px 0;">
            <li>
                <strong style="cursor: pointer;" onclick="game.network.sendRpc({ name: 'JoinPartyByShareKey', partyShareKey: '${psk}' });">${psk}</strong> - <a href="${lnk}" target="_blank" color="purple">[Link]</a>
            </li>
        </div>
        <br />
    `;
}
game.ui.components.MenuParty.update = function () {
    var parties = this.ui.getParties();
    var playerIsLeader = this.ui.getPlayerPartyLeader();
    var playerPartyData = parties[this.ui.getPlayerPartyId()];
    var playerPartyMembers = this.ui.getPlayerPartyMembers();
    var serverId = this.ui.getOption('serverId');
    var staleElems = {};
    var availableParties = 0;

    this.clonedPartyElems ||= {};
    this.closedGridElem ||= getId("privateHud2");

    for (var partyId in this.partyElems) {
        staleElems[partyId] = true;
    }
    for (var partyId in parties) {
        var partyData = parties[partyId];
        var partyElem = this.partyElems[partyId];
        var partyNameSanitized = window.filterXSS(partyData.partyName);
        delete staleElems[partyId];
        if (!this.partyElems[partyId]) {
            partyElem = this.ui.createElement("<div class=\"hud-party-link\"></div>");
            this.clonedPartyElems[partyId] = this.ui.createElement("<div class=\"hud-party-link\"></div>");
            this.partyElems[partyId] = partyElem;
            this.gridElem.appendChild(partyElem);
            this.closedGridElem.appendChild(this.clonedPartyElems[partyId]);
            partyElem.addEventListener('click', this.onPartyJoinRequestHandler(partyData.partyId).bind(this));
        }
        if (partyData.isOpen) {
            partyElem.style.display = 'block';
            this.clonedPartyElems[partyId].style.display = "none";
            availableParties++;
        }
        else {
            partyElem.style.display = 'none';
            this.clonedPartyElems[partyId].style.display = "block";
        }
        if (this.ui.getPlayerPartyId() === partyData.partyId) {
            partyElem.classList.add('is-active');
            partyElem.classList.remove('is-disabled');

            this.clonedPartyElems[partyId].classList.add('is-active');
            this.clonedPartyElems[partyId].classList.remove('is-disabled');
        }
        else if (partyData.memberCount === this.maxPartySize) {
            partyElem.classList.remove('is-active');
            partyElem.classList.add('is-disabled');

            this.clonedPartyElems[partyId].classList.remove('is-active');
            this.clonedPartyElems[partyId].classList.add('is-disabled');
        }
        else {
            partyElem.classList.remove('is-active');
            partyElem.classList.remove('is-disabled');

            this.clonedPartyElems[partyId].classList.remove('is-active');
            this.clonedPartyElems[partyId].classList.remove('is-disabled');
        }
        partyElem.innerHTML = "<strong>" + partyNameSanitized + "</strong><small>id: " + partyData.partyId + "</small> <span>" + partyData.memberCount + "/" + this.maxPartySize + "</span>";
        this.clonedPartyElems[partyId].innerHTML = "<strong>" + partyNameSanitized + "</strong><small>id: " + partyData.partyId + "</small> <span>" + partyData.memberCount + "/" + this.maxPartySize + "</span>";
    }
    for (var partyId in staleElems) {
        if (!this.partyElems[partyId]) {
            continue;
        }
        this.partyElems[partyId].remove();
        this.clonedPartyElems[partyId].remove();
        delete this.partyElems[partyId];
        delete this.clonedPartyElems[partyId];
    }
    for (var i in this.memberElems) {
        this.memberElems[i].remove();
        delete this.memberElems[i];
    }
    for (var i in playerPartyMembers) {
        var playerName = window.filterXSS(playerPartyMembers[i].displayName);
        var memberElem = this.ui.createElement(
            "<div class=\"hud-member-link\">\n                <strong>" + playerName + "</strong>\n                <small>" + (playerPartyMembers[i].isLeader === 1 ? 'Leader' : 'Member') + "</small>\n                <div class=\"hud-member-actions\">\n                    <a class=\"hud-member-can-sell btn" + (!playerIsLeader || playerPartyMembers[i].isLeader === 1 ? ' is-disabled' : '') + (playerPartyMembers[i].canSell === 1 ? ' is-active' : '') + "\"><span class=\"hud-can-sell-tick\"></span> Can sell buildings</a>\n                    <a class=\"hud-member-kick btn btn-red" + (!playerIsLeader || playerPartyMembers[i].isLeader === 1 ? ' is-disabled' : '') + "\">Kick</a>\n                </div>\n            </div>"
        );
        this.membersElem.appendChild(memberElem);
        this.memberElems[i] = memberElem;
        if (playerIsLeader && playerPartyMembers[i].isLeader === 0) {
            var kickElem = memberElem.querySelector('.hud-member-kick');
            var canSellElem = memberElem.querySelector('.hud-member-can-sell');
            kickElem.addEventListener('click', this.onPartyMemberKick(i).bind(this));
            canSellElem.addEventListener('click', this.onPartyMemberCanSellToggle(i).bind(this));
        }
    }
    if (availableParties > 0) {
        this.gridEmptyElem.style.display = 'none';
    }
    else {
        this.gridEmptyElem.style.display = 'block';
    }
    if (!playerPartyData) {
        this.tagInputElem.setAttribute('disabled', 'true');
        this.tagInputElem.value = '';
        this.shareInputElem.setAttribute('disabled', 'true');
        this.shareInputElem.value = '';
        this.visibilityElem.classList.add('is-disabled');
        return;
    }
    if (document.activeElement !== this.tagInputElem) {
        this.tagInputElem.value = playerPartyData.partyName;
    }
    if (playerIsLeader) {
        this.tagInputElem.removeAttribute('disabled');
    }
    else {
        this.tagInputElem.setAttribute('disabled', 'true');
    }
    this.shareInputElem.removeAttribute('disabled');
    this.shareInputElem.value = 'https://zombs.io/#/' + serverId + '/' + this.ui.getPlayerPartyShareKey();
    if (playerIsLeader) {
        this.visibilityElem.classList.remove('is-disabled');
    }
    else {
        this.visibilityElem.classList.add('is-disabled');
    }
    if (playerPartyData.isOpen) {
        this.visibilityElem.classList.remove('is-private');
        this.visibilityElem.innerText = 'Public';
    }
    else {
        this.visibilityElem.classList.add('is-private');
        this.visibilityElem.innerText = 'Private';
    }
}
getClass("hud-party-actions")[0].insertAdjacentHTML("afterend", `
<div class="partydiv" style="text-align: center">
  <button class="btn btn-red" style="width: 275.5px;margin: 10px 0 0 3px;box-shadow: none;" onclick="Game.currentGame.network.sendRpc({name: 'LeaveParty'});">Leave</button>
</div>`);
game.ui.components.MenuParty.init();
game.network.addRpcHandler("PartyShareKey", (e) => game.ui.components.MenuParty.onPartyShareKey(e));
game.network.addRpcHandler("SetPartyList", (e) => game.ui.components.MenuParty.onSetPartyList(e));

/* @Settings
   @ModMenu */

getId('hud-menu-settings').innerHTML = `
    <a class="hud-menu-close" onclick="document.getElementById('hud-menu-settings').style.display = 'none';"></a>
    <h3>Advanced</h3>
    <div id="select-page">
        <button id="back-page" class="no-bg"><i class="fas fa-arrow-left"></i></button>
        <span id="page-name">-</span>
        <button id="forward-page" class="no-bg"><i class="fas fa-arrow-right"></i></button>
    </div>
    <div class="hint-controls"></div>
    <div class="hud-settings-grid" page="0"></div>
`;

getClass('hud-settings-grid')[0].innerHTML = `
<div class="hud-settings-page" id="page0">
    <div class="hud-settings-options"></div>
    <div class="hud-settings-more"></div>
</div>
<div class="hud-settings-page" id="page1">
    <div class="hud-settings-options"></div>
    <div class="hud-settings-more"></div>
</div>
<div class="hud-settings-page" id="page2">
    <div class="hud-settings-options"></div>
    <div class="hud-settings-more"></div>
</div>
<div class="hud-settings-page" id="page3">
    <!-- <div class="hud-settings-options"></div>
    <div class="hud-settings-more"></div> -->
    <span>Coming soon...</span>
</div>
<div class="hud-settings-page" id="page4">
    <div class="hud-settings-options"></div>
    <div class="hud-settings-more"></div>
</div>
`;

const hint_enum = {
    0: `<li><strong>Shift + 1</strong> [!] toggles Wall Block.</li>
        <li><strong>Comma</strong> [,] toggles Rebuilder.</li>
        <li><strong>Period</strong> [.] toggles Auto Upgrade.</li>`,

    1: `<li><strong>C</strong> toggles quick join via PSK input.</li>
        <li><strong>G</strong> toggles zoom menu.</li>
        <li><strong>Dash</strong> [-] toggles local info indicators.</li>
        <li><strong>Equal</strong> [=] toggles Auto Bow.</li>
        <li><strong>Semi-colon</strong> [;] deletes your pet.</li>
        <li><strong>Back-tick</strong> ['] revives your pet.</li>`, // anti tiến bịp,
    2: ``,

    3: ``,

    4: `<li><strong>?</strong> toggles Screenshot Mode.</li>
        <li><strong>~</strong> marks your position.</li>`
};

const pageName_enum = {
    0: 'Build',
    1: 'Player',
    2: 'Visual',
    3: 'Clone',
    4: 'Misc.',
};

/* const optionElem_enum = {
    name: 'h2',
    description: 'span',
} */

const menu = {
    page0: {
        AHRC: {
            name: `AHRC`,
            description: `Automatically harvests resources.`,
            more: null
        },
        rebuild: {
            name: `Auto Rebuilder`,
            description: `Automatically rebuilds dead towers.`,
            more: null,
            onCallback: () => { game.rebuilder.saveTowers(); },
            offCallback: () => { game.rebuilder.reset(); },
        },
        autoUpgrade: {
            name: `Auto Upgrader`,
            description: `Automatically upgrades towers.`,
            more: null
        },
        wallBlock: {
            name: `Wall Block`,
            description: `Allows you to place mulitple walls at once.`,
            more: {
                html: `
                    <strong>Change the size of the block that will be placed.</strong>
                    <span>Default value is 5x5, maximum is 15x15 (cells).</span>
                    <div style="display: flex;align-items: center;justify-content: space-between;">
                        Width: <input id="blockX" type="number" placeholder="w" min="3" max="15" value="3" class="btn" style="width: 20%">
                        Height: <input id="blockY" type="number" placeholder="h" min="3" max="15" value="3" class="btn" style="width: 20%">
                    </div>
                `,
                functions: () => {},
            },
        },
        baseSaver: {
            name: "Base Saver",
            description: `Manage all your saved bases here.`,
            more: {
                html: `
                    <div id="base-container"></div>
                    <div id="base-management" style="display: none;">
                        <br><a href="javascript:void(0);" id="return-to-manager" style="color: var(--light-hover-btn);"><i class="fa fa-angle-left"></i> Go back to base manager</a>
                        <br><h2>Design</h2><hr />
                        <input id="target-base-design" type="tel" placeholder="Base string" class="btn">
                        <button id="clear-target-design" class="no-bg underline-red" style="width: calc(50% - 5px);" onclick="document.getElementById('target-base-design').value = '';">Clear</button>
                        <button id="view-target-design" class="no-bg underline-white" style="width: calc(50% - 5px);margin: 0 0 5px 5px;">View</button>
                        <button id="encode-target-design" class="btn-important" style="width: calc(50% - 5px);">Encode</button>
                        <button id="build-design" class="btn-important" style="width: calc(50% - 5px);margin: 0 0 5px 5px;">Build</button>
                        <br></br>
                        <h2>Info</h2><hr />
                        <input id="target-base-name" type="tel" placeholder="Base name" class="btn" maxlength="20">
                        <input id="target-base-description" type="tel" placeholder="Base description" class="btn" maxlength="40">
                        <button id="save-config" class="btn-important" onclick="window.saveCurrentBaseConfig();">Save</button>
                    </div>
                `,
                functions: () => {
                    localStorage.totalSlots ||= 2;
                    getId("return-to-manager").onclick = () => document.querySelector("#more-baseSaver").click();
                    window.createBaseSlot = function() {
                        const oldTotalSlots = parseInt(localStorage.totalSlots);
                        const nextItem = oldTotalSlots + 1;
                        localStorage.totalSlots = nextItem;
                        localStorage[`baseslot${nextItem}`] = '|||';
                        document.querySelector("#more-baseSaver").click();
                    };

                    window.isSlotEmpty = function(index) {
                        const baseData = localStorage[`baseslot${index}`]?.split("|");
                        return !!baseData?.[0];
                    };

                    window.createBaseSlot = function() {
                        const oldTotalSlots = parseInt(localStorage.totalSlots);
                        const nextItem = oldTotalSlots + 1;
                        localStorage.totalSlots = nextItem;
                        localStorage[`baseslot${nextItem}`] = '|||';
                        document.querySelector("#more-baseSaver").click();
                    }

                    window.isSlotEmpty = function(index) {
                        const baseData = localStorage[`baseslot${index}`]?.split("|");
                        return !!baseData?.[0];
                    };

                    window.saveCurrentBaseConfig = function() {
                        const [design, title, description, date] = [...document.querySelectorAll("#target-base-design, #target-base-name, #target-base-description"), new Date().toLocaleDateString()];
                        const baseManagement = getId('base-management');
                        const currentBaseItem = baseManagement.getAttribute('current-item');
                        localStorage[`baseslot${currentBaseItem}`] = `${design.value}|${title.value}|${description.value}|${date}`;
                        return void game.ui.components.PopupOverlay.showHint('Current base configuration saved.');
                    };
                },
                bind: () => {
                    const baseContainer = getId('base-container');
                    const baseManagement = getId('base-management');
                    baseContainer.innerHTML = '';
                    baseManagement.style.display = "none";

                    for (let i = 1; i < parseInt(localStorage.totalSlots) + 1; i++) {
                        const baseItem = document.createElement('div');
                        const baseData = localStorage[`baseslot${i}`]?.split("|");
                        const baseId = genUUID();

                        baseData?.[1] && (baseData[1] = window.filterXSS(baseData[1]));
                        baseData?.[2] && (baseData[2] = window.filterXSS(baseData[2]));

                        baseItem.classList.add('base-card');
                        baseItem.id = `base-item-${i}`;
                        baseItem.setAttribute('item-base', i);
                        baseItem.innerHTML = `
                            <strong></strong>
                            <span style="color: rgba(255, 255, 255, 0.4);font-size: 13px;display: flex;"></span>
                            <span style="color: rgba(255, 255, 255, 0.4);font-size: 13px;display: flex;justify-content: flex-end;transform: translate(0px, -40px);"></span>
                        `;

                        const [title, description, date] = baseItem.children;
                        title.innerText = baseData?.[1] || "Unoccupied";
                        description.innerText = baseData?.[2] || "-";
                        date.innerText = baseData?.[3] || "";

                        baseItem.onclick = function() {
                            const [designField, titleField, descriptionField] = [...document.querySelectorAll("#target-base-design, #target-base-name, #target-base-description")];
                            designField.value = baseData?.[0] || "";
                            titleField.value = baseData?.[1] || "";
                            descriptionField.value = baseData?.[2] || "";

                            getId("encode-target-design").onclick = game.builder.recordBase.bind(game.builder);
                            getId("view-target-design").onclick = () => game.ui.components.PlacementOverlay.showOverlay(designField.value, 10000);

                            baseManagement.setAttribute('current-item', i);
                            for (let otherItem = 1; otherItem < parseInt(localStorage.totalSlots) + 1; otherItem++) {
                                const item = getId(`base-item-${otherItem}`);
                                const isThisItem = item.getAttribute('item-base') == i;
                                if (isThisItem) continue;
                                item.style.display = "none";
                            };

                            baseItem.style.transform = 'translate(0px, 306px)';
                            getId('add-base-slot').style.display = "none";
                            baseManagement.style.display = "block";

                            getId("build-design").onclick = function() {
                                game.ui.components.PopupOverlay.showConfirmation("Are you sure you want to build base?", 5000, function() {
                                    game.builder.buildBase(designField.value);
                                });
                            }
                        };
                        baseContainer.appendChild(baseItem);
                    }
                    const addItem = document.createElement('div');
                    addItem.classList.add('base-card');
                    addItem.id = "add-base-slot";
                    addItem.innerHTML = `
                        <strong style="position: absolute;top: 20px;left: 40px;opacity: 0.4;"><i class="fas fa-plus"></i></strong>
                        <span style="color: rgba(255, 255, 255, 0.4);display: flex;justify-content: flex-end;position: absolute;left: 65px;top: 20px;">Add another base slot</span>
                    `;
                    addItem.onclick = window.createBaseSlot;
                    baseContainer.appendChild(addItem);
                },
            },
        },
    },
    page1: {
        chatSpam: {
            name: `Chat Spammer`,
            description: `Annoys other players with messages.`,
            more: {
                html: `
                    <strong>Input the text that you want to spam.</strong>
                    <span>If no text is inputted, random messages with be sent.</span>
                    <input id="chat-spam-input" placeholder="Spam text here..." />
                `,
                functions: () => {
                    const spamInputElem = getId("chat-spam-input");
                    spamInputElem.oninput = game.spam.onchange.bind(game.spam);
                }
            },
            onCallback: () => { game.spam.start(); },
            offCallback: () => { game.spam.stop(); },
        },
        autoAim: {
            name: `Auto Aim`,
            description: "Aims at the nearest player.",
            more: null
        },
        autoHeal: {
            name: "Auto Heal",
            description: "Heals your player/pet automatically.",
            more: {
                html: `
                    <strong>Define the health percentage that the auto heal will kick in if below it.</strong>
                    <span>Default value is 25%.</span>
                    <input id="auto-heal-threshold" type="number" placeholder="Enter a valid percentile here..." min="1" max="100" value="25" />
                `,
                functions: () => {},
            }
        },
        frss: {
            name: "Exact Resource Counter",
            description: `"De-truncate" your resource counter.`,
            more: null,
        },
    },
    page2: {
        ground: {
            name: "Render Ground",
            description: "Toggles rendering of the ground.",
            more: null,
            onCallback: () => { game.renderer.ground.setVisible(true); },
            offCallback: () => { game.renderer.ground.setVisible(false); },
        },
        npcs: {
            name: "Render NPCs",
            description: "Toggles rendering of NPCs.",
            more: null,
            onCallback: () => { game.renderer.npcs.setVisible(true); },
            offCallback: () => { game.renderer.npcs.setVisible(false); },
        },
        projectiles: {
            name: "Render Projectiles",
            description: "Toggles rendering of projectiles.",
            more: null,
            onCallback: () => { game.renderer.projectiles.setVisible(true); },
            offCallback: () => { game.renderer.projectiles.setVisible(false); },
        },
        scenery: {
            name: "Render Environment",
            description: "Toggles rendering of the environment.",
            more: null,
            onCallback: () => { game.renderer.scenery.setVisible(true); },
            offCallback: () => { game.renderer.scenery.setVisible(false); },
        },
        groupingGrid: {
            name: "Grouping Grid",
            description: "<strong>Warning</strong>: conflicts with ground rendering.",
            more: null,
            onCallback: () => {
                game.renderer.ground.attachments[0].attachments[1].setAlpha(0.625);
                game.renderer.ground.attachments[0].attachments[1].setScale(200 / 48);
                game.renderer.ground.attachments[0].attachments[1].sprite.width /= 200 / 48;
                game.renderer.ground.attachments[0].attachments[1].sprite.height /= 200 / 48;
            },
            offCallback: () => {
                game.renderer.ground.attachments[0].attachments[1].setAlpha(1);
                game.renderer.ground.attachments[0].attachments[1].setScale(1);
                game.renderer.ground.attachments[0].attachments[1].sprite.width *= 200 / 48;
                game.renderer.ground.attachments[0].attachments[1].sprite.height *= 200 / 48;
            },
        }
    },
    /*
    page3: {}, */
    page4: {
        autoUpTowers: {
            name: "Building Support",
            description: "Auto-upgrades low-health towers.",
            more: {
                html: `
                    <strong>Define the health percentage to upgrade buildings when their health is below it.</strong>
                    <span>Default value is 20%.</span>
                    <input id="auto-tower-upgrade-threshold" type="number" placeholder="Enter a valid percentile here..." min="1" max="99" value="20" />
                `,
                functions: () => {},
            }
        }
    },
};

function refreshMore(page) {
    const container = document.querySelector("#" + page + " > div.hud-settings-more");
    for (let children of container.children) {
        children.style.display = "none";
    }
}

function refreshPage(pageState, page) {
    for (let i = 0; i < 5; i++) getId(`page${i}`).style.display = "none";
};

function setPage(page) {
    const pageState = page;
    switch(page) {
        case "forward":
            page = parseInt(getClass('hud-settings-grid')[0].getAttribute('page')) + 1;
            break;
        case "backwards":
            page = parseInt(getClass('hud-settings-grid')[0].getAttribute('page')) - 1;
            break;
    }
    refreshPage(pageState, page);
    if (page < 0) page = 4;
    if (page > 4) page = 0;
    getId(`page${page}`).style.display = "flex";
    getId('page-name').innerText = pageName_enum[page];
    getClass('hint-controls')[0].innerHTML = hint_enum[page];
    getClass('hud-settings-grid')[0].setAttribute('page', page);
};
setPage('0');
getId('back-page').addEventListener('click', () => setPage("backwards"));
getId('forward-page').addEventListener('click', () => setPage("forward"));

// @Misc.
Game.currentGame.world.removeEntity = function(t) {
    if (["Tree", "Stone", "NeutralCamp"].indexOf(this.entities[t].fromTick.model) > -1) return;
    this.renderer.remove(this.entities[t]);
    const model = this.entities[t].currentModel;
    if (Game.currentGame.getModelEntityPooling(model.modelName)) {
        model.reset();
        this.modelEntityPool[model.modelName].push(model);
    }
    this.entityGrid.removeEntity(parseInt(t));
    delete this.entities[t];
};

const toolbar_enum = {
    "Pickaxe": { index: 0, hasNextTier: true },
    "Spear": { index: 1, hasNextTier: true },
    "Bow": { index: 2, hasNextTier: true },
    "Bomb": { index: 3, hasNextTier: true },
    "HealthPotion": { index: 4, hasNextTier: false },
    "PetHealthPotion": { index: 5, hasNextTier: false },
};
getClass('hud-top-center')[0].insertAdjacentHTML('afterbegin', `
    <div class="hud-center-toolbar">
        ${Object.keys(toolbar_enum).map(itemName => {
            return `<a class="hud-center-toolbar-item" data-item="${itemName}" data-tier="1"></a>`;
        }).join("\n")}
    </div>
`);
[...document.querySelector("#hud > div.hud-top-center > div").children].forEach(elem => {
    elem.onclick = () => {
        const itemName = elem.getAttribute("data-item");
        const itemTier = elem.getAttribute("data-tier");
        game.network.sendRpc({name: "BuyItem", itemName, tier: parseInt(itemTier)});
    };
});

game.network.addRpcHandler("SetItem", ({itemName, tier}) => {
    if (Object.keys(toolbar_enum).indexOf(itemName) > -1) {
        const { index, hasNextTier } = toolbar_enum[itemName];
        if (!hasNextTier) return;
        document.querySelector("#hud > div.hud-top-center > div").children[index].classList[tier == 8 ? "add" : "remove"]("is-disabled");
        document.querySelector("#hud > div.hud-top-center > div").children[index].setAttribute("data-tier", tier + 1);
    };
});

/* @Functions */
// @Options
game.options.options = {
    autoBow: false,
    getRSS: false,
    AHRC: false,
    rebuild: false,
    autoUpgrade: false,
    heal: true,
    revive: true,
    frss: false,
    chatSpam: false,
    autoAim: false,
    wallBlock: false,
    autoHeal: true,

    ground: true,
    npcs: true,
    projectiles: true,
    scenery: true,
    groupingGrid: false,

    autoUpTowers: false,
}

// @Marker
game.markers = {
    currentIndex: 0,
    allMarkers: [],
    placeMarker: function({x, y, id, timeout, shouldIndicateIndex}) {
        id ||= Math.random();
        shouldIndicateIndex && (this.currentIndex++);
        const markerLeft = parseInt(Math.round(x / game.world.getWidth() * 100)) - 4;
        const markerTop = parseInt(Math.round(y / game.world.getHeight() * 100)) - 14;

        const markerElem = document.createElement("div");
        markerElem.style.left = `${markerLeft}%`;
        markerElem.style.top = `${markerTop}%`;
        markerElem.style.display = "block";
        markerElem.style.color = "white";
        markerElem.style.position = "absolute";
        markerElem.classList.add("map-display");
        markerElem.id = id;
        markerElem.innerHTML = `<i class='fa fa-map-marker'>${shouldIndicateIndex ? this.currentIndex : ""}</i>`;
        getId("hud-map").insertAdjacentElement("beforeend", markerElem);

        this.allMarkers.push({x, y});
        timeout && setTimeout(() => {
            getId(`${id}`).remove();
        }, 240000);
    }
}

// @RSSOverhead
game.rssOverhead = {
    allowedRSS: true,
    assignOldTick: function(player, playerTick) {
        const [wood_1, stone_1, gold_1, token_1, px_1, py_1, info] = playerTick;
        player.targetTick.oldWood = wood_1;
        player.targetTick.oldStone = stone_1;
        player.targetTick.oldGold = gold_1;
        player.targetTick.oldToken = token_1;
        player.targetTick.oldPX = px_1;
        player.targetTick.oldPY = py_1;
        player.targetTick.info = info;
        player.targetTick.name = player.targetTick.info;
    },
    onTick: function(entities) {
        const options = game.options.options;
        if (options.getRSS) !this.allowedRSS && (this.allowedRSS = true);
        if (options.getRSS || this.allowedRSS) {
            for (let uid in entities) {
                const player = game.world.entities[uid];
                if (!!player?.fromTick?.name) {
                    let wood_1 = counter(player.targetTick.wood);
                    let stone_1 = counter(player.targetTick.stone);
                    let gold_1 = counter(player.targetTick.gold);
                    let token_1 = counter(player.targetTick.token);
                    let px_1 = counter(player.targetTick.position.x);
                    let py_1 = counter(player.targetTick.position.y);
                    let timeout_1 = player.targetTick.isPaused ? "On Timeout" : "";
                    if (options.getRSS && !player.targetTick.oldName) {
                        player.targetTick.oldName = player.targetTick.name;
                        let info = `
  ${window.filterXSS(player.targetTick.oldName)}; score: ${player.targetTick.score.toLocaleString()}
  UID: ${player.targetTick.uid}
  W: ${wood_1}, S: ${stone_1}, G: ${gold_1}, T: ${token_1}
  partyId: ${Math.round(player.targetTick.partyId)}
  timeDead: ${msToTime(player.targetTick.timeDead)}
                    ${timeout_1}




`;
                        this.assignOldTick(player, [wood_1, stone_1, gold_1, token_1, px_1, py_1, info]);
                    }
                    if (!options.getRSS && player.targetTick.oldName) {
                        player.targetTick.info = player.targetTick.oldName;
                        player.targetTick.name = player.targetTick.info;
                        player.targetTick.oldName = null;
                    }
                    if (options.getRSS) {
                        if (player.targetTick.oldGold !== gold_1
                            || player.targetTick.oldWood !== wood_1
                            || player.targetTick.oldStone !== stone_1
                            || player.targetTick.oldToken !== token_1
                            || player.targetTick.oldPX !== px_1
                            || player.targetTick.oldPY !== py_1) {
                            let info = `
  ${window.filterXSS(player.targetTick.oldName)}; score: ${player.targetTick.score.toLocaleString()}
  UID: ${player.targetTick.uid}
  W: ${wood_1}, S: ${stone_1}, G: ${gold_1}, T: ${token_1}
  partyId: ${Math.round(player.targetTick.partyId)}
  timeDead: ${msToTime(player.targetTick.timeDead)}
                    ${timeout_1}




`;
                            this.assignOldTick(player, [wood_1, stone_1, gold_1, token_1, px_1, py_1, info]);
                        }
                    }
                }
            }
        }
        if (!options.getRSS) this.allowedRSS = false;
    },
}

// @AHRC
game.ahrc = {
    checkedHarvesters: new Set(),
    workingHarvesters: new Set(),
    onTick: function() {
        const options = game.options.options;
        if (options.AHRC) {
            for (let uid in game.world.entities) {
                const entity = game.world.entities[uid];
                if (entity.targetTick.model == "Harvester" && entity.targetTick.partyId == game.ui.playerPartyId && game.ui.playerTick.gold > 0.69) {
                    if (this.checkedHarvesters.has(uid)) {
                        if (entity.fromTick.stone !== entity.targetTick.stone || entity.fromTick.wood !== entity.targetTick.wood) {
                            this.workingHarvesters.add(uid);
                        };
                    } else {
                        this.checkedHarvesters.add(uid);
                        game.network.sendRpc({name: "AddDepositToHarvester", uid: parseInt(uid), deposit: 0.69});
                    };
                };
                if (this.workingHarvesters.has(uid)) {
                    const amount = entity.fromTick.tier * 0.05 - 0.02;
                    game.network.sendRpc({name: "AddDepositToHarvester", uid: parseInt(uid), deposit: amount});
                    game.network.sendRpc({name: "CollectHarvester", uid: parseInt(uid)});
                };
            };
        }
    }
}

// @Rebuilder
game.rebuilder = {
    savedTowers: new Map(),
    shouldBeReplaced: [],
    goldStash: null,
    reset: function() {
        this.savedTowers = new Map();
    },
    saveTowers: function() {
        for (let i in game.ui.buildings) {
            const {x, y, type} = game.ui.buildings[i];
            this.savedTowers.set(parseInt(i), {x, y, type});
        }
    },
    onLocalBuilding: function(buildings) {
        const options = game.options.options;
        const allBuildings = Object.values(game.ui.buildings);
        this.goldStash = allBuildings.find(building => building.type == "GoldStash");
        if (options.rebuild) {
            for (let i in buildings) {
                const {dead, uid, x, y, type, tier} = buildings[i];
                if (dead === 1) {
                    if (this.savedTowers.get(uid) !== undefined) {
                        this.shouldBeReplaced.push({x, y, type});
                    }
                } else {
                    const oldBuilding = getByValue(this.savedTowers, {x, y, type});
                    if (oldBuilding) {
                        this.savedTowers.set(parseInt(uid), {x, y, type});
                        this.shouldBeReplaced = this.shouldBeReplaced.filter(e => !equal(e, {x, y, type}));
                    }
                }
            }
        }
    },
    onTick: function(entities) {
        const options = game.options.options;
        if (options.rebuild && this.goldStash) {
            for (let i = this.shouldBeReplaced.length - 1; i >= 0; i--) {
                const deadTower = this.shouldBeReplaced[i];
                const {x, y, type} = deadTower;
                game.network.sendRpc({name: "MakeBuilding", type, x, y, yaw: 0});
            }
        }
    },
};

// @Builder
game.builder = {
    towerCodes: ["Wall", "Door", "SlowTrap", "ArrowTower", "CannonTower", "MeleeTower", "BombTower", "MagicTower", "GoldMine", "Harvester"],

    buildBase: function(design) {
        const goldStash = game.rebuilder.goldStash;

        if (typeof design !== "string") throw new Error("Argument must be given as a string.");
        if (goldStash === undefined) throw new Error("You must have a gold stash to be able to use this.");

        const towers = design.split(";");

        for (let towerStr of towers) {
            const tower = towerStr.split(",");

            if (tower[0] === "") continue;
            if (tower.length < 4) throw new Error(`${JSON.stringify(tower)} contains an issue that must be fixed before this design can be replicated.`);

            Game.currentGame.network.sendRpc({
                name: "MakeBuilding",
                type: this.towerCodes[parseInt(tower[0])],
                x: goldStash.x - parseInt(tower[1]),
                y: goldStash.y - parseInt(tower[2]),
                yaw: parseInt(tower[3])
            });
        };
    },

    recordBase: function() {
        const goldStash = game.rebuilder.goldStash;
        let baseStr = "";
        for (let i in game.ui.buildings) {
            const building = game.ui.buildings[i];
            if (this.towerCodes.indexOf(building.type) < 0) continue;

            let yaw = 0;

            if (["Harvester", "MeleeTower"].includes(building.type)) {
                if (game.world.entities[building.uid] !== undefined) yaw = game.world.entities[building.uid].targetTick.yaw;
            }
            baseStr += `${this.towerCodes.indexOf(building.type)},${goldStash.x - building.x},${goldStash.y - building.y},${yaw};`;
        }
        getId('target-base-design').value = baseStr;
    }
};

// @AutoAim
game.autoAim = {
    targets: [],
    onTick: function() {
        const options = game.options.options;
        if (options.autoAim) {
            this.targets = [];
            const entities = Object.values(game.world.entities);

            for (let entity of entities) {
                if (entity.fromTick.model == "GamePlayer" && entity.targetTick.partyId !== game.ui.playerPartyId && entity.fromTick.dead == 0) {
                    this.targets.push(entity.fromTick);
                };
            };

            if (this.targets.length > 0) {
                const myPos = game.ui.playerTick.position;
                this.targets.sort((a, b) => {
                    return measureDistance(myPos, a.position) - measureDistance(myPos, b.position);
                });

                const target = this.targets[0];
                let reversedAim = game.inputPacketCreator.screenToYaw((target.position.x - myPos.x) * 100, (target.position.y - myPos.y) * 100);
                game.inputPacketCreator.lastAnyYaw = reversedAim;
                game.network.sendPacket(3, {mouseMoved: reversedAim});
            }
        };
    }
};

// @AutoUpTowers
game.autoUpTowers = {
    shouldHaveBeenUpgraded: {},
    onTick: function({entities}) {
        const options = game.options.options;
        if (options.autoUpTowers) {
            for (let uid in entities) {
                const currentEntity = entities[uid];
                const worldEntity = game.world.entities[uid];

                if (currentEntity == true || worldEntity == undefined) continue;
                if (uid in this.shouldHaveBeenUpgraded && worldEntity.targetTick.tier != this.shouldHaveBeenUpgraded[uid]) continue;

                if (uid in game.ui.buildings && typeof currentEntity.health == 'number') {
                    const buildingHealth = (currentEntity.health / worldEntity.targetTick.maxHealth) * 100;
                    const threshold = getId("auto-tower-upgrade-threshold").valueAsNumber;
                    if (buildingHealth <= threshold && worldEntity.targetTick.tier != game.ui.components.BuildingOverlay.getGoldStashTier()) {
                        game.network.sendRpc({name: "UpgradeBuilding", uid: parseInt(uid)});
                        this.shouldHaveBeenUpgraded[uid] = structuredClone(worldEntity.targetTick.tier);
                    };
                };
            };
        };
    },
};

// @Buildings
game.ui.components.PlacementOverlay.showOverlay = function (design, timeout) {
    this.buildingId && this.cancelPlacing();
    const goldStash = game.rebuilder.goldStash;

    if (typeof design !== "string") throw new Error("Argument must be given as a string.");
    if (goldStash === null) throw new Error("You must have a gold stash to be able to use this.");

    this.overlayEntities && (this.overlayEntities.length > 0 && this.overlayEntities.map(e => game.renderer.ui.removeAttachment(e)));
    this.overlayEntities = [];
    this.overlayDesign = design;
    this.isShowingOverlay = true;
    game.renderer.follow(game.world.entities[goldStash.uid]);
    setTimeout(() => {
        const towers = design.split(";"),
              schema = this.ui.getBuildingSchema();

        for (let towerStr of towers) {
            const towerData = towerStr.split(",");
            const [type, xWorld, yWorld, yaw] = towerData;
            const towerLength = towerData.length

            if (type === "") continue;
            if (towerLength.length < 4) throw new Error(`${JSON.stringify(towerLength)} contains an issue that must be fixed before this design can be replicated.`);

            const buildingType = schema[game.builder.towerCodes[parseInt(type)]],
                  placeholderEntity = Game.currentGame.assetManager.loadModel(buildingType.modelName, {}),
                  { x, y } = game.renderer.worldToUi(goldStash.x - parseInt(xWorld), goldStash.y - parseInt(yWorld));
            placeholderEntity.setAlpha(0.5);
            placeholderEntity.setRotation(parseInt(yaw));
            placeholderEntity.setPosition(x, y);

            Game.currentGame.renderer.ui.addAttachment(placeholderEntity);
            this.overlayEntities.push(placeholderEntity);
        }
        timeout && setTimeout(game.ui.components.PlacementOverlay.hideOverlay.bind(this), timeout);
    }, 50);
}

game.ui.components.PlacementOverlay.hideOverlay = function() {
    for (let entity of this.overlayEntities) game.renderer.ui.removeAttachment(entity);
    game.renderer.follow(game.world.entities[game.world.myUid]);
    this.isShowingOverlay = false;
    this.overlayDesign = null;
}

game.ui.components.PlacementOverlay.onResize = function() {
    this.isShowingOverlay && game.ui.components.PlacementOverlay.showOverlay(this.overlayDesign);
}

game.ui.components.BuildingOverlay.createResourceCostString = function (schema, schemaTier, amountOfBuildings) {
    void 0 === schemaTier && (schemaTier = 0x1);
    void 0 === amountOfBuildings && (amountOfBuildings = 0x1);
    var totalCost = [],
        resources = {
            'wood': `wood`,
            'stone': `stone`,
            'gold': 'gold',
            'token': 'tokens'
        },
        playerTick = Game.currentGame.ui.getPlayerTick();
    for (var resource in resources) {
        var resourceCost = resource + `Costs`;
        if (schema[resourceCost] && schema[resourceCost][schemaTier - 1]) {
            var currentTotalCost = schema[resourceCost][schemaTier - 1] * amountOfBuildings,
                canAfford = playerTick && playerTick[resource] >= currentTotalCost;
            canAfford
                ? totalCost.push('<span\x20class=\x22hud-resource-' + resources[resource] + '\x22>' + currentTotalCost.toLocaleString() + '\x20' + resources[resource] + `</span>`)
            : totalCost.push(`<span class="hud-resource-` + resources[resource] + ` hud-resource-low">` + currentTotalCost.toLocaleString() + '\x20' + resources[resource] + `</span>`);
        }
    }
    return totalCost.length > 0 ? totalCost.join(',\x20') : `<span class="hud-resource-free">Free</span>`;
}

game.ui.components.BuildingOverlay.createResourceRefundString = function (buildingType, buildingSchema, tier) {
    void 0 === tier && (tier = 1);
    var totalCost = [],
        buildingsByTier = {},
        buildings = Object.values(game.ui.buildings).filter(e => e.type == buildingType),
        resources = {
            'wood': `wood`,
            'stone': `stone`,
            'gold': `gold`,
            'token': 'tokens'
        };
    for (let building of buildings) {
        buildingsByTier[building.tier] ||= 0;
        buildingsByTier[building.tier]++;
    }
    for (var resource in resources) {
        var resourceCost = resource + `Costs`;
        if (buildingSchema[resourceCost]) {
            var totalTierCost = 0;
            if (this.shouldUpgradeAll) {
                for (let i = 1; i <= tier; i++) {
                    totalTierCost = Math.floor(
                        buildingSchema[resourceCost].slice(0, i)
                        .reduce((prev, curr) => prev + curr, 0) / 2
                    ) * buildingsByTier[i];
                }
            } else totalTierCost = Math.floor(buildingSchema[resourceCost].slice(0, tier).reduce((prev, curr) => prev + curr, 0) / 2);
            totalTierCost && totalCost.push('<span\x20class=\x22hud-resource-' + resources[resource] + '\x22>' + totalTierCost.toLocaleString() + '\x20' + resources[resource] + `</span>`);
        }
    }
    return totalCost.length > 0 ? totalCost.join(',\x20') : `<span class="hud-resource-free">None</span>`;
}

game.ui.components.BuildingOverlay.sellBuilding = function () {
    if (this.buildingUid) {
        if ('GoldStash' == this.buildingId) {
            game.ui.components.PopupOverlay.showConfirmation(`Are you sure you want to delete all buildings?`, 5000, function() {
                sellAll();
            });
            return this.stopWatching();
        }
        if (this.shouldUpgradeAll) {
            const id = this.buildingId;
            game.ui.components.PopupOverlay.showConfirmation(`Are you sure you want to delete all buildings of this type?`, 5000, function() {
                sellAllByType(id);
            });
        } else Game.currentGame.network.sendRpc({name: 'DeleteBuilding', uid: this.buildingUid});
    }
}

game.ui.components.BuildingOverlay.startWatching = function(buildingId) {
    this.buildingUid && this.stopWatching();
    let buildings = this.ui.getBuildings(),
        building = buildings[buildingId];
    if (!building) return;
    this.buildingUid = buildingId;
    this.buildingId = building.type;
    this.buildingTier = building.tier;
    let schema = this.ui.getBuildingSchema(),
        buildingSchema = schema[this.buildingId];
    if ('GoldStash' == this.buildingId) {
        var world = Game.currentGame.world,
            cellSize = world.entityGrid.getCellSize();
        this.rangeIndicator = game.assetManager.loadModel('RangeIndicatorModel', {
            'width': this.maxStashDistance * cellSize * 2,
            'height': this.maxStashDistance * cellSize * 2
        });
        Game.currentGame.renderer.ground.addAttachment(this.rangeIndicator);
    } else {
        buildingSchema.rangeTiers && (
            this.rangeIndicator = game.assetManager.loadModel('RangeIndicatorModel', {
                'isCircular': true,
                'radius': buildingSchema.rangeTiers[this.buildingTier - 1] * 0.57071
            }), Game.currentGame.renderer.ground.addAttachment(this.rangeIndicator)
        );
    };
    this.componentElem.innerHTML = `<div class="hud-tooltip-building">
            <h2>` + buildingSchema.name + `</h2>
            <h3>Tier <span class="hud-building-tier">` + this.buildingTier + `</span> Building</h3>
            <div class="hud-tooltip-health">
                <span class="hud-tooltip-health-bar" style="width:100%;"></span>
            </div>
            <div class="hud-tooltip-body">
                <div class="hud-building-stats"></div>
                <p class="hud-building-actions">
                    <span class="hud-building-dual-btn">
                        <a class="btn btn-purple hud-building-deposit">Refuel</a>
                        <a class="btn btn-gold hud-building-collect">Collect</a>
                    </span>
                    <a class="btn btn-gold hud-building-upgrade">Upgrade</a>
                    <a class="btn btn-red hud-building-sell">Sell</a>
                </p>
            </div>
        </div>`;
    this.tierElem = this.componentElem.querySelector(`.hud-building-tier`);
    this.healthBarElem = this.componentElem.querySelector(`.hud-tooltip-health-bar`);
    this.statsElem = this.componentElem.querySelector(`.hud-building-stats`);
    this.actionsElem = this.componentElem.querySelector(`.hud-building-actions`);
    this.depositElem = this.componentElem.querySelector(`.hud-building-deposit`);
    this.dualBtnElem = this.componentElem.querySelector(`.hud-building-dual-btn`);
    this.collectElem = this.componentElem.querySelector(`.hud-building-collect`);
    this.upgradeElem = this.componentElem.querySelector('.hud-building-upgrade');
    this.sellElem = this.componentElem.querySelector('.hud-building-sell');
    `Harvester` !== this.buildingId && (this.dualBtnElem.style.display = `none`);
    this.depositElem.addEventListener(`click`, this.depositIntoBuilding.bind(this));
    this.collectElem.addEventListener(`click`, this.collectFromBuilding.bind(this));
    this.upgradeElem.addEventListener(`click`, this.upgradeBuilding.bind(this));
    this.sellElem.addEventListener(`click`, this.sellBuilding.bind(this));
    this.show();
    this.update();
}
game.ui.components.BuildingOverlay.update = function () {
    if (this.buildingUid) {
        const buildingEntity = Game.currentGame.world.getEntityByUid(this.buildingUid);
        if (!buildingEntity) return void this.stopWatching();

        let renderer = Game.currentGame.renderer,
            buildingUiPosition = renderer.worldToScreen(buildingEntity.getPositionX(), buildingEntity.getPositionY()),
            buildingTick = buildingEntity.getTargetTick(),
            buildingsSchema = this.ui.getBuildingSchema(),
            buildings = this.ui.getBuildings(),
            buildingSchema = buildingsSchema[this.buildingId],
            building = buildings[this.buildingUid];
        if (!building) return void this.stopWatching();

        let buildingHeight = buildingSchema.gridHeight,
            buildingScale = (buildingSchema.gridWidth, buildingHeight / 2 * 48 * (renderer.getScale() / window.devicePixelRatio)),
            buildingTier = building.tier,
            buildingSchemaTier = 1,
            isBuildingMaxed = false,
            isBuildingAtMaxTier = false,
            currentStats = {},
            nextTierStats = {},
            sameBuildings = 1,
            buildingStats = {
                'health': `Health`,
                'damage': `Damage`,
                'range': 'Range',
                'gps': 'Gold/Sec',
                'harvest': `Harvest/Sec`,
                'harvestCapacity': `Capacity`
            };

        if (buildingSchema.tiers) {
            const stashTier = this.getGoldStashTier();
            building.tier < buildingSchema.tiers ? (buildingSchemaTier = building.tier + 1, isBuildingMaxed = false) : (buildingSchemaTier = building.tier, isBuildingMaxed = true);
            isBuildingAtMaxTier = !isBuildingMaxed && (building.tier < stashTier || `GoldStash` === this.buildingId);
        }
        for (let buildingStat in buildingStats) {
            let currentStat = `<small>&mdash;</small>`,
                nextTierStat = '<small>&mdash;</small>';
            buildingSchema[buildingStat + `Tiers`] && (
                currentStat = buildingSchema[buildingStat + `Tiers`][buildingTier - 1].toLocaleString(),
                isBuildingMaxed || (
                    nextTierStat = buildingSchema[buildingStat + 'Tiers'][buildingSchemaTier - 1].toLocaleString()
                ),
                currentStats[buildingStat] = '<p>' + buildingStats[buildingStat] + `: <strong class="hud-stats-current">` + currentStat + '</strong></p>',
                nextTierStats[buildingStat] = `<p>` + buildingStats[buildingStat] + ':\x20<strong\x20class=\x22hud-stats-next\x22>' + nextTierStat + `</strong></p>`
            );
        }
        if (this.shouldUpgradeAll) {
            sameBuildings = 0;
            for (let buildingUid in buildings) {
                // parseInt(buildingUid); tf
                buildings[buildingUid].type === this.buildingId && buildings[buildingUid].tier === building.tier && sameBuildings++;
            }
        }
        let costString = this.createResourceCostString(buildingSchema, buildingSchemaTier, sameBuildings),
            refundString = this.createResourceRefundString(this.buildingId, buildingSchema, building.tier),
            buildingHealth = Math.round(buildingTick.health / buildingTick.maxHealth * 100);

        buildingTick.partyId !== this.ui.getPlayerPartyId() ? this.actionsElem.style.display = `none` : this.actionsElem.style.display = 'block';
        this.tierElem.innerHTML = building.tier.toString();
        this.buildingTier = building.tier;
        this.healthBarElem.style.width = buildingHealth + '%';
        if (Object.keys(currentStats).length > 0) {
            let currentStatValues = '',
                nextTierStatValues = '';
            for (let stat in currentStats) currentStatValues += currentStats[stat];
            for (let stat in nextTierStats) nextTierStatValues += nextTierStats[stat];
            this.statsElem.innerHTML = `
                <div class="hud-stats-current hud-stats-values">
                    ` + currentStatValues + `
                </div>
                <div class="hud-stats-next hud-stats-values">
                    ` + nextTierStatValues + `
                </div>
            `;
        } else this.statsElem.innerHTML = '';
        if (`Harvester` === this.buildingId) {
            let depositAmount = Math.floor(buildingTick.depositMax / 10),
                isDepositPossible = buildingTick.depositMax - buildingTick.deposit < depositAmount;
            isDepositPossible ? this.depositElem.classList.add('is-disabled') : this.depositElem.classList.remove(`is-disabled`);
            this.shouldUpgradeAll
                ? this.depositElem.innerHTML = 'Refuel\x20All\x20<small>(' + (depositAmount * sameBuildings).toLocaleString() + ` gold)</small>`
                : this.depositElem.innerHTML = 'Refuel\x20<small>(' + depositAmount.toLocaleString() + ` gold)</small>`;
        }
        isBuildingAtMaxTier ? this.upgradeElem.classList.remove(`is-disabled`) : this.upgradeElem.classList.add('is-disabled');
        this.shouldUpgradeAll ? (
            this.upgradeElem.innerHTML = `Upgrade All <small>(` + costString + ')</small>',
            this.sellElem.innerHTML = `Sell All <small>(` + refundString + ')</small>'
        ) : (
            this.upgradeElem.innerHTML = `Upgrade <small>(` + costString + `)</small>`,
            this.sellElem.innerHTML = `Sell <small>(` + refundString + `)</small>`
        );
        `GoldStash` == this.buildingId ? (
            this.sellElem.innerHTML = `Sell All Buildings`,
            this.sellElem.classList.remove(`is-disabled`),
            this.isSellingAll && this.sellElem.classList.add(`is-disabled`)
        ) : this.ui.getPlayerPartyCanSell() ? (
            this.sellElem.classList.remove(`is-disabled`)
        ) : (
            this.sellElem.classList.add(`is-disabled`),
            this.sellElem.innerHTML = `Need Permission to Sell`
        );
        this.componentElem.style.left = buildingUiPosition.x - this.componentElem.offsetWidth / 0x2 + 'px';
        this.componentElem.style.top = buildingUiPosition.y - buildingScale - this.componentElem.offsetHeight - 0x14 + 'px';
        this.rangeIndicator && this.rangeIndicator.setPosition(buildingEntity.getPositionX(), buildingEntity.getPositionY());
    }
}

// @Spammer
game.spam = {
    literallyEveryUnicodeEver: null,
    randomSpamText: [
        // `${garbageGenerator()} BIG RAID ${garbageGenerator()}`,
        `?verify`,
        "hi",
        "ez",
        "Super Idol的笑容都没你的甜八月正午的阳光都没你耀眼热爱 105 °C的你滴滴清纯的蒸馏水",
        "Zǎoshang hǎo zhōngguó xiànzài wǒ yǒu BING CHILLING 🥶🍦",
        "Wǒ hěn xǐhuān BING CHILLING 🥶🍦 Dànshì sùdù yǔ jīqíng 9 bǐ BING CHILLING 🥶🍦",
    ],
    spamInterval: null,
    spamText: '',
    fetchUnicode: async function() {
        if (!this.literallyEveryUnicodeEver) {
            this.literallyEveryUnicodeEver = await fetch('https://raw.githubusercontent.com/bits/UTF-8-Unicode-Test-Documents/master/UTF-8_sequence_unseparated/utf8_sequence_0-0xffff_assigned_printable_unseparated.txt')
                .then(response => response.text())
                .then(data => { return data; });
            this.randomSpamText.push(`${garbageGenerator()} BIG RAID ${garbageGenerator()}`);
        }
    },
    onchange: function({target}) {
        this.spamText = target.value;
    },
    start: function() {
        this.spamInterval = setInterval(() => {
            let text;
            if (this.spamText !== '') text = `${garbageGenerator()} ${this.spamText} ${garbageGenerator()}`;
            else text = getRandomItem(this.randomSpamText);
            game.network.sendRpc({
                name: "SendChatMessage",
                channel: "Local",
                message: text
            });
        }, 1050);
    },
    stop: function() {
        clearInterval(this.spamInterval);
    }
};

// @Clones

function genUUID() {
    return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(
        /[018]/g, c => (
            c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4
        ).toString(16)
    );
};

function getRandomItem(array) {
    return array[Math.floor(Math.random() * array.length)];
};

function garbageGenerator(garbageLength = 25) {
    let garbageCharacters = game.spam.literallyEveryUnicodeEver;
    let garbage = "";
    for (let i = 0; i < garbageLength; i++) garbage += garbageCharacters[Math.floor(Math.random() * garbageCharacters.length)];
    return garbage;
}

function isEven(number) {
    return number % 2 === 0;
};
function isEntityOccupied(x, y) {
    const cell = game.world.entityGrid.getCellIndexes(x, y, { width: 1, height: 1 });
    const entity = game.world.entityGrid.getEntitiesInCell(cell);
    return Object.keys(entity).length > 0;
};
function placeWallBlock(blockWidth, blockHeight, data) {
    const widthOffset = isEven(blockWidth) ? 0 : 1;
    const heightOffset = isEven(blockHeight) ? 0 : 1;
    for (let x = -((blockWidth - widthOffset) / 2) * 48; x <= (blockWidth - widthOffset) / 2 * 48; x += 48) {
        for (let y = -((blockHeight - heightOffset) / 2) * 48; y <= (blockHeight - heightOffset) / 2 * 48; y += 48) {
            const posX = data.x + x,
                  posY = data.y + y,
                  shouldPlace = !isEntityOccupied(posX, posY);
            shouldPlace && game.network.sendPacket(9, {name: "MakeBuilding", type: "Wall", x: posX, y: posY, yaw: 0});
        };
    };
};

function sellAllByType(type) {
    if (!game.ui.playerPartyCanSell) return;
    let lastSoldBuilding = null;
    let allBuildings = [Infinity, ...Object.values(game.ui.buildings).filter(e => e.type == type)];
    let sellInterval = () => {
        if (!game.ui.buildings[lastSoldBuilding?.uid]) allBuildings.shift();
        if (window.sellBreak || allBuildings.length == 0) return;
        const target = allBuildings[0];
        if (target !== undefined && !game.ui.buildings[target]?.dead) {
            lastSoldBuilding = target;
            Game.currentGame.network.sendRpc({
                name: "DeleteBuilding",
                uid: parseInt(target.uid)
            });
            setTimeout(sellInterval, 50);
        };
    };
    sellInterval();
};

function sellAll() {
    game.ui.components.BuildingOverlay.isSellingAll = true;
    const sellInterval = () => {
        if (window.sellBreak) return;
        if (Object.keys(game.ui.buildings).length > 1 && game.ui.playerPartyCanSell) {
            Game.currentGame.network.sendRpc({
                name: "DeleteBuilding",
                uid: parseInt(Object.keys(game.ui.buildings)[1])
            });
            setTimeout(() => { sellInterval(); }, 100);
            Object.keys(game.ui.buildings).length == 2 && (game.ui.components.BuildingOverlay.isSellingAll = false);
        }
    }
    sellInterval();
}

const measureDistance = (obj1, obj2) => {
    if (!(obj1.x && obj1.y && obj2.x && obj2.y)) return Infinity;
    let xDif = obj2.x - obj1.x;
    let yDif = obj2.y - obj1.y;
    return Math.abs((Math.pow(xDif, 2)) + (Math.pow(yDif, 2)));
};

function getByValue(map, searchValue) {
    for (let [key, value] of map.entries()) {
        if (equal(value, searchValue)) return key;
    }
}

function equal(a, b) {
    if (a === b) return true;

    if (a && b && typeof a == 'object' && typeof b == 'object') {
        if (a.constructor !== b.constructor) return false;

        var length, i, keys;

        if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();
        if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();

        keys = Object.keys(a);
        length = keys.length;
        if (length !== Object.keys(b).length) return false;

        for (i = length; i-- !== 0;) {
            if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
        }

        for (i = length; i-- !== 0;) {
            var key = keys[i];
            if (!equal(a[key], b[key])) return false;
        }

        return true;
    }

    return a!==a && b!==b;
};

const addFunctionToElem = ({ id, option, buttonText, colors, onCallback, offCallback }) => {
    const options = game.options.options;
    colors ||= 'btn-red?btn-theme';

    getId(id).addEventListener('click', e => {
        let toggleColor = colors.split('?');
        if (options[option] === false) {
            options[option] = true;
            toggleColor[1] && e.target.classList.remove(toggleColor[1]);
            e.target.classList.add(toggleColor[0]);
            buttonText === undefined || (e.target.innerText = `Disable ${buttonText}`);
            onCallback?.();
        } else {
            options[option] = false;
            e.target.classList.remove(toggleColor[0]);
            toggleColor[1] && e.target.classList.add(toggleColor[1]);
            buttonText === undefined || (e.target.innerText = `Enable ${buttonText}`);
            offCallback?.();
        }
    });
}

function counter(e = 0) {
    if (e <= -0.99949999999999999e24) {
        return Math.round(e/-1e23)/-10 + "TT";
    }
    if (e <= -0.99949999999999999e21) {
        return Math.round(e/-1e20)/-10 + "TB";
    }
    if (e <= -0.99949999999999999e18) {
        return Math.round(e/-1e17)/-10 + "TM";
    }
    if (e <= -0.99949999999999999e15) {
        return Math.round(e/-1e14)/-10 + "TK";
    }
    if (e <= -0.99949999999999999e12) {
        return Math.round(e/-1e11)/-10 + "T";
    }
    if (e <= -0.99949999999999999e9) {
        return Math.round(e/-1e8)/-10 + "B";
    }
    if (e <= -0.99949999999999999e6) {
        return Math.round(e/-1e5)/-10 + "M";
    }
    if (e <= -0.99949999999999999e3) {
        return Math.round(e/-1e2)/-10 + "K";
    }
    if (e <= 0.99949999999999999e3) {
        return Math.round(e) + "";
    }
    if (e <= 0.99949999999999999e6) {
        return Math.round(e/1e2)/10 + "K";
    }
    if (e <= 0.99949999999999999e9) {
        return Math.round(e/1e5)/10 + "M";
    }
    if (e <= 0.99949999999999999e12) {
        return Math.round(e/1e8)/10 + "B";
    }
    if (e <= 0.99949999999999999e15) {
        return Math.round(e/1e11)/10 + "T";
    }
    if (e <= 0.99949999999999999e18) {
        return Math.round(e/1e14)/10 + "TK";
    }
    if (e <= 0.99949999999999999e21) {
        return Math.round(e/1e17)/10 + "TM";
    }
    if (e <= 0.99949999999999999e24) {
        return Math.round(e/1e20)/10 + "TB";
    }
    if (e <= 0.99949999999999999e27) {
        return Math.round(e/1e+23)/10 + "TT";
    }
    if (e >= 0.99949999999999999e27) {
        return Math.round(e/1e+23)/10 + "TT";
    }
}

const getClock = () => {
    let date = new Date(),
        d = date.getDate(),
        d1 = date.getDay(),
        h = date.getHours(),
        m = date.getMinutes(),
        s = date.getSeconds(),
        session = "PM";

    if (h == 2){
        h = 12;
    };

    if (h < 13) {
        session = "AM"
    };
    if (h > 12){
        session = "PM";
        h -= 12;
    };

    h = (h < 10) ? "0" + h : h;
    m = (m < 10) ? "0" + m : m;
    s = (s < 10) ? "0" + s : s;
    return `${h}:${m} ${session}`;
}

function msToTime(s) {

    // Pad to 2 or 3 digits, default is 2
    function pad(n, z) {
        z = z || 2;
        return ('00' + n).slice(-z);
    }

    var ms = s % 1000;
    s = (s - ms) / 1000;
    var secs = s % 60;
    s = (s - secs) / 60;
    var mins = s % 60;
    var hrs = (s - mins) / 60;

    return pad(hrs) + ':' + pad(mins) + ':' + pad(secs) + '.' + pad(ms, 3);
}

function ssMode() {
    const mba = document.querySelectorAll([".hud-bottom-right", ".hud-bottom-left", ".hud-bottom-center", ".hud-center-left", ".hud-center-right", ".hud-chat", ".hud-top-right", ".hud-center-toolbar"]);
    for (let mb of mba) {
        if (mb !== ".hud-center-toolbar") mb.style.display = (mb.style.display === "none") ? "block" : "none";
        else mb.style.display = (mb.style.display === "none") ? "flex" : "none";
    }
    document.querySelector(".hud-bottom-right").appendChild(document.querySelector("#hud-shield-bar"));
    document.querySelector(".hud-bottom-right").appendChild(document.querySelector("#hud-health-bar"));
    document.querySelector(".hud-bottom-right").insertAdjacentElement("afterbegin", document.querySelector("#hud-party-icons"));
    document.querySelector(".hud-bottom-left").insertAdjacentElement("afterbegin", document.querySelector("#hud-day-night-ticker"));
};

// @TickUpdates
game.tickUpdate = {
    playerTickUpdate: {
        resourceElem: game.ui.components.Resources,
        hasEquipedPotion: false,
        lastTickHealth: 100,
        onTick: function(player) {
            const options = game.options.options;
/*             const playerHealth = (player.health / player.maxHealth) * 100;
            const healThreshold = getId("auto-heal-threshold").valueAsNumber || 25;
            if (options.autoHeal && playerHealth <= healThreshold) this.healPlayer(); */

            if (options.autoHeal) {
                if (!game.ui.inventory.HealthPotion && player.gold >= 100) {
                    Game.currentGame.network.sendRpc({name: "BuyItem", itemName: "HealthPotion", tier: 1});
                };
                const playerHealth = (player.health / player.maxHealth) * 100;
                this.hasEquipedPotion = (this.lastTickHealth <= playerHealth);
                const healThreshold = getId("auto-heal-threshold").valueAsNumber || 15;
                if (playerHealth <= healThreshold && !this.hasEquipedPotion) {
                    Game.currentGame.network.sendRpc({name: "EquipItem", itemName: "HealthPotion", tier: 1});
                };
                this.lastTickHealth = playerHealth;
            };

            if (!options.frss) return;
            const resources = ["wood", "stone", "gold"];
            for (let rs of resources) {
                this.resourceElem[`${rs}Elem`].innerHTML = Math.round(player[rs]).toLocaleString();
            };
            this.resourceElem.tokensElem.innerHTML = Math.round(player.token).toLocaleString();
        },
    },
    petTickUpdate: {
        buyItem: (itemName, tier = 1) => Game.currentGame.network.sendRpc({name: "BuyItem", itemName, tier}),
        equipItem: (itemName, tier = 1) => Game.currentGame.network.sendRpc({name: "EquipItem", itemName, tier}),
        petLevelEnum: [8, 16, 24, 32, 48, 64, 96],
        petTokenEnum: [100, 100, 100, 100, 200, 200, 300, Infinity],
        onTick: function(pet) {
            const options = game.options.options;
            if (options.autoHeal) {
                if (pet.health <= 0) {
                    this.buyItem("PetRevive");
                    this.equipItem("PetRevive");
                }
                let petHealth = (pet.health / pet.maxHealth) * 100;
                const healThreshold = getId("auto-heal-threshold").valueAsNumber || 25;
                if (petHealth <= healThreshold) {
                    this.buyItem('PetHealthPotion');
                    this.equipItem("PetHealthPotion");
                }
            };
            this.petLevelEnum.indexOf(game.ui.components.MenuShop.shopItems[pet.model].level) > -1 && (
                game.ui.playerTick.token >= this.petTokenEnum[pet.tier - 1] && this.buyItem(pet.model, pet.tier + 1)
            )
        },
    },
}

/* @Handlers + Bindings */
game.network.sendRpc = function(data) {
    if (data.name === "MakeBuilding" && data.type === "Wall" && game.options.options.wallBlock) {
        const blockWidth = document.querySelector('#blockX').valueAsNumber;
        const blockHeight = document.querySelector('#blockY').valueAsNumber;
        placeWallBlock(blockWidth, blockHeight, data);
        return;
    };
    this.sendPacket(9, data);
};
game.network.addEntityUpdateHandler((e) => {
    const {entities} = e;
    const options = game.options.options;
    if (options.autoBow) {
        game.network.sendInput({space: 0});
        game.network.sendInput({space: 1});
    };

    if (options.autoUpgrade) {
        for (let uid in game.ui.buildings) {
            const building = game.ui.buildings[uid];
            if (building?.type !== "GoldStash" && building?.tier >= game.rebuilder.goldStash.tier) continue;
            if (building?.dead || !(uid in game.world.entities)) continue;
            game.network.sendRpc({name: "UpgradeBuilding", uid: parseInt(uid)});
        };
    };

    game.autoUpTowers.onTick(e);
    game.ahrc.onTick();
    game.rssOverhead.onTick(entities);
    game.rebuilder.onTick(entities);
    game.autoAim.onTick();
});
game.spam.fetchUnicode();
game.network.addRpcHandler("LocalBuilding", game.rebuilder.onLocalBuilding.bind(game.rebuilder));
game.ui._events.playerPetTickUpdate.push(game.tickUpdate.petTickUpdate.onTick.bind(game.tickUpdate.petTickUpdate));
game.ui._events.playerTickUpdate.push(game.tickUpdate.playerTickUpdate.onTick.bind(game.tickUpdate.playerTickUpdate));

(function MapFunctionsToElem() {
    for (let page in menu) {
        const [optionsElem, moreElem] = document.querySelectorAll("#" + page + " > div");
        for (let option in menu[page]) {
            const {name, description, more, onCallback, offCallback} = menu[page][option];
            const hasMore = !!more;
            const itemElem = document.createElement("div");
            itemElem.innerHTML = `
                <h2>${name}</h2>
                <span>${description}</span>
                ${game.options.options[option] === undefined ? "" : `
                    <button id="toggle-${option}" ${game.options.options[option] ? `class="underline-red"` : ""}>
                        ${game.options.options[option] ? "Disable" : "Enable"}
                    </button>
                `}
                ${hasMore ? `<a id="more-${option}"></a>` : ""}
            `;
            optionsElem.appendChild(itemElem);

            if (hasMore) {
                const {html, functions, bind} = more;
                const moreContainer = document.createElement("div");
                moreContainer.innerHTML = html;
                moreContainer.style.display = "none";
                moreElem.appendChild(moreContainer);
                functions?.();

                const toggleElem = getId("more-" + option);
                toggleElem.onclick = () => {
                    refreshMore(page);
                    bind?.();
                    moreContainer.style.display = "block";
                }
            }
            game.options.options[option] === undefined || addFunctionToElem({id: 'toggle-' + option, option, buttonText: '', colors: "underline-red?", onCallback, offCallback});
        }
    }
})();

/* @Keybinds */
document.addEventListener('keydown', function(e) {
    if (document.activeElement.tagName.toLowerCase() !== "input" && document.activeElement.tagName.toLowerCase() !== "textarea") {
        if (e.key == "/") {
            game.network.sendRpc({name: "EquipItem", itemName: "PetCARL", tier: game.ui.inventory?.PetCARL?.tier || 1});
        }
        if (e.key === "?") {
            ssMode();
        };
    }
});

document.addEventListener('keyup', function(e) {
    if (document.activeElement.tagName.toLowerCase() !== "input" && document.activeElement.tagName.toLowerCase() !== "textarea") {
        if (e.key == "~") {
            game.markers.placeMarker({
                x: game.ui.playerTick.position.x,
                y: game.ui.playerTick.position.y,
                timeout: false,
                shouldIndicateIndex: true,
            });
        }
        if (e.key == '-') {
            game.options.options.getRSS = !game.options.options.getRSS;
        }
        if (e.key == "=") {
            game.world.inWorld && (
                game.ui.playerTick.gold > 100 && (
                    game.ui.inventory.Bow || (
                        game.network.sendRpc({name: "BuyItem", itemName: "Bow", tier: 1}),
                        game.ui.inventory.Bow = {itemName: 'Bow', tier: 1, stacks: 1}
                    )
                ),
                game.ui.inventory.Bow && (
                    game.options.options.autoBow = !game.options.options.autoBow,
                    game.options.options.autoBow && game.network.sendRpc({name: "EquipItem", itemName: "Bow", tier: game.ui.inventory.Bow.tier})
                )
            )
        }
        if (e.key == ",") {
            getId('toggle-rebuild').click();
        }
        if (e.key == ".") {
            getId("toggle-autoUpgrade").click();
        }
        if (e.key == ";") {
            game.ui.getPlayerPetUid() && Game.currentGame.network.sendRpc({name: "DeleteBuilding", uid: game.ui.getPlayerPetUid()});
        }
        if (e.key == "'") {
            game.ui.getPlayerPetUid() && (
                game.network.sendRpc({name: "BuyItem", itemName: "PetRevive", tier: 1}),
                game.network.sendRpc({name: "EquipItem", itemName: "PetRevive", tier: 1})
            );
        }
        if (e.code == 'KeyG') {
            const wheelDisplay = getClass('interaction-wheel')[0];
            wheelDisplay.style.display = (wheelDisplay.style.display === 'block') ? 'none' : 'block';
        }
        if (e.code == 'KeyC' && !e.ctrlKey) {
            setTimeout(() => {
                document.querySelector('#joinWithPsk').style.display = 'block';
                document.querySelector('#joinWithPsk').focus();
                document.querySelector('#joinWithPsk').value = "";
            }, 100);
        }
        if (e.code == "KeyN") {
            game.zoom.zoomIn();
        }
        if (e.code == "KeyM") {
            game.zoom.zoomOut();
        }
        if (e.key == "Escape") {
            getClass('interaction-wheel')[0].style.display = 'none';
        }
        if (e.key == "!") {
            getId("toggle-wallBlock").click();
        }
    }
});

document.querySelector('#joinWithPsk').addEventListener('keyup', (e) => {
    if (e.key == "Enter" || e.key == "Escape") {
        e.target.style.display = 'none';
        (e.key == "Enter") && game.network.sendRpc({name: "JoinPartyByShareKey", partyShareKey: e.target.value});
    }
})