diff --git a/TODO.txt b/TODO.txt index 1aefc1132..2a90746b3 100644 --- a/TODO.txt +++ b/TODO.txt @@ -216,15 +216,20 @@ * bufix: mini map doesn't load in time DONE 12/7/2023 * bugfix: private game host game doesn't start DONE 12/8/2023 * add NA map DONE 12/8/2023 -* add Oceania map 12/8/2023 +* add Oceania map DONE 12/8/2023 +* max price for units DONE 12/9/2024 +* better unit scaling DONE 12/9/2024 * store in BigQuery +* make hard & impossible harder * clicking on a player's name in the rank UI should teleport you to him (pretty useful to know who's who and to locate small nations) * record commit hash of game * record game winner +* add panama canal NA +* make boats work on lakes (& oceania) * replay stored games -* max price for units * when player dies, don't remove atom bombs * remove alliance when player dies +* only check islands/clusters when being attacked * alert on attack * alert on unit captured or destroyed * nuking an enemy and accidentally destroying a trade ship shouldn't break the alliance and make you a traitor diff --git a/package-lock.json b/package-lock.json index 06a1a2f17..a32e4a502 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "name": "openfront-client", "dependencies": { "@datastructures-js/priority-queue": "^6.3.1", + "@google-cloud/bigquery": "^7.9.1", "@google-cloud/storage": "^7.14.0", "@types/dompurify": "^3.0.5", "@types/express": "^4.17.21", @@ -2351,6 +2352,92 @@ "node": ">=18" } }, + "node_modules/@google-cloud/bigquery": { + "version": "7.9.1", + "resolved": "https://registry.npmjs.org/@google-cloud/bigquery/-/bigquery-7.9.1.tgz", + "integrity": "sha512-ZkcRMpBoFLxIh6TiQBywA22yT3c2j0f07AHWEMjtYqMQzZQbFrpxuJU2COp3tyjZ91ZIGHe4gY7/dGZL88cltg==", + "license": "Apache-2.0", + "dependencies": { + "@google-cloud/common": "^5.0.0", + "@google-cloud/paginator": "^5.0.2", + "@google-cloud/precise-date": "^4.0.0", + "@google-cloud/promisify": "^4.0.0", + "arrify": "^2.0.1", + "big.js": "^6.0.0", + "duplexify": "^4.0.0", + "extend": "^3.0.2", + "is": "^3.3.0", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/bigquery/node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@google-cloud/bigquery/node_modules/big.js": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.2.tgz", + "integrity": "sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ==", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/bigjs" + } + }, + "node_modules/@google-cloud/bigquery/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@google-cloud/common": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-5.0.2.tgz", + "integrity": "sha512-V7bmBKYQyu0eVG2BFejuUjlBt+zrya6vtsKdY+JxMM/dNntPF41vZ9+LhOshEUH01zOHEqBSvI7Dad7ZS6aUeA==", + "license": "Apache-2.0", + "dependencies": { + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "^4.0.0", + "arrify": "^2.0.1", + "duplexify": "^4.1.1", + "extend": "^3.0.2", + "google-auth-library": "^9.0.0", + "html-entities": "^2.5.2", + "retry-request": "^7.0.0", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/common/node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@google-cloud/paginator": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", @@ -2373,6 +2460,15 @@ "node": ">=8" } }, + "node_modules/@google-cloud/precise-date": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/precise-date/-/precise-date-4.0.0.tgz", + "integrity": "sha512-1TUx3KdaU3cN7nfCdNf+UVqA/PSX29Cjcox3fZZBtINlRrXVTmUkQnCKv2MbBUbCopbK4olAT1IHl76uZyCiVA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@google-cloud/projectify": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", @@ -8767,6 +8863,15 @@ "node": ">= 0.10" } }, + "node_modules/is": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", + "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", diff --git a/package.json b/package.json index 6dc47bfcf..e03563acd 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ }, "dependencies": { "@datastructures-js/priority-queue": "^6.3.1", + "@google-cloud/bigquery": "^7.9.1", "@google-cloud/storage": "^7.14.0", "@types/dompurify": "^3.0.5", "@types/express": "^4.17.21", diff --git a/src/core/configuration/Config.ts b/src/core/configuration/Config.ts index 41ebc70e2..30cf8854a 100644 --- a/src/core/configuration/Config.ts +++ b/src/core/configuration/Config.ts @@ -64,7 +64,6 @@ export interface Config { defensePostRange(): number defensePostDefenseBonus(): number falloutDefenseModifier(): number - maxUnitCost(): number } export interface Theme { diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index a1c712391..5a1f84914 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -8,10 +8,6 @@ import { pastelTheme } from "./PastelTheme"; export class DefaultConfig implements Config { - maxUnitCost(): number { - return 99_999_999 - } - cityPopulationIncrease(): number { return 250_000 } @@ -38,71 +34,73 @@ export class DefaultConfig implements Config { } unitInfo(type: UnitType): UnitInfo { - const fn = () => { - switch (type) { - case UnitType.TransportShip: - return { - cost: () => 0, - territoryBound: false - } - case UnitType.Destroyer: - return { - cost: (p: Player) => (p.units(UnitType.Destroyer).length + 1) * 250_000, - territoryBound: false - } - case UnitType.Battleship: - return { - cost: (p: Player) => (p.units(UnitType.Battleship).length + 1) * 500_000, - territoryBound: false - } - case UnitType.Shell: - return { - cost: (p: Player) => 0, - territoryBound: false - } - case UnitType.Port: - return { - cost: (p: Player) => Math.pow(2, p.units(UnitType.Port).length) * 250_000, - territoryBound: true - } - case UnitType.AtomBomb: - return { - cost: () => 1_000_000, - territoryBound: false - } - case UnitType.HydrogenBomb: - return { - cost: () => 5_000_000, - territoryBound: false - } - case UnitType.TradeShip: - return { - cost: () => 0, - territoryBound: false - } - case UnitType.MissileSilo: - return { - cost: () => 1_000_000, - territoryBound: true - } - case UnitType.DefensePost: - return { - cost: (p: Player) => Math.pow(2, p.units(UnitType.DefensePost).length) * 100_000, - territoryBound: true - } - case UnitType.City: - return { - cost: (p: Player) => Math.pow(2, p.units(UnitType.City).length) * 250_000, - territoryBound: true - } - default: - assertNever(type) - } + switch (type) { + case UnitType.TransportShip: + return { + cost: () => 0, + territoryBound: false + } + case UnitType.Destroyer: + return { + cost: (p: Player) => (p.units(UnitType.Destroyer).length + 1) * 250_000, + territoryBound: false + } + case UnitType.Battleship: + return { + cost: (p: Player) => (p.units(UnitType.Battleship).length + 1) * 500_000, + territoryBound: false + } + case UnitType.Shell: + return { + cost: () => 0, + territoryBound: false + } + case UnitType.Port: + return { + cost: (p: Player) => + Math.min( + 10_000_000, + Math.pow(2, p.units(UnitType.Port).length) * 250_000 + ), + territoryBound: true + } + case UnitType.AtomBomb: + return { + cost: () => 1_000_000, + territoryBound: false + } + case UnitType.HydrogenBomb: + return { + cost: () => 5_000_000, + territoryBound: false + } + case UnitType.TradeShip: + return { + cost: () => 0, + territoryBound: false + } + case UnitType.MissileSilo: + return { + cost: () => 1_000_000, + territoryBound: true + } + case UnitType.DefensePost: + return { + cost: (p: Player) => + Math.min( + 500_000, + (p.units(UnitType.DefensePost).length + 1) * 100_000 + ), + territoryBound: true + } + case UnitType.City: + return { + cost: (p: Player) => Math.pow(2, p.units(UnitType.City).length) * 125_000, + territoryBound: true + } + default: + assertNever(type) } - const ui = fn() - const oldCost = ui.cost - ui.cost = (p: Player) => Math.min(this.maxUnitCost(), oldCost(p)) - return ui } defaultDonationAmount(sender: Player): number { return Math.floor(sender.troops() / 3) diff --git a/src/core/configuration/DevConfig.ts b/src/core/configuration/DevConfig.ts index 29b58a46a..db8e9aae1 100644 --- a/src/core/configuration/DevConfig.ts +++ b/src/core/configuration/DevConfig.ts @@ -5,7 +5,7 @@ export const devConfig = new class extends DefaultConfig { unitInfo(type: UnitType): UnitInfo { const info = super.unitInfo(type) const oldCost = info.cost - info.cost = (p: Player) => oldCost(p) / 1000 + // info.cost = (p: Player) => oldCost(p) / 1000 return info } maxUnitCost(): number { diff --git a/src/server/Archive.ts b/src/server/Archive.ts index 9d5c78eea..e3e838568 100644 --- a/src/server/Archive.ts +++ b/src/server/Archive.ts @@ -1,7 +1,10 @@ import { GameConfig, GameID, GameRecord, GameRecordSchema, Turn } from "../core/Schemas"; import { Storage } from '@google-cloud/storage'; +import { BigQuery } from '@google-cloud/bigquery'; const storage = new Storage(); +const bigquery = new BigQuery(); + export async function archive(gameRecord: GameRecord) { try {