tank states

This commit is contained in:
tresabhi committed 2026-09-22 16:13:27 -05:00
1 parent 6c9ce58af5
commit 7d02f6311f
16 files changed
+110 -349

No files matched your search

+6 -1
View File
@@ -16,7 +16,7 @@ export abstract class BlitzKitAPI {
abstract skills(): Promise<SkillDefinitions>;
abstract provisions(): Promise<ProvisionDefinitions>;
abstract consumables(): Promise<ConsumableDefinitions>;
abstract equipments(): Promise<EquipmentDefinitions>;
abstract equipment(): Promise<EquipmentDefinitions>;
abstract maps(): Promise<MapDefinitions>;
abstract models(): Promise<ModelDefinitions>;
abstract camouflages(): Promise<CamouflageDefinitions>;
@@ -29,4 +29,9 @@ export abstract class BlitzKitAPI {
const tanks = await this.tanks();
return tanks.tanks[id];
}
async model(id: number) {
const models = await this.models();
return models.models[id];
}
}
+1 -1
View File
@@ -53,7 +53,7 @@ export class ClientBlitzKitAPI extends BlitzKitAPI {
return fetchPB(alias("api", "/definitions/skills.pb"), SkillDefinitions);
}
equipments() {
equipment() {
return fetchPB(
alias("api", "/definitions/equipment.pb"),
EquipmentDefinitions,
@@ -4,7 +4,7 @@ import { ServerBlitzKitAPI4 } from "./4_maps";
export abstract class ServerBlitzKitAPI5 extends ServerBlitzKitAPI4 {
@Cache()
async equipments() {
async equipment() {
const equipmentDefinitions = EquipmentDefinitions.create();
Object.entries(this.optionalDevices!.root).forEach(
@@ -105,228 +105,6 @@ export abstract class ServerBlitzKitAPI7 extends ServerBlitzKitAPI6 {
});
}
}
function applyCanonicalBonuses(map: Record<string, string>) {
for (const key in map) {
entry.bonuses[key] = provision.script.bonusValues![map[key]];
}
}
function applyRootBonuses(map: Record<string, string>) {
for (const key in map) {
entry.bonuses[key] = provision.script[map[key]];
}
}
function applyAttributeBonuses(map: Record<string, string>) {
for (const key in map) {
if (!(map[key] in provision.script.attributes!)) continue;
entry.bonuses[key] = provision.script.attributes![map[key]];
}
}
switch (provision.script["#text"]) {
case "Stimulator":
applyCanonicalBonuses({
crew_level_increase: "crewLevelIncrease",
});
break;
case "AntiHighExplosive":
applyCanonicalBonuses({
anti_high_explosive_factor: "factor",
});
break;
case "Fuel":
applyCanonicalBonuses({
engine_power_increase: "enginePowerIncrease",
turret_rotation_speed_increase: "turretRotationSpeedIncrease",
});
break;
case "SafetySet":
applyCanonicalBonuses({
crew_chance_to_hit_factor: "crewChanceToHitFactor",
repair_speed_increase: "repairSpeedIncrease",
fire_protection_increase: "fireProtectionIncrease",
});
break;
case "SmallHPStock":
applyRootBonuses({
hp_stock_percent: "hpStockPercent",
});
break;
case "GearOil":
applyCanonicalBonuses({
forward_speed_limit_bias: "fwdSpeedLimitBias",
backward_speed_limit_bias: "bkwdSpeedLimitBias",
engine_power_increase: "enginePowerIncrease",
});
break;
case "GunPowder":
applyCanonicalBonuses({
projectile_speed_factor: "projectileSpeedFactor",
});
break;
case "AttributesModifier":
applyAttributeBonuses({
firm_ground_passability_increase: "firmGroundPassabilityIncrease",
medium_ground_passability_increase:
"mediumGroundPassabilityIncrease",
repair_speed_factor: "repairSpeedFactor",
common_damage_factor: "commonDamageFactor",
gun_reload_factor: "gunReloadFactor",
pump_gun_reload_factor: "pumpGunReloadFactor",
gun_aiming_factor: "gunAimingFactor",
engine_power_factor: "enginePowerFactor",
speed_limits_factor: "speedLimitsFactor",
chassis_health_factor: "chassisHealthFactor",
});
break;
case "LifestealHeal":
applyCanonicalBonuses({
life_steal_factor: "lifestealFactor",
life_steal_random_factor: "lifestealRandomFactor",
kill_heal_factor: "killHealFactor",
heal_factor: "healFactor",
heal_random_factor: "healRandomFactor",
});
break;
case "RammingDamage":
applyCanonicalBonuses({
ramming_damage_factor: "rammingDamageFactor",
dealing_ramming_damage_factor: "dealingRammingDamageFactor",
});
break;
case "AllyBuff":
applyCanonicalBonuses({
gun_reloading_bonus: "gunReloadingBonus",
gun_aiming_time_bonus: "gunAimingTimeBonus",
vision_radius_bonus: "visionRadiusBonus",
});
applyRootBonuses({
range: "range",
max_bonus_sources: "maxBonusSources",
});
break;
case "Stealth":
applyRootBonuses({
sixth_sense_delay: "sixthSenseDelay",
stop_observing_delay_factor: "stopObservingDelayFactor",
});
break;
case "Revenge":
applyRootBonuses({
revenge_damage_percentage: "revengeDamagePercentage",
effect_duration: "effectDuration",
});
break;
case "ComeBackKit":
applyRootBonuses({
time_for_come_back: "timeForComeBack",
simple_mode: "simpleMode",
});
break;
case "EngineAndSpeedBoost":
applyCanonicalBonuses({
engine_power_ability_factor: "enginePowerAbilityFactor",
speed_limit_ability_bonus: "speedLimitAbilityBonus",
});
break;
case "EnemyReloadTime":
// bonusValues is empty in the observed data.
break;
case "Duplet":
applyCanonicalBonuses({
shot_dispersion_angle_factor: "shotDispersionAngleFactor",
aiming_time_factor: "aimingTimeFactor",
time_between_shots: "timeBetweenShots",
});
break;
case "Fortune":
applyCanonicalBonuses({
increased_damage_chance: "increasedDamageChance",
increase_damage_factor: "increaseDamageFactor",
});
break;
case "VampiricCurse":
applyRootBonuses({
curse_strength: "curseStrength",
curse_reduction: "curseReduction",
curse_heal: "curseHeal",
});
break;
case "VampiricDeath":
applyRootBonuses({
explode_damage_percent: "explodeDamagePercent",
range: "range",
});
break;
case "PermanentDamageShield":
applyRootBonuses({
damage_reduction_percent: "damageReductionPercent",
});
break;
case "BigBoss":
applyCanonicalBonuses({
gun_reload_factor: "gunReloadFactor",
common_damage_factor: "commonDamageFactor",
engine_power_factor: "enginePowerFactor",
speed_limits_factor: "speedLimitsFactor",
max_health_percent_bonus: "maxHealthPercentBonus",
max_statics_damage_percent: "maxStaticsDamagePercent",
});
break;
case "VampiricBoost":
break;
case "ZombieMark":
applyAttributeBonuses({
dealing_ramming_damage_factor: "dealingRammingDamageFactor",
ramming_damage_factor: "rammingDamageFactor",
engine_power_factor: "enginePowerFactor",
speed_limits_factor: "speedLimitsFactor",
common_damage_factor: "commonDamageFactor",
});
break;
case "HumanMark":
applyRootBonuses({
opt_device_ramming_damage_override:
"optDeviceRammingDamageOverride",
});
// `bonusValues.item` is a nested array and therefore isn't
// appropriate for applyCanonicalBonuses().
break;
default:
console.warn(`Unhandled script type ${provision.script["#text"]}`);
console.dir(provision.script, { depth: null, colors: true });
}
});
return provisionDefinitions;
@@ -1,12 +1,16 @@
import { GunDefinition, ProvisionDefinitions, TankDefinition } from "../../../protos/src/blitzkit";
import {
GunDefinition,
ProvisionDefinitions,
TankDefinition,
} from "../../../protos/src/blitzkit";
import { checkConsumableProvisionInclusivity } from "./checkConsumableProvisionInclusivity";
export function availableProvisions(
tank: TankDefinition,
gun: GunDefinition,
provisionDefinitions: ProvisionDefinitions
provisionDefinitions: ProvisionDefinitions,
) {
return Object.values(provisionDefinitions.provisions).filter((provision) =>
checkConsumableProvisionInclusivity(provision, tank, gun)
checkConsumableProvisionInclusivity(provision, tank, gun),
);
}
@@ -1,5 +1,10 @@
import { GunDefinition, ProvisionDefinitions, TankDefinition } from '../protos';
import { availableProvisions } from './availableProvisions';
import {
GunDefinition,
ProvisionDefinitions,
TankDefinition,
} from "@blitzkit/protos";
import { BlitzScripts } from "../types/blitzScripts";
import { availableProvisions } from "./availableProvisions";
const PROVISION_PREFERENCES = [
19, // improved fuel
@@ -14,9 +19,10 @@ function infinityFallback(value: number) {
export function createDefaultProvisions(
tank: TankDefinition,
gun: GunDefinition,
provisionDefinitions: ProvisionDefinitions,
provisions: ProvisionDefinitions,
scripts: BlitzScripts,
) {
const provisionsList = availableProvisions(tank, gun, provisionDefinitions);
const provisionsList = availableProvisions(tank, gun, provisions);
return provisionsList
.sort(
@@ -24,7 +30,11 @@ export function createDefaultProvisions(
infinityFallback(PROVISION_PREFERENCES.indexOf(a.id)) -
infinityFallback(PROVISION_PREFERENCES.indexOf(b.id)),
)
.sort((a, b) => (b.crew ?? 0) - (a.crew ?? 0))
.sort(
(a, b) =>
(scripts.provisions[b.id].bonusValues?.crewLevelIncrease ?? 0) -
(scripts.provisions[a.id].bonusValues?.crewLevelIncrease ?? 0),
)
.slice(0, tank.max_provisions)
.map(({ id }) => id);
}
+2
View File
@@ -1,3 +1,5 @@
import { BlitzEffectScript } from "./blitzEffectScript";
export interface BlitzScripts {
consumables: Record<number, BlitzEffectScript>;
provisions: Record<number, BlitzEffectScript>;
+2 -2
View File
@@ -1,4 +1,4 @@
import { ProvisionConsumableScript } from "@blitzkit/core/src/types/provisionConsumableScript";
import { BlitzEffectScript } from "./blitzEffectScript";
import { ConsumablesVehicleFilter } from "./consumablesVehicleFilter";
export interface ProvisionsCommon {
@@ -13,6 +13,6 @@ export interface ProvisionsCommon {
include: { vehicle: ConsumablesVehicleFilter; nations?: string };
exclude?: { vehicle: ConsumablesVehicleFilter; nations?: string };
};
script: ProvisionConsumableScript;
script: BlitzEffectScript;
};
}
@@ -11,8 +11,6 @@ message ConsumableDefinitions {
message Consumable {
required uint32 id = 1;
required I18nString name = 2;
required uint32 cooldown = 3;
optional float duration = 4;
required bool game_mode_exclusive = 5;
repeated ConsumableTankFilter include = 6;
repeated ConsumableTankFilter exclude = 7;
@@ -17,7 +17,4 @@ message Provision {
required bool game_mode_exclusive = 5;
repeated ConsumableTankFilter include = 6;
repeated ConsumableTankFilter exclude = 7;
map<string, float> bonuses = 8;
map<string, string> effects = 9;
}
@@ -1,39 +0,0 @@
import {
createDefaultProvisions,
Equalizer,
type ProvisionDefinitions,
type TankDefinition,
} from "@blitzkit/core";
import type { DuelMember } from "../../stores/duel";
import { genericDefaultEquipmentMatrix } from "../../stores/duel/constants";
export function tankToDuelMember(
tank: TankDefinition,
provisionDefinitions: ProvisionDefinitions,
) {
const turret = tank.turrets.at(-1)!;
const gun = turret.guns.at(-1)!;
return {
camouflage: true,
consumables: [],
equipmentMatrix: genericDefaultEquipmentMatrix,
cooldownBooster: 0,
provisions: createDefaultProvisions(tank, gun, provisionDefinitions),
engine: tank.engines.at(-1)!,
gun,
shell: tank.turrets.at(-1)!.guns.at(-1)!.shells[0],
tank,
track: tank.tracks.at(-1)!,
turret,
assaultDistance: (gun.assault_ranges?.ranges[0].distance ?? 0) / 2,
} satisfies DuelMember;
}
export const defaultEqualizer: Equalizer = {
armor: 1,
damage: 1,
health: 1,
module_health: 1,
penetration: 1,
};
@@ -4,7 +4,6 @@ import { computeCharacteristics } from "../../../../../blitzkit/computeCharacter
import { withErrorWrapper } from "../../../../../hocs/withErrorWrapper";
import { withLocale } from "../../../../../hocs/withLocale";
import { useAwait } from "../../../../../hooks/useAwait";
import { useEquipment } from "../../../../../hooks/useEquipment";
import { Tankopedia } from "../../../../../stores/tankopedia";
import styles from "./_index.module.css";
@@ -19,7 +18,7 @@ export const Page = withErrorWrapper(
Tankopedia.useInitialization(protagonistTank);
const protagonist = Tankopedia.use((state) => state.protagonist);
const protagonistEquipment = useEquipment(protagonistTank.tank!);
// const protagonistEquipment = useEquipment(protagonistTank.tank!);
const characteristics = useMemo(
() => computeCharacteristics(),
@@ -1,7 +0,0 @@
import type { EquipmentMatrix } from '.';
export const genericDefaultEquipmentMatrix: EquipmentMatrix = [
[-1, 1, -1],
[0, 0, 0],
[0, 0, 0],
];
-56
View File
@@ -1,56 +0,0 @@
import {
ModelDefinition,
type EngineDefinition,
type GunDefinition,
type ShellDefinition,
type TankDefinition,
type TrackDefinition,
type TurretDefinition,
} from "@blitzkit/core";
import { Varuna } from "varuna";
import { api } from "../../blitzkit/api";
import { tankToDuelMember } from "../../core/blitzkit/tankToDuelMember";
type EquipmentMatrixItem = -1 | 0 | 1;
type EquipmentMatrixRow = [
EquipmentMatrixItem,
EquipmentMatrixItem,
EquipmentMatrixItem,
];
export type EquipmentMatrix = [
EquipmentMatrixRow,
EquipmentMatrixRow,
EquipmentMatrixRow,
];
export interface DuelMember {
tank: TankDefinition;
engine: EngineDefinition;
turret: TurretDefinition;
gun: GunDefinition;
shell: ShellDefinition;
track: TrackDefinition;
equipmentMatrix: EquipmentMatrix;
consumables: number[];
provisions: number[];
camouflage: boolean;
cooldownBooster: number;
assaultDistance: number;
}
export interface DuelStore {
protagonist: DuelMember;
antagonist: DuelMember;
equalize: boolean;
}
const provisionDefinitions = await api.provisions();
export const Duel = new Varuna<
DuelStore,
{ tank: TankDefinition; model: ModelDefinition }
>(({ tank, model }) => {
const protagonist = tankToDuelMember(tank, provisionDefinitions);
return { protagonist, antagonist: protagonist, model, equalize: false };
});
+37 -5
View File
@@ -1,6 +1,11 @@
import {
createDefaultSkills,
EngineDefinition,
GunDefinition,
ShellDefinition,
TankDefinition,
TrackDefinition,
TurretDefinition,
type ModelDefinition,
} from "@blitzkit/core";
import type { Vector3 } from "three";
@@ -9,6 +14,8 @@ import { api } from "../blitzkit/api";
import type { ArmorType } from "../components/Armor/components/SpacedArmorScene";
import type { ExternalModuleVariant } from "../components/Armor/components/SpacedArmorSceneComponent";
import type { XP_MULTIPLIERS } from "../components/Tankopedia/TechTreeSection";
import { createTankState } from "../tankopedia/createTankState";
import type { EquipmentMatrix } from "./duel";
import { TankopediaDisplay } from "./tankopediaPersistent/constants";
export interface ShotLayerBase {
@@ -76,13 +83,34 @@ export enum TankopediaRelativeAgainst {
All,
}
export interface TankState {
tank: TankDefinition;
model: ModelDefinition;
engine: EngineDefinition;
turret: TurretDefinition;
gun: GunDefinition;
shell: ShellDefinition;
track: TrackDefinition;
equipment_matrix: EquipmentMatrix;
consumables: number[];
provisions: number[];
camouflage: boolean;
cooldown_booster: number;
assault_distance: number;
}
interface Tankopedia {
disturbed: boolean;
revealed: boolean;
protagonist: TankState;
shot?: Shot;
skills: Record<string, number>;
relativeAgainst: TankopediaRelativeAgainst;
model: ModelDefinition;
editStatic: boolean;
highlightArmor?: {
editingPlate: boolean;
@@ -105,15 +133,19 @@ interface Tankopedia {
statSearch?: string;
}
const skillDefinitions = await api.skills();
const skills = await api.skills();
const models = await api.models();
export const Tankopedia = new Varuna<Tankopedia, ModelDefinition>((model) => ({
export const Tankopedia = new Varuna<Tankopedia, TankDefinition>((tank) => ({
disturbed: false,
revealed: false,
protagonist: createTankState(tank),
model: models.models[tank.id],
relativeAgainst: TankopediaRelativeAgainst.Class,
editStatic: false,
skills: createDefaultSkills(skillDefinitions),
model,
skills: createDefaultSkills(skills),
xpMultiplier: 1,
requestedDisplay: TankopediaDisplay.Model,
display: TankopediaDisplay.Model,
@@ -0,0 +1,38 @@
import { createDefaultProvisions, type TankDefinition } from "@blitzkit/core";
import { api } from "../blitzkit/api";
import type { EquipmentMatrix } from "../stores/duel";
import type { TankState } from "../stores/tankopedia";
const models = await api.models();
const provisions = await api.provisions();
const scripts = await api.scripts();
export const genericDefaultEquipmentMatrix: EquipmentMatrix = [
[-1, 1, -1],
[0, 0, 0],
[0, 0, 0],
];
export function createTankState(tank: TankDefinition) {
const turret = tank.turrets.at(-1)!;
const gun = turret.guns.at(-1)!;
return {
tank,
model: models.models[turret.id],
engine: tank.engines.at(-1)!,
turret,
gun,
shell: gun.shells[0],
track: tank.tracks.at(-1)!,
equipment_matrix: genericDefaultEquipmentMatrix,
consumables: [],
provisions: createDefaultProvisions(tank, gun, provisions, scripts),
camouflage: true,
cooldown_booster: 0,
assault_distance: (gun.assault_ranges?.ranges[0].distance ?? 0) / 2,
} satisfies TankState;
}