mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
Compare commits
14
Commits
a38239a6f9
...
960279bf00
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
960279bf00 | ||
|
|
4247004b13 | ||
|
|
960ca4b0f4 | ||
|
|
424d9944ad | ||
|
|
a75c47865b | ||
|
|
c2714c4d6b | ||
|
|
1c505818a4 | ||
|
|
444705347f | ||
|
|
19556b5fc9 | ||
|
|
e52f003c1b | ||
|
|
4fc57d7fc1 | ||
|
|
8af41b91b5 | ||
|
|
347efa75fe | ||
|
|
1619ee1e00 |
No files matched your search
@@ -139,7 +139,14 @@ jobs:
|
||||
|
||||
- name: Trigger deployment
|
||||
|
||||
if: ${{ github.ref == 'refs/heads/production' }}
|
||||
# Deploy if:
|
||||
# 1. Running cron from main using content from production to target production
|
||||
# 2. Running manually and targeting non-production
|
||||
if: >-
|
||||
${{
|
||||
github.ref == 'refs/heads/production' ||
|
||||
(inputs.environment != '' && inputs.environment != 'production')
|
||||
}}
|
||||
|
||||
env:
|
||||
TOKEN: ${{ secrets.GH_NODE_DEPLOY_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# BlitzKit 1.31.3
|
||||
|
||||
This update updates BlitzKit to work with the game's new ID system for game modes.
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "blitzkit",
|
||||
"version": "1.31.2",
|
||||
"version": "1.31.3",
|
||||
"license": "BlitzKit License",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { ShellDefinition, ShellType } from "../../../protos/src/blitzkit";
|
||||
|
||||
export function canSplash(
|
||||
type: ShellDefinition["type"],
|
||||
): type is ShellType.SHELL_TYPE_HE {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Strings } from "@blitzkit/i18n";
|
||||
import {
|
||||
CamouflageDefinitions,
|
||||
ConsumableDefinitions,
|
||||
@@ -9,8 +10,7 @@ import {
|
||||
ProvisionDefinitions,
|
||||
SkillDefinitions,
|
||||
TankDefinitions,
|
||||
} from "@blitzkit/core";
|
||||
import { Strings } from "@blitzkit/i18n";
|
||||
} from "@blitzkit/protos";
|
||||
import { BlitzScripts } from "../../types/blitzScripts";
|
||||
|
||||
export abstract class AbstractBlitzKitAPI {
|
||||
|
||||
@@ -8,24 +8,26 @@ import {
|
||||
CamouflagesYaml,
|
||||
CombatRolesYaml,
|
||||
ConsumablesCommon,
|
||||
ConsumableTankCategoryFilterCategory,
|
||||
CrewType,
|
||||
I18nString,
|
||||
MapsYaml,
|
||||
ModuleType,
|
||||
OptionalDevices,
|
||||
OptionalDeviceSlots,
|
||||
ProvisionsCommon,
|
||||
ShellKind,
|
||||
ShellType,
|
||||
SquadBattleTypeStylesYaml,
|
||||
TankClass,
|
||||
TankmenAvatar,
|
||||
toUniqueId,
|
||||
VehicleDefinitionList,
|
||||
} from "@blitzkit/core";
|
||||
import { AbstractVFS } from "@blitzkit/core/src/blitzkit/vfs/abstract";
|
||||
import locales from "@blitzkit/i18n/locales.json";
|
||||
import {
|
||||
ConsumableTankCategoryFilterCategory,
|
||||
CrewType,
|
||||
I18nString,
|
||||
ModuleType,
|
||||
ShellType,
|
||||
TankClass,
|
||||
} from "@blitzkit/protos";
|
||||
import type { extname, parse } from "node:path";
|
||||
import { parse as parseYaml } from "yaml";
|
||||
import { AbstractBlitzKitAPI } from "../abstract";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Avatar, fetchGlossary, Gallery } from "@blitzkit/core";
|
||||
import { fetchGlossary } from "@blitzkit/core";
|
||||
import locales from "@blitzkit/i18n/locales.json";
|
||||
import { Avatar, Gallery } from "@blitzkit/protos";
|
||||
import { Cache } from "./0_base";
|
||||
import { ServerBlitzKitAPI9 } from "./9_game";
|
||||
|
||||
@@ -11,7 +12,7 @@ export abstract class ServerBlitzKitAPI10 extends ServerBlitzKitAPI9 {
|
||||
await Promise.all(
|
||||
locales.supported.map(async (supported) => {
|
||||
const glossary = await fetchGlossary(
|
||||
supported.blitz ?? supported.locale,
|
||||
supported.variant_blitz_local ?? supported.locale,
|
||||
);
|
||||
|
||||
console.log(
|
||||
|
||||
@@ -1,30 +1,32 @@
|
||||
import {
|
||||
AssaultRanges,
|
||||
BlitzCrewType,
|
||||
BlitzModuleType,
|
||||
BlitzTankClass,
|
||||
ChassisDefinitionsList,
|
||||
Crew,
|
||||
EngineDefinitionsList,
|
||||
Equalizer,
|
||||
GunDefinition,
|
||||
GunDefinitionsList,
|
||||
ModuleType,
|
||||
ResearchCost,
|
||||
ShellDefinitionsList,
|
||||
ShellKind,
|
||||
sluggify,
|
||||
TankDefinitions,
|
||||
TankPrice,
|
||||
TankPriceType,
|
||||
TankType,
|
||||
toUniqueId,
|
||||
TurretDefinitionsList,
|
||||
Unlock,
|
||||
UnlocksListing,
|
||||
VehicleDefinitionList,
|
||||
VehicleDefinitions,
|
||||
} from "@blitzkit/core";
|
||||
import {
|
||||
AssaultRanges,
|
||||
Crew,
|
||||
Equalizer,
|
||||
GunDefinition,
|
||||
ModuleType,
|
||||
ResearchCost,
|
||||
TankDefinitions,
|
||||
TankPrice,
|
||||
TankPriceType,
|
||||
TankType,
|
||||
Unlock,
|
||||
} from "@blitzkit/protos";
|
||||
import { Cache, ServerBlitzKitAPI0 } from "./0_base";
|
||||
|
||||
export abstract class ServerBlitzKitAPI1 extends ServerBlitzKitAPI0 {
|
||||
@@ -50,10 +52,11 @@ export abstract class ServerBlitzKitAPI1 extends ServerBlitzKitAPI0 {
|
||||
async tanks() {
|
||||
const tankDefinitions = TankDefinitions.create();
|
||||
|
||||
const gameModeNativeNames: Record<string, number> = {};
|
||||
const gameModeNativeNames: Record<string, string> = {};
|
||||
|
||||
const squadBattleTypeGameModeNativeNameMatches =
|
||||
this.squadBattleTypeStyles!.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll(
|
||||
/"(\d+)" -> "(battleType\/([a-zA-Z]+))"/g,
|
||||
/eGameMode\.(\w+) -> "(battleType\/([a-zA-Z]+))"/g,
|
||||
);
|
||||
const gameTypeGameModeNativeNameMatches =
|
||||
this.gameTypeSelectorStyles!.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll(
|
||||
@@ -61,8 +64,7 @@ export abstract class ServerBlitzKitAPI1 extends ServerBlitzKitAPI0 {
|
||||
);
|
||||
|
||||
for (const match of squadBattleTypeGameModeNativeNameMatches) {
|
||||
const id = Number(match[1]);
|
||||
gameModeNativeNames[match[3]] = id;
|
||||
gameModeNativeNames[match[3]] = match[1];
|
||||
}
|
||||
|
||||
for (const match of gameTypeGameModeNativeNameMatches) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CamouflageDefinitions } from "@blitzkit/core";
|
||||
import { CamouflageDefinitions } from "@blitzkit/protos";
|
||||
import { Cache } from "./0_base";
|
||||
import { ServerBlitzKitAPI1 } from "./1_tanks";
|
||||
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import {
|
||||
Armor,
|
||||
BlitzCrewType,
|
||||
ChassisDefinitionsList,
|
||||
Crew,
|
||||
EngineDefinitionsList,
|
||||
GunDefinitionsList,
|
||||
ModelDefinitions,
|
||||
TankParameters,
|
||||
toUniqueId,
|
||||
TurretDefinitionsList,
|
||||
Vector3,
|
||||
VehicleDefinitionArmor,
|
||||
VehicleDefinitionList,
|
||||
VehicleDefinitions,
|
||||
} from "@blitzkit/core";
|
||||
import { Armor, Crew, ModelDefinitions, Vector3 } from "@blitzkit/protos";
|
||||
import { Vector3Tuple } from "three";
|
||||
import { Cache } from "./0_base";
|
||||
import { ServerBlitzKitAPI2 } from "./2_camouflages";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EquipmentDefinitions, EquipmentSlot } from "@blitzkit/core";
|
||||
import { EquipmentDefinitions, EquipmentSlot } from "@blitzkit/protos";
|
||||
import { Cache } from "./0_base";
|
||||
import { ServerBlitzKitAPI4 } from "./4_maps";
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import {
|
||||
BlitzTankFilterDefinitionCategory,
|
||||
Consumable,
|
||||
ConsumableDefinitions,
|
||||
} from "@blitzkit/core";
|
||||
import { BlitzTankFilterDefinitionCategory } from "@blitzkit/core";
|
||||
import { Consumable, ConsumableDefinitions } from "@blitzkit/protos";
|
||||
import { Cache } from "./0_base";
|
||||
import { ServerBlitzKitAPI5 } from "./5_equipments";
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import {
|
||||
BlitzTankFilterDefinitionCategory,
|
||||
Provision,
|
||||
ProvisionDefinitions,
|
||||
} from "@blitzkit/core";
|
||||
import { BlitzTankFilterDefinitionCategory } from "@blitzkit/core";
|
||||
import { Provision, ProvisionDefinitions } from "@blitzkit/protos";
|
||||
import { Cache } from "./0_base";
|
||||
import { ServerBlitzKitAPI6 } from "./6_consumables";
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { BlitzTankClass, SkillDefinitions } from "@blitzkit/core";
|
||||
import { BlitzTankClass } from "@blitzkit/core";
|
||||
|
||||
import { SkillDefinitions } from "@blitzkit/protos";
|
||||
import { Cache } from "./0_base";
|
||||
import { ServerBlitzKitAPI7 } from "./7_provisions";
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { AvailableNationsYaml, GameDefinitions } from "@blitzkit/core";
|
||||
import { AvailableNationsYaml } from "@blitzkit/core";
|
||||
|
||||
import { GameDefinitions } from "@blitzkit/protos";
|
||||
import { Cache } from "./0_base";
|
||||
import { ServerBlitzKitAPI8 } from "./8_skills";
|
||||
|
||||
@@ -27,9 +28,9 @@ export abstract class ServerBlitzKitAPI9 extends ServerBlitzKitAPI8 {
|
||||
).available_nations;
|
||||
|
||||
for (const match of this.squadBattleTypeStyles!.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll(
|
||||
/"(\d+)" -> "(battleType\/([a-zA-Z]+))"/g,
|
||||
/eGameMode\.(\w+) -> "(battleType\/([a-zA-Z]+))"/g,
|
||||
)) {
|
||||
const id = Number(match[1]);
|
||||
const id = match[1];
|
||||
const name = this.getString(match[2]);
|
||||
|
||||
gameDefinitions.gameModes[id] = {
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
ProvisionDefinitions,
|
||||
TankDefinition,
|
||||
} from "../../../protos/src/blitzkit";
|
||||
import { isTankCompatible } from "../tankopedia";
|
||||
|
||||
export function availableProvisions(
|
||||
tank: TankDefinition,
|
||||
@@ -10,6 +11,6 @@ export function availableProvisions(
|
||||
provisionDefinitions: ProvisionDefinitions,
|
||||
) {
|
||||
return Object.values(provisionDefinitions.provisions).filter((provision) =>
|
||||
checkConsumableProvisionInclusivity(provision, tank, gun),
|
||||
isTankCompatible(tank, gun, provision.include, provision.exclude),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { GunDefinition } from "@blitzkit/protos";
|
||||
import { CaseType } from "../types";
|
||||
|
||||
export const gunTypeOrder: CaseType<GunDefinition>[] = [
|
||||
"regular",
|
||||
"auto_loader",
|
||||
"auto_reloader",
|
||||
];
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./gunTypeOrder";
|
||||
export * from "./tankClassOrder";
|
||||
export * from "./treeTypeOrder";
|
||||
@@ -1,10 +1,11 @@
|
||||
export * from "../../protos/src/blitzkit";
|
||||
export * from "./api";
|
||||
export * from "./blitz";
|
||||
export * from "./blitzkit";
|
||||
export * from "./config";
|
||||
export * from "./imgur";
|
||||
export * from "./math";
|
||||
export * from "./patreon";
|
||||
export * from "./statistics";
|
||||
export * from "./streams";
|
||||
export * from "./tankopedia";
|
||||
export * from "./types";
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./isTankCompatible";
|
||||
+5
-14
@@ -1,20 +1,11 @@
|
||||
import {
|
||||
ConsumableTankCategoryFilterCategory,
|
||||
type GunDefinition,
|
||||
type TankDefinition,
|
||||
type TankInclusivityFilter,
|
||||
} from "@blitzkit/core";
|
||||
GunDefinition,
|
||||
TankDefinition,
|
||||
TankInclusivityFilter,
|
||||
} from "@blitzkit/protos";
|
||||
|
||||
export function useCompatibility(tank: TankDefinition, gun: GunDefinition) {
|
||||
return function (
|
||||
include?: TankInclusivityFilter[],
|
||||
exclude?: TankInclusivityFilter[],
|
||||
) {
|
||||
return tankCompatibility(tank, gun, include, exclude);
|
||||
};
|
||||
}
|
||||
|
||||
function tankCompatibility(
|
||||
export function isTankCompatible(
|
||||
tank: TankDefinition,
|
||||
gun: GunDefinition,
|
||||
include?: TankInclusivityFilter[],
|
||||
@@ -0,0 +1,5 @@
|
||||
export type CaseType<T> = T extends {
|
||||
gun_type?: { $case: infer U; value: any };
|
||||
}
|
||||
? U
|
||||
: never;
|
||||
@@ -4,6 +4,7 @@ export * from "./blitzStrings";
|
||||
export * from "./blitzTankFilterDefinitionCategory";
|
||||
export * from "./camouflagesXml";
|
||||
export * from "./camouflagesYaml";
|
||||
export * from "./caseType";
|
||||
export * from "./chassisDefinitionsList";
|
||||
export * from "./combatRolesYaml";
|
||||
export * from "./consumablesCommon";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { I18nString } from "../../core/src";
|
||||
import locales from "../locales.json";
|
||||
import locales from "@blitzkit/i18n/locales.json";
|
||||
import { I18nString } from "@blitzkit/protos";
|
||||
|
||||
export function unwrapper(locale = locales.default) {
|
||||
export function unwrapper(locale: string) {
|
||||
return function (i18nString: I18nString) {
|
||||
if (locale in i18nString.locales) {
|
||||
return i18nString.locales[locale];
|
||||
|
||||
+68
-319
@@ -82,6 +82,74 @@
|
||||
}
|
||||
},
|
||||
|
||||
"tools": {
|
||||
"tanks": {
|
||||
"name": "Tankopedia",
|
||||
"slogan": "Blitz tank encyclopedia",
|
||||
"action": "Find tanks"
|
||||
},
|
||||
"avatars": {
|
||||
"name": "Avatars",
|
||||
"slogan": "Blitz player avatars",
|
||||
"action": "Browse all"
|
||||
},
|
||||
"backgrounds": {
|
||||
"name": "Backgrounds",
|
||||
"slogan": "Blitz player backgrounds",
|
||||
"action": "View all"
|
||||
},
|
||||
"api": {
|
||||
"name": "API",
|
||||
"action": "To docs",
|
||||
"slogan": "BlitzKit hosted API"
|
||||
},
|
||||
"players": {
|
||||
"name": "Players",
|
||||
"slogan": "Player statistics",
|
||||
"action": "Stalk player"
|
||||
},
|
||||
"compare": {
|
||||
"name": "Compare",
|
||||
"slogan": "Compare tanks characteristics",
|
||||
"action": "Compare now"
|
||||
},
|
||||
"performance": {
|
||||
"name": "Performance",
|
||||
"slogan": "Tank performance statistics",
|
||||
"action": "View tanks"
|
||||
},
|
||||
"playlist": {
|
||||
"name": "Playlist",
|
||||
"slogan": "Tank playlist generator",
|
||||
"action": "Generate"
|
||||
},
|
||||
"mixer": {
|
||||
"name": "Mixer",
|
||||
"slogan": "Tank module mixer",
|
||||
"action": "Mix and match"
|
||||
},
|
||||
"session": {
|
||||
"name": "Session",
|
||||
"slogan": "Live player statistics",
|
||||
"action": "Track"
|
||||
},
|
||||
"guesser": {
|
||||
"name": "Guesser",
|
||||
"slogan": "Silhouette mini-game",
|
||||
"action": "Play"
|
||||
},
|
||||
"changelogs": {
|
||||
"name": "Changelogs",
|
||||
"slogan": "BlitzKit patch notes",
|
||||
"action": "Read up"
|
||||
},
|
||||
"settings": {
|
||||
"name": "Settings",
|
||||
"slogan": "",
|
||||
"action": ""
|
||||
}
|
||||
},
|
||||
|
||||
"home": {
|
||||
"tanks": "Tanks",
|
||||
"players": "Players",
|
||||
@@ -427,325 +495,6 @@
|
||||
}
|
||||
},
|
||||
|
||||
"tools": {
|
||||
"backgrounds": {
|
||||
"name": "Backgrounds",
|
||||
"description": "Profile backgrounds",
|
||||
"seo_description": "World of Tanks Blitz player profile backgrounds"
|
||||
},
|
||||
|
||||
"players": {
|
||||
"name": "Players",
|
||||
"description": "Player statistics",
|
||||
"seo_description": "Player statistics in World of Tanks Blitz"
|
||||
},
|
||||
|
||||
"playlist": {
|
||||
"reset": "Reset",
|
||||
"shuffle": "Shuffle",
|
||||
"remove": "Remove played",
|
||||
"tracking": "BlitzKit is tracking your battles",
|
||||
|
||||
"sort": {
|
||||
"button": "Sort by",
|
||||
"last_played": "Last played",
|
||||
"least_played": "Least played"
|
||||
},
|
||||
|
||||
"table": {
|
||||
"index": "#",
|
||||
"never": "Never",
|
||||
"tank": "Tank",
|
||||
"last_played": "Last played",
|
||||
"battles": "Career battles",
|
||||
"unrecorded": "--",
|
||||
"wins": "Wins"
|
||||
},
|
||||
|
||||
"large_warning": {
|
||||
"title": "That's a lot of tanks!",
|
||||
"description": "Are you sure you want to create a playlist of {count} tanks? That's {time} hours of gameplay at 50% winrate!",
|
||||
"cancel": "Cancel",
|
||||
"proceed": "Proceed"
|
||||
},
|
||||
|
||||
"generate": "Generate playlist",
|
||||
"name": "Playlist",
|
||||
"description": "Tank playlist generator",
|
||||
"seo_description": "Don't know what to play in World of Tanks Blitz? Use BlitzKit Playlist to get suggestions!",
|
||||
"pool_size": "{count} possible tanks"
|
||||
},
|
||||
|
||||
"mixer": {
|
||||
"name": "Mixer",
|
||||
"description": "Mix tank modules",
|
||||
"seo_description": "Mix and match hulls, turrets, and guns of tanks in World of Tanks Blitz",
|
||||
"screenshot": "Screenshot",
|
||||
"randomize": "Randomize"
|
||||
},
|
||||
|
||||
"guesser": {
|
||||
"name": "Guesser",
|
||||
"description": "Silhouette mini-game",
|
||||
"seo_description": "Play a tank guessing game with a silhouette in World of Tanks Blitz",
|
||||
"search": {
|
||||
"placeholder": "Type to guess...",
|
||||
"skip": "Skip",
|
||||
"guess": "Guess",
|
||||
"next": "Next",
|
||||
"no_results": "No results"
|
||||
},
|
||||
"stats": "{correct} / {total} correct, {streak} streak",
|
||||
"help": {
|
||||
"title": "Partially reveal tank?",
|
||||
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
|
||||
"cancel": "Cancel",
|
||||
"reveal": "Reveal"
|
||||
},
|
||||
"clear": "Clear",
|
||||
"select_all": "Select all",
|
||||
"tier": "Tier {tier}"
|
||||
},
|
||||
|
||||
"compare": {
|
||||
"name": "Compare",
|
||||
"description": "Compare tank statistics",
|
||||
"seo_description": "Compare tanks statistics and loadouts in World of Tanks Blitz",
|
||||
|
||||
"actions": {
|
||||
"add": {
|
||||
"button": "Add",
|
||||
"title": "Add tanks",
|
||||
"description": "Select tanks to add to comparison"
|
||||
},
|
||||
"clear": "Clear",
|
||||
"deltas": {
|
||||
"none": "No deltas",
|
||||
"percentage": "Percentage",
|
||||
"absolute": "Absolute"
|
||||
}
|
||||
},
|
||||
|
||||
"no_tanks": {
|
||||
"title": "No tanks selected",
|
||||
"body": "Press the Add button to get started"
|
||||
},
|
||||
|
||||
"table": {
|
||||
"equipment": {
|
||||
"clear": "Clear",
|
||||
"apply_to_all": "Apply to all"
|
||||
},
|
||||
"skills": {
|
||||
"clear": "Clear",
|
||||
"maximize": "Maximize",
|
||||
"equalize": "Equalize"
|
||||
},
|
||||
"modules": {
|
||||
"stock": "Stock",
|
||||
"upgrade": "Upgrade"
|
||||
},
|
||||
"provisions": {
|
||||
"clear": "Clear this tank",
|
||||
"clear_all": "Clear all tanks"
|
||||
},
|
||||
"consumables": {
|
||||
"clear": "Clear this tank",
|
||||
"clear_all": "Clear all tanks"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"performance": {
|
||||
"name": "Performance",
|
||||
"description": "Tank performance statistics",
|
||||
"seo_description": "Best tanks and their performances in World of Tanks Blitz",
|
||||
"estimation": {
|
||||
"body": "Estimated stats based on {count} players. Updated {age} ago.",
|
||||
"hours": "{count} hours",
|
||||
"minutes": "{count} minutes"
|
||||
},
|
||||
"player_count": {
|
||||
"label": "Player count"
|
||||
},
|
||||
"table": {
|
||||
"tanks": {
|
||||
"header": "Tank",
|
||||
"total": "Total"
|
||||
},
|
||||
"stats": {
|
||||
"winrate": "Winrate",
|
||||
"players": "Players",
|
||||
"damage": "Damage",
|
||||
"survival": "Survival",
|
||||
"xp": "XP",
|
||||
"kills": "Kills",
|
||||
"spots": "Spots",
|
||||
"accuracy": "Accuracy",
|
||||
"shots": "Shots",
|
||||
"hits": "Hits",
|
||||
"damage_ratio": "Dmg. ratio",
|
||||
"damage_taken": "Dmg. taken",
|
||||
"capture_points": "Cap points"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"avatars": {
|
||||
"name": "Avatars",
|
||||
"description": "Blitz player avatars",
|
||||
"seo_description": "All World of Tanks Blitz player avatars",
|
||||
"last_updated": "Last updated on {time}",
|
||||
"search": {
|
||||
"hint": "Search avatars...",
|
||||
"results": "{count} avatars"
|
||||
}
|
||||
},
|
||||
|
||||
"session": {
|
||||
"name": "Session",
|
||||
"description": "Live session stats",
|
||||
"seo_description": "Track your stats in real time as you play World of Tanks Blitz",
|
||||
"search": {
|
||||
"lookup": "Look up a player to get started",
|
||||
"hint": "Search players...",
|
||||
"searching": "Searching...",
|
||||
"no_results": "No players found"
|
||||
},
|
||||
"controls": {
|
||||
"title": "Tracking {name}",
|
||||
"subtitle": "Since {time}",
|
||||
"reset": "Reset",
|
||||
"change": "Change"
|
||||
},
|
||||
"no_cc": {
|
||||
"title": "You're attempting to track a CC account",
|
||||
"body": "CC (community contributor) accounts do not have public tank statistics. We are in the process of adding embeds for CC accounts, stay tuned.",
|
||||
"alright": "Alright"
|
||||
},
|
||||
"no_battles": {
|
||||
"title": "No battles",
|
||||
"hint": "Go ahead and play a game"
|
||||
},
|
||||
"table": {
|
||||
"tank": "Tank",
|
||||
"total": "Total",
|
||||
"remove": "Remove"
|
||||
}
|
||||
},
|
||||
|
||||
"tier_list": {
|
||||
"name": "Tier list",
|
||||
"description": "Rank Blitz tanks",
|
||||
"seo_description": "Explore tier lists ranking tanks from best to worst, based on player ratings and statistical performance in World of Tanks Blitz",
|
||||
"buttons": {
|
||||
"reset": "Reset",
|
||||
"share": "Share"
|
||||
}
|
||||
},
|
||||
|
||||
"embed": {
|
||||
"name": "Embed",
|
||||
"description": "Embeds for streaming",
|
||||
"seo_description": "Create embeds for streaming World of Tanks Blitz with extreme customization",
|
||||
"coming_soon": "More embeds coming soon.",
|
||||
"suggest": "Suggest ideas.",
|
||||
|
||||
"preview": {
|
||||
"title": "Example preview",
|
||||
"upload": "Upload test background"
|
||||
},
|
||||
|
||||
"configuration": {
|
||||
"import": {
|
||||
"title": "Import",
|
||||
"description": "Paste your current URL if you have one already to import your settings",
|
||||
"hint": "Paste URL...",
|
||||
"apply": "Apply",
|
||||
"idiot_title": "Invalid URL",
|
||||
"idiot_description": "Try pasting the URL that BlitzKit previously generated, you silly goose!",
|
||||
"idiot_button": "Okay, my bad"
|
||||
},
|
||||
|
||||
"export": {
|
||||
"title": "Export",
|
||||
"description": "Copy all properties for you streaming software",
|
||||
"generate": {
|
||||
"button": "Generate URL",
|
||||
"title": "Generate URL",
|
||||
"subtitle": "Search an account to track",
|
||||
"cancel": "Cancel",
|
||||
"success": {
|
||||
"title": "Copy URL",
|
||||
"info": "Generated the URL for {name}. The embed will start tracking as soon as the URL is pasted. Reset at any time by right-clicking on PC or long-pressing on mobile and then selecting \"Reset\".",
|
||||
"back": "Back",
|
||||
"copy": "Copy URL"
|
||||
}
|
||||
},
|
||||
"css": "Copy CSS",
|
||||
"width": "Copy Width",
|
||||
"height": "Copy Height",
|
||||
"framerate": "Copy Framerate"
|
||||
},
|
||||
|
||||
"customize": {
|
||||
"title": "Customize",
|
||||
"reset": "Reset",
|
||||
"description": "Bells and whistles of your embed"
|
||||
}
|
||||
},
|
||||
|
||||
"types": {
|
||||
"breakdown": {
|
||||
"name": "Breakdown",
|
||||
"options": {
|
||||
"width": "Width",
|
||||
"height": "Height",
|
||||
"showTotal": "Show total",
|
||||
"listGap": "List gap",
|
||||
"listMaxTanks": "Max tanks",
|
||||
"cardRadius": "Card radius",
|
||||
"cardHeaderBackgroundColor": "Card header background color",
|
||||
"cardHeaderPadding": "Card header padding",
|
||||
"cardTitle": "Card title",
|
||||
"cardTitleTypeColor": "Card title type color",
|
||||
"cardTitleClassIcon": "Card title class icon",
|
||||
"cardBodyBackgroundColor": "Card body background color",
|
||||
"cardBodyPadding": "Card body padding",
|
||||
"columnGap": "Column gap",
|
||||
"columnValue": "Column value",
|
||||
"columnLabel": "Column label",
|
||||
"column1": "Column 1",
|
||||
"column2": "Column 2",
|
||||
"column3": "Column 3",
|
||||
"column4": "Column 4",
|
||||
"column1CustomLabel": "Column 1 label",
|
||||
"column2CustomLabel": "Column 2 label",
|
||||
"column3CustomLabel": "Column 3 label"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"more": {
|
||||
"name": "More coming soon",
|
||||
"description": "What we're making",
|
||||
"seo_description": "What we're making with BlitzKit"
|
||||
},
|
||||
|
||||
"api": {
|
||||
"name": "API",
|
||||
"description": "BlitzKit hosted API",
|
||||
"seo_description": "BlitzKit hosted static API for World of Tanks Blitz"
|
||||
},
|
||||
|
||||
"changelogs": {
|
||||
"name": "Changelogs",
|
||||
"description": "BlitzKit changelogs",
|
||||
"seo_description": "Changelogs and patch notes for BlitzKit"
|
||||
}
|
||||
},
|
||||
|
||||
"footer": {
|
||||
"motto": "Everything World of Tanks Blitz",
|
||||
"copyright": "Unaffiliated with Wargaming or any service",
|
||||
|
||||
@@ -7,7 +7,7 @@ import "i18n.proto";
|
||||
message GameDefinitions {
|
||||
required string version = 1;
|
||||
repeated string nations = 2;
|
||||
map<uint32, GameMode> gameModes = 3;
|
||||
map<string, GameMode> gameModes = 3;
|
||||
map<uint32, GameModeRole> roles = 4;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ message TankDefinitions {
|
||||
message TankDefinition {
|
||||
required uint32 id = 1;
|
||||
required string slug = 2;
|
||||
map<uint32, uint32> roles = 3;
|
||||
map<string, uint32> roles = 3;
|
||||
required bool fixed_camouflage = 5;
|
||||
repeated int32 camouflages = 6;
|
||||
repeated int32 ancestors = 7;
|
||||
|
||||
@@ -29,15 +29,15 @@ for (const root of ROOTS) {
|
||||
|
||||
for (const file of filesRaw) {
|
||||
if (file.endsWith(".proto")) {
|
||||
files.push(file);
|
||||
const name = file.slice(0, -6);
|
||||
|
||||
index += `export * from "./${name}";\n`;
|
||||
args += `${root}/${file}\n`;
|
||||
|
||||
files.push(file);
|
||||
}
|
||||
|
||||
if (file.endsWith(".ts")) {
|
||||
const name = file.slice(0, -3);
|
||||
|
||||
index += `export * from "./${name}";\n`;
|
||||
|
||||
await rm(`${root}/${file}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,7 @@ import { correctZYTuple } from "../../../core/blitz/correctZYTuple";
|
||||
import { nameToArmorId } from "../../../core/blitzkit/nameToArmorId";
|
||||
import { resolveArmor } from "../../../core/blitzkit/resolveThickness";
|
||||
import { useArmor } from "../../../hooks/useArmor";
|
||||
import { useTankModelDefinition } from "../../../hooks/useTankModelDefinition";
|
||||
import { useTankTransform } from "../../../hooks/useTankTransform";
|
||||
import { Duel } from "../../../stores/duel";
|
||||
import { DuelModule, useDuel } from "../../../hooks/useDuel";
|
||||
import { ModelTankWrapper } from "./ModelTankWrapper";
|
||||
import { PrimaryArmorSceneComponent } from "./PrimaryArmorSceneComponent";
|
||||
|
||||
@@ -14,10 +12,10 @@ export const PrimaryArmorScene = memo(() => {
|
||||
const wrapper = useRef<Group>(null);
|
||||
const turretContainer = useRef<Group>(null!);
|
||||
const gunContainer = useRef<Group>(null!);
|
||||
const tank = Duel.use((state) => state.protagonist.tank);
|
||||
const track = Duel.use((state) => state.protagonist.track);
|
||||
const turret = Duel.use((state) => state.protagonist.turret);
|
||||
const gun = Duel.use((state) => state.protagonist.gun);
|
||||
const tank = useDuel("protagonist", DuelModule.Tank);
|
||||
const track = useDuel("protagonist", DuelModule.Track);
|
||||
const turret = useDuel("protagonist", DuelModule.Turret);
|
||||
const gun = useDuel("protagonist", DuelModule.Gun);
|
||||
const armorGltf = useArmor(tank.id).gltf;
|
||||
const armorNodes = Object.values(armorGltf.nodes);
|
||||
const tankModelDefinition = useTankModelDefinition();
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ import { jsxTree } from "../../../../core/blitzkit/jsxTree";
|
||||
import { Duel } from "../../../../stores/duel";
|
||||
import { Tankopedia } from "../../../../stores/tankopedia";
|
||||
import { TankopediaPersistent } from "../../../../stores/tankopediaPersistent";
|
||||
import { transitionEvent } from "../../../Tankopedia/HeroSection/components/TankSandbox/components/Lighting";
|
||||
import { transitionEvent } from "../../../Lighting";
|
||||
import fragmentShader from "./shaders/fragment.glsl?raw";
|
||||
import vertexShader from "./shaders/vertex.glsl?raw";
|
||||
import { spacedArmorRenderTarget } from "./target";
|
||||
|
||||
@@ -27,7 +27,7 @@ import { discardClippingPlane } from "../../../core/three/discardClippingPlane";
|
||||
import { useEquipment } from "../../../hooks/useEquipment";
|
||||
import { Duel } from "../../../stores/duel";
|
||||
import { Tankopedia } from "../../../stores/tankopedia";
|
||||
import { transitionEvent } from "../../Tankopedia/HeroSection/components/TankSandbox/components/Lighting";
|
||||
import { transitionEvent } from "../../Lighting";
|
||||
import { ArmorType } from "./SpacedArmorScene";
|
||||
import type {
|
||||
ArmorUserData,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { useFrame } from "@react-three/fiber";
|
||||
|
||||
export function AutoClear() {
|
||||
export function CanvasAutoClear() {
|
||||
useFrame(({ gl }) => {
|
||||
gl.clear();
|
||||
}, 0);
|
||||
+7
-7
@@ -5,13 +5,13 @@ import type { QuicklimeEvent } from "quicklime";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { PerspectiveCamera, Vector3 } from "three";
|
||||
import { OrbitControls as OrbitControlsClass } from "three-stdlib";
|
||||
import { api } from "../../../../../../api/dynamic";
|
||||
import { applyPitchYawLimits } from "../../../../../../core/blitz/applyPitchYawLimits";
|
||||
import { hasEquipment } from "../../../../../../core/blitzkit/hasEquipment";
|
||||
import { Pose, poseEvent } from "../../../../../../core/blitzkit/pose";
|
||||
import { controlsEnabledEvent } from "../../../../../../core/controlsEnabled";
|
||||
import { Duel } from "../../../../../../stores/duel";
|
||||
import { Tankopedia } from "../../../../../../stores/tankopedia";
|
||||
import { api } from "../api/dynamic";
|
||||
import { applyPitchYawLimits } from "../core/blitz/applyPitchYawLimits";
|
||||
import { hasEquipment } from "../core/blitzkit/hasEquipment";
|
||||
import { Pose, poseEvent } from "../core/blitzkit/pose";
|
||||
import { controlsEnabledEvent } from "../core/controlsEnabled";
|
||||
import { Tankopedia } from "../stores/tankopedia";
|
||||
|
||||
const poseDistances: Record<Pose, number> = {
|
||||
[Pose.HullDown]: 15,
|
||||
@@ -29,7 +29,7 @@ interface ControlsProps {
|
||||
enableRotate?: boolean;
|
||||
}
|
||||
|
||||
export function Controls({
|
||||
export function CanvasControl({
|
||||
autoRotate = true,
|
||||
zoomable = true,
|
||||
enableRotate = true,
|
||||
+11
-2
@@ -1,11 +1,15 @@
|
||||
import { TankClass } from "@blitzkit/core";
|
||||
import { TankClass } from "@blitzkit/protos";
|
||||
import type { ComponentProps, ReactNode } from "react";
|
||||
import { ClassHeavy } from "./components/ClassHeavy";
|
||||
import { ClassLight } from "./components/ClassLight";
|
||||
import { ClassMedium } from "./components/ClassMedium";
|
||||
import { ClassTankDestroyer } from "./components/ClassTankDestroyer";
|
||||
|
||||
export const classIcons: Record<
|
||||
interface ClassIconProps extends ComponentProps<"svg"> {
|
||||
class: TankClass;
|
||||
}
|
||||
|
||||
const classIcons: Record<
|
||||
TankClass,
|
||||
(props: ComponentProps<"svg">) => ReactNode
|
||||
> = {
|
||||
@@ -14,3 +18,8 @@ export const classIcons: Record<
|
||||
[TankClass.TANK_CLASS_LIGHT]: ClassLight,
|
||||
[TankClass.TANK_CLASS_MEDIUM]: ClassMedium,
|
||||
};
|
||||
|
||||
export function ClassIcon({ class: tankClass, ...props }: ClassIconProps) {
|
||||
const Icon = classIcons[tankClass];
|
||||
return <Icon {...props} />;
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useModel } from "../hooks/useModel";
|
||||
import { Guess, GuessState } from "../stores/guess";
|
||||
import { Controls } from "./CanvasControl";
|
||||
import { Lighting } from "./Lighting";
|
||||
import { SmartCanvas } from "./SmartCanvas";
|
||||
import { Controls } from "./Tankopedia/HeroSection/components/TankSandbox/components/Control";
|
||||
import { Lighting } from "./Tankopedia/HeroSection/components/TankSandbox/components/Lighting";
|
||||
import { TankModel } from "./Tankopedia/HeroSection/components/TankSandbox/components/TankModel";
|
||||
import { TankModel } from "./TankModel";
|
||||
|
||||
const BRIGHTNESS = 2 ** -5.5;
|
||||
const CONTRAST = 2 ** 2;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { CaseType } from "@blitzkit/core";
|
||||
import type { GunDefinition } from "@blitzkit/protos";
|
||||
import type { ComponentProps, ReactNode } from "react";
|
||||
import { GunAutoloaderIcon } from "../GunAutoloaderIcon";
|
||||
import { GunAutoreloaderIcon } from "../GunAutoreloaderIcon";
|
||||
import { GunRegularIcon } from "../GunRegularIcon";
|
||||
|
||||
const typeIcons: Record<
|
||||
CaseType<GunDefinition>,
|
||||
(props: ComponentProps<"svg">) => ReactNode
|
||||
> = {
|
||||
regular: GunRegularIcon,
|
||||
auto_loader: GunAutoloaderIcon,
|
||||
auto_reloader: GunAutoreloaderIcon,
|
||||
};
|
||||
|
||||
interface Props extends ComponentProps<"svg"> {
|
||||
type: CaseType<GunDefinition>;
|
||||
}
|
||||
|
||||
export function GunTypeIcon({ type, ...props }: Props) {
|
||||
const Icon = typeIcons[type];
|
||||
return <Icon {...props} />;
|
||||
}
|
||||
+4
-4
@@ -1,13 +1,13 @@
|
||||
import { useEffect } from "react";
|
||||
import { degToRad } from "three/src/math/MathUtils.js";
|
||||
import { api } from "../../../../../../api/dynamic";
|
||||
import { applyPitchYawLimits } from "../../../../../../core/blitz/applyPitchYawLimits";
|
||||
import { modelTransformEvent } from "../../../../../../core/blitzkit/modelTransform";
|
||||
import { Duel } from "../../../../../../stores/duel";
|
||||
import { api } from "../api/dynamic";
|
||||
import { applyPitchYawLimits } from "../core/blitz/applyPitchYawLimits";
|
||||
import { modelTransformEvent } from "../core/blitzkit/modelTransform";
|
||||
|
||||
const modelDefinitions = await api.models();
|
||||
|
||||
export function InitialAligner() {
|
||||
export function InitialCameraAligner() {
|
||||
const tank = Duel.use((state) => state.protagonist.tank);
|
||||
const turret = Duel.use((state) => state.protagonist.turret);
|
||||
const gun = Duel.use((state) => state.protagonist.gun);
|
||||
+3
-3
@@ -12,9 +12,9 @@ import {
|
||||
} from "react";
|
||||
import { HemisphereLight, SpotLight, type Group } from "three";
|
||||
import { degToRad, lerp } from "three/src/math/MathUtils.js";
|
||||
import { Tankopedia } from "../../../../../../stores/tankopedia";
|
||||
import { TankopediaPersistent } from "../../../../../../stores/tankopediaPersistent";
|
||||
import { TankopediaDisplay } from "../../../../../../stores/tankopediaPersistent/constants";
|
||||
import { Tankopedia } from "../stores/tankopedia";
|
||||
import { TankopediaPersistent } from "../stores/tankopediaPersistent";
|
||||
import { TankopediaDisplay } from "../stores/tankopediaPersistent/constants";
|
||||
|
||||
const ANGLE = degToRad(15);
|
||||
const REVEAL_ANIMATION_TIME = 3;
|
||||
@@ -4,8 +4,8 @@ import type { QuicklimeEvent } from "quicklime";
|
||||
import { forwardRef, Suspense, useEffect, useRef } from "react";
|
||||
import type { OrbitControls as OrbitControlsClass } from "three-stdlib";
|
||||
import { controlsEnabledEvent } from "../../core/controlsEnabled";
|
||||
import { Lighting } from "../Tankopedia/HeroSection/components/TankSandbox/components/Lighting";
|
||||
import { SceneProps } from "../Tankopedia/HeroSection/components/TankSandbox/components/SceneProps";
|
||||
import { Lighting } from "../Lighting";
|
||||
import { SceneProps } from "../SceneProps";
|
||||
import { MixerFallback } from "./components/MixerFallback";
|
||||
import { Model } from "./components/Model";
|
||||
|
||||
|
||||
File renamed without changes.
@@ -9,7 +9,7 @@ import {
|
||||
import { Jimp } from "jimp";
|
||||
import { useCallback, type RefObject } from "react";
|
||||
import { useLocale } from "../hooks/useLocale";
|
||||
import { screenshotReadyEvent } from "./Tankopedia/HeroSection/components/TankSandbox/components/SceneProps";
|
||||
import { screenshotReadyEvent } from "./SceneProps";
|
||||
|
||||
interface Props extends ButtonProps {
|
||||
canvas: RefObject<HTMLCanvasElement>;
|
||||
|
||||
+10
-10
@@ -1,18 +1,18 @@
|
||||
import { invalidate, useThree, type ThreeEvent } from "@react-three/fiber";
|
||||
import { useRef } from "react";
|
||||
import { Group, Mesh, MeshStandardMaterial, Vector2 } from "three";
|
||||
import { applyPitchYawLimits } from "../../../../../../core/blitz/applyPitchYawLimits";
|
||||
import { hasEquipment } from "../../../../../../core/blitzkit/hasEquipment";
|
||||
import { jsxTree } from "../../../../../../core/blitzkit/jsxTree";
|
||||
import { modelTransformEvent } from "../../../../../../core/blitzkit/modelTransform";
|
||||
import { controlsEnabledEvent } from "../../../../../../core/controlsEnabled";
|
||||
import { useModel } from "../../../../../../hooks/useModel";
|
||||
import { useTankModelDefinition } from "../../../../../../hooks/useTankModelDefinition";
|
||||
import { useTankTransform } from "../../../../../../hooks/useTankTransform";
|
||||
import { Duel } from "../../../../../../stores/duel";
|
||||
import { Tankopedia } from "../../../../../../stores/tankopedia";
|
||||
import { TankopediaDisplay } from "../../../../../../stores/tankopediaPersistent/constants";
|
||||
import { ModelTankWrapper } from "../../../../../Armor/components/ModelTankWrapper";
|
||||
import { applyPitchYawLimits } from "../core/blitz/applyPitchYawLimits";
|
||||
import { hasEquipment } from "../core/blitzkit/hasEquipment";
|
||||
import { jsxTree } from "../core/blitzkit/jsxTree";
|
||||
import { modelTransformEvent } from "../core/blitzkit/modelTransform";
|
||||
import { controlsEnabledEvent } from "../core/controlsEnabled";
|
||||
import { useModel } from "../hooks/useModel";
|
||||
import { useTankTransform } from "../hooks/useTankTransform";
|
||||
import { Tankopedia } from "../stores/tankopedia";
|
||||
import { TankopediaDisplay } from "../stores/tankopediaPersistent/constants";
|
||||
import { ModelTankWrapper } from "./Armor/components/ModelTankWrapper";
|
||||
|
||||
export function TankModel() {
|
||||
const protagonist = Duel.use((draft) => draft.protagonist);
|
||||
@@ -4,9 +4,9 @@ import {
|
||||
resolveDpm,
|
||||
SEARCH_KEYS,
|
||||
unionBoundingBox,
|
||||
type TankDefinition,
|
||||
} from "@blitzkit/core";
|
||||
import { literals } from "@blitzkit/i18n/src/literals";
|
||||
import type { TankDefinition } from "@blitzkit/protos";
|
||||
import fuzzysort from "fuzzysort";
|
||||
import { uniq } from "lodash-es";
|
||||
import { memo, useMemo } from "react";
|
||||
@@ -16,6 +16,7 @@ import { awaitableTankNames } from "../../core/awaitables/tankNames";
|
||||
import { filterTanks } from "../../core/blitzkit/filterTanks";
|
||||
import { resolveReload } from "../../core/blitzkit/resolveReload";
|
||||
import { useLocale } from "../../hooks/useLocale";
|
||||
import { useStrings } from "../../hooks/useStrings";
|
||||
import { App } from "../../stores/app";
|
||||
import { TankFilters } from "../../stores/tankFilters";
|
||||
import { TankopediaPersistent } from "../../stores/tankopediaPersistent";
|
||||
@@ -53,7 +54,8 @@ const [gameDefinitions, modelDefinitions, tankDefinitions, tankNames] =
|
||||
|
||||
export const TankSearch = memo<TankSearchProps>(
|
||||
({ compact, onSelect, onSelectAll, skeleton }) => {
|
||||
const { strings, locale } = useLocale();
|
||||
const strings = useStrings();
|
||||
const locale = useLocale();
|
||||
const wargaming = App.use((state) => state.logins.wargaming);
|
||||
const awaitedTanksDefinitionsArray = Object.values(tankDefinitions.tanks);
|
||||
const tankopediaSort = TankSort.use();
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import {
|
||||
alias,
|
||||
GunDefinition,
|
||||
ShellType,
|
||||
TANK_CLASSES,
|
||||
TANK_TYPES,
|
||||
TankType,
|
||||
gunTypeOrder,
|
||||
TIER_ROMAN_NUMERALS,
|
||||
treeTypeOrder,
|
||||
} from "@blitzkit/core";
|
||||
import { tankClassOrder } from "@blitzkit/core/src/config/tankClassOrder";
|
||||
import { literals } from "@blitzkit/i18n";
|
||||
import locales from "@blitzkit/i18n/locales.json";
|
||||
import { ShellType, TankType } from "@blitzkit/protos";
|
||||
import {
|
||||
LockClosedIcon,
|
||||
LockOpen2Icon,
|
||||
@@ -16,18 +15,17 @@ import {
|
||||
TrashIcon,
|
||||
} from "@radix-ui/react-icons";
|
||||
import { times } from "lodash-es";
|
||||
import { Fragment, type ComponentProps, type ReactNode } from "react";
|
||||
import { Fragment } from "react";
|
||||
import { api } from "../../api/dynamic";
|
||||
import { useLocale } from "../../hooks/useLocale";
|
||||
import { useStrings } from "../../hooks/useStrings";
|
||||
import { useUnwrapper } from "../../hooks/useUnwrapper";
|
||||
import { App } from "../../stores/app";
|
||||
import { TankFilters, type CaseType } from "../../stores/tankFilters";
|
||||
import { TankFilters } from "../../stores/tankFilters";
|
||||
import { Button } from "../Button";
|
||||
import { classIcons } from "../ClassIcon";
|
||||
import { ClassIcon } from "../ClassIcon";
|
||||
import { DropdownMenu } from "../DropdownMenu";
|
||||
import { Flex } from "../Flex";
|
||||
import { GunAutoloaderIcon } from "../GunAutoloaderIcon";
|
||||
import { GunAutoreloaderIcon } from "../GunAutoreloaderIcon";
|
||||
import { GunRegularIcon } from "../GunRegularIcon";
|
||||
import { GunTypeIcon } from "../GunTypeIcon";
|
||||
import { IconButton } from "../IconButton";
|
||||
import { MissingShellIcon } from "../MissingShellIcon";
|
||||
import { ResearchedIcon } from "../ResearchedIcon";
|
||||
@@ -42,14 +40,13 @@ const consumableDefinitions = await api.consumables();
|
||||
const provisionDefinitions = await api.provisions();
|
||||
const tankDefinitions = await api.tanks();
|
||||
|
||||
const gameModeRoleSets: Record<number, Set<number>> = {};
|
||||
const gameModeRoleSets: Record<string, Set<number>> = {};
|
||||
|
||||
for (const tankIdString in tankDefinitions.tanks) {
|
||||
const gameMode = tankDefinitions.tanks[tankIdString];
|
||||
|
||||
for (const gameModeIdString in gameMode.roles) {
|
||||
const role = gameMode.roles[gameModeIdString];
|
||||
const gameModeId = Number(gameModeIdString);
|
||||
for (const gameModeId in gameMode.roles) {
|
||||
const role = gameMode.roles[gameModeId];
|
||||
|
||||
if (gameModeId in gameModeRoleSets) {
|
||||
gameModeRoleSets[gameModeId].add(role);
|
||||
@@ -60,7 +57,7 @@ for (const tankIdString in tankDefinitions.tanks) {
|
||||
}
|
||||
|
||||
const gameModeRoles: {
|
||||
gameModeId: number;
|
||||
gameModeId: string;
|
||||
consumables: number[];
|
||||
provisions: number[];
|
||||
}[] = [];
|
||||
@@ -69,8 +66,7 @@ const allGameModeConsumables = new Set<number>();
|
||||
const allGameModeProvisions = new Set<number>();
|
||||
|
||||
for (const gameModeIdString in gameModeRoleSets) {
|
||||
const gameModeId = Number(gameModeIdString);
|
||||
const roles = gameModeRoleSets[gameModeId];
|
||||
const roles = gameModeRoleSets[gameModeIdString];
|
||||
const consumables = new Set<number>();
|
||||
const provisions = new Set<number>();
|
||||
|
||||
@@ -113,7 +109,7 @@ for (const gameModeIdString in gameModeRoleSets) {
|
||||
}
|
||||
|
||||
gameModeRoles.push({
|
||||
gameModeId: gameModeId,
|
||||
gameModeId: gameModeIdString,
|
||||
consumables: Array.from(consumables.values()),
|
||||
provisions: Array.from(provisions.values()),
|
||||
});
|
||||
@@ -149,25 +145,12 @@ const shellTypeIcons: Record<ShellType, string> = {
|
||||
[ShellType.SHELL_TYPE_HEAT]: "hc",
|
||||
};
|
||||
|
||||
const GUN_TYPE_ICONS: Record<
|
||||
CaseType<GunDefinition>,
|
||||
(props: ComponentProps<"svg">) => ReactNode
|
||||
> = {
|
||||
regular: GunRegularIcon,
|
||||
auto_loader: GunAutoloaderIcon,
|
||||
auto_reloader: GunAutoreloaderIcon,
|
||||
};
|
||||
|
||||
const TANK_TYPE_COLORS: Record<TankType, string> = {
|
||||
[TankType.TANK_TYPE_RESEARCHABLE]: "gray",
|
||||
[TankType.TANK_TYPE_PREMIUM]: "amber",
|
||||
[TankType.TANK_TYPE_COLLECTOR]: "blue",
|
||||
};
|
||||
|
||||
const GUN_TYPES = Object.keys(
|
||||
GUN_TYPE_ICONS,
|
||||
) as (keyof typeof GUN_TYPE_ICONS)[];
|
||||
|
||||
const MAX_ICONS = 4;
|
||||
|
||||
const TIERS = times(10, (i) => 10 - i);
|
||||
@@ -209,7 +192,7 @@ function ResetButton() {
|
||||
}
|
||||
|
||||
function TiersFilter() {
|
||||
const { strings } = useLocale();
|
||||
const strings = useStrings();
|
||||
const tiersRaw = TankFilters.use((state) => state.tiers);
|
||||
const tiers = tiersRaw.length === 0 ? TIERS : tiersRaw;
|
||||
|
||||
@@ -286,7 +269,7 @@ function NationsFilter() {
|
||||
const rawNations = TankFilters.use((state) => state.nations);
|
||||
const nations =
|
||||
rawNations.length === 0 ? gameDefinitions.nations : rawNations;
|
||||
const { strings } = useLocale();
|
||||
const strings = useStrings();
|
||||
|
||||
return (
|
||||
<DropdownMenu.Root modal={false}>
|
||||
@@ -368,9 +351,9 @@ function NationsFilter() {
|
||||
}
|
||||
|
||||
function ClassFilter() {
|
||||
const { strings } = useLocale();
|
||||
const strings = useStrings();
|
||||
const rawClasses = TankFilters.use((state) => state.classes);
|
||||
const classes = rawClasses.length === 0 ? TANK_CLASSES : rawClasses;
|
||||
const classes = rawClasses.length === 0 ? tankClassOrder : rawClasses;
|
||||
|
||||
return (
|
||||
<DropdownMenu.Root modal={false}>
|
||||
@@ -378,10 +361,10 @@ function ClassFilter() {
|
||||
<Button size="minor" color="gray" variant="surface">
|
||||
<Flex>
|
||||
{classes.map((tankClass, index) => {
|
||||
const Icon = classIcons[tankClass];
|
||||
return (
|
||||
<Icon
|
||||
<ClassIcon
|
||||
key={tankClass}
|
||||
class={tankClass}
|
||||
style={{
|
||||
color: "var(--gray-12)",
|
||||
opacity: 1,
|
||||
@@ -398,9 +381,8 @@ function ClassFilter() {
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<DropdownMenu.Content>
|
||||
{TANK_CLASSES.map((tankClass) => {
|
||||
{tankClassOrder.map((tankClass) => {
|
||||
const selected = rawClasses.includes(tankClass);
|
||||
const Icon = classIcons[tankClass];
|
||||
|
||||
return (
|
||||
<DropdownMenu.CheckboxItem
|
||||
@@ -420,7 +402,10 @@ function ClassFilter() {
|
||||
}}
|
||||
checked={selected}
|
||||
>
|
||||
<Icon style={{ width: "1.25em", height: "1.25em" }} />
|
||||
<ClassIcon
|
||||
class={tankClass}
|
||||
style={{ width: "1.25em", height: "1.25em" }}
|
||||
/>
|
||||
|
||||
{strings.common.tank_class_medium[tankClass]}
|
||||
</DropdownMenu.CheckboxItem>
|
||||
@@ -449,8 +434,8 @@ function ClassFilter() {
|
||||
|
||||
function GunTypeFilter() {
|
||||
const rawGunTypes = TankFilters.use((state) => state.gunType);
|
||||
const gunTypes = rawGunTypes.length === 0 ? GUN_TYPES : rawGunTypes;
|
||||
const { strings } = useLocale();
|
||||
const gunTypes = rawGunTypes.length === 0 ? gunTypeOrder : rawGunTypes;
|
||||
const strings = useStrings();
|
||||
|
||||
return (
|
||||
<DropdownMenu.Root modal={false}>
|
||||
@@ -458,11 +443,10 @@ function GunTypeFilter() {
|
||||
<Button size="minor" color="gray" variant="surface">
|
||||
<Flex>
|
||||
{gunTypes.map((gunType) => {
|
||||
const Icon = GUN_TYPE_ICONS[gunType];
|
||||
|
||||
return (
|
||||
<Icon
|
||||
<GunTypeIcon
|
||||
key={gunType}
|
||||
type={gunType}
|
||||
style={{
|
||||
margin: gunType === "regular" ? "0 -0.125em" : undefined,
|
||||
opacity: 1,
|
||||
@@ -478,9 +462,8 @@ function GunTypeFilter() {
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<DropdownMenu.Content>
|
||||
{GUN_TYPES.map((gunType) => {
|
||||
{gunTypeOrder.map((gunType) => {
|
||||
const selected = rawGunTypes.includes(gunType);
|
||||
const Icon = GUN_TYPE_ICONS[gunType];
|
||||
|
||||
return (
|
||||
<DropdownMenu.CheckboxItem
|
||||
@@ -498,7 +481,10 @@ function GunTypeFilter() {
|
||||
checked={selected}
|
||||
key={gunType}
|
||||
>
|
||||
<Icon style={{ width: "1.25em", height: "1.25em" }} />
|
||||
<GunTypeIcon
|
||||
type={gunType}
|
||||
style={{ width: "1.25em", height: "1.25em" }}
|
||||
/>
|
||||
|
||||
{strings.common.gun_types[gunType]}
|
||||
</DropdownMenu.CheckboxItem>
|
||||
@@ -526,9 +512,9 @@ function GunTypeFilter() {
|
||||
}
|
||||
|
||||
function TypeFilter() {
|
||||
const { strings } = useLocale();
|
||||
const strings = useStrings();
|
||||
const rawTypes = TankFilters.use((state) => state.types);
|
||||
const types = rawTypes.length === 0 ? TANK_TYPES : rawTypes;
|
||||
const types = rawTypes.length === 0 ? treeTypeOrder : rawTypes;
|
||||
|
||||
return (
|
||||
<DropdownMenu.Root modal={false}>
|
||||
@@ -556,7 +542,7 @@ function TypeFilter() {
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<DropdownMenu.Content>
|
||||
{TANK_TYPES.map((tankType) => {
|
||||
{treeTypeOrder.map((tankType) => {
|
||||
const selected = rawTypes.includes(tankType);
|
||||
|
||||
return (
|
||||
@@ -628,7 +614,7 @@ function IndividualShellFilter({
|
||||
index: number;
|
||||
premium?: boolean;
|
||||
}) {
|
||||
const { strings } = useLocale();
|
||||
const strings = useStrings();
|
||||
const shells = TankFilters.use((state) => state.shells);
|
||||
|
||||
return (
|
||||
@@ -730,7 +716,7 @@ function IndividualShellFilter({
|
||||
|
||||
function OwnershipFilter() {
|
||||
const wargaming = App.use((state) => state.logins.wargaming);
|
||||
const { strings } = useLocale();
|
||||
const strings = useStrings();
|
||||
|
||||
return wargaming ? (
|
||||
<OwnershipFilterInternal />
|
||||
@@ -837,7 +823,8 @@ function TestFilter() {
|
||||
}
|
||||
|
||||
function ConsumablesFilter() {
|
||||
const { unwrap, strings } = useLocale();
|
||||
const strings = useStrings();
|
||||
const unwrap = useUnwrapper();
|
||||
const rawConsumables = TankFilters.use((state) => state.consumables);
|
||||
const consumables =
|
||||
rawConsumables.length === 0 ? consumablesArray : rawConsumables;
|
||||
@@ -931,7 +918,8 @@ function ConsumablesFilter() {
|
||||
}
|
||||
|
||||
function ProvisionsFilter() {
|
||||
const { unwrap, strings } = useLocale();
|
||||
const strings = useStrings();
|
||||
const unwrap = useUnwrapper();
|
||||
const rawProvisions = TankFilters.use((state) => state.provisions);
|
||||
const provisions =
|
||||
rawProvisions.length === 0 ? provisionsArray : rawProvisions;
|
||||
@@ -1025,7 +1013,8 @@ function ProvisionsFilter() {
|
||||
}
|
||||
|
||||
function GameModeAbilitiesFilter() {
|
||||
const { unwrap, strings } = useLocale();
|
||||
const strings = useStrings();
|
||||
const unwrap = useUnwrapper();
|
||||
const rawAbilities = TankFilters.use((state) => state.abilities);
|
||||
const rawPowers = TankFilters.use((state) => state.powers);
|
||||
|
||||
|
||||
@@ -21,8 +21,7 @@ export function GameModeSection() {
|
||||
</Heading>
|
||||
|
||||
<Flex justify="center" gap="4" wrap="wrap" px="4">
|
||||
{roles.map(([key, value]) => {
|
||||
const id = Number(key);
|
||||
{roles.map(([id, value]) => {
|
||||
const gameMode = gameDefinitions.gameModes[id];
|
||||
|
||||
return (
|
||||
|
||||
-200
@@ -1,200 +0,0 @@
|
||||
import {
|
||||
forwardRef,
|
||||
Suspense,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
} from "react";
|
||||
import { applyPitchYawLimits } from "../../../../../core/blitz/applyPitchYawLimits";
|
||||
import { modelTransformEvent } from "../../../../../core/blitzkit/modelTransform";
|
||||
import { Pose, poseEvent } from "../../../../../core/blitzkit/pose";
|
||||
import { useEquipment } from "../../../../../hooks/useEquipment";
|
||||
import { useModel } from "../../../../../hooks/useModel";
|
||||
import { useTankModelDefinition } from "../../../../../hooks/useTankModelDefinition";
|
||||
import { Duel } from "../../../../../stores/duel";
|
||||
import { Tankopedia } from "../../../../../stores/tankopedia";
|
||||
import { TankopediaPersistent } from "../../../../../stores/tankopediaPersistent";
|
||||
import { TankopediaDisplay } from "../../../../../stores/tankopediaPersistent/constants";
|
||||
import { Armor } from "../../../../Armor";
|
||||
import { ArmorPlateDisplay } from "../../../../Armor/components/ArmorPlateDisplay";
|
||||
import { ShotDisplay } from "../../../../Armor/components/ShotDisplay";
|
||||
import {
|
||||
StaticArmor,
|
||||
type ThicknessRange,
|
||||
} from "../../../../Armor/components/StaticArmor";
|
||||
import { SmartCanvas } from "../../../../SmartCanvas";
|
||||
import { AutoClear } from "./components/AutoClear";
|
||||
import { Controls } from "./components/Control";
|
||||
import { InitialAligner } from "./components/InitialAligner";
|
||||
import { Lighting } from "./components/Lighting";
|
||||
import { SceneProps } from "./components/SceneProps";
|
||||
import { TankModel } from "./components/TankModel";
|
||||
|
||||
interface TankSandboxProps {
|
||||
thicknessRange: ThicknessRange;
|
||||
}
|
||||
|
||||
export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
||||
({ thicknessRange }, ref) => {
|
||||
const canvas = useRef<HTMLCanvasElement>(null);
|
||||
const hasImprovedVerticalStabilizer = useEquipment(122);
|
||||
const hasDownImprovedVerticalStabilizer = useEquipment(124);
|
||||
const protagonistGun = Duel.use((state) => state.protagonist.gun);
|
||||
const protagonistTurret = Duel.use((state) => state.protagonist.turret);
|
||||
const tankModelDefinition = useTankModelDefinition();
|
||||
const turretModelDefinition =
|
||||
tankModelDefinition.turrets[protagonistTurret.id];
|
||||
const gunModelDefinition = turretModelDefinition.guns[protagonistGun.id];
|
||||
const display = Tankopedia.use((state) => state.display);
|
||||
const hideTankModelUnderArmor = TankopediaPersistent.use(
|
||||
(state) => state.hideTankModelUnderArmor,
|
||||
);
|
||||
|
||||
const id = Duel.use((state) => state.protagonist.tank.id);
|
||||
const { hasPbr } = useModel(id);
|
||||
|
||||
useImperativeHandle(ref, () => canvas.current!, []);
|
||||
|
||||
function handlePointerDown() {
|
||||
window.addEventListener("pointermove", handlePointerMove);
|
||||
window.addEventListener("pointerup", handlePointerUp);
|
||||
}
|
||||
function handlePointerMove(event: PointerEvent) {
|
||||
event.preventDefault();
|
||||
}
|
||||
function handlePointerUp(event: PointerEvent) {
|
||||
event.preventDefault();
|
||||
|
||||
window.removeEventListener("pointermove", handlePointerMove);
|
||||
window.removeEventListener("pointerup", handlePointerUp);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
function handlePoseEvent(pose: Pose) {
|
||||
switch (pose) {
|
||||
case Pose.HullDown: {
|
||||
const [pitch, yaw] = applyPitchYawLimits(
|
||||
-Infinity,
|
||||
0,
|
||||
gunModelDefinition.pitch!,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
hasDownImprovedVerticalStabilizer,
|
||||
);
|
||||
|
||||
modelTransformEvent.dispatch({ pitch, yaw });
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Pose.FaceHug: {
|
||||
const [pitch, yaw] = applyPitchYawLimits(
|
||||
Infinity,
|
||||
0,
|
||||
gunModelDefinition.pitch!,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
|
||||
modelTransformEvent.dispatch({ pitch, yaw });
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Pose.Default:
|
||||
const [pitch, yaw] = applyPitchYawLimits(
|
||||
0,
|
||||
0,
|
||||
gunModelDefinition.pitch!,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
|
||||
modelTransformEvent.dispatch({ pitch, yaw });
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
poseEvent.on(handlePoseEvent);
|
||||
|
||||
return () => {
|
||||
poseEvent.off(handlePoseEvent);
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const [pitch, yaw] = applyPitchYawLimits(
|
||||
modelTransformEvent.last!.pitch,
|
||||
modelTransformEvent.last!.yaw,
|
||||
gunModelDefinition.pitch!,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
|
||||
modelTransformEvent.dispatch({ pitch, yaw });
|
||||
}, [protagonistGun, protagonistTurret]);
|
||||
|
||||
useEffect(() => {
|
||||
if (display !== TankopediaDisplay.DynamicArmor) {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.shot = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
if (display !== TankopediaDisplay.StaticArmor) {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.highlightArmor = undefined;
|
||||
});
|
||||
}
|
||||
}, [display]);
|
||||
|
||||
return (
|
||||
<SmartCanvas
|
||||
resize={{ debounce: 0 }}
|
||||
ref={canvas}
|
||||
gl={{
|
||||
clippingPlanes: [],
|
||||
localClippingEnabled: true,
|
||||
preserveDrawingBuffer: true,
|
||||
}}
|
||||
shadows="soft"
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerMissed={() => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.shot = undefined;
|
||||
draft.highlightArmor = undefined;
|
||||
});
|
||||
}}
|
||||
style={{
|
||||
userSelect: "none",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
outline: undefined,
|
||||
}}
|
||||
>
|
||||
<Lighting hasPbr={hasPbr} />
|
||||
|
||||
<SceneProps />
|
||||
{(display === TankopediaDisplay.Model ||
|
||||
(display === TankopediaDisplay.DynamicArmor &&
|
||||
!hideTankModelUnderArmor)) && <TankModel />}
|
||||
<ShotDisplay />
|
||||
<ArmorPlateDisplay />
|
||||
<AutoClear />
|
||||
|
||||
<Suspense>
|
||||
{/* Controls within Suspense to allow for frame-perfect start of camera auto-rotate */}
|
||||
<Controls />
|
||||
|
||||
{display === TankopediaDisplay.DynamicArmor && <Armor />}
|
||||
{display === TankopediaDisplay.StaticArmor && (
|
||||
<StaticArmor thicknessRange={thicknessRange} />
|
||||
)}
|
||||
</Suspense>
|
||||
|
||||
<InitialAligner />
|
||||
</SmartCanvas>
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { Consumable } from "@blitzkit/core";
|
||||
import { TrashIcon } from "@radix-ui/react-icons";
|
||||
import { useCompatibility } from "../../hooks/useCompatibility";
|
||||
import { useConsumables } from "../../hooks/useConsumables";
|
||||
import { useProtagonist } from "../../hooks/useProtagonist";
|
||||
import { useProtagonistGun } from "../../hooks/useProtagonistGun";
|
||||
import { useProtagonistTank } from "../../hooks/useProtagonistTank";
|
||||
import { useStrings } from "../../hooks/useStrings";
|
||||
import { useTankCompatibility } from "../../hooks/useTankCompatibility";
|
||||
import { Tankopedia } from "../../stores/tankopedia";
|
||||
import { hasUpgrades } from "../../tankopedia/hasUpgrades";
|
||||
import { vehicleStatusKeys } from "../../tankopedia/tankState";
|
||||
@@ -19,7 +19,7 @@ interface TankopediaLoadoutProps {
|
||||
}
|
||||
|
||||
export function TankopediaLoadout({ characteristics }: TankopediaLoadoutProps) {
|
||||
const tank = useProtagonist();
|
||||
const tank = useProtagonistTank();
|
||||
const showModules = hasUpgrades(tank);
|
||||
|
||||
return (
|
||||
@@ -120,9 +120,9 @@ interface ConsumablesProps {
|
||||
function Consumables({ characteristics }: ConsumablesProps) {
|
||||
const strings = useStrings();
|
||||
const consumables = useConsumables();
|
||||
const tank = useProtagonist();
|
||||
const tank = useProtagonistTank();
|
||||
const gun = useProtagonistGun();
|
||||
const isCompatible = useCompatibility(tank, gun);
|
||||
const isCompatible = useTankCompatibility(tank, gun);
|
||||
|
||||
return (
|
||||
<div className={styles.section}>
|
||||
@@ -165,7 +165,7 @@ function Consumable({ id, consumable }: ConsumableProps) {
|
||||
const isSelected = Tankopedia.use((state) =>
|
||||
state.protagonist.consumables.includes(id),
|
||||
);
|
||||
const tank = useProtagonist();
|
||||
const tank = useProtagonistTank();
|
||||
const unwrap = useUnwrap();
|
||||
|
||||
return (
|
||||
@@ -214,7 +214,7 @@ function Consumable({ id, consumable }: ConsumableProps) {
|
||||
|
||||
function Equipment() {
|
||||
const strings = useStrings();
|
||||
const tank = useProtagonist();
|
||||
const tank = useProtagonistTank();
|
||||
const equipment = useEquipment(tank.tank!);
|
||||
|
||||
return (
|
||||
@@ -273,7 +273,7 @@ interface EquipmentSlotProps {
|
||||
|
||||
function EquipmentSlot({ slot, rowIndex, columnIndex }: EquipmentSlotProps) {
|
||||
const equipmentIndex = rowIndex * equipmentColumns + columnIndex;
|
||||
const tank = useProtagonist();
|
||||
const tank = useProtagonistTank();
|
||||
const equipment = useEquipment(tank.tank!);
|
||||
const { price } = equipment.price.unlock_slot_prices[equipmentIndex];
|
||||
|
||||
@@ -307,7 +307,7 @@ function EquipmentOption({
|
||||
columnIndex,
|
||||
}: EquipmentOptionProps) {
|
||||
const equipmentIndex = rowIndex * equipmentColumns + columnIndex;
|
||||
const tank = useProtagonist();
|
||||
const tank = useProtagonistTank();
|
||||
const equipment = useEquipment(tank.tank!);
|
||||
const slot = equipment.preset.slots[equipmentIndex];
|
||||
const id = slot.options_catalog_i_ds[optionIndex];
|
||||
@@ -346,7 +346,7 @@ function EquipmentOption({
|
||||
|
||||
function Modules() {
|
||||
const strings = useStrings();
|
||||
const tank = useProtagonist();
|
||||
const tank = useProtagonistTank();
|
||||
const upgrades = Tankopedia.use((state) => state.protagonist.upgrades);
|
||||
const alternates = Tankopedia.use((state) => state.protagonist.alternates);
|
||||
const isStock = useMemo(() => {
|
||||
@@ -496,7 +496,7 @@ interface LineElementProps {
|
||||
function LineElement({ index, lineName, stage }: LineElementProps) {
|
||||
const upgrades = Tankopedia.use((state) => state.protagonist.upgrades);
|
||||
const alternates = Tankopedia.use((state) => state.protagonist.alternates);
|
||||
const tank = useProtagonist();
|
||||
const tank = useProtagonistTank();
|
||||
const strings = useStrings();
|
||||
const gameStrings = useGameStrings("TankEntity");
|
||||
const upgradePreset = useUpgradePreset(tank.tank!.tank_upgrade_preset);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
.wrapper {
|
||||
height: calc(100dvh - 16rem);
|
||||
position: relative;
|
||||
|
||||
.sandbox {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.canvas {
|
||||
background-color: black;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
import {
|
||||
forwardRef,
|
||||
Suspense,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
} from "react";
|
||||
import { applyPitchYawLimits } from "../../core/blitz/applyPitchYawLimits";
|
||||
import { modelTransformEvent } from "../../core/blitzkit/modelTransform";
|
||||
import { Pose, poseEvent } from "../../core/blitzkit/pose";
|
||||
import { useEquipment } from "../../hooks/useEquipment";
|
||||
import { useModel } from "../../hooks/useModel";
|
||||
import { useProtagonistGun } from "../../hooks/useProtagonistGun";
|
||||
import { useProtagonistTank } from "../../hooks/useProtagonistTank";
|
||||
import { useProtagonistTurret } from "../../hooks/useProtagonistTurret";
|
||||
import { Tankopedia } from "../../stores/tankopedia";
|
||||
import { TankopediaPersistent } from "../../stores/tankopediaPersistent";
|
||||
import { TankopediaDisplay } from "../../stores/tankopediaPersistent/constants";
|
||||
import { Armor } from "../Armor";
|
||||
import { ArmorPlateDisplay } from "../Armor/components/ArmorPlateDisplay";
|
||||
import { ShotDisplay } from "../Armor/components/ShotDisplay";
|
||||
import {
|
||||
StaticArmor,
|
||||
type ThicknessRange,
|
||||
} from "../Armor/components/StaticArmor";
|
||||
import { CanvasAutoClear } from "../CanvasAutoClear";
|
||||
import { CanvasControl } from "../CanvasControl";
|
||||
import { InitialCameraAligner } from "../InitialCameraAligner";
|
||||
import { Lighting } from "../Lighting";
|
||||
import { SceneProps } from "../SceneProps";
|
||||
import { SmartCanvas } from "../SmartCanvas";
|
||||
import { TankModel } from "../TankModel";
|
||||
import styles from "./index.module.css";
|
||||
|
||||
interface TankSandboxProps {
|
||||
thicknessRange: ThicknessRange;
|
||||
}
|
||||
|
||||
export const TankopediaSandbox = forwardRef<
|
||||
HTMLCanvasElement,
|
||||
TankSandboxProps
|
||||
>(({ thicknessRange }, ref) => {
|
||||
const canvas = useRef<HTMLCanvasElement>(null);
|
||||
const hasImprovedVerticalStabilizer = useEquipment(122);
|
||||
const hasDownImprovedVerticalStabilizer = useEquipment(124);
|
||||
|
||||
const tank = useProtagonistTank();
|
||||
const gun = useProtagonistGun();
|
||||
const turret = useProtagonistTurret();
|
||||
|
||||
const model = Tankopedia.use((state) => state.protagonist.model);
|
||||
const turretModelDefinition = model.turrets[turret.id];
|
||||
const gunModelDefinition = turretModelDefinition.guns[gun.id];
|
||||
const display = Tankopedia.use((state) => state.display);
|
||||
const hideTankModelUnderArmor = TankopediaPersistent.use(
|
||||
(state) => state.hideTankModelUnderArmor,
|
||||
);
|
||||
|
||||
const { hasPbr } = useModel(tank.id);
|
||||
|
||||
useImperativeHandle(ref, () => canvas.current!, []);
|
||||
|
||||
function handlePointerDown() {
|
||||
window.addEventListener("pointermove", handlePointerMove);
|
||||
window.addEventListener("pointerup", handlePointerUp);
|
||||
}
|
||||
function handlePointerMove(event: PointerEvent) {
|
||||
event.preventDefault();
|
||||
}
|
||||
function handlePointerUp(event: PointerEvent) {
|
||||
event.preventDefault();
|
||||
|
||||
window.removeEventListener("pointermove", handlePointerMove);
|
||||
window.removeEventListener("pointerup", handlePointerUp);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
function handlePoseEvent(pose: Pose) {
|
||||
switch (pose) {
|
||||
case Pose.HullDown: {
|
||||
const [pitch, yaw] = applyPitchYawLimits(
|
||||
-Infinity,
|
||||
0,
|
||||
gunModelDefinition.pitch!,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
hasDownImprovedVerticalStabilizer,
|
||||
);
|
||||
|
||||
modelTransformEvent.dispatch({ pitch, yaw });
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Pose.FaceHug: {
|
||||
const [pitch, yaw] = applyPitchYawLimits(
|
||||
Infinity,
|
||||
0,
|
||||
gunModelDefinition.pitch!,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
|
||||
modelTransformEvent.dispatch({ pitch, yaw });
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Pose.Default:
|
||||
const [pitch, yaw] = applyPitchYawLimits(
|
||||
0,
|
||||
0,
|
||||
gunModelDefinition.pitch!,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
|
||||
modelTransformEvent.dispatch({ pitch, yaw });
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
poseEvent.on(handlePoseEvent);
|
||||
|
||||
return () => {
|
||||
poseEvent.off(handlePoseEvent);
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const [pitch, yaw] = applyPitchYawLimits(
|
||||
modelTransformEvent.last!.pitch,
|
||||
modelTransformEvent.last!.yaw,
|
||||
gunModelDefinition.pitch!,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
|
||||
modelTransformEvent.dispatch({ pitch, yaw });
|
||||
}, [gun, turret]);
|
||||
|
||||
useEffect(() => {
|
||||
if (display !== TankopediaDisplay.DynamicArmor) {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.shot = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
if (display !== TankopediaDisplay.StaticArmor) {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.highlightArmor = undefined;
|
||||
});
|
||||
}
|
||||
}, [display]);
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<SmartCanvas
|
||||
className={styles.canvas}
|
||||
resize={{ debounce: 0 }}
|
||||
ref={canvas}
|
||||
gl={{
|
||||
clippingPlanes: [],
|
||||
localClippingEnabled: true,
|
||||
preserveDrawingBuffer: true,
|
||||
}}
|
||||
shadows="soft"
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerMissed={() => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.shot = undefined;
|
||||
draft.highlightArmor = undefined;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Lighting hasPbr={hasPbr} />
|
||||
|
||||
<SceneProps />
|
||||
{(display === TankopediaDisplay.Model ||
|
||||
(display === TankopediaDisplay.DynamicArmor &&
|
||||
!hideTankModelUnderArmor)) && <TankModel />}
|
||||
|
||||
<ShotDisplay />
|
||||
<ArmorPlateDisplay />
|
||||
<CanvasAutoClear />
|
||||
|
||||
<Suspense>
|
||||
{/* Controls within Suspense to allow for frame-perfect start of camera auto-rotate */}
|
||||
<CanvasControl />
|
||||
|
||||
{display === TankopediaDisplay.DynamicArmor && <Armor />}
|
||||
{display === TankopediaDisplay.StaticArmor && (
|
||||
<StaticArmor thicknessRange={thicknessRange} />
|
||||
)}
|
||||
</Suspense>
|
||||
|
||||
<InitialCameraAligner />
|
||||
</SmartCanvas>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
import { ArrowLeftIcon } from "@radix-ui/react-icons";
|
||||
import { api } from "../api/dynamic";
|
||||
import type { Tool } from "../constants/tools";
|
||||
import { getStrings } from "../core/i18n/getStrings";
|
||||
import { Logo } from "../icons/Logo";
|
||||
import { Back } from "./Back";
|
||||
import { Flex } from "./Flex";
|
||||
@@ -14,9 +14,8 @@ interface Props {
|
||||
}
|
||||
|
||||
const { tool, locale } = Astro.props;
|
||||
const strings = getStrings(locale);
|
||||
const toolStrings =
|
||||
strings.website.tools[tool.id as keyof typeof strings.website.tools];
|
||||
const strings = await api.strings(locale);
|
||||
const toolStrings = strings.tools[tool.id as keyof typeof strings.tools];
|
||||
---
|
||||
|
||||
<style>
|
||||
|
||||
@@ -12,10 +12,7 @@ interface Props {
|
||||
|
||||
const { locale, tool, minor } = Astro.props;
|
||||
const strings = await api.strings(locale);
|
||||
const toolStrings =
|
||||
strings.website.tools[
|
||||
(tool.strings ?? tool.id) as keyof typeof strings.website.tools
|
||||
];
|
||||
const toolStrings = strings.tools[tool.id as keyof typeof strings.tools];
|
||||
---
|
||||
|
||||
<style>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { degToRad } from "three/src/math/MathUtils.js";
|
||||
|
||||
export const fov = 20;
|
||||
|
||||
export const initialCameraR = 15;
|
||||
export const initialCameraPhi = degToRad(90 - 10);
|
||||
export const initialCameraTheta = degToRad(90 + 15);
|
||||
|
||||
export const cameraSwingVerticalAmplitude = degToRad(5);
|
||||
export const cameraSwingVerticalFrequency = Math.E ** -6;
|
||||
export const cameraSwingHorizontalAmplitude = degToRad(30);
|
||||
export const cameraSwingHorizontalFrequency = Math.E ** -5;
|
||||
|
||||
export const cameraCenterCorrectionAmplitude = 0.8;
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { TankDefinition } from "@blitzkit/core";
|
||||
import { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity";
|
||||
import type { TankDefinition } from "@blitzkit/protos";
|
||||
import { times } from "lodash-es";
|
||||
import { api } from "../../api/dynamic";
|
||||
import { isTankCompatible } from "../../hooks/useTankCompatibility";
|
||||
import type { TankFilters } from "../../stores/tankFilters";
|
||||
|
||||
const SHELLS = times(3, (index) => index);
|
||||
@@ -51,10 +51,11 @@ export async function filterTank(
|
||||
filters.consumables.every((consumable) =>
|
||||
tank.turrets.some((turret) =>
|
||||
turret.guns.some((gun) =>
|
||||
checkConsumableProvisionInclusivity(
|
||||
consumableDefinitions.consumables[consumable],
|
||||
isTankCompatible(
|
||||
tank,
|
||||
gun,
|
||||
consumableDefinitions.consumables[consumable].include,
|
||||
consumableDefinitions.consumables[consumable].exclude,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -63,10 +64,11 @@ export async function filterTank(
|
||||
filters.provisions.every((provision) =>
|
||||
tank.turrets.some((turret) =>
|
||||
turret.guns.some((gun) =>
|
||||
checkConsumableProvisionInclusivity(
|
||||
provisionDefinitions.provisions[provision],
|
||||
isTankCompatible(
|
||||
tank,
|
||||
gun,
|
||||
provisionDefinitions.provisions[provision].include,
|
||||
provisionDefinitions.provisions[provision].exclude,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import type {
|
||||
EngineDefinition,
|
||||
GunDefinition,
|
||||
ShellDefinition,
|
||||
TankDefinition,
|
||||
TrackDefinition,
|
||||
TurretDefinition,
|
||||
} from "@blitzkit/protos";
|
||||
import { api } from "../api/dynamic";
|
||||
import { Tankopedia } from "../stores/tankopedia";
|
||||
import type { DuelSide } from "./useEquipment";
|
||||
|
||||
export enum DuelModule {
|
||||
Tank,
|
||||
Track,
|
||||
Engine,
|
||||
Turret,
|
||||
Gun,
|
||||
Shell,
|
||||
}
|
||||
|
||||
const tanks = await api.tanks();
|
||||
|
||||
export function useDuel(
|
||||
side: DuelSide,
|
||||
module: DuelModule.Tank,
|
||||
): TankDefinition;
|
||||
export function useDuel(
|
||||
side: DuelSide,
|
||||
module: DuelModule.Track,
|
||||
): TrackDefinition;
|
||||
export function useDuel(
|
||||
side: DuelSide,
|
||||
module: DuelModule.Engine,
|
||||
): EngineDefinition;
|
||||
export function useDuel(
|
||||
side: DuelSide,
|
||||
module: DuelModule.Turret,
|
||||
): TurretDefinition;
|
||||
export function useDuel(side: DuelSide, module: DuelModule.Gun): GunDefinition;
|
||||
export function useDuel(
|
||||
side: DuelSide,
|
||||
module: DuelModule.Shell,
|
||||
): ShellDefinition;
|
||||
|
||||
export function useDuel(side: DuelSide, module: DuelModule) {
|
||||
const tankId = Tankopedia.use((state) => state[side].tank);
|
||||
const tank = tanks.tanks[tankId];
|
||||
|
||||
if (module === DuelModule.Tank) return tank;
|
||||
|
||||
if (module === DuelModule.Track) {
|
||||
const trackId = Tankopedia.use((state) => state[side].track);
|
||||
return tank.tracks.find((track) => track.id === trackId)!;
|
||||
}
|
||||
|
||||
if (module === DuelModule.Engine) {
|
||||
const engineId = Tankopedia.use((state) => state[side].engine);
|
||||
return tank.engines.find((engine) => engine.id === engineId)!;
|
||||
}
|
||||
|
||||
const turretId = Tankopedia.use((state) => state[side].turret);
|
||||
const turret = tank.turrets.find((turret) => turret.id === turretId)!;
|
||||
|
||||
if (module === DuelModule.Turret) return turret;
|
||||
|
||||
const gunId = Tankopedia.use((state) => state[side].gun);
|
||||
const gun = turret.guns.find((gun) => gun.id === gunId)!;
|
||||
|
||||
if (module === DuelModule.Gun) return gun;
|
||||
|
||||
const shellId = Tankopedia.use((state) => state[side].shell);
|
||||
return gun.shells.find((shell) => shell.id === shellId)!;
|
||||
}
|
||||
@@ -1,18 +1,20 @@
|
||||
import { useMemo } from "react";
|
||||
import { api } from "../api/dynamic";
|
||||
import { Duel } from "../stores/duel";
|
||||
import { Tankopedia } from "../stores/tankopedia";
|
||||
|
||||
const equipmentDefinitions = await api.equipment();
|
||||
const equipment = await api.equipment();
|
||||
const tanks = await api.tanks();
|
||||
|
||||
export function useEquipment(id: number, antagonist = false) {
|
||||
const member = Duel.use(
|
||||
(state) => state[antagonist ? "antagonist" : "protagonist"]!,
|
||||
);
|
||||
const preset = equipmentDefinitions.presets[member.tank.equipment_preset];
|
||||
const equipmentMatrix = Duel.use(
|
||||
(state) =>
|
||||
state[antagonist ? "antagonist" : "protagonist"]!.equipmentMatrix,
|
||||
export type DuelSide = "protagonist" | "antagonist";
|
||||
|
||||
export function useEquipment(id: number, side: DuelSide) {
|
||||
const member = Tankopedia.use((state) => state[side]);
|
||||
const tank = tanks.tanks[member.tank];
|
||||
const preset = equipment.presets[tank.equipment_preset];
|
||||
const equipmentMatrix = Tankopedia.use(
|
||||
(state) => state[side].equipment_matrix,
|
||||
);
|
||||
|
||||
const value = useMemo(() => {
|
||||
return preset.slots.some((slot, index) => {
|
||||
const row = Math.floor(index / 3);
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { api } from "../api/dynamic";
|
||||
import { Tankopedia } from "../stores/tankopedia";
|
||||
import { useAwait } from "./useAwait";
|
||||
|
||||
export function useProtagonist() {
|
||||
const id = Tankopedia.use((state) => state.protagonist.tank);
|
||||
const tank = useAwait(() => api.tank(id), `tank-${id}`);
|
||||
return tank;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { Tankopedia } from "../stores/tankopedia";
|
||||
import { useProtagonistTurret } from "./useProtagonistTurret";
|
||||
|
||||
export function useProtagonistGun() {
|
||||
const id = Tankopedia.use((state) => state.protagonist.gun);
|
||||
const turret = useProtagonistTurret();
|
||||
return turret.guns[id];
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { Tankopedia } from "../stores/tankopedia";
|
||||
import { useProtagonist } from "./useProtagonist";
|
||||
|
||||
export function useProtagonistTurret() {
|
||||
const id = Tankopedia.use((state) => state.protagonist.gun);
|
||||
const tank = useProtagonist();
|
||||
return tank.turrets[id];
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { isTankCompatible } from "@blitzkit/core";
|
||||
import {
|
||||
GunDefinition,
|
||||
TankDefinition,
|
||||
TankInclusivityFilter,
|
||||
} from "@blitzkit/protos";
|
||||
|
||||
export function useTankCompatibility(tank: TankDefinition, gun: GunDefinition) {
|
||||
return function (
|
||||
include?: TankInclusivityFilter[],
|
||||
exclude?: TankInclusivityFilter[],
|
||||
) {
|
||||
return isTankCompatible(tank, gun, include, exclude);
|
||||
};
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { Tankopedia } from "../stores/tankopedia";
|
||||
|
||||
export function useTankModelDefinition() {
|
||||
return Tankopedia.use((state) => state.model);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { unwrapper } from "@blitzkit/i18n";
|
||||
import { useLocale } from "./useLocale";
|
||||
|
||||
export function useUnwrapper() {
|
||||
const locale = useLocale();
|
||||
const unwrap = unwrapper(locale);
|
||||
|
||||
return unwrap;
|
||||
}
|
||||
@@ -17,15 +17,15 @@ export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => {
|
||||
const bannerMatches: { name: string; path: string }[] = [];
|
||||
|
||||
for (const match of gameTypeSelectorStyles.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll(
|
||||
/eGameMode\.([a-zA-Z]+) -> "~res:([^"]+)"/g,
|
||||
/eGameMode\.(\w+) -> "~res:([^"]+)"/g,
|
||||
)) {
|
||||
bannerMatches.push({ name: match[1], path: match[2] });
|
||||
}
|
||||
|
||||
for (const match of squadBattleTypeStyles.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll(
|
||||
/"(\d+)" -> "battleType\/([a-zA-Z]+)"/g,
|
||||
/eGameMode\.(\w+) -> "battleType\/([a-zA-Z]+)"/g,
|
||||
)) {
|
||||
const id = Number(match[1]);
|
||||
const id = match[1];
|
||||
const name = match[2];
|
||||
let path = bannerMatches.find(
|
||||
(bannerMatch) => bannerMatch.name.toLowerCase() === name.toLowerCase(),
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { useMemo } from "react";
|
||||
import { api } from "../../../../../api/dynamic";
|
||||
import { computeCharacteristics } from "../../../../../tankopedia/computeCharacteristics";
|
||||
import { TankopediaLoadout } from "../../../../../components/TankopediaLoadout";
|
||||
import { TankopediaSandbox } from "../../../../../components/TankopediaSandbox";
|
||||
import { withErrorWrapper } from "../../../../../hocs/withErrorWrapper";
|
||||
import { withLocale } from "../../../../../hocs/withLocale";
|
||||
import { useAwait } from "../../../../../hooks/useAwait";
|
||||
import { Tankopedia } from "../../../../../stores/tankopedia";
|
||||
import { computeCharacteristics } from "../../../../../tankopedia/computeCharacteristics";
|
||||
import styles from "./_index.module.css";
|
||||
|
||||
interface PageProps {
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
import type { CaseType } from "@blitzkit/core";
|
||||
import type {
|
||||
GunDefinition,
|
||||
ShellType,
|
||||
TankClass,
|
||||
TankType,
|
||||
} from "@blitzkit/core";
|
||||
} from "@blitzkit/protos";
|
||||
import { Varuna } from "varuna";
|
||||
|
||||
export type CaseType<T> = T extends {
|
||||
gun_type?: { $case: infer U; value: any };
|
||||
}
|
||||
? U
|
||||
: never;
|
||||
|
||||
export interface TankFilters {
|
||||
tiers: number[];
|
||||
nations: string[];
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import {
|
||||
createDefaultSkills,
|
||||
ShellDefinition,
|
||||
TankDefinition,
|
||||
} from "@blitzkit/core";
|
||||
import { createDefaultSkills } from "@blitzkit/core";
|
||||
import type { ShellDefinition, TankDefinition } from "@blitzkit/protos";
|
||||
import type { Vector3 } from "three";
|
||||
import { Varuna } from "varuna";
|
||||
import { api } from "../api/dynamic";
|
||||
@@ -83,6 +80,7 @@ interface Tankopedia {
|
||||
revealed: boolean;
|
||||
|
||||
protagonist: TankState;
|
||||
antagonist: TankState;
|
||||
|
||||
shot?: Shot;
|
||||
skills: Record<string, number>;
|
||||
@@ -117,6 +115,7 @@ export const Tankopedia = new Varuna<Tankopedia, TankDefinition>((tank) => ({
|
||||
revealed: false,
|
||||
|
||||
protagonist: createTankState(tank),
|
||||
antagonist: createTankState(tank),
|
||||
model: models.models[tank.id],
|
||||
|
||||
relativeAgainst: TankopediaRelativeAgainst.Class,
|
||||
|
||||
Reference in new issue
Block a user