init (client, renderer) {
    this.inChat = false
    const chat = this.shadowRoot.querySelector('#chat')
    const gameMenu = document.getElementById('pause-screen')
    const chatInput = this.shadowRoot.querySelector('#chatinput')

    renderer.domElement.requestPointerLock = renderer.domElement.requestPointerLock ||
            renderer.domElement.mozRequestPointerLock ||
            renderer.domElement.webkitRequestPointerLock

    // Show chat
    chat.style.display = 'block'

    // Esc event - Doesnt work with onkeypress?! - keypressed is deprecated uk
    document.addEventListener('keydown', e => {
      if (gameMenu.inMenu) return
      if (!this.inChat) return
      e = e || window.event
      if (e.code === 'Escape') {
        disableChat()
      } else if (e.code === 'ArrowUp') {
        if (this.chatHistoryPos === 0) return
        chatInput.value = this.chatHistory[--this.chatHistoryPos] !== undefined ? this.chatHistory[this.chatHistoryPos] : ''
        setTimeout(() => { chatInput.setSelectionRange(-1, -1) }, 0)
      } else if (e.code === 'ArrowDown') {
        if (this.chatHistoryPos === this.chatHistory.length) return
        chatInput.value = this.chatHistory[++this.chatHistoryPos] !== undefined ? this.chatHistory[this.chatHistoryPos] : ''
        setTimeout(() => { chatInput.setSelectionRange(-1, -1) }, 0)
      }
    })

    const keyBindScrn = document.getElementById('keybinds-screen')

    // Chat events
    document.addEventListener('keypress', e => {
      if (gameMenu.inMenu) return
      e = e || window.event
      if (this.inChat === false) {
        keyBindScrn.keymaps.forEach(km => {
          if (e.code === km.key) {
            switch (km.defaultKey) {
              case 'KeyT':
                setTimeout(() => this.enableChat(false), 0)
                break
              case 'Slash':
                setTimeout(() => this.enableChat(true), 0)
                break
            }
          }
        })

        return false
      }

      if (!this.inChat) return

      e.stopPropagation()

      if (e.code === 'Enter') {
        this.chatHistory.push(chatInput.value)
        client.write('chat', { message: chatInput.value })
        disableChat()
      }
    })

    const disableChat = () => {
      this.inChat = false
      hideChat()
      renderer.domElement.requestPointerLock()
    }

    const hideChat = () => {
      // Clear chat input
      chatInput.value = ''
      // Unfocus it
      chatInput.blur()
      // Hide it
      chatInput.style.display = 'none'
      // Hide extended chat history
      chat.style.maxHeight = 'var(--chatHeight)'
      chat.scrollTop = chat.scrollHeight // Stay bottom of the list
      document.querySelector('#hud').shadowRoot.querySelector('#chat').shadowRoot.querySelectorAll('.chat-message').forEach(e => e.classList.remove('chat-message-chat-opened'))
    }

    client.on('chat', (packet) => {
      // Reading of chat message
      const fullmessage = JSON.parse(packet.message.toString())
      const msglist = []

      const colorF = (color) => {
        return color.trim().startsWith('#') ? `color:${color}` : styles[color] ?? undefined
      }

      const readMsg = (msglist, msg) => {
        const styles = {
          color: msg.color,
          bold: !!msg.bold,
          italic: !!msg.italic,
          underlined: !!msg.underlined,
          strikethrough: !!msg.strikethrough,
          obfuscated: !!msg.obfuscated
        }

        if (msg.text) {
          msglist.push({
            text: msg.text,
            ...styles
          })
        }

        if (msg.translate) {
          const tText = window.mcData.language[msg.translate] ?? msg.translate

          if (msg.with) {
            const splited = tText.split(/%s|%\d+\$s/g)

            let i = 0
            splited.forEach((spl, j, arr) => {
              msglist.push({ text: spl, ...styles })

              if (j + 1 < arr.length) {
                if (msg.with[i]) {
                  if (typeof msg.with[i] === 'string') {
                    readMsg(msglist, {
                      ...styles,
                      text: msg.with[i]
                    })
                  } else {
                    readMsg(msglist, {
                      ...styles,
                      ...msg.with[i]
                    })
                  }
                }
                i++
              }
            })
          } else {
            msglist.push({
              text: tText,
              ...styles
            })
          }
        }

        if (msg.extra) {
          msg.extra.forEach(ex => {
            readMsg(msglist, { ...styles, ...ex })
          })
        }
      }

      readMsg(msglist, fullmessage)

      const li = document.createElement('li')
      msglist.forEach(msg => {
        const span = document.createElement('span')
        span.appendChild(document.createTextNode(msg.text))
        span.setAttribute(
          'style',
                `${msg.color ? colorF(msg.color.toLowerCase()) + `; text-shadow: 1px 1px 0px ${colorShadow(colorF(msg.color.toLowerCase()).replace('color:', ''))}` : styles.white}; ${
                msg.bold ? styles.bold + ';' : ''
                }${msg.italic ? styles.italic + ';' : ''}${
                msg.strikethrough ? styles.strikethrough + ';' : ''
                }${msg.underlined ? styles.underlined + ';' : ''}`
        )
        li.appendChild(span)
      })
      chat.appendChild(li)
      chat.scrollTop = chat.scrollHeight // Stay bottom of the list
      // fading
      li.classList.add('chat-message')
      if (this.inChat) {
        li.classList.add('chat-message-chat-opened')
      }
      setTimeout(() => {
        li.classList.add('chat-message-fadeout')
        li.classList.add('chat-message-fade')
        setTimeout(() => {
          li.classList.add('chat-message-faded')
        }, 3000)
      }, 5000)
    })

    hideChat()
  }
@param {globalThis.THREE.Renderer} client
@param {import('minecraft-protocol').Client} renderer