From 6f537b43536f6195be054e895b67960dbf55e995 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 9 Oct 2020 21:31:40 +0000 Subject: [PATCH] update client --- last_updated.txt | 2 +- src/api/ContractsAPI.ts | 10 +- src/api/EthereumAccountManager.ts | 5 +- src/api/GameManager.ts | 5 +- src/api/PlanetHelper.ts | 40 ++-- src/api/UIStateStorageManager.ts | 4 + src/app/GameWindow.tsx | 10 +- src/app/GameWindowLayout.tsx | 18 +- src/app/GameWindowPanes/CoordsPane.tsx | 4 +- src/app/GameWindowPanes/HatPane.tsx | 4 +- src/app/GameWindowPanes/ModalPane.tsx | 155 +++++++++------ src/app/GameWindowPanes/PlanetContextPane.tsx | 2 +- src/app/GameWindowPanes/PlanetDexPane.tsx | 4 +- src/app/GameWindowPanes/PlanetPreview.tsx | 6 +- src/app/GameWindowPanes/PlanetScape.tsx | 12 +- src/app/GameWindowPanes/PlayerInfoPane.tsx | 18 +- src/app/GameWindowPanes/SettingsPane.tsx | 16 +- src/app/GameWindowPanes/Tooltip.tsx | 48 +++-- src/app/board/CanvasRenderer.ts | 184 +++++++++++------- src/app/board/ControllableCanvas.tsx | 74 ++++--- src/app/board/Viewport.ts | 12 +- src/utils/ProcgenUtils.ts | 27 ++- src/utils/Utils.ts | 55 ++++-- 23 files changed, 457 insertions(+), 258 deletions(-) diff --git a/last_updated.txt b/last_updated.txt index 580cab57..4c8a7fa8 100644 --- a/last_updated.txt +++ b/last_updated.txt @@ -1 +1 @@ -last updated: Sun Oct 4 02:09:09 UTC 2020 +last updated: Fri Oct 9 21:31:40 UTC 2020 diff --git a/src/api/ContractsAPI.ts b/src/api/ContractsAPI.ts index 18211dda..2f0fcf71 100644 --- a/src/api/ContractsAPI.ts +++ b/src/api/ContractsAPI.ts @@ -676,7 +676,7 @@ class ContractsAPI extends EventEmitter { const playerIds = await aggregateBulkGetter( nPlayers, - 50, + 200, async (start, end) => (await contract.bulkGetPlayers(start, end)).map(address) ); @@ -725,7 +725,7 @@ class ContractsAPI extends EventEmitter { const arrivalsUnflattened = await aggregateBulkGetter( nPlanets, - 500, + 1000, async (start, end) => { return ( await contract.bulkGetPlanetArrivals(start, end) @@ -748,7 +748,7 @@ class ContractsAPI extends EventEmitter { terminalEmitter.println('(4/6) Getting planet IDs...'); const planetIds = await aggregateBulkGetter( nPlanets, - 1000, + 2000, async (start, end) => await contract.bulkGetPlanetIds(start, end), true ); @@ -757,7 +757,7 @@ class ContractsAPI extends EventEmitter { RawPlanetExtendedInfo >( nPlanets, - 500, + 1000, async (start, end) => await contract.bulkGetPlanetsExtendedInfo(start, end), true @@ -765,7 +765,7 @@ class ContractsAPI extends EventEmitter { terminalEmitter.println('(6/6) Getting planet data...'); const rawPlanets = await aggregateBulkGetter( nPlanets, - 500, + 1000, async (start, end) => await contract.bulkGetPlanets(start, end), true ); diff --git a/src/api/EthereumAccountManager.ts b/src/api/EthereumAccountManager.ts index 42840f41..288d5075 100644 --- a/src/api/EthereumAccountManager.ts +++ b/src/api/EthereumAccountManager.ts @@ -1,5 +1,5 @@ import * as stringify from 'json-stable-stringify'; -import { Provider, TransactionReceipt } from '@ethersproject/providers'; +import { JsonRpcProvider, TransactionReceipt } from '@ethersproject/providers'; import { providers, Contract, Wallet, utils } from 'ethers'; import { EthAddress } from '../_types/global/GlobalTypes'; import { address } from '../utils/CheckedTypeUtils'; @@ -7,7 +7,7 @@ import { address } from '../utils/CheckedTypeUtils'; class EthereumAccountManager { static instance: EthereumAccountManager | null = null; - private readonly provider: Provider; + private readonly provider: JsonRpcProvider; private signer: Wallet | null; private readonly knownAddresses: EthAddress[]; @@ -15,6 +15,7 @@ class EthereumAccountManager { const isProd = process.env.NODE_ENV === 'production'; const url = isProd ? 'https://rpc.xdaichain.com/' : 'http://localhost:8545'; this.provider = new providers.JsonRpcProvider(url); + this.provider.pollingInterval = 8000; this.signer = null; this.knownAddresses = []; const knownAddressesStr = localStorage.getItem('KNOWN_ADDRESSES'); diff --git a/src/api/GameManager.ts b/src/api/GameManager.ts index dcb75b0c..9d0c5cc5 100644 --- a/src/api/GameManager.ts +++ b/src/api/GameManager.ts @@ -605,14 +605,17 @@ class GameManager extends EventEmitter implements AbstractGameManager { } async addAccount(coords: WorldCoords): Promise { + /* const homePlanetId = locationIdFromBigInt(mimcHash(coords.x, coords.y)); const planet = this.getPlanetWithId(homePlanetId); if (!planet || planet.owner !== this.account) { return Promise.resolve(false); } + */ await this.localStorageManager.setHomeCoords(coords); this.initMiningManager(coords); this.homeCoords = coords; + this.homeHash = locationIdFromBigInt(mimcHash(coords.x, coords.y)); return true; } @@ -631,7 +634,7 @@ class GameManager extends EventEmitter implements AbstractGameManager { // initialize in a lower perlin area do { const t = Math.random() * 2 * Math.PI; - const r = (0.5 + Math.random() * 0.5) * this.worldRadius; + const r = (0.7 + Math.random() * 0.3) * this.worldRadius; x = Math.floor(Math.cos(t) * r); y = Math.floor(Math.sin(t) * r); p = perlin({ x, y }, false); diff --git a/src/api/PlanetHelper.ts b/src/api/PlanetHelper.ts index 7d00f12e..252e7f47 100644 --- a/src/api/PlanetHelper.ts +++ b/src/api/PlanetHelper.ts @@ -35,9 +35,12 @@ import { } from './ContractsAPI'; import NotificationManager from '../utils/NotificationManager'; -interface MemoizedCoordHashes { - [x: number]: { [y: number]: Location }; -} +type CoordsString = string; +type MemoizedCoordHashes = Map; + +const getCoordsString = (coords: WorldCoords): CoordsString => { + return `${coords.x},${coords.y}`; +}; export class PlanetHelper { private readonly planets: PlanetMap; @@ -66,7 +69,7 @@ export class PlanetHelper { this.address = address; this.planets = planets; this.contractConstants = contractConstants; - this.coordsToLocation = {}; + this.coordsToLocation = new Map(); this.planetLocationMap = {}; const planetArrivalIds: PlanetVoyageIdMap = {}; const arrivals: VoyageMap = {}; @@ -108,12 +111,12 @@ export class PlanetHelper { // set interval to update all planets every 120s setInterval(() => { for (const planetId of Object.keys(this.planets)) { - setTimeout(() => { - const planet = this.planets[planetId]; - if (planet && hasOwner(planet)) { - this.updatePlanetToTime(planet, Date.now()); - } - }, Math.floor(120000 * Math.random())); // evenly distribute updates + // setTimeout(() => { + const planet = this.planets[planetId]; + if (planet && hasOwner(planet)) { + this.updatePlanetToTime(planet, Date.now()); + } + // }, Math.floor(120000 * Math.random())); // evenly distribute updates } }, 120000); } @@ -188,14 +191,13 @@ export class PlanetHelper { // returns an empty planet if planet is not in contract // returns null if this isn't a planet, according to hash and coords public getPlanetWithCoords(coords: WorldCoords): Planet | null { - const { x, y } = coords; - let location: Location | null = null; - if (this.coordsToLocation[x] && this.coordsToLocation[x][y]) { - location = this.coordsToLocation[x][y]; - } + const str = getCoordsString(coords); + + const location = this.coordsToLocation.get(str); if (!location) { return null; } + return this.getPlanetWithLocation(location); } @@ -217,9 +219,11 @@ export class PlanetHelper { public addPlanetLocation(planetLocation: Location): void { this.planetLocationMap[planetLocation.hash] = planetLocation; - const { x, y } = planetLocation.coords; - if (!this.coordsToLocation[x]) this.coordsToLocation[x] = {}; - this.coordsToLocation[x][y] = planetLocation; + const str = getCoordsString(planetLocation.coords); + if (!this.coordsToLocation.has(str)) { + this.coordsToLocation.set(str, planetLocation); + } + if (!this.planets[planetLocation.hash]) { this.planets[planetLocation.hash] = this.defaultPlanetFromLocation( planetLocation diff --git a/src/api/UIStateStorageManager.ts b/src/api/UIStateStorageManager.ts index 81db848d..206532f3 100644 --- a/src/api/UIStateStorageManager.ts +++ b/src/api/UIStateStorageManager.ts @@ -17,6 +17,7 @@ export enum UIDataKey { notifMove = 'notifMove', newPlayer = 'newPlayer', + highPerf = 'highPerf', } export type UIDataValue = boolean; @@ -34,6 +35,7 @@ export type UIData = { notifMove: boolean; newPlayer: boolean; + highPerf: boolean; }; export const defaultUserData: UIData = { @@ -50,6 +52,8 @@ export const defaultUserData: UIData = { notifMove: true, newPlayer: true, + + highPerf: false, }; export function useStoredUIState( diff --git a/src/app/GameWindow.tsx b/src/app/GameWindow.tsx index 652c1703..9cf142a1 100644 --- a/src/app/GameWindow.tsx +++ b/src/app/GameWindow.tsx @@ -1,5 +1,6 @@ import _ from 'lodash'; import React, { useContext, useEffect, useState, createContext } from 'react'; +import { useStoredUIState, UIDataKey } from '../api/UIStateStorageManager'; import UIEmitter, { UIEmitterEvent } from '../utils/UIEmitter'; import { copyPlanetStats, PlanetStatsInfo } from '../utils/Utils'; import { EthAddress, Planet } from '../_types/global/GlobalTypes'; @@ -30,6 +31,8 @@ export const AccountContext = createContext(null); export const SelectedContext = createContext(null); export const SelectedStatContext = createContext(null); +export const HiPerfContext = createContext(null); + export default function GameWindow() { const uiManager = useContext(GameUIManagerContext); const [selected, setSelected] = useState(null); @@ -121,12 +124,17 @@ export default function GameWindow() { setAccount(uiManager.getAccount()); }, [uiManager]); + // make this into a context (fix this later) + const hiPerfHook = useStoredUIState(UIDataKey.highPerf, uiManager); + return ( - + + + diff --git a/src/app/GameWindowLayout.tsx b/src/app/GameWindowLayout.tsx index bee0d209..b9363645 100644 --- a/src/app/GameWindowLayout.tsx +++ b/src/app/GameWindowLayout.tsx @@ -44,8 +44,13 @@ import { useStoredUIState, UIDataKey } from '../api/UIStateStorageManager'; import GameUIManager from './board/GameUIManager'; import GameUIManagerContext from './board/GameUIManagerContext'; import { PrivatePane } from './GameWindowPanes/PrivatePane'; +import { Hook } from '../_types/global/GlobalTypes'; -export function GameWindowLayout() { +export function GameWindowLayout({ + hiPerfHook, +}: { + hiPerfHook: Hook; +}) { const planetDetHook = useState(true); const helpHook = useState(false); const leaderboardHook = useState(false); @@ -59,6 +64,7 @@ export function GameWindowLayout() { const privateHook = useState(false); const uiManager = useContext(GameUIManagerContext); + const newPlayerHook = useStoredUIState( UIDataKey.newPlayer, uiManager @@ -66,7 +72,7 @@ export function GameWindowLayout() { return ( - + {/* modals (fragment is purely semantic) */} <> @@ -83,7 +89,11 @@ export function GameWindowLayout() { - + @@ -114,7 +124,7 @@ export function GameWindowLayout() { - + diff --git a/src/app/GameWindowPanes/CoordsPane.tsx b/src/app/GameWindowPanes/CoordsPane.tsx index 66999ce8..48d34852 100644 --- a/src/app/GameWindowPanes/CoordsPane.tsx +++ b/src/app/GameWindowPanes/CoordsPane.tsx @@ -71,10 +71,12 @@ const StyledCoordsPane = styled.div` width: 16em; height: 4em; `; -export function CoordsPane() { +export function CoordsPane({ hiPerf }: { hiPerf: boolean }) { const [hovering, setHovering] = useState(false); const [hidden, setHidden] = useState(false); + if (hiPerf) return <>; + return ( setHidden((b) => !b)} diff --git a/src/app/GameWindowPanes/HatPane.tsx b/src/app/GameWindowPanes/HatPane.tsx index 9b853806..41834166 100644 --- a/src/app/GameWindowPanes/HatPane.tsx +++ b/src/app/GameWindowPanes/HatPane.tsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import styled from 'styled-components'; import { Sub } from '../../components/Text'; import { HAT_SIZES } from '../../utils/constants'; -import { getPlanetColors } from '../../utils/ProcgenUtils'; +import { getPlanetCosmetic } from '../../utils/ProcgenUtils'; import UIEmitter, { UIEmitterEvent } from '../../utils/UIEmitter'; import { EthAddress, Planet } from '../../_types/global/GlobalTypes'; import GameUIManager from '../board/GameUIManager'; @@ -96,7 +96,7 @@ export function HatPane({ hook }: { hook: ModalHook }) {
HAT - {getPlanetColors(selected).hatType} + {getPlanetCosmetic(selected).hatType}
HAT Level diff --git a/src/app/GameWindowPanes/ModalPane.tsx b/src/app/GameWindowPanes/ModalPane.tsx index 0faa516b..626d7dee 100644 --- a/src/app/GameWindowPanes/ModalPane.tsx +++ b/src/app/GameWindowPanes/ModalPane.tsx @@ -1,4 +1,10 @@ -import React, { useState, useRef, useEffect, useLayoutEffect } from 'react'; +import React, { + useState, + useRef, + useEffect, + useLayoutEffect, + useCallback, +} from 'react'; import styled from 'styled-components'; import dfstyles from '../../styles/dfstyles'; import WindowManager, { TooltipName } from '../../utils/WindowManager'; @@ -214,6 +220,22 @@ export type ModalProps = { type Coords = { x: number; y: number }; +const clipX = (x, width) => { + let newX = x; + if (newX + width > window.innerWidth) { + newX = window.innerWidth - width; + } else if (newX < 0) newX = 0; + return newX; +}; + +const clipY = (y, height) => { + let newY = y; + if (newY + height > window.innerHeight) { + newY = window.innerHeight - height; + } else if (newY < 0) newY = 0; + return newY; +}; + export function ModalPane({ children, title, @@ -228,95 +250,97 @@ export function ModalPane({ hideClose?: boolean; style?: React.CSSProperties; }) { + const windowManager = WindowManager.getInstance(); + const [coords, setCoords] = useState(null); const [delCoords, setDelCoords] = useState(null); + const [mousedownCoords, setMousedownCoords] = useState(null); + // TODO clean this up and merge them into one guy? const [styleClicking, setStyleClicking] = useState(false); + const [clicking, setClicking] = useState(false); const [zIndex, setZIndex] = useState(GameWindowZIndex.Modal); + const push = useCallback(() => setZIndex(windowManager.getIndex()), [ + windowManager, + ]); const containerRef = useRef(document.createElement('div')); const headerRef = useRef(document.createElement('div')); - const windowManager = WindowManager.getInstance(); - - const clipX = (x, delX) => { - let newLeft = x + delX; - if (newLeft + containerRef.current.offsetWidth > window.innerWidth) { - newLeft = window.innerWidth - containerRef.current.offsetWidth; - } else if (newLeft < 0) newLeft = 0; - return newLeft; - }; - const getLeft = () => { - if (!coords) return; - if (!delCoords) return coords.x; - return clipX(coords.x, delCoords.x); - }; - const clipY = (y, delY) => { - let newTop = y + delY; - if (newTop + containerRef.current.offsetHeight > window.innerHeight) { - newTop = window.innerHeight - containerRef.current.offsetHeight; - } else if (newTop < 0) newTop = 0; - return newTop; - }; - const getTop = () => { - if (!coords) return; - if (!delCoords) return coords.y; - return clipY(coords.y, delCoords.y); - }; - + // attach mouse down handler useEffect(() => { if (!coords) return; const myCurrent = headerRef.current; - let oldMouse: null | { x: number; y: number } = null; - const myCoords = coords; - let delX = 0; - let delY = 0; const doMouseDown = (e) => { - oldMouse = { x: e.clientX, y: e.clientY }; + setMousedownCoords({ x: e.clientX, y: e.clientY }); + setClicking(true); }; - const doMouseUp = () => { - if (!oldMouse) return; // is null, something messed up - const newCoords = { - x: clipX(myCoords.x, delX), - y: clipY(myCoords.y, delY), - }; + myCurrent.addEventListener('mousedown', doMouseDown); - oldMouse = null; - setDelCoords(null); - setCoords(newCoords); + return () => { + myCurrent.removeEventListener('mousedown', doMouseDown); }; + }, [coords, containerRef]); + + // attach mousemove handler + useEffect(() => { const doMouseMove = (e) => { - if (!oldMouse) return; // is null, something messed up + if (!mousedownCoords) return; // is null, something messed up + if (!visible || !clicking) return; - delX = e.clientX - oldMouse.x; - delY = e.clientY - oldMouse.y; + const delX = e.clientX - mousedownCoords.x; + const delY = e.clientY - mousedownCoords.y; setDelCoords({ x: delX, y: delY }); }; - myCurrent.addEventListener('mousedown', doMouseDown); - window.addEventListener('mouseup', doMouseUp); - window.addEventListener('mouseleave', doMouseUp); - window.addEventListener('mousemove', doMouseMove); + if (visible && clicking) { + window.addEventListener('mousemove', doMouseMove); + } + return () => { + window.removeEventListener('mousemove', doMouseMove); + }; + }, [visible, clicking, mousedownCoords]); + + // attach mouseup handler + useEffect(() => { + if (!mousedownCoords || !coords) return; + + const doMouseUp = (e) => { + const delX = e.clientX - mousedownCoords.x; + const delY = e.clientY - mousedownCoords.y; + + const newCoords = { + x: clipX(coords.x + delX, containerRef.current.offsetWidth), + y: clipY(coords.y + delY, containerRef.current.offsetHeight), + }; + + setMousedownCoords(null); + setDelCoords(null); + setCoords(newCoords); + setClicking(false); + }; + if (visible && clicking) { + window.addEventListener('mouseleave', doMouseUp); + window.addEventListener('mouseup', doMouseUp); + } return () => { - myCurrent.removeEventListener('mousedown', doMouseDown); window.removeEventListener('mouseup', doMouseUp); window.addEventListener('mouseleave', doMouseUp); - window.removeEventListener('mousemove', doMouseMove); }; - }, [coords]); + }, [visible, clicking, mousedownCoords, coords]); // inits at center useLayoutEffect(() => { const newX = 0.5 * (window.innerWidth - containerRef.current.offsetWidth); const newY = 0.5 * (window.innerHeight - containerRef.current.offsetHeight); setCoords({ x: newX, y: newY }); - setZIndex(windowManager.getIndex()); - }, [containerRef, windowManager]); + push(); + }, [containerRef, windowManager, push]); // if fixCorner, fix to corner useLayoutEffect(() => { @@ -329,19 +353,32 @@ export function ModalPane({ // push to top useLayoutEffect(() => { - setZIndex(windowManager.getIndex()); - }, [visible, windowManager]); + push(); + }, [visible, windowManager, push]); + + // calculate real values + const [left, setLeft] = useState(0); + const [top, setTop] = useState(0); + + useLayoutEffect(() => { + if (!coords) return; + const delX = delCoords ? delCoords.x : 0; + const delY = delCoords ? delCoords.y : 0; + + setLeft(clipX(coords.x + delX, containerRef.current.offsetWidth)); + setTop(clipY(coords.y + delY, containerRef.current.offsetHeight)); + }, [coords, delCoords, containerRef]); return ( setZIndex(windowManager.getIndex())} + onMouseDown={push} >
(null); diff --git a/src/app/GameWindowPanes/PlanetDexPane.tsx b/src/app/GameWindowPanes/PlanetDexPane.tsx index c8a5a9be..c5cd6c9c 100644 --- a/src/app/GameWindowPanes/PlanetDexPane.tsx +++ b/src/app/GameWindowPanes/PlanetDexPane.tsx @@ -18,7 +18,7 @@ import { getPlanetRank, } from '../../utils/Utils'; import dfstyles from '../../styles/dfstyles'; -import { getPlanetName, getPlanetColors } from '../../utils/ProcgenUtils'; +import { getPlanetName, getPlanetCosmetic } from '../../utils/ProcgenUtils'; import _ from 'lodash'; import { SelectedContext } from '../GameWindow'; import { SilverIcon } from '../Icons'; @@ -195,7 +195,7 @@ const ColorIcon = styled.span<{ color: string }>` export function PlanetThumb({ planet }: { planet: Planet }) { const radius = 5 + 3 * planet.planetLevel; // const radius = 5 + 3 * PlanetLevel.MAX; - const { baseColor, backgroundColor } = getPlanetColors(planet); + const { baseColor, backgroundColor } = getPlanetCosmetic(planet); const ringW = radius * 1.5; const ringH = Math.max(2, ringW / 7); diff --git a/src/app/GameWindowPanes/PlanetPreview.tsx b/src/app/GameWindowPanes/PlanetPreview.tsx index 0afb09a3..cac6a132 100644 --- a/src/app/GameWindowPanes/PlanetPreview.tsx +++ b/src/app/GameWindowPanes/PlanetPreview.tsx @@ -3,7 +3,7 @@ import React, { useRef, useEffect, useState } from 'react'; import styled from 'styled-components'; import dfstyles from '../../styles/dfstyles'; import { - getPlanetColors, + getPlanetCosmetic, PixelCoords, planetPerlin, planetRandom, @@ -89,7 +89,7 @@ class PlanetPreviewRenderer { drawAsteroids(): void { const { ctx, bufferCtx, bufferCanvas, planet } = this; - const colors = getPlanetColors(planet); + const colors = getPlanetCosmetic(planet); const perlin = planet ? planetPerlin(planet.locationId) : (_: PixelCoords) => 0; @@ -166,7 +166,7 @@ class PlanetPreviewRenderer { drawPlanet(): void { const { ctx, bufferCtx, bufferCanvas, planet } = this; - const colors = getPlanetColors(planet); + const colors = getPlanetCosmetic(planet); const perlin = planet ? planetPerlin(planet.locationId) : (_: PixelCoords) => 0; diff --git a/src/app/GameWindowPanes/PlanetScape.tsx b/src/app/GameWindowPanes/PlanetScape.tsx index d93b28a8..6cdbd6d0 100644 --- a/src/app/GameWindowPanes/PlanetScape.tsx +++ b/src/app/GameWindowPanes/PlanetScape.tsx @@ -8,7 +8,7 @@ import { StatIdx, } from '../../_types/global/GlobalTypes'; import { - getPlanetColors, + getPlanetCosmetic, PixelCoords, planetPerlin, planetRandom, @@ -152,6 +152,7 @@ class PlanetscapeRenderer { frameRequestId: number; private TICK_SIZE = 2; + frameCount = 0; constructor(scapeCanvas: HTMLCanvasElement, moonCanvas: HTMLCanvasElement) { this.planet = null; @@ -176,7 +177,8 @@ class PlanetscapeRenderer { } draw() { - this.drawMoon(); + this.frameCount++; + if (this.frameCount % 3 === 0) this.drawMoon(); // this.drawScape(); this.frameRequestId = window.requestAnimationFrame(this.draw); } @@ -193,7 +195,7 @@ class PlanetscapeRenderer { return; } - const colors = getPlanetColors(planet); + const colors = getPlanetCosmetic(planet); const bonuses = bonusFromHex(planet.locationId); const rand = planetRandom(planet.locationId); @@ -299,7 +301,7 @@ class PlanetscapeRenderer { ctx.clearRect(0, 0, canvas.width, canvas.height); return; } - const colors: PlanetCosmeticInfo = getPlanetColors(planet); + const colors: PlanetCosmeticInfo = getPlanetCosmetic(planet); ctx.globalAlpha = 1; const perlin: (x: PixelCoords) => number = planetPerlin(planet.locationId); @@ -456,7 +458,7 @@ export function PlanetScape({ // make sure bg color matches planet useEffect(() => { - const planetColors = getPlanetColors(planet); + const planetColors = getPlanetCosmetic(planet); setColor(planetColors.backgroundColor); }, [planet]); diff --git a/src/app/GameWindowPanes/PlayerInfoPane.tsx b/src/app/GameWindowPanes/PlayerInfoPane.tsx index f7d72c03..e1f8d36e 100644 --- a/src/app/GameWindowPanes/PlayerInfoPane.tsx +++ b/src/app/GameWindowPanes/PlayerInfoPane.tsx @@ -48,6 +48,9 @@ export function PlayerInfoPane({ hook: twitterHook }: { hook: ModalHook }) { const [rank, setRank] = useState(0); const [score, setScore] = useState(0); + const [energy, setEnergy] = useState(0); + const [silver, setSilver] = useState(0); + useEffect(() => { if (!uiManager || !account) return; @@ -62,6 +65,9 @@ export function PlayerInfoPane({ hook: twitterHook }: { hook: ModalHook }) { setRank(myRank); setScore(myScore); + + setEnergy(uiManager.getEnergyOfPlayer(account)); + setSilver(uiManager.getSilverOfPlayer(account)); }; const intervalId = setInterval(updateRankAndScore, 60000); @@ -97,21 +103,13 @@ export function PlayerInfoPane({ hook: twitterHook }: { hook: ModalHook }) { Energy - - {account && uiManager - ? formatNumber(uiManager.getEnergyOfPlayer(account)) - : '...'} - + {formatNumber(energy)}
Silver - - {account && uiManager - ? formatNumber(uiManager.getSilverOfPlayer(account)) - : '...'} - + {formatNumber(silver)}
diff --git a/src/app/GameWindowPanes/SettingsPane.tsx b/src/app/GameWindowPanes/SettingsPane.tsx index 780b64f5..de655eb2 100644 --- a/src/app/GameWindowPanes/SettingsPane.tsx +++ b/src/app/GameWindowPanes/SettingsPane.tsx @@ -5,7 +5,7 @@ import { useStoredUIState, UIDataKey } from '../../api/UIStateStorageManager'; import { Sub, Red, White } from '../../components/Text'; import dfstyles from '../../styles/dfstyles'; import { ONE_DAY } from '../../utils/Utils'; -import { EthAddress } from '../../_types/global/GlobalTypes'; +import { EthAddress, Hook } from '../../_types/global/GlobalTypes'; import GameUIManager from '../board/GameUIManager'; import GameUIManagerContext from '../board/GameUIManagerContext'; import { AccountContext } from '../GameWindow'; @@ -58,9 +58,11 @@ const StyledSettingsPane = styled.div` export function SettingsPane({ hook, privateHook, + hiPerfHook, }: { hook: ModalHook; privateHook: ModalHook; + hiPerfHook: Hook; }) { const account = useContext(AccountContext); const uiManager = useContext(GameUIManagerContext); @@ -71,6 +73,8 @@ export function SettingsPane({ uiManager ); + const [hiPerf, setHiPerf] = hiPerfHook; + const [allowTx, setAllowTx] = useState(false); const updateAllowTx = (newVal: boolean): void => { if (!account) return; @@ -303,7 +307,7 @@ export function SettingsPane({
-

Manage notification settings.

+

Manage other settings.

Show notifications for MOVE setNotifMove(e.target.checked)} />
+
+ High-performance mode (lower quality, faster speed!) + setHiPerf(e.target.checked)} + /> +
diff --git a/src/app/GameWindowPanes/Tooltip.tsx b/src/app/GameWindowPanes/Tooltip.tsx index 786db048..d72de641 100644 --- a/src/app/GameWindowPanes/Tooltip.tsx +++ b/src/app/GameWindowPanes/Tooltip.tsx @@ -1,11 +1,17 @@ -import React, { useState, useEffect, useRef, useLayoutEffect } from 'react'; +import React, { + useState, + useEffect, + useRef, + useLayoutEffect, + useContext, +} from 'react'; import styled, { keyframes, css } from 'styled-components'; import dfstyles from '../../styles/dfstyles'; import WindowManager, { TooltipName, WindowManagerEvent, } from '../../utils/WindowManager'; -import { GameWindowZIndex } from '../GameWindow'; +import { GameWindowZIndex, HiPerfContext } from '../GameWindow'; import { TooltipContent } from './TooltipPanes'; // activate TooltipName on mouseenter, deactivate on mouse leave @@ -58,6 +64,8 @@ export function TooltipTrigger({ const [pushed, setPushed] = useState(false); + const hiPerf = useContext(HiPerfContext); + const windowManager = WindowManager.getInstance(); useEffect(() => { @@ -99,7 +107,7 @@ export function TooltipTrigger({ display={display} style={{ ...style }} className={className} - anim={shift} + anim={shift && !hiPerf} onMouseEnter={() => setHovering(true)} onMouseLeave={() => setHovering(false)} > @@ -125,7 +133,7 @@ const StyledTooltip = styled.div<{ display: ${(props) => (props.visible ? 'block' : 'none')}; `; -export function Tooltip() { +export function Tooltip({ hiPerf }: { hiPerf: boolean }) { const [top, setTop] = useState(0); const [left, setLeft] = useState(0); @@ -142,7 +150,7 @@ export function Tooltip() { const [height, setHeight] = useState(20); const [width, setWidth] = useState(20); - // sync current + // subscribe to tooltip changes useEffect(() => { const checkTooltip = () => { const current = windowManager.getTooltip(); @@ -157,23 +165,17 @@ export function Tooltip() { }; }, [windowManager]); + // sync visible to current tooltip useEffect(() => { - const doMouseMove = (e) => { - setLeft(e.clientX); - setTop(e.clientY); - }; - const checkTooltip = () => { const current = windowManager.getTooltip(); if (current === TooltipName.None) setVisible(false); else setVisible(true); }; - window.addEventListener('mousemove', doMouseMove); windowManager.on(WindowManagerEvent.TooltipUpdated, checkTooltip); return () => { - window.removeEventListener('mousemove', doMouseMove); windowManager.removeListener( WindowManagerEvent.TooltipUpdated, checkTooltip @@ -181,11 +183,30 @@ export function Tooltip() { }; }, [windowManager, height, width]); + // sync mousemove event + useEffect(() => { + const doMouseMove = (e) => { + if (!visible) return; + setLeft(e.clientX); + setTop(e.clientY); + }; + + if (visible) { + window.addEventListener('mousemove', doMouseMove); + } + + return () => { + window.removeEventListener('mousemove', doMouseMove); + }; + }, [visible]); + + // sync size to content size useLayoutEffect(() => { setHeight(elRef.current.offsetHeight); setWidth(elRef.current.offsetWidth); }, [elRef.current.offsetHeight, elRef, visible]); + // point it in the right direction based on quadrant useLayoutEffect(() => { if (left < window.innerWidth / 2) { setLeftOffset(10); @@ -200,8 +221,7 @@ export function Tooltip() { } }, [left, top, width, height]); - const tooltipArr: Array = []; - tooltipArr[TooltipName.None] = <>; + if (hiPerf) return
; return ( ; - zone0ChunkMap: Map; zone1ChunkMap: Map; zone2ChunkMap: Map; @@ -45,6 +43,11 @@ class CanvasRenderer { imgPattern: CanvasPattern | null; + frameCount: number; + highPerf: boolean; + now: number; + selected: Planet | null; + private constructor( canvas: HTMLCanvasElement, gameUIManager: GameUIManager, @@ -60,13 +63,17 @@ class CanvasRenderer { this.gameUIManager = gameUIManager; this.perlinThreshold1 = gameUIManager.getPerlinThresholds()[0]; this.perlinThreshold2 = gameUIManager.getPerlinThresholds()[1]; - this.planetToCosmetic = {}; this.zone0ChunkMap = new Map(); this.zone1ChunkMap = new Map(); this.zone2ChunkMap = new Map(); this.imgPattern = ctx.createPattern(image, 'repeat'); + this.frameCount = 0; + this.now = Date.now(); + this.highPerf = false; + this.selected = null; + this.frame(); autoBind(this); } @@ -99,14 +106,14 @@ class CanvasRenderer { return canvasRenderer; } - private frame() { + private draw() { const viewport = Viewport.getInstance(); const exploredChunks = this.gameUIManager.getExploredChunks(); this.zone0ChunkMap = new Map(); this.zone1ChunkMap = new Map(); this.zone2ChunkMap = new Map(); - let planetLocations: Location[] = []; + const planetLocations: Location[] = []; for (const exploredChunk of exploredChunks) { if (viewport.intersectsViewport(exploredChunk)) { let chunkMap: Map; @@ -124,19 +131,6 @@ class CanvasRenderer { } } - planetLocations = planetLocations.sort((a, b) => { - const aLevel = this.gameUIManager.getPlanetLevel(a.hash); - const bLevel = this.gameUIManager.getPlanetLevel(b.hash); - // TODO: throw error if either is null - if (aLevel === null) { - return 1; - } - if (bLevel === null) { - return -1; - } - return bLevel - aLevel; - }); - this.drawCleanBoard(); this.drawKnownChunks([ this.zone0ChunkMap.values(), @@ -154,7 +148,24 @@ class CanvasRenderer { this.drawBorders(); this.drawMiner(); + } + + private frame() { + this.frameCount++; + + if (this.frameCount % 60 === 0) { + // don't need to update that often + this.highPerf = this.gameUIManager.getUIDataItem(UIDataKey.highPerf); + } + + // make the tick depend on detail level? + const tick = this.highPerf ? 3 : 1; + if (this.frameCount % tick === 0) { + this.selected = this.gameUIManager.getSelectedPlanet(); + this.now = Date.now(); + this.draw(); + } this.frameRequestId = window.requestAnimationFrame(this.frame.bind(this)); } @@ -226,20 +237,41 @@ class CanvasRenderer { } private drawPlanets(planetLocations: Location[]) { - for (const location of planetLocations) { - this.drawPlanetAtLocation(location); + for (let l = PlanetLevel.MAX; l >= PlanetLevel.MIN; l--) { + for (let i = 0; i < planetLocations.length; i++) { + this.drawPlanetAtLocation(planetLocations[i], l); + } } } - private drawPlanetAtLocation(location: Location) { + private drawPlanetAtLocation(location: Location, atLevel: PlanetLevel) { const uiManager = this.gameUIManager; const planetLevel = uiManager.getPlanetDetailLevel(location.hash); - if (planetLevel === null || planetLevel < uiManager.getDetailLevel()) { - return; // so we don't call getPlanetWithLocation, which triggers updates every second - } + if (planetLevel !== atLevel) return; // strictly for ordering + + const isSelected = location.hash === this.selected?.locationId; const planet = uiManager.getPlanetWithId(location.hash); - if (!planet) { - return null; + if (!planet) return; // if we messed up somehow + + const isVeryBig = planetLevel >= 6; + + const radius = uiManager.getRadiusOfPlanetLevel(planet.planetLevel); + const viewport = Viewport.getInstance(); + const radiusReal = viewport.worldToCanvasDist(radius); + + const MIN_RADIUS = 2; + if (!isSelected) { + if (radiusReal < MIN_RADIUS && !isVeryBig) return; // detail level fallback + if (planetLevel === null || planetLevel < uiManager.getDetailLevel()) { + return; // so we don't call getPlanetWithLocation, which triggers updates every second + } + } + + if (isSelected || isVeryBig) { + this.ctx.globalAlpha = 1; + } else { + const alpha = Math.max(0, 0.15 * (radiusReal - MIN_RADIUS)); + this.ctx.globalAlpha = Math.min(alpha, 1); } const energy = planet ? Math.ceil(planet.energy) : 0; @@ -247,17 +279,14 @@ class CanvasRenderer { const silver = planet ? Math.floor(planet.silver) : 0; const center = { x: location.coords.x, y: location.coords.y }; - const radius = uiManager.getRadiusOfPlanetLevel(planet.planetLevel); - - if (!this.planetToCosmetic[planet.locationId]) { - this.planetToCosmetic[planet.locationId] = getPlanetColors(planet); - } - const colors = this.planetToCosmetic[planet.locationId]; + const colors = getPlanetCosmetic(planet); const myRotation = (-40 + (colors.baseHue % 80)) * (Math.PI / 180); /* draw ring back */ - const numRings = getPlanetRank(planet); + const rank = getPlanetRank(planet); + // const numRings = this.highPerf ? (rank > 0 ? 1 : 0) : rank; + const numRings = rank; // const numRings = 2; for (let i = 0; i < numRings; i++) @@ -274,15 +303,23 @@ class CanvasRenderer { /* draw planet */ // hp bar 1 - this.ctx.globalAlpha = 0.7; if (hasOwner(planet)) { if (uiManager.isOwnedByMe(planet)) { - this.drawLoopWithCenter(center, radius * 1.2, 1, 'white'); + this.drawLoopWithCenter( + center, + radius * 1.2, + 1, + 'rgba(255, 255, 255, 0.7)' + ); } else { - this.drawLoopWithCenter(center, radius * 1.2, 1, getOwnerColor(planet)); + this.drawLoopWithCenter( + center, + radius * 1.2, + 1, + getOwnerColor(planet, 0.7) + ); } } - this.ctx.globalAlpha = 1.0; this.drawPlanetBody(center, radius, planet); @@ -325,7 +362,11 @@ class CanvasRenderer { const current = uiManager.getDetailLevel(); const det = uiManager.getPlanetDetailLevel(planet.locationId); if (det === null) return; - if (det > current + 1) { + if (det > current + 1 || isSelected) { + if (!isSelected && !isVeryBig) { + this.ctx.globalAlpha = Math.min(0.1 * radiusReal, 1); + } + const fromPlanet = uiManager.getMouseDownPlanet(); const fromCoords = uiManager.getMouseDownCoords(); const toPlanet = uiManager.getHoveringOverPlanet(); @@ -386,7 +427,7 @@ class CanvasRenderer { x: center.x, y: center.y - 1.1 * radius - (planet.owner ? 0.75 : 0.25), }, - uiManager.isOwnedByMe(planet) ? 'white' : getOwnerColor(planet) + uiManager.isOwnedByMe(planet) ? 'white' : getOwnerColor(planet, 1) ); // hp bar 2 if (uiManager.isOwnedByMe(planet)) { @@ -403,7 +444,7 @@ class CanvasRenderer { radius * 1.2, 3, (planet.energy / planet.energyCap) * 100, - getOwnerColor(planet) + getOwnerColor(planet, 1) ); } } else if (!hasOwner(planet) && energy > 0) { @@ -435,12 +476,14 @@ class CanvasRenderer { ); } } + + this.ctx.globalAlpha = 1; } private drawVoyages() { const voyages = this.gameUIManager.getAllVoyages(); for (const voyage of voyages) { - const now = Date.now() / 1000; + const now = this.now / 1000; if (now < voyage.arrivalTime) { const isMyVoyage = voyage.player === this.gameUIManager.getAccount(); this.drawVoyagePath( @@ -474,7 +517,7 @@ class CanvasRenderer { } else if (!fromLoc && fromPlanet && toLoc) { // can draw a red ring around dest, but don't know source location const myMove = voyage.player === this.gameUIManager.getAccount(); - const now = Date.now() / 1000; + const now = this.now / 1000; const timeLeft = voyage.arrivalTime - now; const radius = (timeLeft * fromPlanet.speed) / 100; this.drawLoopWithCenter( @@ -492,7 +535,7 @@ class CanvasRenderer { // know source and destination locations const myMove = voyage.player === this.gameUIManager.getAccount(); - const now = Date.now() / 1000; + const now = this.now / 1000; let proportion = (now - voyage.departureTime) / (voyage.arrivalTime - voyage.departureTime); @@ -562,11 +605,10 @@ class CanvasRenderer { } private drawSelectedRangeRing() { - const uiManager = this.gameUIManager; - const selected = uiManager.getSelectedPlanet(); + const selected = this.selected; if (!selected) return; - const loc = uiManager.getLocationOfPlanet(selected.locationId); + const loc = this.gameUIManager.getLocationOfPlanet(selected.locationId); if (!loc) return; const { x, y } = loc?.coords; @@ -614,7 +656,9 @@ class CanvasRenderer { if (selected.owner === emptyAddress) return; - const forcesSending = uiManager.getForcesSending(selected.locationId); // [0, 100] + const forcesSending = this.gameUIManager.getForcesSending( + selected.locationId + ); // [0, 100] const totalForces = (forcesSending / 100) * selected.energy; const scaled = (forcesSending * selected.energy) / selected.energyCap; @@ -823,32 +867,32 @@ class CanvasRenderer { ctx.save(); ctx.translate(centerCanvasCoords.x, centerCanvasCoords.y); - const angle = Date.now() * 0.001; + const angle = this.now * 0.001; const drawAsteroid = (t: number, color: string) => { const theta = t + angle; const x = Math.cos(theta); const y = Math.sin(theta); - const clip = (tt) => tt % (Math.PI * 2); - - ctx.lineWidth = r; - - for (let i = 1; i <= 8; i++) { - ctx.globalAlpha = 0.04; - - ctx.beginPath(); - ctx.arc(0, 0, orbit, clip(theta - 0.1 * i), clip(theta)); - - ctx.strokeStyle = color; - ctx.stroke(); - ctx.strokeStyle = 'white'; - ctx.stroke(); - ctx.stroke(); + if (!this.highPerf) { + const clip = (tt) => tt % (Math.PI * 2); + ctx.lineWidth = r; + const oldAlpha = ctx.globalAlpha; + for (let i = 1; i <= 8; i++) { + ctx.globalAlpha = 0.04 * oldAlpha; + + ctx.beginPath(); + ctx.arc(0, 0, orbit, clip(theta - 0.1 * i), clip(theta)); + + ctx.strokeStyle = color; + ctx.stroke(); + ctx.strokeStyle = 'white'; + ctx.stroke(); + ctx.stroke(); + } + ctx.globalAlpha = oldAlpha; } - ctx.globalAlpha = 1; - ctx.beginPath(); ctx.fillStyle = color; ctx.arc(orbit * x, orbit * y, r, 0, 2 * Math.PI); @@ -934,7 +978,7 @@ class CanvasRenderer { ctx.save(); ctx.translate(center.x, center.y); - const colors = this.planetToCosmetic[planet.locationId]; + const colors = getPlanetCosmetic(planet); if (planet.planetResource === PlanetResource.NONE) { ctx.fillStyle = colors.previewColor; @@ -945,7 +989,7 @@ class CanvasRenderer { // silver-producing for (let i = 0; i < 5; i++) { - const t = (i * (Math.PI * 2)) / 5 + Date.now() * 0.0003; + const t = (i * (Math.PI * 2)) / 5 + this.now * 0.0003; ctx.fillStyle = colors.asteroidColor; ctx.beginPath(); diff --git a/src/app/board/ControllableCanvas.tsx b/src/app/board/ControllableCanvas.tsx index 9375401c..7592e69a 100644 --- a/src/app/board/ControllableCanvas.tsx +++ b/src/app/board/ControllableCanvas.tsx @@ -15,6 +15,7 @@ import WindowManager, { CursorState, WindowManagerEvent, } from '../../utils/WindowManager'; +import _ from 'lodash'; export default function ControllableCanvas() { // html canvas element width and height. viewport dimensions are tracked by viewport obj @@ -77,6 +78,7 @@ export default function ControllableCanvas() { const { deltaY } = e; uiEmitter.emit(UIEmitterEvent.CanvasScroll, deltaY); } + const throttledWheel = _.throttle(onWheel, 60); const canvas = canvasRef.current; const img = imgRef.current; @@ -89,7 +91,7 @@ export default function ControllableCanvas() { CanvasRenderer.initialize(canvas, gameUIManager, img); // We can't attach the wheel event onto the canvas due to: // https://www.chromestatus.com/features/6662647093133312 - canvas.addEventListener('wheel', onWheel); + canvas.addEventListener('wheel', throttledWheel); window.addEventListener('resize', onResize); uiEmitter.on(UIEmitterEvent.UIChange, doResize); @@ -97,29 +99,53 @@ export default function ControllableCanvas() { return () => { Viewport.destroyInstance(); CanvasRenderer.destroyInstance(); - canvas.removeEventListener('wheel', onWheel); + canvas.removeEventListener('wheel', throttledWheel); window.removeEventListener('resize', onResize); uiEmitter.removeListener(UIEmitterEvent.UIChange, doResize); }; }, [gameUIManager, uiEmitter, doResize, imgRef, loaded]); - if (!gameUIManager) { - console.error('GameUIManager context is null'); - return
; - } - - function onMouseEvent( - emitEventName: UIEmitterEvent, - mouseEvent: React.MouseEvent - ) { - if (!canvasRef.current) { - return; + // attach event listeners + useEffect(() => { + if (!canvasRef.current) return; + const canvas = canvasRef.current; + + function onMouseEvent( + emitEventName: UIEmitterEvent, + mouseEvent: React.MouseEvent + ) { + const rect = canvas.getBoundingClientRect(); + const canvasX = mouseEvent.clientX - rect.left; + const canvasY = mouseEvent.clientY - rect.top; + uiEmitter.emit(emitEventName, { x: canvasX, y: canvasY }); } - const rect = canvasRef.current.getBoundingClientRect(); - const canvasX = mouseEvent.clientX - rect.left; - const canvasY = mouseEvent.clientY - rect.top; - uiEmitter.emit(emitEventName, { x: canvasX, y: canvasY }); - } + + const onMouseDown = (e) => { + onMouseEvent(UIEmitterEvent.CanvasMouseDown, e); + }; + // this is the root of the mousemove event + const onMouseMove = (e) => { + onMouseEvent(UIEmitterEvent.CanvasMouseMove, e); + }; + const onMouseUp = (e) => { + onMouseEvent(UIEmitterEvent.CanvasMouseUp, e); + }; + // TODO convert this to mouseleave + const onMouseOut = () => { + uiEmitter.emit(UIEmitterEvent.CanvasMouseOut); + }; + + canvas.addEventListener('mousedown', onMouseDown); + canvas.addEventListener('mousemove', onMouseMove); + canvas.addEventListener('mouseup', onMouseUp); + canvas.addEventListener('mouseout', onMouseOut); + return () => { + canvas.removeEventListener('mousedown', onMouseDown); + canvas.removeEventListener('mousemove', onMouseMove); + canvas.removeEventListener('mouseup', onMouseUp); + canvas.removeEventListener('mouseout', onMouseOut); + }; + }, [canvasRef, uiEmitter]); return (
{ - onMouseEvent(UIEmitterEvent.CanvasMouseDown, e); - }} - onMouseMove={(e) => { - onMouseEvent(UIEmitterEvent.CanvasMouseMove, e); - }} - onMouseUp={(e) => { - onMouseEvent(UIEmitterEvent.CanvasMouseUp, e); - }} - onMouseOut={() => { - uiEmitter.emit(UIEmitterEvent.CanvasMouseOut); - }} ref={canvasRef} width={width} height={height} diff --git a/src/app/board/Viewport.ts b/src/app/board/Viewport.ts index 416dc93c..92eb8253 100644 --- a/src/app/board/Viewport.ts +++ b/src/app/board/Viewport.ts @@ -3,6 +3,7 @@ import { WorldCoords, CanvasCoords, distL2 } from '../../utils/Coordinates'; import autoBind from 'auto-bind'; import AbstractUIManager from './AbstractUIManager'; import { ExploredChunkData, Planet } from '../../_types/global/GlobalTypes'; +import _ from 'lodash'; class Viewport { // The sole listener for events from Canvas @@ -101,9 +102,14 @@ class Viewport { canvas ); + // temporary, we do this because otherwise new players see a faded out home planet + viewport.zoomIn(); + viewport.zoomIn(); + viewport.centerCoords(gameUIManager.getHomeCoords()); + uiEmitter .on(UIEmitterEvent.CanvasMouseDown, viewport.onMouseDown) - .on(UIEmitterEvent.CanvasMouseMove, viewport.onMouseMove) + .on(UIEmitterEvent.CanvasMouseMove, _.throttle(viewport.onMouseMove, 33)) .on(UIEmitterEvent.CanvasMouseUp, viewport.onMouseUp) .on(UIEmitterEvent.CanvasMouseOut, viewport.onMouseOut) .on(UIEmitterEvent.CanvasScroll, viewport.onScroll) @@ -125,6 +131,10 @@ class Viewport { this.centerWorldCoords = { x, y }; } + centerCoords(coords: WorldCoords): void { + this.centerWorldCoords = coords; + } + centerChunk(chunk: ExploredChunkData): void { const { bottomLeft, sideLength } = chunk.chunkFootprint; this.centerWorldCoords = { diff --git a/src/utils/ProcgenUtils.ts b/src/utils/ProcgenUtils.ts index 4648b094..5ce4449c 100644 --- a/src/utils/ProcgenUtils.ts +++ b/src/utils/ProcgenUtils.ts @@ -24,7 +24,13 @@ export const hslStr: (h: number, s: number, l: number) => string = ( ) => { return `hsl(${h % 360},${s}%,${l}%)`; }; + +const huesByHash = new Map(); + function hashToHue(hash: string): number { + if (huesByHash.has(hash)) { + return huesByHash.get(hash) || 0; + } let seed = bigInt(hash, 16).and(0xffffff).toString(16); seed = '0x' + '0'.repeat(6 - seed.length) + seed; @@ -36,9 +42,14 @@ export const getPlayerColor: (player: EthAddress) => string = (player) => { return hslStr(hashToHue(player.slice(2)), 100, 70); // remove 0x }; -export const getOwnerColor: (planet: Planet) => string = (planet) => { +export const getOwnerColor: (planet: Planet, alpha: number) => string = ( + planet, + alpha +) => { if (planet.owner === emptyAddress) return '#996666'; - return planet.owner ? getPlayerColor(planet.owner) : 'hsl(0,1%,50%)'; + return planet.owner + ? getPlayerColor(planet.owner) + : `hsla(0,1%,50%,${alpha})`; }; export type PixelCoords = { @@ -99,10 +110,16 @@ const grayColors: PlanetCosmeticInfo = { hatType: HatType.GraduationCap, }; -export const getPlanetColors: (planet: Planet | null) => PlanetCosmeticInfo = ( - planet -) => { +const cosmeticByLocId = new Map(); + +export const getPlanetCosmetic: ( + planet: Planet | null +) => PlanetCosmeticInfo = (planet) => { if (!planet) return grayColors; + if (cosmeticByLocId.has(planet.locationId)) { + return cosmeticByLocId.get(planet.locationId) || grayColors; + } + const baseHue = hashToHue(planet.locationId); const baseColor = hslStr(baseHue % 360, 70, 60); diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index cbf241e9..8c63a401 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -235,27 +235,40 @@ export const aggregateBulkGetter = async ( const start = i * querySize; const end = Math.min((i + 1) * querySize, total); promises.push( - getterFn(start, end) - .then((res) => { - if ( - printProgress && - Math.floor((soFar * 20) / total) !== - Math.floor(((soFar + querySize) * 20) / total) - ) { - // print every 5% - let percent = Math.floor(((soFar + querySize) * 20) / total) * 5; - percent = Math.min(percent, 100); - terminalEmitter.print(`${percent}%... `); - } - soFar += querySize; - return res; - }) - .catch((err) => { - console.error( - `error ${JSON.stringify(err)} occurred querying ${start}-${end}` - ); - return []; - }) + new Promise(async (resolve) => { + let res: T[] = []; + const tries = 0; + while (res.length === 0) { + // retry with exponential backoff if request fails + await new Promise((resolve) => { + setTimeout(resolve, Math.max(15, 2 ** tries - 1) * 1000); + }); + res = await getterFn(start, end) + .then((res) => { + if ( + printProgress && + Math.floor((soFar * 20) / total) !== + Math.floor(((soFar + querySize) * 20) / total) + ) { + // print every 5% + let percent = + Math.floor(((soFar + querySize) * 20) / total) * 5; + percent = Math.min(percent, 100); + terminalEmitter.print(`${percent}%... `); + } + soFar += querySize; + console.log(`retrieved ${start}-${end}.`); + return res; + }) + .catch(() => { + console.error( + `error occurred querying ${start}-${end}. retrying...` + ); + return []; + }); + } + resolve(res); + }) ); } const unflattenedResults = await Promise.all(promises);