diff --git a/src/ts/common/color.ts b/src/ts/common/color.ts index f6b7040..a0b5944 100644 --- a/src/ts/common/color.ts +++ b/src/ts/common/color.ts @@ -157,6 +157,12 @@ export interface HSVA { a: number; } +export interface HSL { + h: number; + s: number; + l: number; +} + export interface RGB { r: number; g: number; @@ -493,3 +499,37 @@ export function h2rgb(h: number): RGB { b: Math.round(b * 255) }; } + +export function rgb2hsl(rgb: RGB): HSL { + const r = rgb.r / 255; + const g = rgb.g / 255; + const b = rgb.b / 255; + let max = Math.max(r, g, b); + let min = Math.min(r, g, b); + let delta = max - min; + let h = 0; + let s; + let l; + + if (max === min) h = 0; + else if (r === max) h = (g - b) / delta; + else if (g === max) h = 2 + (b - r) / delta; + else if (b === max) h = 4 + (r - g) / delta; + + h = Math.min(h * 60, 360); + if (h < 0) h += 360; + l = (min + max) / 2; + + if (max === min) s = 0; + else if (l <= 0.5) s = delta / (max + min); + else s = delta / (2 - max - min); + h = Math.floor(h); + s = Math.floor(s * 100); + l = Math.floor(l * 100); + + return { h, s, l }; +} + +export function hsl2CSS(hsl: HSL) { + return `hsl(${hsl.h},${hsl.s}%,${hsl.l}%)`; +} diff --git a/src/ts/common/interfaces.ts b/src/ts/common/interfaces.ts index 4fa653d..e9ab8b8 100644 --- a/src/ts/common/interfaces.ts +++ b/src/ts/common/interfaces.ts @@ -683,6 +683,7 @@ export interface BrowserSettings { scale?: number; walkByDefault?: boolean; brightNight?: boolean; + timestamp?: '24' | '12'; } export interface EntityTypeName { diff --git a/src/ts/components/shared/chat-box/chat-box.pug b/src/ts/components/shared/chat-box/chat-box.pug index d8cca68..dd47242 100644 --- a/src/ts/components/shared/chat-box/chat-box.pug +++ b/src/ts/components/shared/chat-box/chat-box.pug @@ -12,7 +12,7 @@ span.emote-sample.mr-2 emote-box([emote]="e.names[0]") - emote-box.game-button.chat-emoji-button([emote]="btnEmoji" (click)="openBox()" (mouseenter)="onMouseEnter($event)") + emote-box.game-button.chat-emoji-button([emote]="btnEmoji" (click)="toggleEmojiBox()" (mouseenter)="onMouseEnter($event)") button.game-button.chat-send-button((click)="send($event)" title="Send message" aria-label="Send message") fa-icon([icon]="sendIcon") diff --git a/src/ts/components/shared/chat-box/chat-box.scss b/src/ts/components/shared/chat-box/chat-box.scss index 533c587..96e0e9a 100644 --- a/src/ts/components/shared/chat-box/chat-box.scss +++ b/src/ts/components/shared/chat-box/chat-box.scss @@ -1,5 +1,4 @@ @import '../../../../styles/partials/variables'; - .chat-box { max-width: 500px; height: 37px; @@ -34,7 +33,7 @@ z-index: 1; padding: 8px; font-size: 14px; - padding-right: 35px; + padding-right: 65px; padding-left: 90px; border-radius: $border-radius-base; outline: none; @@ -52,27 +51,27 @@ .chat-party > &, .chat-party-think > & { color: $chat-party; } - + .chat-say > &, .chat-think > & { color: $chat-color; } - + .chat-sup > & { color: $supporter-1; } - + .chat-sup1 > & { color: $supporter-1; } - + .chat-sup2 > & { color: $supporter-2; } - + .chat-sup3 > & { color: $supporter-3; } - + .chat-whisper > & { color: $chat-whisper; } @@ -88,6 +87,7 @@ } .chat-emoji-button { + user-select: none; position: absolute; right: 30px; bottom: 3px; @@ -113,7 +113,7 @@ left: 0; right: 0; width: 100%; - height: 190px; + height: 190px; overflow-y: auto; &::-webkit-scrollbar { @@ -121,7 +121,7 @@ } &::-webkit-scrollbar-thumb { - background: rgb(77, 77, 77); + background: rgb(77, 77, 77); border-radius: 14px; } } @@ -147,7 +147,7 @@ &:hover { background: rgba(0, 255, 255, 0.2); border-radius: 2px; - } + } } .emote-sample emote-box { diff --git a/src/ts/components/shared/chat-box/chat-box.ts b/src/ts/components/shared/chat-box/chat-box.ts index 4181c66..df7b8eb 100644 --- a/src/ts/components/shared/chat-box/chat-box.ts +++ b/src/ts/components/shared/chat-box/chat-box.ts @@ -13,6 +13,7 @@ import { AutocompleteState, autocompleteMesssage, replaceEmojis, emojis } from ' import { replaceNodes } from '../../../client/htmlUtils'; import { invalidEnumReturn } from '../../../common/utils'; import { findMatchingEntityNames, findEntityOrMockByAnyMeans, findBestEntityByName } from '../../../client/handlers'; +import * as _ from 'lodash'; const chatTypeNames: string[] = []; const chatTypeClasses: string[] = []; @@ -45,7 +46,7 @@ export class ChatBox implements AfterViewInit, OnDestroy { readonly maxSayLength = SAY_MAX_LENGTH; readonly commentIcon = faComment; readonly sendIcon = faAngleDoubleRight; - readonly emotes = emojis.map(e => e); + readonly emotes = emojis; emojiBoxState = 'none'; @ViewChild('inputElement', { static: true }) inputElement!: ElementRef; @ViewChild('typeBox', { static: true }) typeBox!: ElementRef; @@ -96,13 +97,13 @@ export class ChatBox implements AfterViewInit, OnDestroy { this.subscriptions.forEach(s => s.unsubscribe()); } addEmoji(emoji: string) { - this.emojiBoxState = 'none'; + this.toggleEmojiBox(); if (!this.message) this.message = emoji; else if (this.input.maxLength > this.message.length) { this.message += emoji; } } - openBox() { + toggleEmojiBox() { if (this.emojiBoxState === 'none') this.emojiBoxState = 'inline-block'; else this.emojiBoxState = 'none'; @@ -291,8 +292,8 @@ export class ChatBox implements AfterViewInit, OnDestroy { } onMouseEnter(event: MouseEvent) { if (!event.target) return; - const value = emojis[Math.floor(Math.random() * emojis.length)]; - this.btnEmoji = value.names[0]; + const emoji = _.sample(emojis) || emojis[0]; + this.btnEmoji = emoji.names[0]; } toggleChatType() { const chatTypes = getChatTypes(this.game); diff --git a/src/ts/components/shared/chat-log/chat-log.pug b/src/ts/components/shared/chat-log/chat-log.pug index 2d1aa0e..6a3b75a 100644 --- a/src/ts/components/shared/chat-log/chat-log.pug +++ b/src/ts/components/shared/chat-log/chat-log.pug @@ -26,3 +26,4 @@ button.game-button.chat-log-toggle(#toggleButton (click)="toggle()" title="Toggl path.main-bubble(fill="currentColor" d="M416 192c0-88.4-93.1-160-208-160S0 103.6 0 192c0 34.3 14.1 65.9 38 92-13.4 30.2-35.5 54.2-35.8 54.5-2.2 2.3-2.8 5.7-1.5 8.7S4.8 352 8 352c36.6 0 66.9-12.3 88.7-25 32.2 15.7 70.3 25 111.3 25 114.9 0 208-71.6 208-160z") path(fill="currentColor" d="M538 412c23.9-26 38-57.7 38-92 0-66.9-53.5-124.2-129.3-148.1.9 6.6 1.3 13.3 1.3 20.1 0 105.9-107.7 192-240 192-10.8 0-21.3-.8-31.7-1.9C207.8 439.6 281.8 480 368 480c41 0 79.1-9.2 111.3-25 21.8 12.7 52.1 25 88.7 25 3.2 0 6.1-1.9 7.3-4.8 1.3-2.9.7-6.3-1.5-8.7-.3-.3-22.4-24.2-35.8-54.5z") .whispers-count(#count) + \ No newline at end of file diff --git a/src/ts/components/shared/chat-log/chat-log.scss b/src/ts/components/shared/chat-log/chat-log.scss index 1cd13eb..f9cfce1 100644 --- a/src/ts/components/shared/chat-log/chat-log.scss +++ b/src/ts/components/shared/chat-log/chat-log.scss @@ -160,11 +160,35 @@ $resizer-offset: $resizer-size / 2; } .chat-log-filter { + padding-bottom: 5px; border: none; width: 100px; - &::placeholder { - color: #ccc; - } +} + +.chat-log-filter::-webkit-input-placeholder { + opacity: 1; + color: white; + text-shadow: 0px 0px 4px #000000; +} + +.chat-log-filter:-moz-placeholder { + /* Firefox 18- */ + opacity: 1; + color: white; + text-shadow: 0px 0px 4px #000000; +} + +.chat-log-filter::-moz-placeholder { + /* Firefox 19+ */ + opacity: 1; + color: white; + text-shadow: 0px 0px 4px #000000; +} + +.chat-log-filter:-ms-input-placeholder { + opacity: 1; + color: white; + text-shadow: 0px 0px 4px #000000; } @media only screen and (max-width: 360px) { diff --git a/src/ts/components/shared/chat-log/chat-log.ts b/src/ts/components/shared/chat-log/chat-log.ts index 1c16c8d..d3d11d7 100644 --- a/src/ts/components/shared/chat-log/chat-log.ts +++ b/src/ts/components/shared/chat-log/chat-log.ts @@ -12,6 +12,8 @@ import { DEFAULT_CHATLOG_OPACITY, PONY_TYPE, SECOND } from '../../../common/cons import { faCaretUp, faArrowDown } from '../../../client/icons'; import { sampleMessages } from '../../../common/debugData'; import { findEntityById } from '../../../common/worldMap'; +import { colorToRGBA, rgb2hsl, HSL, hsl2CSS } from '../../../common/color'; +import * as moment from 'moment'; interface IndexEntryUser { id: number; @@ -102,11 +104,9 @@ export function createChatLogLineDOM(clickLabel: ClickHandler, clickName: ClickH line.root = element('div', 'chat-line', [ element('span', 'chat-line-lead'), line.time = element('span', 'chat-line-name', [ - textNode('['), line.timeContent = element( 'span', 'chat-line-time-content', [textNode('')], undefined), line.index = element('span', 'chat-line-time-index', [line.indexText = textNode('')]), - textNode('] '), ]), line.label = element( 'span', 'chat-line-label mr-1', [line.labelText = textNode('')], undefined, { click: () => clickLabel(line.entry) }), @@ -125,7 +125,7 @@ export function createChatLogLineDOM(clickLabel: ClickHandler, clickName: ClickH return line; } -export function updateChatLogLine(line: ChatLogLineDOM, entry: ChatLogMessage) { +export function updateChatLogLine(line: ChatLogLineDOM, entry: ChatLogMessage, hourMode?: '12' | '24') { const { classes, label, message, prefix, suffix } = entry; const hasSpace = message.indexOf(' ') !== -1; @@ -135,27 +135,23 @@ export function updateChatLogLine(line: ChatLogLineDOM, entry: ChatLogMessage) { line.labelText.nodeValue = label ? `[${label}]` : ''; updateChatLogName(line, entry); - updateTime(line); + updateTime(line, hourMode); line.prefixText.nodeValue = prefix || ''; line.suffixText.nodeValue = suffix ? ` ${suffix}: ` : ': '; replaceNodes(line.message, message); } -function updateTime(line: ChatLogLineDOM) { +function updateTime(line: ChatLogLineDOM, hourMode?: '12' | '24') { line.time.style.display = 'inline'; - replaceNodes(line.timeContent, getCurrentTime()); + if (!hourMode) return; + if (hourMode === '24') + replaceNodes(line.timeContent, `[${moment().format('HH:mm:ss')}] `); + if (hourMode === '12') + replaceNodes(line.timeContent, `[${moment().format('LTS')}] `); } -function getCurrentTime() { - const date = new Date(); - const hours = date.getHours().toString(); - const minutes = date.getMinutes().toString(); - const seconds = date.getSeconds().toString(); - return `${hours.length === 1 ? `0${hours}` : hours}:${minutes.length === 1 ? `0${minutes}` : minutes}:${seconds.length === 1 ? `0${seconds}` : seconds}`; -} - -function setNameColors(line: ChatLogLineDOM | undefined, colors?: string[] | null) { +function setNameColors(line: ChatLogLineDOM | undefined, colors?: string[]) { if (!colors || !line) return; if (colors[1]) line.name.style.color = colors[1]; if (colors[0]) line.nameContent.style.color = colors[0]; @@ -230,6 +226,7 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { local: ChatLogMessage[] = []; party: ChatLogMessage[] = []; whisper: ChatLogMessage[] = []; + filterColor = this.inactiveBg; unread = 0; private subscriptions: Subscription[] = []; private startX = 0; @@ -241,9 +238,8 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { private indexes = new Map(); private messageCounter = 0; private lastOpacity = 0; - private autoClear: null | NodeJS.Timeout = null; - private autoUnfocus: null | NodeJS.Timeout = null; - filterColor = this.inactiveBg; + private autoClear?: NodeJS.Timeout; + private autoUnfocus?: NodeJS.Timeout; constructor( private game: PonyTownGame, private settingsService: SettingsService, @@ -457,7 +453,7 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { if (!value) { this.clearTimeOutAutoClear(); this.unFocus(); - this.showHideChatLogLines('', false); + this.filterChatLogLines('', false); return; } else this.filterColor = this.bg; @@ -479,9 +475,10 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { value = value.slice(1); try { const regExp = new RegExp(value); - this.showHideChatLogLines(regExp, toLowerCase); + this.filterChatLogLines(regExp, toLowerCase); this.filterTab.nativeElement.style.color = ''; - } catch (err) { + } catch (err) {// If the user inputs invalid regExp we just notify him by changing text color to red + if (DEVELOPMENT) console.error(err); this.filterTab.nativeElement.style.color = '#ff6666'; } return; @@ -490,9 +487,9 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { value = value.toLowerCase(); toLowerCase = true; } - this.showHideChatLogLines(value, toLowerCase); + this.filterChatLogLines(value, toLowerCase); } - showHideChatLogLines(content: string | RegExp, caseSensitive: boolean) { + filterChatLogLines(content: string | RegExp, caseSensitive: boolean) { const lines = this.linesElement.getElementsByTagName('div'); for (let i = 0; i < lines.length; i++) { @@ -503,11 +500,11 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { if (typeof content === 'string') { if (caseSensitive) - textContent.toLowerCase().includes(content) ? lines[i].hidden = false : lines[i].hidden = true; + lines[i].hidden = !textContent.toLowerCase().includes(content); else - textContent.includes(content) ? lines[i].hidden = false : lines[i].hidden = true; + lines[i].hidden = !textContent.includes(content); } else { - textContent.match(content) ? lines[i].hidden = false : lines[i].hidden = true; + lines[i].hidden = !textContent.match(content); } } } @@ -518,77 +515,41 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { lines[i].hidden = false; } } - private getRGB(hex: string) { - const bigint = parseInt(hex, 16); - const r = (bigint >> 16) & 255; - const g = (bigint >> 8) & 255; - const b = bigint & 255; - return [r, g, b]; - } - private RGBToHSL(rgb: number[]) { - let r = rgb[0] / 255; - let g = rgb[1] / 255; - let b = rgb[2] / 255; - let max = Math.max(r, g, b); - let min = Math.min(r, g, b); - let delta = max - min; - let h = 0; - let s; - let l; - - if (max === min) h = 0; - else if (r === max) h = (g - b) / delta; - else if (g === max) h = 2 + (b - r) / delta; - else if (b === max) h = 4 + (r - g) / delta; - - h = Math.min(h * 60, 360); - - if (h < 0) h += 360; - - l = (min + max) / 2; - - if (max === min) s = 0; - else if (l <= 0.5) s = delta / (max + min); - else s = delta / (2 - max - min); - h = Math.floor(h); - s = Math.floor(s * 100); - l = Math.floor(l * 100); - - if (l < 40) { - if (l > 20 && l < 40) { - l += 20; - if (s > 11) s -= 11; + brightenDarkColors(hsl: HSL) { + if (hsl.l < 40) { + if (hsl.l > 20 && hsl.l < 40) { + hsl.l += 20; + if (hsl.s > 11) hsl.s -= 11; } else { - l = 40; - if (s > 11) s = 0; + hsl.l = 40; + if (hsl.s > 11) hsl.s = 0; } } - - return `hsl(${h}, ${s}%, ${l}%)`; + return hsl; } fixColor(color: string) { while (color.length <= 7) { color = `0${color}`; } - color = color.replace(/0/g, '1'); return color.substr(0, 6); } - private getCharaterColors(id: number | undefined) { - if (!id) return null; + private getCharacterColors(id: number | undefined) { + if (!id) return; const entity = findEntityById(this.game.map, id) as Pony; - if (!entity || !entity.palettePonyInfo) return null; + if (!entity || !entity.palettePonyInfo) return; let colors = []; - let body = entity.palettePonyInfo.body; - let mane = entity.palettePonyInfo.mane; + let { body, mane } = entity.palettePonyInfo; if (body && body.palette && body.palette.colors[1]) { - let bodyColor = this.fixColor(body.palette.colors[1].toString(16)); - const RGB = this.getRGB(bodyColor); - colors.push(this.RGBToHSL(RGB)); + const rgb = colorToRGBA(body.palette.colors[1]); + const hsl = rgb2hsl(rgb); + this.brightenDarkColors(hsl); + colors.push(hsl2CSS(hsl)); } if (mane && mane.palette && mane.palette.colors[1]) { - let maneColor = this.fixColor(mane.palette.colors[1].toString(16)); - const RGB = this.getRGB(maneColor); - colors.push(this.RGBToHSL(RGB)); + const rgb = colorToRGBA(mane.palette.colors[1]); + const hsl = rgb2hsl(rgb); + this.brightenDarkColors(hsl); + colors.push(hsl2CSS(hsl)); } else if (colors && colors[0]) { colors.push((colors[0])); } @@ -596,12 +557,12 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { } clearTimeOutAutoClear() { if (this.autoClear) clearTimeout(this.autoClear); - this.autoClear = null; + this.autoClear = undefined; this.filterColor = this.inactiveBg; } clearTimeOutAutoUnfocus() { if (this.autoUnfocus) clearTimeout(this.autoUnfocus); - this.autoUnfocus = null; + this.autoUnfocus = undefined; } unFocus() { this.clearTimeOutAutoUnfocus(); @@ -616,8 +577,7 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { const open = this.open; const scrolledToEnd = open ? this.scrolledToEnd : false; const tab = this.activeTab; - let colors: string[] | null = null; - colors = this.getCharaterColors(message.id); + const colors = this.getCharacterColors(message.id); this.addEntryToList(this.local, GENERAL_CHAT_LIMIT, open && tab === 'local', entry); setNameColors(entry.dom, colors); @@ -678,7 +638,7 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { if (isOpen) { entry.dom = removedDom || createChatLogLineDOM(this.clickLabel, this.clickNameHandler); - updateChatLogLine(entry.dom, entry); + updateChatLogLine(entry.dom, entry, this.settings.timestamp); this.linesElement.appendChild(entry.dom.root); } } @@ -694,6 +654,7 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { this.regenerateList(); this.scrollToEnd(); this.updateTabs(); + this.filterChat(); } } private updateTabs() { @@ -741,8 +702,8 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { this.messages.forEach(entry => { if (!entry.dom) { entry.dom = createChatLogLineDOM(this.clickLabel, this.clickNameHandler); - setNameColors(entry.dom, this.getCharaterColors(entry.entityId)); - updateChatLogLine(entry.dom, entry); + setNameColors(entry.dom, this.getCharacterColors(entry.entityId)); + updateChatLogLine(entry.dom, entry, this.settings.timestamp); } lines.appendChild(entry.dom.root); @@ -759,7 +720,8 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { } if (resizeX) { - this.settings.chatlogWidth = clamp(x - this.startX, 200 + 74.7, 2000); + const filterBoxWidth = 80; + this.settings.chatlogWidth = clamp(x - this.startX, 200 + filterBoxWidth, 2000); } if (resizeY) { diff --git a/src/ts/components/shared/settings-modal/settings-modal.pug b/src/ts/components/shared/settings-modal/settings-modal.pug index db4c1b1..6633b88 100644 --- a/src/ts/components/shared/settings-modal/settings-modal.pug +++ b/src/ts/components/shared/settings-modal/settings-modal.pug @@ -71,6 +71,17 @@ [(checked)]="account.ignoreNonFriendWhispers" help="Don't show any whispers from players who are not on your friends list") | Only allow whispers from friends + .form-group + .form-group + label#chatlog-opacity-label Message timestamp + div + .btn-group.ml-2(btnCheckbox) + button.btn.btn-outline-secondary.d-none.d-sm-block([class.active]='off' (click)='switchTimestamp()') Off + button.btn.btn-outline-secondary.d-none.d-sm-block([class.active]='twelve' (click)='switchTimestamp("12")') 12 + button.btn.btn-outline-secondary.d-none.d-sm-block([class.active]='twentyFour' (click)='switchTimestamp("24")') 24 + span.ml-2 {{timestampText}} + //small.form-text.text-muted.mt-0 Shows message time + .form-group label#chatlog-opacity-label Chatlog background opacity div @@ -78,7 +89,6 @@ [(value)]="account.chatlogOpacity" style="max-width: 300px" labelledBy="chatlog-opacity-label") | {{(account.chatlogOpacity / 100) | percent}} small.form-text.text-muted.mt-0 Changes chatlog background to make it more readable or less obtrusive. - .form-group label#chatlog-range-label Chatlog range div slider-bar.mr-2( diff --git a/src/ts/components/shared/settings-modal/settings-modal.ts b/src/ts/components/shared/settings-modal/settings-modal.ts index fe5a292..222e8ca 100644 --- a/src/ts/components/shared/settings-modal/settings-modal.ts +++ b/src/ts/components/shared/settings-modal/settings-modal.ts @@ -26,6 +26,10 @@ export class SettingsModal implements OnInit, OnDestroy { readonly graphicsIcon = faImage; readonly exportIcon = faDownload; readonly importIcon = faUpload; + off = false; + twelve = false; + twentyFour = false; + timestampText: 'Disabled' | '24 Hour mode' | '12 Hour mode' = 'Disabled'; @Output() close = new EventEmitter(); account: AccountSettings = {}; browser: BrowserSettings = {}; @@ -59,6 +63,7 @@ export class SettingsModal implements OnInit, OnDestroy { this.browser = this.settingsService.browser; this.setupDefaults(); this.subscription = this.game.onLeft.subscribe(() => this.cancel()); + this.updateTimestampRadios(); } ngOnDestroy() { this.finishChatlogRange(); @@ -100,6 +105,34 @@ export class SettingsModal implements OnInit, OnDestroy { this.settingsService.saveBrowserSettings(this.browser); this.close.emit(); } + switchTimestamp(state?: string) { + if (!state) this.browser.timestamp = undefined; + else if (state === '12') this.browser.timestamp = '12'; + else if (state === '24') this.browser.timestamp = '24'; + this.updateTimestampRadios(); + } + turnOffTimestampRadioButtons() { + this.off = false; + this.twelve = false; + this.twentyFour = false; + } + updateTimestampRadios() { + this.turnOffTimestampRadioButtons(); + switch (this.browser.timestamp) { + case '12': + this.twelve = true; + this.timestampText = '12 Hour mode'; + break; + case '24': + this.twentyFour = true; + this.timestampText = '24 Hour mode'; + break; + default: + this.off = true; + this.timestampText = 'Disabled'; + break; + } + } updateChatlogRange(range: number | undefined) { document.body.classList.add('translucent-modals'); updateRangeIndicator(range, this.game);